CONTENTS | PREV | NEXT | Java 2D API |
The Font class represents an instance of a font face from the collection of font faces available on the system. Examples of common font faces include Helvetica Bold and Courier Bold Italic.There is a rich body of information that can be used to describe a font, including its name, the type technology it uses, its version, and its style parameters. Every Font object contains attributes for font name, size, transform, and other font features such as weight and posture. You can access this data directly through the convenience methods provided by the Font class.
The Font class also provides access to metric and outline information through the getGlyphMetrics and getGlyphOutline1 methods. The Shape returned by getGlyphOutline is scaled using the Font size and transform, but does not reflect the transform associated with any Graphics2D object.
Before a piece of text can be displayed, it is necessary to determine exactly where each character should be placed. This is typically managed by the system, which supplies a set of algorithms that compute the layout based on information contained in the font (such as the font metrics) and provided by the client (such as the text itself and the requested point size).The Java 2D API provides text layout facilities that handle most common cases, including text strings with mixed fonts, mixed languages, and bidirectional text. (For more information, see "International Text" on page 47.)
In some situations, you might want to compute the text layout yourself so that you can control exactly what glyphs are used and where they are placed. Using information such as glyph sizes, kerning tables, and ligature information, you can construct your own algorithms for computing the text layout, bypassing the system's layout mechanism.
The GlyphSet class provides a way to display the results of custom layout mechanisms. A GlyphSet object can be thought of as the output of an algorithm that takes a string and computes exactly how the string should be displayed. The system has a built-in algorithm and the Java 2D API lets advanced clients define their own algorithms. Normally, when you construct a StyledString object, you pass in the text you want to be displayed. The system then processes this text and builds a GlyphSet object for you, based on its layout algorithm.
A GlyphSet object is basically an array of glyphs and glyph locations. Glyphs are used instead of characters to provide total control over layout characteristics such as kerning and ligatures. For example, when displaying the string "final", you might want to replace the leading "fi" substring with the ligature "fi". In this case, the GlyphSet object will have fewer glyphs than the number of characters in the original string.
Figure 4-1 and Figure 4-2 illustrate how GlyphSet objects are used with the default layout mechanism and with custom layout mechanisms. In Figure 4-1, the client builds a StyledString object and passes it to the drawString method. The built-in layout algorithm determines which glyphs to use and where each of them should be placed. This information is stored internally using instances of GlyphSet. These GlyphSet objects are then passed to a glyph rendering routine that does the actual drawing.
In Figure 4-2, the client assembles the information necessary to lay out the text. A custom layout algorithm is used to determine which glyphs to use and where they should be placed. In this example, the "fi" substring is replaced with the "fi" ligature. This layout information is then stored in a GlyphSet object. The GlyphSet object is then passed to the drawString method, which passes it directly to the glyph renderer.
To develop software that can be deployed in international markets, you must be able to present text in different languages in a way that conforms to the rules of the appropriate writing system. To do this, you need to be able to properly measure text, divide large blocks of text into individual lines, highlight text selections, and detect hits on text. The Java 2D text and font APIs support these operations, enabling you to develop truly international applications.You use the TextLayout class to lay out international text. The text can contain multiple styles and characters from different writing systems, including Arabic and Hebrew. (Arabic and Hebrew are particularly difficult to display because you must reshape and reorder the text to achieve an acceptable representation.)
Even if you write English-only software, TextLayout simplifies the process of displaying and measuring text. And unless you're a typography expert, when you use TextLayout to display English text you'll get superior typography.
Unless you are working with a monospace font, different characters have different widths. This means that all positioning and measuring of text has to take into account exactly which characters are used, not just how many. For example, to right-align a column of numbers displayed in a proportional font, you can't simply use extra spaces to position the text. To properly align the column, you need to know the exact widths of each number so you can adjust accordingly. If the column was displayed in a monospace font, padding with extra spaces would work fine.Text is often displayed using multiple fonts and styles, such as bold or italic. In this case, even the same character can have different shapes and widths, depending on how it is styled. To properly position, measure, or render text, you need to keep track of each individual character and the style applied to that character. Fortunately, the text layout classes make that easy to do.
Complicated writing systems, such as Hebrew and Arabic, present additional challenges. Each character needs to be properly shaped and ordered. When you're displaying international text, it is particularly important to take the context into account. Drawing or measuring text out of context will produce unacceptable results.
The particular shape used to represent a character is called a glyph. Many different glyphs can be used to represent a single character, depending on the font and style. The glyph that is used can also vary depending on the preceding and following glyphs.For example, in handwritten cursive text, characters take on different shapes depending on how they connect to the surrounding characters.
In some writing systems, particularly Arabic, the context of a glyph must always be taken into account. Unlike in English, cursive forms are mandatory in Arabic; it is unacceptable to present text without using cursive forms.
Depending on the context, these cursive forms can differ radically in shape. For example, the Arabic letter heh has the four cursive forms shown in Figure 4-3.
Although these four forms are quite different from one another, such cursive shape-changing is not fundamentally different from cursive writing in English.In some contexts, two glyphs can change shape even more radically and merge to form a single glyph. For example, most English fonts contain special glyphs that are used when the letter f is followed by certain other characters. The merged glyphs take into account the overhang on the letter f and combine the characters in a natural-looking way, instead of letting the letters simply collide or appear to be too far apart. This type of merged glyph is called a ligature.
Ligatures are also used in Arabic. In fact, the use of some ligatures is mandatory--it is unacceptable to present certain character combinations without using the appropriate ligature. When ligatures are formed from Arabic characters, the shapes change even more radically than they do in English. For example, Figure 4-5 illustrates how the two Arabic characters a form a ligature b.
Glyphs are the visual representations of characters. Shapes, sizes, and positions of glyphs are dependent on their context.
In Java, text is encoded using Unicode character encoding. Text that uses Unicode character encoding is stored in memory in logical order. Logical order is the order in which characters and words are read and written. The logical order is not necessarily the same as the visual order, the order in which the corresponding glyphs appear.The visual order for glyphs in a particular writing system (script) is called the script order. For example, the script order for Roman text is left-to-right and the script order for Arabic and Hebrew is right-to-left.
Some writing systems have other rules for arranging glyphs and words on lines of text in addition to the script order. For example, Arabic and Hebrew numbers run left-to-right, even though the letters run right-to-left. (This means that Arabic and Hebrew, even with no embedded English text, are truly bidirectional.)
A writing system's visual order must be maintained even when languages are mixed together. This is illustrated in Figure 4-6, in which an Arabic phrase is part of an English sentence.
Note: In this and subsequent examples, Arabic and Hebrew text is represented by uppercase letters and spaces are represented by an underscore. Each character is shown in a character box with the corresponding glyphs displayed below it.
Even though they are part of an English sentence, the Arabic words are displayed in the Arabic script order, right-to-left. Because the italicized Arabic word is logically after the Arabic in plain text, it is visually to the left of the plain text.When a a line with a mixture of left-to-right and right-to-left text is displayed, the base direction is significant. The base direction is the script order of the predominant writing system. For example, if the text is primarily English with some embedded Arabic, then the base direction is left to right. If the text is primarily Arabic with some embedded English or numbers, then the base direction is right to left.
The base direction determines the order in which segments of text with a common direction are displayed. In the example shown in Figure 4-6, the base direction is left to right. There are three directional runs in this example: the English text at the beginning of the sentence is left to right, the Arabic text is right to left, and the period is again left to right.
Graphics are often embedded in the flow of text. These inline graphics behave like glyphs in terms of how they affect the text flow and line wrapping. Such inline graphics need to be positioned using the same bidirectional layout algorithm so that they appear in the proper location in the flow of characters.
For more information about the precise algorithm used to order glyphs within a line, see the description of the Bidirectional Algorithm in The Unicode Standard, Version 2.0, Section 3.11.
The TextLayout class manages the positioning and ordering of glyphs for you. You use TextLayout to manage:
At first glance, there seems to be an obvious solution for drawing styled text. Set a drawing position for the text and then for each style run: set the appropriate Font in the graphics context, draw the characters in the style run, and then update the drawing position by the width of the glyphs. (A style run is a substring of characters that share the same style.)However, this approach doesn't work for bidirectional text. Figure 4-7 illustrates what would happen if you used this technique to draw an English sentence that has Arabic text embedded in it. The Arabic text is incorrectly broken into two pieces. The style run boundaries break the directional runs in the embedded Arabic text and the style runs are drawn in the wrong order. (Figure 4-6 shows what this text should look like.)
The correct solution is to use TextLayout, which automatically displays the text with the correct shaping and ordering.The base direction of the text is normally set using an attribute (style) on the text. If that attribute is missing, TextLayout follows the Unicode bidirectional algorithm and derives the base direction from the initial characters in the paragraph.
To correctly shape and order the glyphs representing a line of text, TextLayout must know the full context of the text. If the text fits on a single line, such as a single-word label for a button or menu, or a line in a dialog box, you can construct a TextLayout directly from the text. If you have more text than can fit on a single line or want to break text on a single line into tabbed segments, you cannot construct a TextLayout directly. You must use a LineBreakMeasurer to provide sufficient context.
To draw a styled text string with TextLayout:
In the following example, the user coordinate system is translated to the desired origin and the TextLayout is then drawn at location (0, 0).
myGraphics.translate(x, y);
TextLayout layout = new TextLayout(styledString);
myGraphics.drawString(layout, 0, 0);
In editable text, a caret is used to graphically represent the current insertion point, the position in the text where new characters will be inserted. Typically, a caret is shown as a blinking vertical bar between two glyphs. New characters are inserted and displayed at the caret's location.For simple left-to-right text, you could calculate the caret's horizontal position by adding the widths of the glyphs between the beginning of the line and the insertion point and then draw the caret as a vertical line whose height is the height of the text line.
For bidirectional text, this simple approach would fail because you're adding the widths of glyphs before a character offset, not the widths of glyphs to the left of a glyph offset. If you used this technique to draw the text from our previous example, the caret would be drawn in the wrong place:
Furthermore, if you add the widths of glyphs out of their context, then their metrics will not match the display because shaping might take place.
There is another serious problem with caret display in bidirectional text: insertion offsets on directional boundaries have two possible caret positions. This is illustrated in Figure 4-9. Because the two glyphs that correspond to the character offset are not displayed adjacent to one another, the character offset corresponds to two possible visual positions: after the _ and before the A. If you entered an Arabic character, its glyph would appear immediately before (to the right of) the A; if you entered an English character, its glyph would appear after (to the right) of the _. (In this example, the carets are shown as bars to indicate the glyph that they correspond to.)To handle this type of situation, some systems display dual carets, a primary caret and a secondary caret.
TextLayout automatically handles these caret display complexities. Given an insertion offset, the getCaretShapes method returns a two-element array of Shapes: element 0 contains the primary caret and element 1 contains a secondary caret, if one exists. You then simply draw the caret Shapes.
In the following example, the primary caret's color is different from the secondary caret's color. This is a common way to differentiate the carets.
Shape[] caretShapes = layout.getCaretShapes(hit);
myGraphics.setColor(PRIMARY_CARET_COLOR);
myGraphics.draw(caretShapes[0]);
if (caretShapes[1] != null){
myGraphics.setColor(SECONDARY_CARET_COLOR);
myGraphics.draw(caretShapes[1]);
}
For italic and oblique glyphs, TextLayout produces angled carets. To produce a consistent user experience, these caret positions are also used as the boundaries between glyphs for highlighting and hit-testing.If you would prefer to use your own shapes for carets, you can retrieve the position and angle of the carets from the TextLayout and draw them yourself. You also don't have to use dual carets, you can choose a different TextLayout policy for displaying carets.
Often, a location in device space must be converted to a text offset. For example, when a user clicks the mouse on selectable text, the location of the mouse is converted to a text offset and used as one end of the selection range. Logically, this is the inverse of positioning a caret.Hit testing in simple left-to-right text is essentially a process of measuring glyph widths until the glyph at the correct location is found. This position is then mapped back to a character offset.
Again, the simple approach fails for bidirectional text. A single visual location in the display can correspond to two very different offsets in the source text. Fortunately, the side of the offset can help to distinguish between the two alternatives.TextLayout manages the details of hit testing for you. The hitTestChar method takes x and y coordinates from the mouse as arguments and returns an instance of a TextHitInfo class, which contains the insertion offset for the specified position and the side that the hit was on.
TextHitInfo hit = layout.hitTestChar(x, y);
int insertionOffset = hit.getInsertionOffset();
The insertion offset is the nearest offset in the text: if it is past the end of the line, then the offset at that end is returned.Hit testing and caret positioning are consistent with one another. Maintaining this invariant in italic text, in which the carets are angled, requires both the x and y coordinates of the point. This is why hitTestChar takes both coordinates as parameters, not just the x coordinate. This also enables hitTestChar to handle future extensions that require both coordinates, such as vertical text for Japanese, Chinese, and Korean scripts
A selected range of characters is represented graphically using a highlight region, an area in which glyphs are displayed with inverse video or against a different background color. Highlight regions, like carets, are more complicated for bidirectional text than for simple left-to-right text.In bidirectional text, a contiguous range of characters might not have a contiguous highlight region when displayed. Conversely, a highlight region showing a visually contiguous range of glyphs may not correspond to a single contiguous range of characters.
This results in two strategies for selection highlighting: logical highlighting and visual highlighting. With logical highlighting, the selected characters are always contiguous in the text model, and the highlight region is allowed to be discontiguous. With visual highlighting, there might be more than one range of selected characters but the highlight region is always contiguous.Logical highlighting is simpler for the programmer to use, since the selected characters are always contiguous in the text. TextLayout supports both logical and visual highlighting.
The getLogicalHighlightShape method takes two insertion offsets as parameters and returns a Shape that represents the highlight region for the selection range corresponding to the two offsets. A simple way to display highlighted text is to fill this Shape with the highlight color and then draw the TextLayout over the Shape.
Shape highlightRegion = layout.getLogicalHighlightShape(hit1, hit2);
graphics.setColor(HIGHLIGHT_COLOR);
graphics.fill(highlightRegion);
graphics.drawString(layout, 0, 0);
All text editors allow the user to move the caret with the arrow keys. Users expect the caret to move in the direction of the pressed arrow key. In left-to-right text, moving the insertion offset is simple: the right arrow key increases the insertion offset by one and the left arrow key decreases it by one. In bidirectional text or in text with ligatures, this behavior would cause the caret to jump across glyphs at direction boundaries and move in the reverse direction within different directional runs.
Smooth arrow movement requires that the character offset not move uniformly. For example, if the current insertion offset is within a run of right-to-left characters, the right arrow key will decrease the insertion offset, and the left arrow key will increase it. Directional boundaries are even more complicated and cause the character offset to jump around quite a bit. Figure 4-14 illustrates what happens when the arrow key is used to move the caret across a directional boundary. Progressing through the three screen positions from left to right correspond to progressing through the character offsets 7, then 19, then 18!Certain glyphs should never have a caret between them; instead, the caret should move as though the glyphs represented a single character. For example, there should never be a caret between an o and an umlaut if they are represented by two separate characters. (See The Unicode Standard, Version 2.0, Chapter 5, for more information.)
You can use TextLayout to determine the insertion offset that results from a left or right arrow key being pressed. Given a TextHitInfo object that represents the current insertion offset, the getNextRightHit method returns a TextHitInfo object that represents the correct insertion offset if the right arrow key is pressed. The getNextLeftHit method provides the same information for the left arrow key.
The following example shows how you might move the current insertion offset in response to a right arrow key.
TextHitInfo newInsertionOffset = layout.getNextRightHit(insertionOffset);
if (newInsertionOffset != null) {
Shape[] caretShapes =
layout.getCaretShapes(newInsertionOffset);
// draw carets...
insertionOffset = newInsertionOffset;
}
TextLayout provides graphical metrics for the entire range of text it represents. Metrics available from TextLayout include ascent, descent, leading, advance, visible advance, and the bounding rectangle.Ascent, descent, and leading are font properties. A font's ascent is the distance from the top of the tallest glyph in the font to the baseline. A font's descent is the distance from the baseline to the bottom of the glyph with the lowest descender. The leading is the recommended vertical distance from the bottom of the descenders to the top of the next line when multiple lines are displayed.
More than one font can be associated with a TextLayout: different style runs might use different fonts. The ascent and descent values for a TextLayout are the maximum values of all of the fonts used in the TextLayout. (The computation of the TextLayout's leading is more complicated; it's not just the maximum leading value.)
The advance of a TextLayout is its length: the distance from the left edge of the leftmost glyph to the right edge of the rightmost glyph. The advance is sometimes referred to as the total advance. The visible advance is the length of the TextLayout without its trailing whitespace.
The bounding box of a TextLayout encloses all of the text in the layout. It includes all the visible glyphs and the caret boundaries. (Some of these might hang over the origin or origin + advance). The bounding box is relative to the origin of the TextLayout, not to any particular screen position.
In the following example, the text in a TextLayout is drawn within the layout's bounding box.
graphics.drawString(layout, 0, 0);
Rectangle2D bounds = layout.getBounds();
graphics.drawRect(bounds.getX()-1, bounds.getY()-1,
bounds.getWidth()+2, bounds.getHeight()+2);
TextLayout is also used to display a piece of text across multiple lines. For example, you might take a paragraph of text, line-wrap the text to a certain width, and display the paragraph as multiple lines of text.To do this, you do not directly create the TextLayouts that represent each line of text--LineBreakMeasurer generates them for you. Bidirectional ordering cannot always be performed correctly unless all of the text in a paragraph is available. LineBreakMeasurer encapsulates enough information about the context to produce correct TextLayouts.
When text is displayed across multiple lines, the length of the lines is usually determined by the width of the display area. Line breaking (line wrapping) is the process of determining where lines begin and end, given a graphical width in which the lines must fit.
The most common strategy is to place as many words on each line as will fit. This strategy is implemented in LineBreakMeasurer. Other more complex line break strategies use hyphenation, or attempt to minimize the differences in line length within paragraphs. These alternative strategies can be implemented by using low-level calls.
To break a paragraph of text into lines, you construct a LineBreakMeasurer with the entire paragraph and then call nextLayout to step through the text and generate TextLayouts for each line.
To do this, LineBreakMeasurer maintains an offset within the text. Initially, the offset is at the beginning of the text. Each call to nextLayout moves the offset by the character count of the TextLayout that was created. When this offset reaches the end of the text, nextLayout returns null.
The visible advance of each TextLayout that the LineBreakMeasurer creates doesn't exceed the specified line width. By varying the width you specify when you call nextLayout, you can break text to fit complicated areas, such as an HTML page with images in fixed positions or tab-stop fields. You can also pass in a BreakIterator to tell LineBreakMeasurer where valid breakpoints are; if you don't supply one the BreakIterator for the default locale is used.
In the following example, a bilingual text segment is drawn line by line. The lines are aligned to either to the left margin or right margin, depending on whether the base text direction is left-to-right or right-to-left.
Point2D pen = initialPosition;
LineBreakMeasurer measurer = new LineBreakMeasurer(styledText, myBreakIterator);
while (true) {
TextLayout layout = measurer.nextLayout(wrappingWidth);
if (layout == null) break;
pen.y += layout.getAscent();
float dx = 0;
if (layout.isLeftToRight())
dx = wrappingWidth - layout.getAdvance();
layout.draw(graphics, pen.x + dx, pen.y);
pen.y += layout.getDescent() + layout.getLeading();
}
.