KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > text > TextLayoutStrategy


1 /*
2  * @(#)TextLayoutStrategy.java 1.22 04/05/05
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7 package javax.swing.text;
8
9 import java.util.*;
10 import java.awt.*;
11 import java.text.AttributedCharacterIterator JavaDoc;
12 import java.text.BreakIterator JavaDoc;
13 import java.awt.font.*;
14 import java.awt.geom.AffineTransform JavaDoc;
15 import javax.swing.event.DocumentEvent JavaDoc;
16 import sun.font.BidiUtils;
17
18 /**
19  * A flow strategy that uses java.awt.font.LineBreakMeasureer to
20  * produce java.awt.font.TextLayout for i18n capable rendering.
21  * If the child view being placed into the flow is of type
22  * GlyphView and can be rendered by TextLayout, a GlyphPainter
23  * that uses TextLayout is plugged into the GlyphView.
24  *
25  * @author Timothy Prinzing
26  * @version 1.22 05/05/04
27  */

28 class TextLayoutStrategy extends FlowView.FlowStrategy JavaDoc {
29
30     /**
31      * Constructs a layout strategy for paragraphs based
32      * upon java.awt.font.LineBreakMeasurer.
33      */

34     public TextLayoutStrategy() {
35     text = new AttributedSegment();
36     }
37
38     // --- FlowStrategy methods --------------------------------------------
39

40     /**
41      * Gives notification that something was inserted into the document
42      * in a location that the given flow view is responsible for. The
43      * strategy should update the appropriate changed region (which
44      * depends upon the strategy used for repair).
45      *
46      * @param e the change information from the associated document
47      * @param alloc the current allocation of the view inside of the insets.
48      * This value will be null if the view has not yet been displayed.
49      * @see View#insertUpdate
50      */

51     public void insertUpdate(FlowView JavaDoc fv, DocumentEvent JavaDoc e, Rectangle alloc) {
52         sync(fv);
53         super.insertUpdate(fv, e, alloc);
54     }
55
56     /**
57      * Gives notification that something was removed from the document
58      * in a location that the given flow view is responsible for.
59      *
60      * @param e the change information from the associated document
61      * @param alloc the current allocation of the view inside of the insets.
62      * @see View#removeUpdate
63      */

64     public void removeUpdate(FlowView JavaDoc fv, DocumentEvent JavaDoc e, Rectangle alloc) {
65         sync(fv);
66         super.removeUpdate(fv, e, alloc);
67     }
68
69     /**
70      * Gives notification from the document that attributes were changed
71      * in a location that this view is responsible for.
72      *
73      * @param changes the change information from the associated document
74      * @param a the current allocation of the view
75      * @param f the factory to use to rebuild if the view has children
76      * @see View#changedUpdate
77      */

78     public void changedUpdate(FlowView JavaDoc fv, DocumentEvent JavaDoc e, Rectangle alloc) {
79         sync(fv);
80         super.changedUpdate(fv, e, alloc);
81     }
82
83     /**
84      * Does a a full layout on the given View. This causes all of
85      * the rows (child views) to be rebuilt to match the given
86      * constraints for each row. This is called by a FlowView.layout
87      * to update the child views in the flow.
88      *
89      * @param v the view to reflow
90      */

91     public void layout(FlowView JavaDoc fv) {
92         super.layout(fv);
93     }
94     
95     /**
96      * Creates a row of views that will fit within the
97      * layout span of the row. This is implemented to execute the
98      * superclass functionality (which fills the row with child
99      * views or view fragments) and follow that with bidi reordering
100      * of the unidirectional view fragments.
101      *
102      * @param row the row to fill in with views. This is assumed
103      * to be empty on entry.
104      * @param pos The current position in the children of
105      * this views element from which to start.
106      * @return the position to start the next row
107      */

108     protected int layoutRow(FlowView JavaDoc fv, int rowIndex, int p0) {
109     int p1 = super.layoutRow(fv, rowIndex, p0);
110     View JavaDoc row = fv.getView(rowIndex);
111     Document JavaDoc doc = fv.getDocument();
112     Object JavaDoc i18nFlag = doc.getProperty(AbstractDocument.I18NProperty);
113     if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) {
114         int n = row.getViewCount();
115         if (n > 1) {
116         AbstractDocument JavaDoc d = (AbstractDocument JavaDoc)fv.getDocument();
117         Element JavaDoc bidiRoot = d.getBidiRootElement();
118         byte[] levels = new byte[n];
119         View JavaDoc[] reorder = new View JavaDoc[n];
120         
121         for( int i=0; i<n; i++ ) {
122             View JavaDoc v = row.getView(i);
123             int bidiIndex =bidiRoot.getElementIndex(v.getStartOffset());
124             Element JavaDoc bidiElem = bidiRoot.getElement( bidiIndex );
125             levels[i] = (byte)StyleConstants.getBidiLevel(bidiElem.getAttributes());
126             reorder[i] = v;
127         }
128         
129         BidiUtils.reorderVisually( levels, reorder );
130         row.replace(0, n, reorder);
131         }
132     }
133     return p1;
134     }
135
136     /**
137      * Adjusts the given row if possible to fit within the
138      * layout span. Since all adjustments were already
139      * calculated by the LineBreakMeasurer, this is implemented
140      * to do nothing.
141      *
142      * @param r the row to adjust to the current layout
143      * span.
144      * @param desiredSpan the current layout span >= 0
145      * @param x the location r starts at.
146      */

147     protected void adjustRow(FlowView JavaDoc fv, int rowIndex, int desiredSpan, int x) {
148     }
149
150     /**
151      * Creates a unidirectional view that can be used to represent the
152      * current chunk. This can be either an entire view from the
153      * logical view, or a fragment of the view.
154      *
155      * @param fv the view holding the flow
156      * @param startOffset the start location for the view being created
157      * @param spanLeft the about of span left to fill in the row
158      * @param rowIndex the row the view will be placed into
159      */

160     protected View JavaDoc createView(FlowView JavaDoc fv, int startOffset, int spanLeft, int rowIndex) {
161     // Get the child view that contains the given starting position
162
View JavaDoc lv = getLogicalView(fv);
163     View JavaDoc row = fv.getView(rowIndex);
164     boolean requireNextWord = (row.getViewCount() == 0) ? false : true;
165     int childIndex = lv.getViewIndex(startOffset, Position.Bias.Forward);
166     View JavaDoc v = lv.getView(childIndex);
167
168     int endOffset = getLimitingOffset(v, startOffset, spanLeft, requireNextWord);
169     if (endOffset == startOffset) {
170         return null;
171     }
172
173     View JavaDoc frag;
174     if ((startOffset==v.getStartOffset()) && (endOffset == v.getEndOffset())) {
175         // return the entire view
176
frag = v;
177     } else {
178         // return a unidirectional fragment.
179
frag = v.createFragment(startOffset, endOffset);
180     }
181
182     if ((frag instanceof GlyphView JavaDoc) && (measurer != null)) {
183         // install a TextLayout based renderer if the view is responsible
184
// for glyphs. If the view represents a tab, the default
185
// glyph painter is used (may want to handle tabs differently).
186
boolean isTab = false;
187         int p0 = frag.getStartOffset();
188         int p1 = frag.getEndOffset();
189         if ((p1 - p0) == 1) {
190         // check for tab
191
Segment JavaDoc s = ((GlyphView JavaDoc)frag).getText(p0, p1);
192         char ch = s.first();
193         if (ch == '\t') {
194             isTab = true;
195         }
196         }
197         TextLayout tl = (isTab) ? null :
198         measurer.nextLayout(spanLeft, text.toIteratorIndex(endOffset),
199                     requireNextWord);
200         if (tl != null) {
201         ((GlyphView JavaDoc)frag).setGlyphPainter(new GlyphPainter2 JavaDoc(tl));
202         }
203     }
204     return frag;
205     }
206
207     /**
208      * Calculate the limiting offset for the next view fragment.
209      * At most this would be the entire view (i.e. the limiting
210      * offset would be the end offset in that case). If the range
211      * contains a tab or a direction change, that will limit the
212      * offset to something less. This value is then fed to the
213      * LineBreakMeasurer as a limit to consider in addition to the
214      * remaining span.
215      *
216      * @param v the logical view representing the starting offset.
217      * @param startOffset the model location to start at.
218      */

219     int getLimitingOffset(View JavaDoc v, int startOffset, int spanLeft, boolean requireNextWord) {
220     int endOffset = v.getEndOffset();
221
222     // check for direction change
223
Document JavaDoc doc = v.getDocument();
224     if (doc instanceof AbstractDocument JavaDoc) {
225         AbstractDocument JavaDoc d = (AbstractDocument JavaDoc) doc;
226             Element JavaDoc bidiRoot = d.getBidiRootElement();
227             if( bidiRoot.getElementCount() > 1 ) {
228                 int bidiIndex = bidiRoot.getElementIndex( startOffset );
229                 Element JavaDoc bidiElem = bidiRoot.getElement( bidiIndex );
230                 endOffset = Math.min( bidiElem.getEndOffset(), endOffset );
231             }
232         }
233
234     // check for tab
235
if (v instanceof GlyphView JavaDoc) {
236         Segment JavaDoc s = ((GlyphView JavaDoc)v).getText(startOffset, endOffset);
237         char ch = s.first();
238         if (ch == '\t') {
239         // if the first character is a tab, create a dedicated
240
// view for just the tab
241
endOffset = startOffset + 1;
242         } else {
243         for (ch = s.next(); ch != Segment.DONE; ch = s.next()) {
244             if (ch == '\t') {
245             // found a tab, don't include it in the text
246
endOffset = startOffset + s.getIndex() - s.getBeginIndex();
247             break;
248             }
249         }
250         }
251     }
252
253     // determine limit from LineBreakMeasurer
254
int limitIndex = text.toIteratorIndex(endOffset);
255     if (measurer != null) {
256         int index = text.toIteratorIndex(startOffset);
257         if (measurer.getPosition() != index) {
258         measurer.setPosition(index);
259         }
260         limitIndex = measurer.nextOffset(spanLeft, limitIndex, requireNextWord);
261     }
262     int pos = text.toModelPosition(limitIndex);
263     return pos;
264     }
265
266     /**
267      * Synchronize the strategy with its FlowView. Allows the strategy
268      * to update its state to account for changes in that portion of the
269      * model represented by the FlowView. Also allows the strategy
270      * to update the FlowView in response to these changes.
271      */

272     void sync(FlowView JavaDoc fv) {
273         View JavaDoc lv = getLogicalView(fv);
274         text.setView(lv);
275
276         Container container = fv.getContainer();
277         FontRenderContext frc = com.sun.java.swing.SwingUtilities2.
278                                     getFontRenderContext(container);
279         BreakIterator JavaDoc iter;
280         Container c = fv.getContainer();
281         if (c != null) {
282             iter = BreakIterator.getLineInstance(c.getLocale());
283         } else {
284             iter = BreakIterator.getLineInstance();
285         }
286
287         measurer = new LineBreakMeasurer(text, iter, frc);
288
289         // If the children of the FlowView's logical view are GlyphViews, they
290
// need to have their painters updated.
291
int n = lv.getViewCount();
292         for( int i=0; i<n; i++ ) {
293             View JavaDoc child = lv.getView(i);
294             if( child instanceof GlyphView JavaDoc ) {
295                 int p0 = child.getStartOffset();
296                 int p1 = child.getEndOffset();
297                 measurer.setPosition(text.toIteratorIndex(p0));
298                 TextLayout layout
299                     = measurer.nextLayout( Float.MAX_VALUE,
300                                            text.toIteratorIndex(p1), false );
301                 ((GlyphView JavaDoc)child).setGlyphPainter(new GlyphPainter2 JavaDoc(layout));
302             }
303         }
304
305         // Reset measurer.
306
measurer.setPosition(text.getBeginIndex());
307
308     }
309
310     // --- variables -------------------------------------------------------
311

312     private LineBreakMeasurer measurer;
313     private AttributedSegment text;
314
315     /**
316      * Implementation of AttributedCharacterIterator that supports
317      * the GlyphView attributes for rendering the glyphs through a
318      * TextLayout.
319      */

320     static class AttributedSegment extends Segment JavaDoc implements AttributedCharacterIterator JavaDoc {
321
322     AttributedSegment() {
323     }
324
325     View JavaDoc getView() {
326         return v;
327     }
328
329     void setView(View JavaDoc v) {
330         this.v = v;
331         Document JavaDoc doc = v.getDocument();
332         int p0 = v.getStartOffset();
333         int p1 = v.getEndOffset();
334         try {
335         doc.getText(p0, p1 - p0, this);
336         } catch (BadLocationException JavaDoc bl) {
337         throw new IllegalArgumentException JavaDoc("Invalid view");
338         }
339         first();
340     }
341
342     /**
343      * Get a boundary position for the font.
344      * This is implemented to assume that two fonts are
345      * equal if their references are equal (i.e. that the
346      * font came from a cache).
347      *
348      * @return the location in model coordinates. This is
349      * not the same as the Segment coordinates.
350      */

351     int getFontBoundary(int childIndex, int dir) {
352         View JavaDoc child = v.getView(childIndex);
353         Font f = getFont(childIndex);
354         for (childIndex += dir; (childIndex >= 0) && (childIndex < v.getViewCount());
355          childIndex += dir) {
356         Font next = getFont(childIndex);
357         if (next != f) {
358             // this run is different
359
break;
360         }
361         child = v.getView(childIndex);
362         }
363         return (dir < 0) ? child.getStartOffset() : child.getEndOffset();
364     }
365
366     /**
367      * Get the font at the given child index.
368      */

369     Font getFont(int childIndex) {
370         View JavaDoc child = v.getView(childIndex);
371         if (child instanceof GlyphView JavaDoc) {
372         return ((GlyphView JavaDoc)child).getFont();
373         }
374         return null;
375     }
376
377     int toModelPosition(int index) {
378         return v.getStartOffset() + (index - getBeginIndex());
379     }
380
381     int toIteratorIndex(int pos) {
382         return pos - v.getStartOffset() + getBeginIndex();
383     }
384
385     // --- AttributedCharacterIterator methods -------------------------
386

387     /**
388      * Returns the index of the first character of the run
389      * with respect to all attributes containing the current character.
390      */

391         public int getRunStart() {
392         int pos = toModelPosition(getIndex());
393         int i = v.getViewIndex(pos, Position.Bias.Forward);
394         View JavaDoc child = v.getView(i);
395         return toIteratorIndex(child.getStartOffset());
396     }
397
398     /**
399      * Returns the index of the first character of the run
400      * with respect to the given attribute containing the current character.
401      */

402         public int getRunStart(AttributedCharacterIterator.Attribute JavaDoc attribute) {
403         if (attribute instanceof TextAttribute) {
404         int pos = toModelPosition(getIndex());
405         int i = v.getViewIndex(pos, Position.Bias.Forward);
406         if (attribute == TextAttribute.FONT) {
407             return toIteratorIndex(getFontBoundary(i, -1));
408         }
409         }
410         return getBeginIndex();
411     }
412
413     /**
414      * Returns the index of the first character of the run
415      * with respect to the given attributes containing the current character.
416      */

417         public int getRunStart(Set<? extends Attribute> attributes) {
418         int index = getBeginIndex();
419         Object JavaDoc[] a = attributes.toArray();
420         for (int i = 0; i < a.length; i++) {
421         TextAttribute attr = (TextAttribute) a[i];
422         index = Math.max(getRunStart(attr), index);
423         }
424         return Math.min(getIndex(), index);
425     }
426
427     /**
428      * Returns the index of the first character following the run
429      * with respect to all attributes containing the current character.
430      */

431         public int getRunLimit() {
432         int pos = toModelPosition(getIndex());
433         int i = v.getViewIndex(pos, Position.Bias.Forward);
434         View JavaDoc child = v.getView(i);
435         return toIteratorIndex(child.getEndOffset());
436     }
437
438     /**
439      * Returns the index of the first character following the run
440      * with respect to the given attribute containing the current character.
441      */

442         public int getRunLimit(AttributedCharacterIterator.Attribute JavaDoc attribute) {
443         if (attribute instanceof TextAttribute) {
444         int pos = toModelPosition(getIndex());
445         int i = v.getViewIndex(pos, Position.Bias.Forward);
446         if (attribute == TextAttribute.FONT) {
447             return toIteratorIndex(getFontBoundary(i, 1));
448         }
449         }
450         return getEndIndex();
451     }
452
453     /**
454      * Returns the index of the first character following the run
455      * with respect to the given attributes containing the current character.
456      */

457         public int getRunLimit(Set<? extends Attribute> attributes) {
458         int index = getEndIndex();
459         Object JavaDoc[] a = attributes.toArray();
460         for (int i = 0; i < a.length; i++) {
461         TextAttribute attr = (TextAttribute) a[i];
462         index = Math.min(getRunLimit(attr), index);
463         }
464         return Math.max(getIndex(), index);
465     }
466
467     /**
468      * Returns a map with the attributes defined on the current
469      * character.
470      */

471         public Map getAttributes() {
472         Object JavaDoc[] ka = keys.toArray();
473         Hashtable h = new Hashtable();
474         for (int i = 0; i < ka.length; i++) {
475         TextAttribute a = (TextAttribute) ka[i];
476         Object JavaDoc value = getAttribute(a);
477         if (value != null) {
478             h.put(a, value);
479         }
480         }
481         return h;
482     }
483
484     /**
485      * Returns the value of the named attribute for the current character.
486      * Returns null if the attribute is not defined.
487      * @param attribute the key of the attribute whose value is requested.
488      */

489         public Object JavaDoc getAttribute(AttributedCharacterIterator.Attribute JavaDoc attribute) {
490         int pos = toModelPosition(getIndex());
491         int childIndex = v.getViewIndex(pos, Position.Bias.Forward);
492         if (attribute == TextAttribute.FONT) {
493         return getFont(childIndex);
494         } else if( attribute == TextAttribute.RUN_DIRECTION ) {
495                 return
496                     v.getDocument().getProperty(TextAttribute.RUN_DIRECTION);
497             }
498         return null;
499     }
500
501     /**
502      * Returns the keys of all attributes defined on the
503      * iterator's text range. The set is empty if no
504      * attributes are defined.
505      */

506         public Set getAllAttributeKeys() {
507         return keys;
508     }
509
510     View JavaDoc v;
511
512     static Set keys;
513
514     static {
515         keys = new HashSet();
516         keys.add(TextAttribute.FONT);
517             keys.add(TextAttribute.RUN_DIRECTION);
518     }
519
520     }
521
522 }
523
Popular Tags