无论我做什么,NSLayoutManager都会隐藏新行字符

问题描述 投票:2回答:2

我试图在我的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仍然不会为新行字符生成字形。

我究竟做错了什么?

swift cocoa cocoa-touch appkit nslayoutmanager
2个回答
2
投票

正如我所知,该类的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)

1
投票

这可能超过了这个问题的实用性,但我是通过Google来到这里的。

至少在macOS 10.14上进行测试时,-[NSLayoutManager setNotShownAttribute:forGlyphAtIndex:]能够在生成该索引的字形后更改其值。关键是,在布线的最后,它将无条件地为换行符号设置setNotShownAttribute:YES。你完成后会得到-[NSLayoutManagerDelegate layoutManager:shouldUseAction:forControlCharacterAtIndex:],你可以在那里重置它:

- (NSControlCharacterAction)layoutManager:(NSLayoutManager *)layoutManager shouldUseAction:(NSControlCharacterAction)action forControlCharacterAtIndex:(NSUInteger)characterIndex {
  if (layoutManager.showsInvisibleCharacters && (action & NSControlCharacterActionLineBreak)) {
    [layoutManager setNotShownAttribute:NO forGlyphAtIndex:[layoutManager glyphIndexForCharacterAtIndex:characterIndex]];
  }

  return action;
}
func layoutManager(_ layoutManager: NSLayoutManager, shouldUse action: NSLayoutManager.ControlCharacterAction, forControlCharacterAt characterIndex: Int) -> NSLayoutManager.ControlCharacterAction {
  if layoutManager.showsInvisibleCharacters, action.contains(.lineBreak) {
    let glyphIndex = layoutManager.glyphIndexForCharacter(at: characterIndex)
    layoutManager.setNotShownAttribute(false, forGlyphAt: glyphIndex)
  }

  return action
}

您会注意到我在示例中使用showsInvisibleCharacters,它实际上适用于内置方法,即使它没有该字符的映射,产生“我不知道”的字形:

newline glyph unknown replacement

使用-[NSLayoutManagerDelegate layoutManager:shouldGenerateGlyphs:properties:characterIndexes:font:forGlyphRange:,您可以使其完美运行:

newline glyph custom replacement

© www.soinside.com 2019 - 2024. All rights reserved.