macoscocoanstextviewappkit

Render some text in uppercase in an NSTextView without changing the underlying string


In an NSTextView, is it possible to render a given range of a string as all-caps, without changing the underlying string itself? The idea is similar to NSLayoutManager's temporary attributes, or CSS' text-transform property.


Solution

  • It might be possible, but you're going to have to implement such support yourself. I don't believe there's anything built in to do that.

    You would have to implement a custom subclass of NSLayoutManager and a custom subclass of NSGlyphGenerator, too. Your custom layout manager class would have an interface similar to the temporary attributes interface. That's because the built-in temporary attributes feature doesn't support attributes that modify layout, but changing the case of characters will modify layout. You will need to store the custom temporary attributes somehow and invalidate layout. Because your custom glyph generator will need them (see below), you may wish to store the temporary attributes in that object.

    Handling your custom attribute will involve substituting different glyphs, so I think you need to use a custom glyph generator. You'd pass an instance of your custom subclass of NSGlyphGenerator to the setter of the layout manager's glyphGenerator property. Your glyph generator will need to interpose itself between the standard implementation and its glyph storage object (which is actually the layout manager in its role as an NSGlyphStorage). So, your subclass would also adopt the NSGlyphStorage protocol.

    You will override the sole glyph generator instance method, -generateGlyphsForGlyphStorage:desiredNumberOfCharacters:glyphIndex:characterIndex:. When the layout manager calls your glyph generator, your override of that method will call through to super, but will substitute self for the glyphStorage parameter. It will have to remember the original glyphStorage in an instance variable, though.

    Then, the superclass's implementation will call various methods from the NSGlyphStorage protocol on your object. If you wanted your implementation to do nothing special, it would just call through to the original glyphStorage object. However, you want to check for your custom attribute and, for any run where it's present, substitute capital letters. This has to happen in the implementation of -attributedString. You will need to make a mutable copy of the attribute string returned by the original glyphStorage (which is the layout manager) and, for any ranges affected by your custom temporary attribute, replace the characters with the localized uppercase versions of those characters.

    You will want to optimize this so you're not constantly duplicating and modifying the (possibly very large) attributed string that is the text storage of the layout manager. Unfortunately, the rather limited interfaces between the layout manager and glyph generator won't make this easy. The text storage will call -textStorage:edited:range:changeInLength:invalidatedRange: on the layout manager when it has been changed, so you can leverage that to invalidate any cached copy you may have.