2016-09-17 1 views
1

Я пытаюсь показать невидимые символы, как новый символ линии в моем подклассе NSTextView. Обычный подход, такой как переопределение метода drawGlyph для NSLayoutManager, является плохой идеей, потому что он слишком медленный и не работает должным образом с многопользовательской компоновкой.NSLayoutManager скрывает новые символы линии, независимо от того, что я делаю

Что я пытаюсь сделать, это переопределить метод setGlyph для NSLayoutManager, чтобы он заменил невидимый «\ 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

Как я понял, реализация setNotShownAttribute по умолчанию NSTypesetter: из класса не изменяется уже сгенерированные глифы в его хранилище глифов. Таким образом, вызов супер не производит никакого эффекта. Мне просто нужно заменить глифы вручную, прежде чем звонить супер.

Таким образом, наиболее эффективная реализация показа невидимых символов (вы увидите разницу во время масштабирования мнения) заключается в следующем:

Ограничение этого подхода: если ваше приложение должно иметь несколько шрифтов в текстовом режиме , то этот подход может быть не такой хорошей идеей, потому что шрифт отображаемых невидимых символов будет другим. И это не то, чего вы, возможно, захотите достичь.

  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 и назначить его своим subclas 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) 
    
+0

Кажется этот код находится в Swift3, как мы можем преобразовать его в Swift 2.3? Я заметил, что «MemoryLayout» недоступен в Swift 2.3. –

Смежные вопросы