Java Technology Home Page
A-Z Index

Java Developer Connection(SM)
Online Training

Downloads, APIs, Documentation
Java Developer Connection
Tutorials, Tech Articles, Training
Online Support
Community Discussion
News & Events from Everywhere
Products from Everywhere
How Java Technology is Used Worldwide
Print Button
 
Training Index

2D Text Tutorial
Lesson 4: Foreign Language Support

By Monica Pawlan

[<<BACK] [CONTENTS] [NEXT>>]

The java.awt.font.TextLayout object lets you draw styled text in any language or script supported by The Unicode Standard--a global character coding system for handling diverse modern, classical, and historical languages. When drawing text, the direction the text is read must be taken into account so all words in the the string display correctly. A TextLayout object maintains the direction of the text and correctly draws it no matter if the string runs left-to-right, right-to-left, or both (bidirectional).

Arabic and Hebrew are bidirectional because their text runs right-to-left and their numbers run left-to-right. Also, any string with embedded text that runs in the opposite direction from the main text (English with embedded Arabic text, for example), is bidirectional.

Bidirectional text presents interesting problems for correctly positioning carets, accurately locating selections, and correctly displaying multiple lines. Also bidirectional and right-to-left text present similar problems for moving the caret in the correct direction in response to right and left arrow key presses.

This lesson describes these issues and demonstrates how a TextLayout object takes care of them. See the International Text in JDK 1.2 paper for more details on the information presented in this lesson.

About the Examples

The following examples support foreign language text with the -text option. Valid -text values are hebrew, english, mixed, arabic, longenglish, and longhebrew. To see the foreign language text, you need to install a unicode font like Bitstream Cyberbit on your system. For example, with a unicode font set up on your system, you can start the DrawSample application as follows to see a text string in Hebrew:

java DrawSample -text hebrew
Hebrew
The SampleUtils.java source code provides text in various languages and other things used by the example applications.

Inserting Text

In editable text, a caret displays where the end user clicks to indicate the insertion point where the end user will enter text. When the insertion point falls between right-to-left text like Arabic and left-to-right text like English, the same character location in the source text (shown on top) maps to two insertion points on the display (shown on the bottom). One location is the insertion point for English text and the other location is the insertion point for Arabic text. In the figure, character location 8 in the source text maps to the space after the word is or the first character in the right-to-left Arabic text in the displayed text.


Note: The Arabic text is in bold capitalized English to help people who do not read Arabic understand the point better.

The TextLayout object ensures the inserted text appears in the correct location on the display based on which character was hit, the side on which the characer was hit, and the language the end user enters.

The next two figures display the same text, which consists of mainly right-to-left text with two left-to-right words (Hello and Arabic) embedded. When the end user clicks on the o in Hello or on the space after the o, dual carets display.

Dual carets consist of a strong and weak caret, and in the figures, the strong caret is red and the weak caret is black. The carets represent boundaries between glyphs for selection highlighting, hit testing, and moving the caret with arrow keys. The TextLayout object draws dual carets because the end user clicked on a directional boundary where right-to-left text meets left-to-right text. If the end user had clicked on a non-directional boundary, the TextLayout object would have drawn a single caret at that location.


Note: If you do not want to use dual carets, you can extend the TextLayout.CaretPolicy class to use something other than dual carets to mark directional boundaries.

Character Hit, Side, and Language

A click on the o on the side of the o towards the Hebrew records that the end user clicked after the o, which is part of the English. This positions the weak (black) caret next to the o and the strong caret (red) in front of the H. If the end user enters English, it appears after the o, and if the end user enters Hebrew, it appears before the H.

A click on the space to the right of the o records that the end user clicked the space, which is part of the Hebrew. This positions the strong (red) caret next to the o and the weak caret (black) in front of the H. If the end user types English, it appears before the H, and if the end user types Hebrew, it appears after the o.


Note: The insertion offset is the nearest one in the text. If it is off one end of the line, the offset at that end is returned.

Caret Positioning

You might be wondering why the caret positions do not include the spaces on either side. Spaces are either left-to-right or right-to-left characters depending on what is next to them. If the characters on both sides of a space are the same kind of character, the space is that kind of character too. Spaces between Arabic words are treated like Arabic characters, and spaces between English words are treated like English characters. When the characters on both sides are different, spaces are treated like the overall direction of the paragraph: If the paragraph as a whole is left-to-right, the space is left-to-right, and if the paragraph as a whole is right-to-left, the space is right-to-left.

In the Hit Test sample, the overall text is right-to-left. The spaces on each side of Hello each have one neighbor that is left-to-right (the English) and one that is right-to-left (the Hebrew). Because the text is right-to-left, the spaces are right-to-left too, and the split carets appear next to the o and H because the spaces being right-to-left belong to the right-to-left text on either side.

Hit Testing

In code, a point returned by a mouse click is passed to the TextLayout.hitTestChar method, which returns a TextHitInfo object that represents the character and side of the character where the end user clicked. If the end user clicks on the o, the hit is to position 5.

However, in the source text, position 5 is before the H, so the TextLayout object checks the TextHitInfo object to find out what side of the character the hit is on and displays the dual carets if the hit is on the side of the o towards the Hebrew. If the hit is on the side of the o towards the l, a single caret is drawn between the l and the o to indicate that the end user will insert English text at that location.

The source text is initialized the way the words are spoken, and not the way they are printed. The source text looks like this:

"\u05D0\u05E0\u05D9 Hello 
   \u05DC\u05D0 \u05DE\u05D1\u05D9\ u05DF " +
"\u05E2\u05D1\u05E8\u05D9\u05EA Arabic 
   \u0644\u0645\u062C\ u0645\u0648\u0639\u0629", map);

The first three unicode characters and the space \u05D0\u05E0\u05D9 define what you see on the display starting on the right side up to the o. You can see that the H is next in the source code, but the o is next on the display. This is because in the right-to-left run, the English word Hello is turned around to display correctly in an embedded left-to-right run.

Determining the location of the insertion point is taken care of by the TextLayout object. All you need to do in your code is specify the caret colors, get the mouse click location, return a TextHitInfo object, and draw the layout and carets.

Here is the code to initialize the colors for the strong and weak carets:

private static final Color 
  STRONG_CARET_COLOR = Color.red;
private static final Color 
  WEAK_CARET_COLOR = Color.black;
Here is the code that draws the TextLayout and the carets. The insertionPoint variable is retrieved from a TextHitInfo object in the HitTestMouseListener method. If the insertion point is not between text running in different directions, only the strong caret draws.
// Draw textLayout.
textLayout.draw(graphics2D, 0, 0);

// Retrieve caret Shapes for insertionIndex.
Shape[] carets = 
  textLayout.getCaretShapes(insertionIndex);

// Draw the carets.  carets[0] is the strong caret, and
// is never null.  carets[1], if it is not null, is the
// weak caret.
graphics2D.setColor(STRONG_CARET_COLOR);
graphics2D.draw(carets[0]);

if (carets[1] != null) {
  graphics2D.setColor(WEAK_CARET_COLOR);
  graphics2D.draw(carets[1]);
}
Here is the HitTestMouseListener method:
private class HitTestMouseListener 
    extends MouseAdapter {

  /**
   * Compute the character position of the mouse click.
  */
  public void mouseClicked(MouseEvent e) {

    Point2D origin = computeLayoutOrigin();

    // Compute the mouse click location relative to
    // textLayout's origin.
    float clickX = (float) (e.getX() - origin.getX());
    float clickY = (float) (e.getY() - origin.getY());

    // Get the character position of the mouse click.
    TextHitInfo currentHit = 
      textLayout.hitTestChar(clickX,  clickY);
    insertionIndex = currentHit.getInsertionIndex();

    // Repaint the Component so the new caret(s) 
    // will be displayed.
    repaint();
  }
}
And here is the complete HitTestSample.java source code.

Selection Highlighting

The next figure shows how a contiguous range of characters in the source text (on the top) might not map to a contiguous highlight region (on the bottom) on screen if the selection range includes left-to-right and right-to-left characters. When the Arabic text is turned around to run right-to-left on the display, the selected portion of the Arabic text is not contiguous with the is and space before it, althout these characters are contiguous in the source text.

The next figure shows how a contiguous highlight region on the display (on the bottom) might not map to a single contiguous range of characters in the source text (on the top). This point is illustrated in the next figure.

A TextLayout object provides two strategies for selection highlighting to handle these two situations: logical highlighting and visual highlighting.

With logical highlighting, the selected characters are always contiguous in the source text, and the highlight region is allowed to be discontiguous on the display. 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 programmers to use because the selected characters are always contiguous in the source. The TextLayout.getLogicalHighlightShape method takes two insertion offsets and returns a Shape that represents the highlight region marked by the two offsets. A recommended way to show highlighting is to fill the Shape with the highlight color, and then draw the TextLayout over the Shape.

Here is the code to get and draw the selection range:

// Retrieve highlight region for selection range.
Shape highlight = 
  textLayout.getLogicalHighlightShape(
  anchorEnd, activeEnd);
// Fill the highlight region with the highlight color.
graphics2D.setColor(HIGHLIGHT_COLOR);
graphics2D.fill(highlight);
Here is the complete SelectionSample.java source code.

Moving the Caret

In bidirectional text, the cursor should move smoothly through the text on the display in the direction that corresponds to the direction of the Arrow key being pressed. The problem, is that right-to-left text is positioned in the source text in the direction it is spoken and not in the direction it is displayed. For a caret to have a smooth journey across the display, the character offset does not move smoothly through the source text. This point is illustrated by the figure.

Progressing through the three screen positions shown on the bottom of the figure from left-to-right corresponds to progressing through the character offsets in the source text in the order of 7, 19, and 18.

The TextLayout object handles the details for you. All your code needs to do is update the insertion index in response to an arrow key press:

private class ArrowKeyListener extends KeyAdapter {
  /**
* Update the insertion index in response to an arrow key.
  */        
  private void handleArrowKey(boolean rightArrow) {
    TextHitInfo newPosition;
    if (rightArrow) {
      newPosition = 
        textLayout.getNextRightHit(insertionIndex);
    }
    else {
      newPosition = 
        textLayout.getNextLeftHit(insertionIndex);
    }

    // getNextRightHit() / getNextLeftHit() will 
    // return null if there is not a caret position 
    // to the right (left) of the current position.
    if (newPosition != null) {
      // Update insertionIndex.
      insertionIndex = newPosition.getInsertionIndex();
      // Repaint the Component so the new caret(s) 
      // will be displayed.
      repaint();
    }
  }
  public void keyPressed(KeyEvent e) {
    int keyCode = e.getKeyCode();
    if (keyCode == KeyEvent.VK_LEFT || 
      keyCode == KeyEvent.VK_RIGHT) {
        handleArrowKey(keyCode == KeyEvent.VK_RIGHT);
     }
  }
}
Here is the complete ArrowKeySample.java source code.

Multiline Text

As you learned in the Draw Multiple Lines of Text section of Lesson 2, a LineBreakMeasurer breaks a paragraph of styled text into lines to fit into the display area. The LineBreakMeasurer object encapsulates enough information about bidirectional text to produce a correct TextLayout without any additional code on your part.

Here is the complete LineBreakSample.java source code.


Print Button
[ This page was updated: 21-Sep-2000 ]
Products & APIs | Developer Connection | Docs & Training | Online Support
Community Discussion | Industry News | Solutions Marketplace | Case Studies
Glossary | Feedback | A-Z Index
For more information on Java technology
and other software from Sun Microsystems, call:
(800) 786-7638
Outside the U.S. and Canada, dial your country's AT&T Direct Access Number first.
Sun Microsystems, Inc.
Copyright © 1995-2000 Sun Microsystems, Inc.
All Rights Reserved. Terms of Use. Privacy Policy.