无论我做什么,NSLayoutManager 都会隐藏换行符 [英] NSLayoutManager hides new line characters no matter what I do

查看:28
本文介绍了无论我做什么,NSLayoutManager 都会隐藏换行符的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图在我的 NSTextView 子类中显示不可见的字符,比如换行符.像覆盖 NSLayoutManager 的 drawGlyph 方法这样的常用方法是一个坏主意,因为它太慢并且不能在多页布局中正常工作.

I'm trying to show invisible characters like the new line character in my NSTextView subclass. The usual approach like overriding drawGlyph method of NSLayoutManager is a bad idea because it's too slow and not work properly with multi-paged layout.

我想要做的是覆盖 NSLayoutManager 的 setGlyph 方法,以便它将不可见的\n"字形替换为¶"字形,将"替换为∙".

What I'm trying to do is to override the setGlyph method of the NSLayoutManager so it would replace invisible "\n" glyph with "¶" glyph and " " with "∙".

它适用于 " " 空格字形,但对换行符没有影响.

And it works on the " " space glyphs but has no effect on the new line characters.

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 并重写了一个方法:

Then I came up with an idea: it looks like NSTypesetter marks new line char ranges like those it shouldn't process at all. So I subclassed NSTypesetter and did override a method:

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

但它不起作用.无论我创建什么字形,NSLayoutManager 仍然不会为新行字符生成字形.

But it's not working. NSLayoutManager still won't generate a glyph for the new line character, no matter what glyph I create.

我做错了什么?

推荐答案

据我所知,类的 NSTypesetter 的 setNotShownAttribute: 的默认实现不会更改其字形存储中已生成的字形.所以,调用 super 不会产生任何效果.我只需要在调用 super 之前手动替换字形.

As I figured out, the default implementation of NSTypesetter's setNotShownAttribute: of the class doesn't change already generated glyphs in its glyph storage. So, call of super doesn't produce any effect. I just have to replace glyphs manually before calling super.

因此,显示不可见字符(缩放视图时您会看到差异)的最有效实现是这样的:

So, the most efficient implementation of showing invisible characters (you will see the difference while zooming the view) is this:

这种方法的局限性:如果你的应用程序必须在文本视图中有多种字体,那么这种方法可能不是一个好主意,因为那些显示不可见字符的字体会有所不同好.这不是您可能想要实现的目标.

Limitations of this approach: if your app has to have multiple fonts in text view, then this approach might not be such a good idea, because the font of those displayed invisible characters will be different as well. And that's not what you might want to achieve.

  1. 子类 NSLayoutManager 并覆盖 setGlyphs 以显示空格字符:

  1. Subclass NSLayoutManager and override setGlyphs to show space chars:

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

  • 子类 NSATSTypesetter 并将其分配给您的 NSLayoutManager 子类.子类将显示换行符并确保每个不可见的字符都用不同的颜色绘制:

  • Subclass NSATSTypesetter and assign it to your NSLayoutManager subclas. The subclass will display the new line characters and make sure that every invisible character will be drawn with a different color:

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

  • 要显示/隐藏不可见字符,只需调用:

  • To show/hide invisible characters just call:

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

  • 这篇关于无论我做什么,NSLayoutManager 都会隐藏换行符的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆