2016-09-17 4 views
1

Ich versuche unsichtbare Zeichen wie das neue Zeilenzeichen in meiner NSTextView Unterklasse zu zeigen. Der übliche Ansatz wie das Überschreiben der drawGlyph-Methode von NSLayoutManager ist eine schlechte Idee, weil sie zu langsam ist und bei mehrseitigen Layouts nicht richtig funktioniert.NSLayoutManager verbirgt neue Zeilenzeichen, egal was ich mache

Was ich versuche ist, die SetGlyph-Methode des NSLayoutManager zu überschreiben, so dass es unsichtbare "\ n" Glyphe durch "¶" Glyphe und "" durch "∙" ersetzen würde.

Und es funktioniert auf die "" Space Glyphs, hat aber keine Auswirkungen auf die neue Zeile Zeichen.

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) 
} 

Dann kam ich mit einer Idee: es sieht aus wie NSTypesetter neue Linie char Bereiche wie die markiert es überhaupt nicht verarbeiten soll. Also habe ich NSTypesetter subklassifiziert und eine Methode überschrieben:

Aber es funktioniert nicht. NSLayoutManager generiert immer noch keine Glyphe für das neue Zeilenzeichen, egal welche Glyphe ich erstelle.

Was mache ich falsch?

Antwort

1

Wie ich herausgefunden habe, ändert die Standardimplementierung von NSTypesetters setNotShowAttribute: der Klasse nicht bereits generierte Glyphen in seinem Glyphspeicher. Also, der Aufruf von Super erzeugt keinen Effekt. Ich muss Glyphen nur manuell ersetzen, bevor ich Super anrufe.

So ist die effizienteste Implementierung unsichtbare Zeichen zeigen (Sie werden den Unterschied sehen, während die Ansicht Zoomen), ist dies:

Einschränkungen dieses Ansatzes: wenn Ihre Anwendung mehrere Schriftarten in Textansicht haben, hat , dann ist dieser Ansatz vielleicht nicht so eine gute Idee, weil die Schriftart der angezeigten unsichtbaren Zeichen auch anders sein wird. Und das möchten Sie vielleicht nicht erreichen.

  1. Subclass NSLayoutManager und außer Kraft setzen setGlyphs Raum Zeichen zeigen:

    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. Subclass NSATSTypesetter und weisen Sie auf Ihre NSLayoutManager subclas. Die Unterklasse wird die neue Zeile Zeichen angezeigt werden und sicherstellen, dass jedes unsichtbares Zeichen wird mit einer anderen Farbe gezeichnet werden:

    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. Zur Ein-/Ausblenden unsichtbare Zeichen rufen Sie einfach an:

    let storageRange = NSRange(location: 0, length: currentTextStorage.length) 
    layoutManager.invalidateGlyphs(forCharacterRange: storageRange, changeInLength: 0, actualCharacterRange: nil) 
    layoutManager.ensureGlyphs(forGlyphRange: storageRange) 
    
+0

Scheint Dieser Code ist in Swift3, wie können wir es in Swift 2.3 konvertieren? Ich habe festgestellt, dass "MemoryLayout" in Swift 2.3 nicht verfügbar ist. –

Verwandte Themen