Mac OS X Essentials • 1:04:27
Cocoa provides a powerful, flexible text system for a wide variety of text handling, display, and editing needs. Learn how to configure and customize the Cocoa text-handling classes, and put them together in many different ways, with special attention paid to facilities new in Leopard.
Speakers: Doug Davidson, Aki Inoue
Unlisted on Apple Developer site
Transcript
This transcript has potential transcription errors. We are working on an improved version.
So good afternoon everyone. I'm Doug Davidson and I'm here to talk to you about the Cocoa text system. Now anyone who has ever worked with Cocoa has seen the text system in action because it's responsible for most of the text you see displayed in the typical Cocoa application.
But what may not be quite so obvious is that there's an enormous depth to it. Many different levels at which you can deal with the text system and particularly which you could customize its behavior. And that's the main thing that I want to talk to you about today. For those of you who are new to Cocoa text, along the way I will also be discussing quite a bit about how it works. And of course, we have a great many things that are new in Leopard I will be discussing from time to time.
So what is it the Cocoa text system does? The main job of the text system is to take a text document, that is characters plus their attributes like color and font and so forth and to convert them into glyphs which are the individual displayable elements to the font and positions so that they can be displayed on the screen. So here's my standard example. We have a character set, a with an acute accent and attributes blue color, Helvetica 64 font, and centered.
And what the text system does is take the character and attributes and convert it into the appropriate glyph or glyphs in the font needed to display this and the position centered, so that we get a nice blue accented a. The text system also deals with all the ways we have of getting text into the system and modifying it, particularly with all the interactions that the user may do with keyboard and mouse and so forth to edit the text. And furthermore, it handles import from and export to a wide variety of text document formats.
So just to orient you, let me point out where we are in the layering of the system. Most of the Cocoa text system of course is in AppKit, many of the fundamental classes are defined in foundation, and those were the toll free bridge that their counterparts at the core foundation level. In addition there's another a framework that's well not new for Leopard, but newly public for Leopard call Core Text which is what the Cocoa text system uses for its low level line layout.
Like many pieces of Cocoa, the text system uses model view controller structure and I'm also going to use that to structure some of the pieces of my talk. At the top of the view level we have classes like NSTextView, the bottom model layer, classes like NSTextStorage, and in the middle are the control objects that mediate between the two like NSLayoutManager and so on.
Now there are a lot of APIs here, it's natural to ask when you're starting where do you want to go first? So here's a brief guide. If you're trying to draw some little piece of text in your view, the natural first place to look is at the API we call a string drawing APIs. I'm going to discuss in a minute.
Or you can use NSCell which in turn uses the string drawing APIs to draw when it needs to draw text. If you want a small bit of editable text, the natural thing to look at would be NSTextField or any of the other controls. If you have large pieces of text, these are editable or not, the natural class to look at is NSTextView.
If you have needs for detailed measurement or specialized drawing of text that beyond what you can get from the string drawing APIs, the place to look is NSLayoutManager at the controller level. I'm going to be talking quite a bit about that later on. Finally there is also Core Text.
Now if you're working in Cocoa, you probably are not going to need to use Core Text directly. It's primarily intended for Carbon developers as a modern replacement for things like QuickDraw and ATSUI so forth. As I said Cocoa, the Cocoa text system does use Core Text. And essentially all the features of Core Text are available for you plus a lot more through the Cocoa text system. But it is there if you want to use it directly.
So let's start at the view level. The view level, the main class in the Cocoa text system is NSTextView. That handles all of the display and editing of all sorts of text. In fact, even a control like NSTextField, if it wants to edit text, it uses NSTextField to do it called a field editor.
And NSTextView is very, very customizable, even if your never subclass at all. It has a large number of options, it has many, many delegate methods and it can be hooked up in a wide variety of configurations. But rather than tell you about it, let me just show you some of that.
( Period of silence )
Take a look at this. So if I just drag an NSTextView into my nib and Interface Builder with zero lines of code, immediately I get all, we want this machine?
( Period of silence )
There we go.
So, if I just drag a text view into my nib in Interface Builder, immediately, with zero lines of code, I have all the features of text edit right at my fingertips in my application. That's rich text, graphics, undo, spell checking, any or all of these that I want for free.
And a few more for Leopard. So for example, automatic detection of URLs, that will be zero lines of code. Tool tips for the URLs, for the links, zero lines of code. How about smart quotes?
( Period of silence )
( Period of silence )
In Leopard, zero lines of code.
But supposing for a minute that you were actually going to write some code. So if you're willing to write, oh say a dozen lines of code, you can start working with multiple instances of NSTextView and NSTextContainer. And you can hook them up in a wide variety of ways. So for example, if you got multiple pages or what I have here is multiple columns.
Going a little further. If you were actually willing to subclass NSTextContainer override two method, maybe another dozen lines of code, you can get all sorts of fancy shapes, arrangements of text. And if you were actually willing to write a custom NS view subclass, maybe 20 lines of code, you can actually put text just about anywhere you want. Oh and one thing I should mention is that the way I've hooked these up, these are not many separate text documents. These are many different views of the same text document. So if I make changes in one, they'll be automatically reflected instantly in all of them.
But really, this is the easy stuff. We've talked about this sort of thing many times at past WWDC sessions. There's copious documentation on it. And there are many examples, many code examples available, including this one. This should be available for download now. The main thing I want to do in this session is to go a little deeper and to explore some more subtle and more thorough going modifications to the text system. So let's go back to the slides.
( Period of silence )
Just for the record, here is a list of some of the principle new feature that we have available through NSTextView in Leopard. But I want to strongly encourage you to take a look at the AppKit release notes you received, and take a look at the headers.
There are many, many new methods that we've added, far more than I could list here right now. All sorts of new individual methods, new options, new delegate methods and so forth. Many of them specific responses, specific requests from you. So go take a look at the headers, go take a look at the release notes. You'll find a lot of nice surprises.
Alright, so now let's start going down a little deeper. Let's go down to the model level. At the model level, the main class of NSText Core Text system is NSTextStorage. That models text document as first of all a string that is an NSString, conceptually of UTF-16 characters plus attributes. So conceptually to each character is associated a dictionary of attributes. And these attributes cover fonts, colors, topography, paragraphs, anything and everything that might affect the way the text is supposed to be laid out and displayed.
Let me back up for a minute and talk a bit about the inheritance hierarchy here. So the basic class is NSAttributedString that is defined in foundation, toll free bridged with CFAttributedString in core foundation and I should mention that CFAttributedString is the principle model class used by Core Text.
So now for the first time we have a unified rich text model across both Carbon and Cocoa. Then the immediate subclass of NSAttributedString is NSMutableAttributedString, which adds mutability, toll free bridged again with CFMutableAttributedString. And then AppKit has a special subclass of NSMutableAttributableString called NSTextStorage. And I'll be getting into its features later.
So how is it you work with NSAttributedString? If you want to get at the characters, then there's a string method that gives you an efficient proxy, an NSString. And you can use all the NSString methods on to get access to the characters of the string, of the AttributedString.
If you want to look at the attributes, well typically it's rare for an attribute to apply to just a single character. Usually they're going to apply to some range, short or long, of characters. So when you ask for the attribute dictionary at a particular index, we return you to that dictionary, but we also return by reference a range, we call the effective range. That is some range over which those, that set of attributes holds. And we don't guarantee it's the longest range, but it's some range over which they are effective.
If you just want a single attribute, again you can just ask for the attribute at a given index, we return it, we also return by reference, again effective range, there's some range for which that attribute holds. If you do want the longest range over which an attribute or set of attributes is valid, then you can ask for it with these longestEffectiveRange methods. It can be a little expensive to compute it especially if the range is very long. So you shouldn't ask for it unless you actually plan to be dealing with that whole range. But it is there if you want it.
Now what are these attributes? As I said, NSAttributedString is defined in foundation. But AppKit adds a great deal to it. And there is an AppKit/NSAttributedString.h header, which is different from the foundation NSAttributedString.h header. And in the AppKit/NSAttributedString header are defined the interesting attributes that the text system understands. For each attribute, there is a string constant, that is the name, the value, the key for that attribute, like NSFontAttributeName, NSForegroundColorAttributeName, NSParagraphStyleAttributeName.
And also is specified what the class is for that, is intended for the values of that attribute. And in addition there's also specified a default value that is going to be used if that attribute doesn't happen to be present. So the default color is black, the default font is Helvetica 12 and so on, so forth.
So one very common programming practice is when you're working with an attributed string, you want to go over it and deal with the different attributes in it and you want to work by ranges of attribute. So this is a common code pattern. Here we start off with a range, range which is the overall range we want to deal with. And we're going to iterate through it by effective ranges for a particular attribute.
In this case, I've chosen to use the ParagraphStyleAttribute which is the one that describes all paragraph level formatting. Line spacing, indents, margins and so forth. So we are going to ask for the ParagraphStyleAttribute at a given index, and here we're getting the longest effective range because we do want to deal with the whole thing.
And that will be our effective range. Now as a default value, NSParagraphStyle DefaultParagraphStyle used if the attribute isn't present. Here I'm not actually doing anything much with it. I'm just logging the value. And then after we've dealt with that effective range, then we go and start over again at the NSMaxRange of the effective range. That is just beyond its end. And get the next value of the attribute and so on by ranges.
So NSMutableAttributedString adds mutability. How can we deal with that? First of all there is a mutableString method that gives an efficient proxy as an NSMutableString that you can use. You can use all of the NSMutableString methods to change the characters. You can also change the characters directly acting on the mutable attributable string with methods like replaceCharactersInRange:withString or withAttributedString. And then there are some convenience methods that do specifically insertion, append and delete and so forth.
If you just want to change attributes, then you can call setAttribute, Attributes:range that will just remove any attributes that are a set these, this set of attributes for that range. Or add attributes to add some dictionary of attributes to the attributes that are already there. Override any that might conflict. Or you can just set a single attribute. Add it or remove it.
So let's go back to our code example. ( Period of silence ) Can we have the slides? Sorry. In this case, we're going to take that same set of code and instead of just logging the value we're going to actually modify it. In this case I've chosen to take that paragraph style and modify it by changing its alignment to center it so to make all the text centered.
So we take the same code as before plus we take that paragraph style for that effective range and we make a mutable copy. And we change it. We call setAlignment, CenterAlignment. And then we take that modified, the copy, and set it back the attribute on the same effective range that the original paragraph style applied to. And then we release it. Very common code pattern when dealing with mutable attributed strings.
So as I said, attributed strings are defined in foundation but a great is added to them in AppKit. And one of the things that is added is in the AppKit/NSStringDrawing.h header. A set of methods that we call the StringDrawing methods, generally. And these include both drawing, simple drawing and measuring of text.
So the basic methods, you just draw an attributed string in a Rect with various options or get it sized again with various options. And again there are a number of convenience methods that take fewer arguments. Some that take just a string and an attribute dictionary instead of an attributed string. These are the first place you should look if you're just trying to draw a piece of text in your view or image or other graphics context.
One thing that I should note when you're using these is that the Cocoa text system uses the attributes defined on attributed strings and not in general where they, where they overlap, not the same things that might be defined on the graphics context. So even if the attributes are not present. So you, for drawing text with the text system, you're never going to be setting the fonts or colors on the graphics context. Instead you'll be setting them as attributes on your attributed strings.
Another thing that the AppKit adds to attributed strings is import from and export to a lot of different text document formats. This includes plain text, RTF, RTFD, HTML, word documents and so on. For Leopard we've added the Open Document format used by Open Office. And the Office Open XML text document format used by Word 2007.
The basic methods for import take either a file URL or an NSData, that would be the contents of the file. And they take a number of options and documentwide attributes and they may return an error. The basic export methods will give you either the data of the document or a file wrapper that you can then write out to disk or do whatever you like with.
Now there are many, many options. There are lots of global documentwide attributes like metadata. And these things are all defined again in the AppKit/AssistDistributedString.h header. I urge you to take a look at it. They are all specified in enormous detail there. Now when you go to import from a rich text document, usually it is not necessary to specify the format that you want to read. The text system can automatically detect all these different formats. You can specify if you want. That's usually just for special cases. For example, if you wanted to read HTML in as source, as plain text, source rather than as the attributed result.
It's a bit different when it comes to encodings for plain text. Fortunately in Leopard we have a new feature. And that is that we have a way of tagging plain text documents according to their encoding using extended attributes. And when NSString writes out a string with an encoding, it will automatically apply this tagging. And NSString and NSAttributedString will use this if it is there when they read in that plain text document much better than having to guess the encoding.
If its not there, of course then you may be forced to specify or if you don't specify we'll try to guess, but it really helps if you can know the encoding of document's in. And just let me put in a plug. If you are planning to use plain text documents, please try wherever possible to use a Unicode encoding like UTF-8, UTF-16.
We also have a command line tool called a textutil that provides access to these import and export features of the Cocoa text system. And it has a man page, you can read it. This is something you can use if you want to use these features from a shell or from a script.
Now in principle, it's possible to have any combination of characters and attributes. But not all combinations of characters and attributes make sense. For example, if you have a surrogate pair, it doesn't really make sense to have different attributes on the low end or the high part of the surrogate pair.
In general, if you have a character cluster like an a with an accent, it doesn't really make sense to have different attributes on the a and the accent. This is not something that the Cocoa text system tries to enforce, but we reserve the right to look at just the attributes on the first character of the cluster. There are some other constraints that can sometimes be enforced.
So the paragraph style attribute that I mentioned describes paragraph level formatting for the text and it really only makes sense if it is actually constant over the whole paragraph. That is from one paragraph break to the next. And so there is a process, we call paragraph style fixing and a method, fix paragraph style attribute and so forth that causes this to be enforced by a setting, the paragraph style.
Again the font that, attribute on a certain characters should be a font that can actually represent those characters. And we have a process again called font fixing and a method that will cause the fonts to be substituted as necessary if they cannot represent the characters to which they are applied. And the text system will substitute a font that can represent those characters.
Also attachments are represented by characters that are supposed to apply to Unicode replacement character FFFC and there's attachment fixing which removed the attachment attributes from any other character. So for all of these, there is a single method, fix attributes in range. And one thing to note about it is that it fixes attributes only by changing attributes. It never changes the underlying characters.
Now I mentioned NSTextStorage as a sub class of NSMutableAttributedString. And it adds two things primarily to NSMutableAttributedString. The first thing is that in an ordinary MutableAttributedString, font attribute fixing happens only when you explicitly ask for it. With NSTextStorage, attribute fixing happens automatically. So whenever you look at the attributes, you can be sure they've been fixed. The second thing that it adds is that whenever an NSTextStorage is changed, it notifies classes in the controller level. It has, maintains a list of NSLayoutManagers. And tells them about its change so that they can keep current any information they may have about the text.
Now it's common that when you're making changes to the text, you may be making many changes at once. And it would be inefficient to notify the layout manager and have it do work for every little change. So what we have is a way for you to batch up all those changes at once and just notify the layout managers at the end.
And the way you do that is to called beginEditing before you sequence of changes and then endEditing when you're done. So here's an example, the same code again. If you're modifying a text storage instead of an arbitrary MutableAttributedString, you start of by calling beginEditing. Then you make all the changes just as before and when you're done you call endEditing.
Now if this text storage happens to be hooked up to an NSTextView, NSTextView adds something. It adds undo. And if you want your change to be undoable, then you also have to notify the text view of what you are doing. And the way you do that is adding a few more lines of code.
First of all, before you want to make your changes, you call out the text view and ask it should change text in range, replacement string or range is replacement strings, you should listen to the answer too. Then you do your changes. One other little thing. The text view adds has, or possibly has its own notion of a default paragraph style that it wants to be used in preference to the system wide default paragraph style.
And then when you're done making your changes, after you've called endEditing, then you call the text view and tell it didChangeText. And that's all you need to do to make it ready for undo. Oh, one other little thing I did here was to set an action name that can be used on the undoManager for the title of the undo menu. Of course, in production code, you would make sure this string was a localized string, right? Now this is starting to look like a lot of code. This is Cocoa. We don't like a lot of code. So we have an easier alternative.
NSTextView implements a method changeAttributes. And you can call this and you, it has one argument, the sender. Is some class that you define, and all the sender has to do is implement a method called convertAttributes. Takes a dictionary, returns a dictionary. And what NSTextView will do is go through and for each set of attributes that needs to be modified, it'll pass in those attributes as a dictionary.
All you do is make your change to those attributes and return it. So for this particular change, centering, all we would have to do is have a center object which would implement convertAttributes to just make this change, center the text for the paragraph style. We wouldn't need to do all of that other stuff. It would be done for us.
So let's talk about customization. Why might I want to customize the text system at the model level with a custom subclass in NSTextStorage? Well, there are several possible reason. One for example, I might want to customize the attribute fixing process. I could have a custom subclass of NSTextStorage and override the attribute fixing and have complete control over it.
Another possibility is that I might have a different way of storing text other than as characters and attribute dictionaries, but I still want to present it to the Cocoa Text system with that sort of interface. In that case I could use a custom text storage that could provide the text system with what it needs.
But store it under an underlying in any form that I want. One sub possibility of this is for example the one that is used by Xcode for its code folding feature. It has an underlying attributed string that represents the entire unfolded text. But then it has a custom subclass of NSTextStorage that presents the folded text derived from that to the text system for display and editing. So now let's go over to the demo machine.
( Period of silence )
( Period of silence )
So the, the system of characters and attributes that we have is fine for a lot of purposes. But what if it doesn't meet your particular needs? Well, you could just change it. What I've got here is a custom NSTextStorage subclass. It works just like an ordinary text storage. For everything in it except for the text color.
And I've specified certain parts of this text as headings and certain parts as sub headings and certain parts as, the rest of it is body text. And so I have this sort of rudimentary style system here, just to show you you can do it. And what this does is for all the heading text, it doesn't apply any color that might be on the text. It applies the heading color.
And for any text that's specified as sub heading, it uses the color that's specified as a sub heading color. And for all the remaining text, uses a color that's specified as a body color. Some nice bright colors. Let's see what this looks like in code. So I have a custom subclass of NSTextStorage. And really there are three methods here that I really need to override. I use a mutable attributable string I call contents to actually store the underlying text.
And then I've replaced characters in range with string. These are the primitives. And that just replaces the characters in the underlying mutable attributed string. And then it calls this method on NSTextStorage, edited range, change in length and that is the method that causes the layout measures to be notified of the change.
Very simple, as is anything you do in any subclass. And likewise with setAttributes, we set the attributes on the underlying contents and then we cause the layout managers to be notified. The real meat of this is in our override of attributes that index effective range that returns the attributes. For every attribute except for the foreground color, we are just going to return the underlying attributes on our contents.
But I have defined a custom attribute that I call headingType. And that specifies whether the text is heading or subheading or body. And whenever I encounter that, then I just synthesize a color based on that as specified elsewhere and I use that as a foreground color. And those are the attributes that are returned. These synthesized set of attributes, I return those to the text system. And those are the attributes that get used. So let's go back to the slides.
( Period of silence )
And now let's go to the controller level and the main class in the Cocoa text system at the controller level is NSLayoutManager. And NSLayoutManager is the boss of the text system. It manages all the processes that go on. You remember that I said that the main job of the text system was to convert characters and attributes into glyphs and their positions. So there are two main processes that are involved here.
One we call glyph generation where we get the glyphs. And the second is layout where we position them into lines. And NSLayoutManager calls on two subsidiary classes. First, NSGlyphGenerator to do the glyph generation and NSTypeSetter to do the layout. And then NSLayoutManager stores all the results. And its NSLayoutManager that actually does the drawing for the view.
So first glyph generation. Now one thing to remember about NSLayoutManager is that it's extremely lazy, just like you and me. It does not calculate a result until it actually needs it. So its not going to do glyph generation until for example some client asks it a question for which it needs to have the glyphs to answer it. Also for Leopard we've added some explicit methods that you can call to explicitly request glyph generation for a specific range.
And when that happens, the layout manager will call on the glyph generator, saying give me the glyphs for this range of characters. And then the glyph generator calls back to the layout manage with the glyphs that it has generated plus some attributes for them. And a layout manager stores them. Now if you want to use glyph generator without layout manager, you could do it. All you have to do is implement these same methods that layout manager does to receive the results from the glyph generator. And there's a protocol for that, glyph storage protocol.
Layout, similarly. Layout is done very lazily. Layout manager is not going to do layout until it actually needs the result. Perhaps some client has asked a question for which it needs layout to give the answer. Or again we have some new explicit calls that you can use to force layout for a particular range of characters or range in the text, the screen.
And when it needs to do layout, the layout manager calls on the typesetter. Typesetter, get me some layout for this range. And then the typesetter calls back to the layout manager, as I'm going to discuss. And the layout manager stores the results. Now there's one very important that is new in Leopard. And that's what we call non-contiguous layout. In Tiger, layout was always contiguous. That is we would never do a layout for one piece of text without having layout for all the text from the beginning of the document to that point.
In Leopard we've added a switch you can turn on. And when the switch is turned on then the layout manager will, is allowed to, doesn't have to but will if it thinks it can do layout for one piece of text without having, necessarily having layout for any other piece of text before or after.
And the reason we implemented this is because it's an enormous performance win for large documents. With this change, with this turned on, text data can easily open and deal with a million line text document. And the reason its not on default is for capability reasons, because it makes significant changes to the behavior of many of the methods in NSLayoutManager. Now if you are using NSLayoutManager only indirectly, say through NSTextView, then you could probably turn this on without worrying much about it.
But if you are actually calling NSLayoutManager's directly, then you will need before try turning this on, you would need to review all your usage of layout manager methods to make sure that, to see whether you need to make any change to respond to this and if you look at the layout manager header, there, it's a bunch of descriptions there of how non-contiguous layout changes behavior of each different method on NSLayoutManager.
So during the layout process, one thing I need to, should mention is that the glyphs that the glyph generator produces aren't necessarily only preliminary, not necessarily final. And the reason is that in many fonts, many writing systems, the actual glyphs that it used depend on context. That is on the other glyphs to the right and left of it in the line. And the glyph generator doesn't know what glyphs are in a line.
It's only the layout manager that puts glyphs in the line. So its, the layout manager has to have, it's only the typesetter that puts the glyphs in the lines. So the typesetter has to have the ability to make contextual changes to the glyphs during the layout process. And it does so by calling again on the layout manager, telling it to insert or remove or rearrange glyphs.
Now in general, it's important to remember the mapping between characters and glyphs is not one to one. It's potentially many to many. A single character can be represented by many glyphs, multiple characters can be represented by one glyph. But we do make the restriction in the layout manager that this mapping is always in order.
That is the glyphs are always stored in logical order, in the same order as the characters from which they are generated. If they need to be reordered say for example for bidirectional text, then we do that by changing their locations and positions in the line, not by changing their indexes in the glyph stream.
( Period of silence )
So after it may have made modification to the glyphs, the typesetter positions them by calling three methods in a particular order. First it tells the layout manager which page, which text container the glyphs have been placed in. Then it tells them what line a particular set of glyphs has been placed in and where that line is and what its size is.
And then for each glyph or each run of glyphs in that line, it tells the layout manager where those glyphs are located. And the layout manager stores all this information. There are a few other things for special circumstances, some glyphs are not shown, some may draw outside of their lines, some may be attachments and the layout manager is told of all these things as well.
So here's a brief example. We have some English language text and some Arabic text. At the top, the underlying characters are the bottom glyphs. There are two things I want to point out. First of all, even in ordinary ASCII English language text, here we have a case where two characters, F and I, are represented by a single glyph, and FI ligature glyph.
Now when we go over to our Arabic text, Arabic is first of all it's written from right to left. Second it's typically a cursive style, handwriting style, writing system. So most glyphs are going to be modified contextually based on the other glyphs to the right and left of them. And you can see that here. First of all, the glyphs remain in order in logical order in the glyph storage, but their positions are reversed, go from right to left plus most of them have a different form in, in context than they would in isolation.
Okay, there's another set of processes that are in some sense the inverse of what we've just discussed. And that's invalidation. The layout manager stores lots of information so it has to know when that information is no longer valid so it can discard it and eventually later recalculate it if necessary.
We have three kinds, display invalidation will tell the view if there's a view to that a certain region needs to be redisplayed. Layout invalidation tells the layout manager that a certain portion of layout isn't valid and can be discarded Glyph invalidation says that the glyphs are no longer valid. And should be discarded plus the layout because that depends on the glyphs.
And if this information needs to be recalculated later, again it'll be done so lazily only as needed. So let me go through an example. The user types a character in some existing text in text view. So the text view gets key down events and eventually ends up inserting that character into the text storage.
Makes a change directly in the text storage. Text storage as I said notifies the layout manager whenever it's changed. That's its job. And then the layout manager discards any information that it had about the layout of the characters around that one that was just typed. So eventually now that text view is going to be, end up being redisplayed. And when that happens, text view is going to say, hey layout manager, what do I need to display? And the layout manager will say, well I don't know that. Ask the glyph generator. Give me some glyphs.
And it will ask the typesetter, give me some layout. And then it will go back to the text view and say, what needs to be displayed, which glyphs need to be displayed? And then finally the text view is going to call on the layout manager to actually display those glyphs.
So the layout manager stores all sorts of information about the layout of text. Anything you might want to know, the layout manager knows all if you ask it. Any of the correspondences between characters and glyphs and positions of glyphs and lines, back and forth. The layout manager has methods to do all these things.
In fact anything that you need to do with measurement of text or also with drawing of text, it goes beyond what the simple string drawing methods can give you, you probably want to go to NSLayoutManager for that, because as I said the layout manager is also what does the display of glyphs for the view. It does it over in this text view. It could do it for your view if you like. There are two main entry points for this. First of all, one for drawBackgroundForFlyphRange:atPoint that does background colors.
It does table borders and backgrounds and so forth. And then there's a second method, drawGlyphsForGlyphRange that does the glyphs and any decorations like underlines and strike throughs that might go over the glyphs. These methods assume that focus is locked on a flipped view or image or other graphics context.
And these are two possible points for overriding. There are some other more specific points. The method showPackedGlyphs:length is specifically for glyph drawing. There are specific methods that you could override for drawing underlines, drawing strike throughs and so forth. There are some other classes that can be overridden for specific kinds of drawing. NSTextAttachmentCell for attachment drawing, NSTextBlock for table cells.
So how can we customize the text system at the controller level? Any of these three classes can be overridden with a custom subclass. You could have a custom glyph generator for example. If you wanted to have full control over the initial mapping between characters and glyphs. You could have a custom typesetter subclass if you want to modify the layout of the lines and the layout of glyphs in lines.
You could have a custom layout manager subclass if you want to make some modifications to any of the drawing that's being done in the text at the level of individual glyphs and individual lines of text. So let's have an example of that. We can go back to the demo machine.
( Period of silence )
So the text system has a lot of different attributes, but what if it doesn't have the one that I want? In this case I've decided that bright colors aren't really enough here. They don't make my headings and subheadings stand out the way I want them to.
So I want to do some extra special fancy drawing behind these headings and subheadings so it really stand out, whoa, like that. So now I've done this with a fancy Bezier paths and gradients to show off some Leopard features. But also to show that you know you could do any kind of crazy drawing that you want right there in the text.
And the way I've done this is that I have a custom layout manager subclass that does my extra special fancy drawing and I have custom typesetter subclass that expands the layout around these lines to make room for my extra special fancy drawing around them. So let's take a look at that in the code.
( Period of silence )
First of all, my custom typesetter subclass. Now NSTypesetter has many different override points so I've chosen one of them here. I've just overridden one method. And all I do is if it's enabled, if it's turned on, then I go back to the text storage and look to see if the line is a special heading or subheading. And if it is, then I just increase the space available by some amount that I know is suitable to make space for my extra special fancy drawing. Very simple.
And then my custom layout manager here the method I've chosen to override is the one I mentioned before for drawing backgrounds because these, this extra special fancy drawing is background behind the text. And again, it's pretty simple. If it's turned on then we're going to go through the text that we're drawing. We're going to go through it line by line.
And see if that is one of our special headings or subheadings and if it is then we're going to take the position and size that line and from it construct a Bezier path and draw that path and a gradient within it to do our extra special fancy drawing.
And then of course we call super to do the normal behavior for everything else. So this is how if the text system doesn't support the kind of drawing that you want it to, you can add just about anything to it that you want.
( Period of silence )
Let's go back to the slides. ( Period of silence ) So for the rest of the talk, I want to turn the stage over to my colleague Aki Inoue whose going to talk about some of the very interesting things you can do with fonts in Cocoa text system.
Thanks Doug.
( Applause )
Hi I'm Aki Inoue from Cocoa group. Today I'd like to discuss font handling in Cocoa especially focusing on customization. Let's get started. There are four principle font handling classes in Cocoa, NSFont, NSFontDescriptor, NSFontManager, and NSFontPanel. I'm going to be discussing each of these later in detail. From the beginning, the font architecture was designed with the Unicode standard in mind. It works with the character exclusively. Since we don't have any legacy font encoding heritage in this environment, we can offer very clean and consistent font API.
And as with other products of Cocoa framework, FontDescriptor, FontManager and FontPanel follows that model view controller design pattern and it gives us straightforward customizability and extensibility for the subsystem. Let's look at, let's talk about NSFont. NSFont is that graphics primitive in Cocoa. This is the object you most likely dealing with when you are handling your style of text. It is roughly equivalent Java AWT font in Java environment or the font handlers in Windows GDI. This is the value for NSFontAttribute name. This is for the font information in your attributed strings.
NSFont encapsulates multiple font information into a single, convenient package. It includes typeface information, size and rendering mode. The size can be specified with a point size and/or transformation, transformation metrics. The font rendering mode consists of graphics rendering appearance settings such as anti aliasing or ideal advancement settings. You can ask NSFont detailed typeface information for rendering or layout.
Those include names, postscript names, following name, you name it. Or you can ask font wide matrix such as ascender, descender, leading. Or you can ask information about each of the glyphs in your font. Such as bounding in box over (unclear) glyph. You can directly access NSFont for those information. However, we are recommending you use the approaches Doug mentioned earlier in this presentation. They are NSDrawing, NSStringDrawing API or NSLayoutManager if you are rendering or measuring text. Since those possibilities provide higher level abstraction, you don't have to deal with going detail of Unicode there.
In Leopard we added some enhancements to NSFont. Prior to Leopard, majority of NSFont instances were cached by the system and never delegated during the lifetime of application. In Leopard, all the font instance follow the Cocoa memory management combination. Subject to the allocation of final revision, depending on your application's memory management conservation.
It gives draw screen more efficient system font users especially if your application needs a large number of typeset usage in your application. But be careful. Since now NSFont objects can be deallocated, it might reveal issues in the application. For example, if you overrelease font instances or productive retain in your variable. It might be deallocated underneath you.
NSFont instances are now toll-free bridged FUI which CTFont from Core Text framework. So that we can now have unified NSFont, unified font representation throughout the system. With NSFontFeatureSettingsAttribute, you can have programming, programmatic access to the advance type review features. Previously only accessible from the typography panel user interface.
And we enhanced the system so your applications can now use other multiple master font or true type variation font. Finally, we completed the API usage clean up process, started in Tiger. NSFont is now fully subclassable. You don't override any of the public API in NSFont object. And customize the behavior in the text system. Now let's talk about other part of Cocoa.
Behind the NSFontPanel view, there is model view controller relationship. NSFontDescriptor is the data model object. And represent each type faces available to your applications. And NSFontManager manages those data object and coordinates with the view objects such as NSFontPanel or NSFont menus. And there's no NSFont menus, just font menu.
NSFontManager, it manage all other font descriptors in your application. When you want to know all the font names available to your applications, ask font manager. When you want to know all the file names that are available to your application, ask the font manager. NSFontManager also manages font collections. You can add, delete, modify font collection. By the way, a font collection is a back of font descriptors. And suppose convenient font manager in facility to your end users.
And NSFontManager can map between type faces. For example, you can map, you can query for mod version of a font. Or you can ask for a different typeface in a different family. And NSFontManager can manage a notion of font select in your application. By managing the current font selection abstractly, it can act as a middle man between the your document object and standard font using interface in Cocoa framework such as NSPanel.
Now let's look at an NSFontPanel. NSFontPanel provides the standard font selection using interface for both Cocoa and Carbon applications. And it has bit point font collection management facility. So end user can manage the font selection right in the panel without going to any special font management application such as Font Book.
It is highly customizable for both end users and programmers. And it gives you access to a variety of features such as typography panel, or text effects such as underline or shadows. Let's talk about customizing NSFontPanel. You can customize font panel in two ways. You can add your own controls to NSFontPanel via calling setAccessoryView. Or you can customize or limit the control available inside of panel depending on your document object using the validateModesForFontPanel method.
Let's talk about, let's take a look at how to add accessory view to your font panel. You can create any view with controls of your choice in Interface Builder and add that view to the font panel using setAccessoryView like this. It's pretty simple. And NSFontPanel just accommodates its frame based on your views frame. This is a normal font panel. And with accessory view. When you want to apply your change in accessory view, just as Doug mentioned in this earlier presentation, you can send change attributes message to your first responder.
The document object reply with convertAttributes multiple time for each of the runs in the document selection.
( Period of silence )
This is how you do it. As with normal action of messages, you can send changeAttributes message to the new target and NSApplication objects write the message appropriate for, appropriate in the creation. It's typical you document object. In the convert attributes, you can return the modified version of the paths (unclear) attributes. Just focusing to the attributes you are interested in.
When you update, you are using interface according to the user selection. You can subclass NSFontManager and implement setSelectedAttributes as multiple message method. On the document object participating in Cocoa text system sends that message to the shared font manager. Inside your algorithm method, you can route the message to your own font accessory view. That's pretty simple. So inside your subclass NSFontManager, you can implement selected attributes is multiple method. Don't forget send message to super so that all the other font are using interface can update its state and you can reroute the message to your own FontAccessoryView.
With your setSelect, setSelectedAttributes with access method with accessory view, it's pretty simple. You can update your user interface depending on your, depending on attributes in you are passing. Of course you can use the Cocoa binding here to further automate that user interface update. Finally by implementing validModesForFontPanel in your document object, you can limit the control available for your, for your document. In this case, I'm already doing NSFontPanel, FaceModeMask, and it's going to be NSFontPanel is going to be displaying just a type face column in addition to the font family column here. Let's talk a look in a demo.
(Unclear)
- Oh.
( Period of silence )
Thank you. This is a slightly modified version of text edit application you are all familiar with. And opening a font panel. As you can see font panel.
( Period of silence )
It's pretty simple. With this accessory view, I can open a drawer with the document object. In this drawer there are a couple of switches you can enable part of the font panels controls. I can disable size and effects. Note that each document implements validate most for font panel and return its own setting. The font panel updates its user interface according to the current document selected. Let's go back to the presentation please.
( Period of silence )
Now finally let me talk about the model object in the font subsystem, NSFontDescriptor. NSFontDescriptor can be considered part of the identity of NSFont instance. With NSFontDescriptor, you can describe what the NSFont is. So by archiving NSFontDescriptor, you can revive what the NSFont was.
NSFontDescriptor is a very rich font query mechanism. Based on the accessory key where you pair pattern, you can query font object inside the application. For example, in this example, I'm specifying LucidaGrande as a family name. And bold as the typeface on the type the font descriptor on the left. And the system returns correctly LucidaGrande Bold typeface with next example, I'm specifying HelveticaNeue as a family name. And it returns all the typefaces belonging to the font family.
Finally, NSFontDescriptor can provide certain aspect of information that doesn't require the graphic system. For example, it can return item defined names such as Postscript names, family names or style characteristics of the typeface such as font traits or weights. Also NSFontDescriptor has certain customizable advanced attributes. With these attributes, the NSFont can change the behavior.
( Period of silence )
Let's take a look one of the attributes here, NSFontFixedAdvanceAttribute. It can specify the advancement used by all the glyphs in a font instantiated from NSFontDescriptor that contains this attribute.
This is how you create NSFont instances that contains advanced attributes. Let's assume this font variable contains a valid font reference. By sending font descriptor, you get back NSFontDescriptor object that describes this NSFont. And I'm getting the advancement for the widest glyph in the font. Box it into NSNumber.
Now create a new dictionary using this body. Here I'm creating copy of the original font descriptor by adding the new advanced attributes. Envelope, I have NSFont instances that's through the fixed-pitch. Let's take a look in a demo. Okay, I have a document that says this is a fixed-pitch font, but obviously this is Times Roman, it's not a fixed-pitch. By specifying this accessory view switch I'm switching, I'm adding the NSFontFixedAdvancedAttribute to the font. And by adjusting the advancement you have a fixed-pitch Times Roman. And you can specify for example for Futura or Arial.
( Period of silence )
As you've seen, NSFonts of Cocoa font subsystem provides enough customizability and extensibility for most of your application needs. So please stick to the standard Cocoa user interface and use NSFont system. That way you can take advantage of all enhancement we're going to do in the future. Let's get back and bring out Doug to wrap this presentation.
( Applause )
Let me just summarize. So the Cocoa text system has a lot of power, a lot of depth and many different ways that you can customize it at many different levels for different uses. In particular, it uses the model view controller organization to provide access point at both, at all of these levels. The model level, the control level and the view level.
For more information, you can look sample code up on the website. Our evangelist isn't here right now I don't believe. But you can contact him at this address. There are some labs coming up. There was a Cocoa lab today, but there's going to be another one tomorrow and we're all going to be there. And for input methods, there is a lab on Friday morning and if you're interested in Core Text, there's another session following this one actually. There's also a lab Friday afternoon.