3

NSTextView サブクラスで改行文字などの非表示の文字を表示しようとしています。NSLayoutManager の drawGlyph メソッドをオーバーライドするような通常のアプローチは、遅すぎて複数ページのレイアウトで適切に機能しないため、お勧めできません。

私がやろうとしているのは、NSLayoutManager の setGlyph メソッドをオーバーライドして、非表示の "\n" グリフを "¶" グリフに、" " を "∙" に置き換えることです。

また、" " スペース グリフでは機能しますが、改行文字には影響しません。

public override func setGlyphs(_ glyphs: UnsafePointer<CGGlyph>, properties props: UnsafePointer<NSGlyphProperty>, characterIndexes charIndexes: UnsafePointer<Int>, font aFont: Font, forGlyphRange glyphRange: NSRange) {
    var substring = (self.currentTextStorage.string as NSString).substring(with: glyphRange)

    // replace invisible characters with visible
    if PreferencesManager.shared.shouldShowInvisibles == true {
        substring = substring.replacingOccurrences(of: " ", with: "\u{00B7}")
        substring = substring.replacingOccurrences(of: "\n", with: "u{00B6}")
    }

    // create a CFString
    let stringRef = substring as CFString
    let count = CFStringGetLength(stringRef)

    // convert processed string to the C-pointer
    let cfRange = CFRangeMake(0, count)
    let fontRef = CTFontCreateWithName(aFont.fontName as CFString?, aFont.pointSize, nil)
    let characters = UnsafeMutablePointer<UniChar>.allocate(capacity: MemoryLayout<UniChar>.size * count)
    CFStringGetCharacters(stringRef, cfRange, characters)

    // get glyphs for the pointer of characters
    let glyphsRef = UnsafeMutablePointer<CGGlyph>.allocate(capacity: MemoryLayout<CGGlyph>.size * count)
    CTFontGetGlyphsForCharacters(fontRef, characters, glyphsRef, count)

    // set those glyphs
    super.setGlyphs(glyphsRef, properties:props, characterIndexes: charIndexes, font: aFont, forGlyphRange: glyphRange)
}

それから私はアイデアを思いつきました: NSTypesetter は、まったく処理してはならないような新しい行の文字範囲をマークしているようです。そこで、NSTypesetter をサブクラス化し、メソッドをオーバーライドしました。

override func setNotShownAttribute(_ flag: Bool, forGlyphRange glyphRange: NSRange) {
    let theFlag = PreferencesManager.shared.shouldShowInvisibles == true ? false : true
    super.setNotShownAttribute(theFlag, forGlyphRange: glyphRange)
}

しかし、それは機能していません。NSLayoutManager は、作成したグリフに関係なく、改行文字のグリフを生成しません。

私は何を間違っていますか?

4

2 に答える 2

4

私が理解したように、クラスの NSTypesetter の setNotShownAttribute: のデフォルトの実装は、そのグリフ ストレージで既に生成されたグリフを変更しません。そのため、super を呼び出しても効果はありません。スーパーを呼び出す前に、グリフを手動で置き換えるだけです。

したがって、非表示の文字を表示する最も効率的な実装は次のとおりです (ビューをズームすると違いがわかります)。

このアプローチの制限:アプリのテキスト ビューに複数のフォントが必要な場合、表示される非表示文字のフォントも異なるため、このアプローチはあまりお勧めできません。そして、それはあなたが達成したいことではありません。

  1. NSLayoutManager をサブクラス化し、setGlyphs をオーバーライドして空白文字を表示します。

    public override func setGlyphs(_ glyphs: UnsafePointer<CGGlyph>, properties props: UnsafePointer<NSGlyphProperty>, characterIndexes charIndexes: UnsafePointer<Int>, font aFont: Font, forGlyphRange glyphRange: NSRange) {
        var substring = (self.currentTextStorage.string as NSString).substring(with: glyphRange)
    
        // replace invisible characters with visible
        if PreferencesManager.shared.shouldShowInvisibles == true {
            substring = substring.replacingOccurrences(of: " ", with: "\u{00B7}")
        }
    
        // create a CFString
        let stringRef = substring as CFString
        let count = CFStringGetLength(stringRef)
    
        // convert processed string to the C-pointer
        let cfRange = CFRangeMake(0, count)
        let fontRef = CTFontCreateWithName(aFont.fontName as CFString?, aFont.pointSize, nil)
        let characters = UnsafeMutablePointer<UniChar>.allocate(capacity: MemoryLayout<UniChar>.size * count)
        CFStringGetCharacters(stringRef, cfRange, characters)
    
        // get glyphs for the pointer of characters
        let glyphsRef = UnsafeMutablePointer<CGGlyph>.allocate(capacity: MemoryLayout<CGGlyph>.size * count)
        CTFontGetGlyphsForCharacters(fontRef, characters, glyphsRef, count)
    
        // set those glyphs
        super.setGlyphs(glyphsRef, properties:props, characterIndexes: charIndexes, font: aFont, forGlyphRange: glyphRange)
    }
    
  2. NSATSTypesetter をサブクラス化し、それを NSLayoutManager サブクラスに割り当てます。サブクラスは改行文字を表示し、すべての非表示文字が異なる色で描画されるようにします。

    class CustomTypesetter: NSATSTypesetter {
    
        override func setNotShownAttribute(_ flag: Bool, forGlyphRange glyphRange: NSRange) {
            var theFlag = flag
    
            if PreferencesManager.shared.shouldShowInvisibles == true   {
                theFlag = false
    
                // add new line glyphs into the glyph storage
                var newLineGlyph = yourFont.glyph(withName: "paragraph")
                self.substituteGlyphs(in: glyphRange, withGlyphs: &newLineGlyph)
    
                // draw new line char with different color
                self.layoutManager?.addTemporaryAttribute(NSForegroundColorAttributeName, value: NSColor.invisibleTextColor, forCharacterRange: glyphRange)
            }
    
            super.setNotShownAttribute(theFlag, forGlyphRange: glyphRange)
        }
    
        /// Currently hadn't found any faster way to draw space glyphs with different color
        override func setParagraphGlyphRange(_ paragraphRange: NSRange, separatorGlyphRange paragraphSeparatorRange: NSRange) {
            super.setParagraphGlyphRange(paragraphRange, separatorGlyphRange: paragraphSeparatorRange)
    
            guard PreferencesManager.shared.shouldShowInvisibles == true else { return }
    
            if let substring = (self.layoutManager?.textStorage?.string as NSString?)?.substring(with: paragraphRange) {
                let expression = try? NSRegularExpression.init(pattern: "\\s", options: NSRegularExpression.Options.useUnicodeWordBoundaries)
                let sunstringRange = NSRange(location: 0, length: substring.characters.count)
    
                if let matches = expression?.matches(in: substring, options: NSRegularExpression.MatchingOptions.withoutAnchoringBounds, range: sunstringRange) {
                    for match in matches {
                        let globalSubRange = NSRange(location: paragraphRange.location + match.range.location, length: 1)
                        self.layoutManager?.addTemporaryAttribute(NSForegroundColorAttributeName, value: Color.invisibleText, forCharacterRange: globalSubRange)
                    }
                }
            }
        }
    }
    
  3. 非表示の文字を表示/非表示にするには、次のように呼び出します。

    let storageRange = NSRange(location: 0, length: currentTextStorage.length)
    layoutManager.invalidateGlyphs(forCharacterRange: storageRange, changeInLength: 0, actualCharacterRange: nil)
    layoutManager.ensureGlyphs(forGlyphRange: storageRange)
    
于 2016-09-17T16:18:24.583 に答える