2016-09-17 15 views
1

我试图在我的NSTextView子类中显示不可见字符,如新行字符。像重写NSLayoutManager的drawGlyph方法这样的常用方法是一个坏主意,因为它太慢,并且不适合多页面布局。NSLayoutManager隐藏新行字符不管我做什么

我想要做的是重写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仍然不会为新行字符生成字形。

我在做什么错?

回答

1

正如我所知,该类的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 subclas。子类将显示新行字符,并确保每一个无形的字符将用不同的颜色来绘制:

    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) 
    
+0

似乎这段代码在Swift3中,我们如何将它转换成Swift 2.3?我注意到Swift 2.3中没有“MemoryLayout”。 –