5.1 Text System Architecture

The following four classes make up the core architecture of Cocoa's text handling system:

  • NSTextStorage is the backbone data model responsible for storing text.

  • NSTextView is responsible for presentation in the view.

  • NSLayoutManager and NSTextContainer act as controllers between the model and the view.

The relationship between these core classes is based on the same Model-View-Controller (MVC) pattern used throughout the Application Kit (and discussed in Chapter 3). Figure 5-2 shows the division of responsibilities in these four classes using the MVC pattern.

Figure 5-2. How the four core text system classes relate to one another in the MVC pattern
figs/cocn_0502.gif

Figure 5-2 shows the relationship between the four classes, but doesn't show the one-to-many relationship that may exist between instances of these classes. Instances of NSTextStorage own and manage one or more NSLayoutManager objects. Similarly, each instance of NSLayoutManager owns one or more NSTextContainer objects, while each text container object is paired with an NSTextView object. The nature of these relationships is what gives Cocoa's text handling system much of its flexibility and power.

5.1.1 NSTextView

NSTextView represents the view, or presentation, layer of the text system; it is the class that facilitates user interaction with the text system. User interaction consists of displaying text onscreen and allowing the user to manipulate what is seen in the text view. NSTextView is a subclass of NSText, which inherits from NSView, which means that text rendering is handled by Quartz. NSTextView provides support for more advanced interactivity features such as drag and drop, rulers, spell checking, cut-and-paste, and speech. It is not only the frontend to the text system, but it is an interface between the text system and almost every relevant Mac OS X technology.

You can create instances of NSTextView within Interface Builder or by using one of two methods:

initWithFrame:

This method creates the entire network of objects, including the text storage object, layout manager, and text container. When you create a text view within Interface Builder, the entire collection of text system objects is set up in this way.

initWithFrame:textContainer:

This method sets the text-view frame and associates the text view with the specified text container. This method is the designated initializer for NSTextView.

5.1.2 NSTextStorage

NSTextStorage makes up the data storage layer for the text system. NSTextStorage's data is stored as a sequence of Unicode characters, which makes the text system capable of localizing an application in any language. Unicode also contains character sets for mathematics and other technical fields. To see the huge number of characters that Unicode can represent,[1] launch Mac OS X's Character Palette from the Input menu, as shown in Figure 5-3.

[1] You can find out more information about Unicode, including the characters that can be represented, at http://www.unicode.org.

Figure 5-3. A tiny selection of Unicode characters in the Character Palette
figs/cocn_0503.gif

NSTextStorage is a subclass of NSMutableAttributedString. Every character in the text storage, therefore, is associated with a set of attributes that define appearance characteristics such as font and color (a single attributes dictionary will probably be applicable to a range of characters, but it might have a different set of attributes for each character). Cocoa defines a standard set of attributes, which were enumerated in Table 4-2. Additionally, developers may choose to assign their own application specific attributes to text, which could support features such as syntax coloring.

As mentioned earlier, NSTextView contains action methods that let controls change the appearance and layout of a selected region of text. These action methods let controls in the user interface (such as a bold-italic-underline button group, or the font and color panels) interact with the contents of the text view. However, using NSTextView's API to effect these attribute changes programmatically is inefficient since those methods are intended for use as user interface actions; it is preferable to use the API provided by NSMutableAttributedString. For example, the method setAttributes:range: takes a dictionary with attribute key-value pairs and a range to which these attributes should be applied. Chapter 2 discusses attributed strings in more detail.

5.1.3 NSLayoutManager

The job of NSLayoutManager is to accurately map characters and glyphs and lay out the resulting glyphs in text containers managed by the layout manager. Figure 5-4 shows a ligature for "Th" in the font Snell Roundhand and illustrates the mapping of characters into glyphs.

Figure 5-4. Mapping Unicode character codes into glyphs
figs/cocn_0504.gif

The distinction between characters and glyphs is important, as it represents the intersection between the text-system's data and view layers. Glyphs, unlike Unicode character codes, can take on many forms, the visual appearance of which depends on the attributes of a particular character such as its font, the other characters around that character, and how ligatures are handled in the font being rendered. For example, the glyph for the letter "T" in the Times font is quite different for the glyph the Zapfino font defines for the same letter. Moreover, multiple characters in a sequence may actually define a single glyph. This is especially true in nonwestern alphabets and in fonts that define ligatures for certain pairs of letters.

You can find an extreme case of a multi-character glyph in the Zapfino font. Open a TextEdit window, change the active font to Zapfino, and then type the font's name. You'll see multiple glyph substitutions as you type the word, culminating in the use of a single glyph for the entire wordZapfino's signature.

The flow of information with NSLayoutManager goes in two directions. You just read about the flow from the data model to the view; however, experience shows that information must flow from the view to the data layer whenever you alter the content by typing, making selections, or changing formatting. To facilitate this, NSLayoutManager must be able reconcile the position of selections and the cursor in the glyph stream with character ranges in the storage layer.

The NSLayoutManager class has nearly 100 instance methods. Most of these methods are responsible for mapping characters to glyphs, setting attributes of glyphs, and controlling how they are laid out in the view. The API discussed here are the methods that control the text containers that define where text is laid out.

5.1.4 NSTextContainer

NSTextContainer defines regions for text display. NSTextContainer's default implementation defines rectangular text regions. However, developers may subclass NSTextContainer to provide an implementation that supports irregular layouts. For example, you could subclass NSTextContainer to support text layout on circular pages instead of rectangularstrange, but true.

Layout managers store text containers in an indexed arraythe order of the text containers in the array is significant, as it determines the order in which the layout manager fills the containers with text. When the first container is filled with text, the layout manager moves to the next, and continues with the remaining containers.

5.1.5 How Text Is Laid Out

When laying out text, NSLayoutManager first converts a run of characters into a mapped sequence of glyphs. Once the layout manager knows exactly what needs to be laid out within a text container, it can check with the text container object for guidance in this layout. To do this, NSLayoutManager determines the bounding rectangle of the line of glyphs and passes it to the current text container as a proposed layout rectangle. The text container looks at this proposed rectangle and compares it to its own bounding rectangle. For example, if the proposed rectangle is too long, the text container returns the largest available rectangle for the current line in the text container to the layout manager. Additionally, the text container returns a remainder rectangle, which is the difference between the proposed rectangle and the accepted rectangle. NSLayoutManager repeats the proposal process with the remainder rectangle, and each successive remainder rectangle until the layout is complete.

When determining how to modify the proposed rectangle, NSTextContainer takes into account the direction in which the glyphs are sequenced in a line, and the direction lines are placed relative to their preceding lines. These directions are referred to as the line sweep direction and line movement direction, respectively. When a text container modifies the proposed rectangle, the text container can shorten the rectangle from the direction of the line sweep, and it is allowed to shift the rectangle in the direction of the line movement. By adhering to these rules, NSTextContainer and NSLayoutManager can break up a continuous line of glyphs into an arranged set of lines that can be rendered in a view. There is a clear division of responsibility here:

  • NSLayoutManager is responsible for mapping the characters to glyphs with all the attributes applied.

  • NSTextContainer is used by the layout manager to break up the glyph line into a series of lines that fit snugly into the region represented by the text container.

The method in NSTextContainer that performs these functions is:

lineFragmentRectForProposedRect:sweepDirection:
              movementDirection:remainingRect:

The sweepDirection: argument is of type NSLineSweepDirection, and the movementDirection: argument is of type NSLineMovementDirection. NSTextContainer returns the remainder rectangle to the sender through the remainingRect: argument, which is a pointer to an NSRect structure. Subclasses override this method to perform custom layout. If the text container object determines that the proposed rectangle cannot fit into the container, then the constant NSZeroRect is returned.



    Part II: API Quick Reference