1 /*
2 * Copyright 1999-2005 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25 package javax.swing.text;
26
27 import java.util;
28 import java.awt;
29 import java.text.AttributedCharacterIterator;
30 import java.text.BreakIterator;
31 import java.awt.font;
32 import java.awt.geom.AffineTransform;
33 import javax.swing.event.DocumentEvent;
34 import sun.font.BidiUtils;
35
36 /**
37 * A flow strategy that uses java.awt.font.LineBreakMeasureer to
38 * produce java.awt.font.TextLayout for i18n capable rendering.
39 * If the child view being placed into the flow is of type
40 * GlyphView and can be rendered by TextLayout, a GlyphPainter
41 * that uses TextLayout is plugged into the GlyphView.
42 *
43 * @author Timothy Prinzing
44 */
45 class TextLayoutStrategy extends FlowView.FlowStrategy {
46
47 /**
48 * Constructs a layout strategy for paragraphs based
49 * upon java.awt.font.LineBreakMeasurer.
50 */
51 public TextLayoutStrategy() {
52 text = new AttributedSegment();
53 }
54
55 // --- FlowStrategy methods --------------------------------------------
56
57 /**
58 * Gives notification that something was inserted into the document
59 * in a location that the given flow view is responsible for. The
60 * strategy should update the appropriate changed region (which
61 * depends upon the strategy used for repair).
62 *
63 * @param e the change information from the associated document
64 * @param alloc the current allocation of the view inside of the insets.
65 * This value will be null if the view has not yet been displayed.
66 * @see View#insertUpdate
67 */
68 public void insertUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) {
69 sync(fv);
70 super.insertUpdate(fv, e, alloc);
71 }
72
73 /**
74 * Gives notification that something was removed from the document
75 * in a location that the given flow view is responsible for.
76 *
77 * @param e the change information from the associated document
78 * @param alloc the current allocation of the view inside of the insets.
79 * @see View#removeUpdate
80 */
81 public void removeUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) {
82 sync(fv);
83 super.removeUpdate(fv, e, alloc);
84 }
85
86 /**
87 * Gives notification from the document that attributes were changed
88 * in a location that this view is responsible for.
89 *
90 * @param changes the change information from the associated document
91 * @param a the current allocation of the view
92 * @param f the factory to use to rebuild if the view has children
93 * @see View#changedUpdate
94 */
95 public void changedUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) {
96 sync(fv);
97 super.changedUpdate(fv, e, alloc);
98 }
99
100 /**
101 * Does a a full layout on the given View. This causes all of
102 * the rows (child views) to be rebuilt to match the given
103 * constraints for each row. This is called by a FlowView.layout
104 * to update the child views in the flow.
105 *
106 * @param v the view to reflow
107 */
108 public void layout(FlowView fv) {
109 super.layout(fv);
110 }
111
112 /**
113 * Creates a row of views that will fit within the
114 * layout span of the row. This is implemented to execute the
115 * superclass functionality (which fills the row with child
116 * views or view fragments) and follow that with bidi reordering
117 * of the unidirectional view fragments.
118 *
119 * @param row the row to fill in with views. This is assumed
120 * to be empty on entry.
121 * @param pos The current position in the children of
122 * this views element from which to start.
123 * @return the position to start the next row
124 */
125 protected int layoutRow(FlowView fv, int rowIndex, int p0) {
126 int p1 = super.layoutRow(fv, rowIndex, p0);
127 View row = fv.getView(rowIndex);
128 Document doc = fv.getDocument();
129 Object i18nFlag = doc.getProperty(AbstractDocument.I18NProperty);
130 if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) {
131 int n = row.getViewCount();
132 if (n > 1) {
133 AbstractDocument d = (AbstractDocument)fv.getDocument();
134 Element bidiRoot = d.getBidiRootElement();
135 byte[] levels = new byte[n];
136 View[] reorder = new View[n];
137
138 for( int i=0; i<n; i++ ) {
139 View v = row.getView(i);
140 int bidiIndex =bidiRoot.getElementIndex(v.getStartOffset());
141 Element bidiElem = bidiRoot.getElement( bidiIndex );
142 levels[i] = (byte)StyleConstants.getBidiLevel(bidiElem.getAttributes());
143 reorder[i] = v;
144 }
145
146 BidiUtils.reorderVisually( levels, reorder );
147 row.replace(0, n, reorder);
148 }
149 }
150 return p1;
151 }
152
153 /**
154 * Adjusts the given row if possible to fit within the
155 * layout span. Since all adjustments were already
156 * calculated by the LineBreakMeasurer, this is implemented
157 * to do nothing.
158 *
159 * @param r the row to adjust to the current layout
160 * span.
161 * @param desiredSpan the current layout span >= 0
162 * @param x the location r starts at.
163 */
164 protected void adjustRow(FlowView fv, int rowIndex, int desiredSpan, int x) {
165 }
166
167 /**
168 * Creates a unidirectional view that can be used to represent the
169 * current chunk. This can be either an entire view from the
170 * logical view, or a fragment of the view.
171 *
172 * @param fv the view holding the flow
173 * @param startOffset the start location for the view being created
174 * @param spanLeft the about of span left to fill in the row
175 * @param rowIndex the row the view will be placed into
176 */
177 protected View createView(FlowView fv, int startOffset, int spanLeft, int rowIndex) {
178 // Get the child view that contains the given starting position
179 View lv = getLogicalView(fv);
180 View row = fv.getView(rowIndex);
181 boolean requireNextWord = (viewBuffer.size() == 0) ? false : true;
182 int childIndex = lv.getViewIndex(startOffset, Position.Bias.Forward);
183 View v = lv.getView(childIndex);
184
185 int endOffset = getLimitingOffset(v, startOffset, spanLeft, requireNextWord);
186 if (endOffset == startOffset) {
187 return null;
188 }
189
190 View frag;
191 if ((startOffset==v.getStartOffset()) && (endOffset == v.getEndOffset())) {
192 // return the entire view
193 frag = v;
194 } else {
195 // return a unidirectional fragment.
196 frag = v.createFragment(startOffset, endOffset);
197 }
198
199 if ((frag instanceof GlyphView) && (measurer != null)) {
200 // install a TextLayout based renderer if the view is responsible
201 // for glyphs. If the view represents a tab, the default
202 // glyph painter is used (may want to handle tabs differently).
203 boolean isTab = false;
204 int p0 = frag.getStartOffset();
205 int p1 = frag.getEndOffset();
206 if ((p1 - p0) == 1) {
207 // check for tab
208 Segment s = ((GlyphView)frag).getText(p0, p1);
209 char ch = s.first();
210 if (ch == '\t') {
211 isTab = true;
212 }
213 }
214 TextLayout tl = (isTab) ? null :
215 measurer.nextLayout(spanLeft, text.toIteratorIndex(endOffset),
216 requireNextWord);
217 if (tl != null) {
218 ((GlyphView)frag).setGlyphPainter(new GlyphPainter2(tl));
219 }
220 }
221 return frag;
222 }
223
224 /**
225 * Calculate the limiting offset for the next view fragment.
226 * At most this would be the entire view (i.e. the limiting
227 * offset would be the end offset in that case). If the range
228 * contains a tab or a direction change, that will limit the
229 * offset to something less. This value is then fed to the
230 * LineBreakMeasurer as a limit to consider in addition to the
231 * remaining span.
232 *
233 * @param v the logical view representing the starting offset.
234 * @param startOffset the model location to start at.
235 */
236 int getLimitingOffset(View v, int startOffset, int spanLeft, boolean requireNextWord) {
237 int endOffset = v.getEndOffset();
238
239 // check for direction change
240 Document doc = v.getDocument();
241 if (doc instanceof AbstractDocument) {
242 AbstractDocument d = (AbstractDocument) doc;
243 Element bidiRoot = d.getBidiRootElement();
244 if( bidiRoot.getElementCount() > 1 ) {
245 int bidiIndex = bidiRoot.getElementIndex( startOffset );
246 Element bidiElem = bidiRoot.getElement( bidiIndex );
247 endOffset = Math.min( bidiElem.getEndOffset(), endOffset );
248 }
249 }
250
251 // check for tab
252 if (v instanceof GlyphView) {
253 Segment s = ((GlyphView)v).getText(startOffset, endOffset);
254 char ch = s.first();
255 if (ch == '\t') {
256 // if the first character is a tab, create a dedicated
257 // view for just the tab
258 endOffset = startOffset + 1;
259 } else {
260 for (ch = s.next(); ch != Segment.DONE; ch = s.next()) {
261 if (ch == '\t') {
262 // found a tab, don't include it in the text
263 endOffset = startOffset + s.getIndex() - s.getBeginIndex();
264 break;
265 }
266 }
267 }
268 }
269
270 // determine limit from LineBreakMeasurer
271 int limitIndex = text.toIteratorIndex(endOffset);
272 if (measurer != null) {
273 int index = text.toIteratorIndex(startOffset);
274 if (measurer.getPosition() != index) {
275 measurer.setPosition(index);
276 }
277 limitIndex = measurer.nextOffset(spanLeft, limitIndex, requireNextWord);
278 }
279 int pos = text.toModelPosition(limitIndex);
280 return pos;
281 }
282
283 /**
284 * Synchronize the strategy with its FlowView. Allows the strategy
285 * to update its state to account for changes in that portion of the
286 * model represented by the FlowView. Also allows the strategy
287 * to update the FlowView in response to these changes.
288 */
289 void sync(FlowView fv) {
290 View lv = getLogicalView(fv);
291 text.setView(lv);
292
293 Container container = fv.getContainer();
294 FontRenderContext frc = sun.swing.SwingUtilities2.
295 getFontRenderContext(container);
296 BreakIterator iter;
297 Container c = fv.getContainer();
298 if (c != null) {
299 iter = BreakIterator.getLineInstance(c.getLocale());
300 } else {
301 iter = BreakIterator.getLineInstance();
302 }
303
304 measurer = new LineBreakMeasurer(text, iter, frc);
305
306 // If the children of the FlowView's logical view are GlyphViews, they
307 // need to have their painters updated.
308 int n = lv.getViewCount();
309 for( int i=0; i<n; i++ ) {
310 View child = lv.getView(i);
311 if( child instanceof GlyphView ) {
312 int p0 = child.getStartOffset();
313 int p1 = child.getEndOffset();
314 measurer.setPosition(text.toIteratorIndex(p0));
315 TextLayout layout
316 = measurer.nextLayout( Float.MAX_VALUE,
317 text.toIteratorIndex(p1), false );
318 ((GlyphView)child).setGlyphPainter(new GlyphPainter2(layout));
319 }
320 }
321
322 // Reset measurer.
323 measurer.setPosition(text.getBeginIndex());
324
325 }
326
327 // --- variables -------------------------------------------------------
328
329 private LineBreakMeasurer measurer;
330 private AttributedSegment text;
331
332 /**
333 * Implementation of AttributedCharacterIterator that supports
334 * the GlyphView attributes for rendering the glyphs through a
335 * TextLayout.
336 */
337 static class AttributedSegment extends Segment implements AttributedCharacterIterator {
338
339 AttributedSegment() {
340 }
341
342 View getView() {
343 return v;
344 }
345
346 void setView(View v) {
347 this.v = v;
348 Document doc = v.getDocument();
349 int p0 = v.getStartOffset();
350 int p1 = v.getEndOffset();
351 try {
352 doc.getText(p0, p1 - p0, this);
353 } catch (BadLocationException bl) {
354 throw new IllegalArgumentException("Invalid view");
355 }
356 first();
357 }
358
359 /**
360 * Get a boundary position for the font.
361 * This is implemented to assume that two fonts are
362 * equal if their references are equal (i.e. that the
363 * font came from a cache).
364 *
365 * @return the location in model coordinates. This is
366 * not the same as the Segment coordinates.
367 */
368 int getFontBoundary(int childIndex, int dir) {
369 View child = v.getView(childIndex);
370 Font f = getFont(childIndex);
371 for (childIndex += dir; (childIndex >= 0) && (childIndex < v.getViewCount());
372 childIndex += dir) {
373 Font next = getFont(childIndex);
374 if (next != f) {
375 // this run is different
376 break;
377 }
378 child = v.getView(childIndex);
379 }
380 return (dir < 0) ? child.getStartOffset() : child.getEndOffset();
381 }
382
383 /**
384 * Get the font at the given child index.
385 */
386 Font getFont(int childIndex) {
387 View child = v.getView(childIndex);
388 if (child instanceof GlyphView) {
389 return ((GlyphView)child).getFont();
390 }
391 return null;
392 }
393
394 int toModelPosition(int index) {
395 return v.getStartOffset() + (index - getBeginIndex());
396 }
397
398 int toIteratorIndex(int pos) {
399 return pos - v.getStartOffset() + getBeginIndex();
400 }
401
402 // --- AttributedCharacterIterator methods -------------------------
403
404 /**
405 * Returns the index of the first character of the run
406 * with respect to all attributes containing the current character.
407 */
408 public int getRunStart() {
409 int pos = toModelPosition(getIndex());
410 int i = v.getViewIndex(pos, Position.Bias.Forward);
411 View child = v.getView(i);
412 return toIteratorIndex(child.getStartOffset());
413 }
414
415 /**
416 * Returns the index of the first character of the run
417 * with respect to the given attribute containing the current character.
418 */
419 public int getRunStart(AttributedCharacterIterator.Attribute attribute) {
420 if (attribute instanceof TextAttribute) {
421 int pos = toModelPosition(getIndex());
422 int i = v.getViewIndex(pos, Position.Bias.Forward);
423 if (attribute == TextAttribute.FONT) {
424 return toIteratorIndex(getFontBoundary(i, -1));
425 }
426 }
427 return getBeginIndex();
428 }
429
430 /**
431 * Returns the index of the first character of the run
432 * with respect to the given attributes containing the current character.
433 */
434 public int getRunStart(Set<? extends Attribute> attributes) {
435 int index = getBeginIndex();
436 Object[] a = attributes.toArray();
437 for (int i = 0; i < a.length; i++) {
438 TextAttribute attr = (TextAttribute) a[i];
439 index = Math.max(getRunStart(attr), index);
440 }
441 return Math.min(getIndex(), index);
442 }
443
444 /**
445 * Returns the index of the first character following the run
446 * with respect to all attributes containing the current character.
447 */
448 public int getRunLimit() {
449 int pos = toModelPosition(getIndex());
450 int i = v.getViewIndex(pos, Position.Bias.Forward);
451 View child = v.getView(i);
452 return toIteratorIndex(child.getEndOffset());
453 }
454
455 /**
456 * Returns the index of the first character following the run
457 * with respect to the given attribute containing the current character.
458 */
459 public int getRunLimit(AttributedCharacterIterator.Attribute attribute) {
460 if (attribute instanceof TextAttribute) {
461 int pos = toModelPosition(getIndex());
462 int i = v.getViewIndex(pos, Position.Bias.Forward);
463 if (attribute == TextAttribute.FONT) {
464 return toIteratorIndex(getFontBoundary(i, 1));
465 }
466 }
467 return getEndIndex();
468 }
469
470 /**
471 * Returns the index of the first character following the run
472 * with respect to the given attributes containing the current character.
473 */
474 public int getRunLimit(Set<? extends Attribute> attributes) {
475 int index = getEndIndex();
476 Object[] a = attributes.toArray();
477 for (int i = 0; i < a.length; i++) {
478 TextAttribute attr = (TextAttribute) a[i];
479 index = Math.min(getRunLimit(attr), index);
480 }
481 return Math.max(getIndex(), index);
482 }
483
484 /**
485 * Returns a map with the attributes defined on the current
486 * character.
487 */
488 public Map getAttributes() {
489 Object[] ka = keys.toArray();
490 Hashtable h = new Hashtable();
491 for (int i = 0; i < ka.length; i++) {
492 TextAttribute a = (TextAttribute) ka[i];
493 Object value = getAttribute(a);
494 if (value != null) {
495 h.put(a, value);
496 }
497 }
498 return h;
499 }
500
501 /**
502 * Returns the value of the named attribute for the current character.
503 * Returns null if the attribute is not defined.
504 * @param attribute the key of the attribute whose value is requested.
505 */
506 public Object getAttribute(AttributedCharacterIterator.Attribute attribute) {
507 int pos = toModelPosition(getIndex());
508 int childIndex = v.getViewIndex(pos, Position.Bias.Forward);
509 if (attribute == TextAttribute.FONT) {
510 return getFont(childIndex);
511 } else if( attribute == TextAttribute.RUN_DIRECTION ) {
512 return
513 v.getDocument().getProperty(TextAttribute.RUN_DIRECTION);
514 }
515 return null;
516 }
517
518 /**
519 * Returns the keys of all attributes defined on the
520 * iterator's text range. The set is empty if no
521 * attributes are defined.
522 */
523 public Set getAllAttributeKeys() {
524 return keys;
525 }
526
527 View v;
528
529 static Set keys;
530
531 static {
532 keys = new HashSet();
533 keys.add(TextAttribute.FONT);
534 keys.add(TextAttribute.RUN_DIRECTION);
535 }
536
537 }
538
539 }