comparison dwtx/draw2d/text/TextFlow.d @ 98:95307ad235d9

Added Draw2d code, still work in progress
author Frank Benoit <benoit@tionex.de>
date Sun, 03 Aug 2008 00:52:14 +0200
parents
children
comparison
equal deleted inserted replaced
96:b492ba44e44d 98:95307ad235d9
1 /*******************************************************************************
2 * Copyright (c) 2000, 2008 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * IBM Corporation - initial API and implementation
10 * Port to the D programming language:
11 * Frank Benoit <benoit@tionex.de>
12 *******************************************************************************/
13 module dwtx.draw2d.text.TextFlow;
14
15 import dwt.dwthelper.utils;
16 import dwtx.dwtxhelper.Collection;
17
18 import dwt.DWT;
19 import dwt.graphics.Color;
20 import dwt.graphics.TextLayout;
21 import dwtx.draw2d.ColorConstants;
22 import dwtx.draw2d.Graphics;
23 import dwtx.draw2d.TextUtilities;
24 import dwtx.draw2d.geometry.Dimension;
25 import dwtx.draw2d.geometry.Point;
26 import dwtx.draw2d.geometry.Rectangle;
27 import dwtx.draw2d.text.InlineFlow;
28 import dwtx.draw2d.text.BidiInfo;
29 import dwtx.draw2d.text.BidiProcessor;
30 import dwtx.draw2d.text.FlowFigureLayout;
31 import dwtx.draw2d.text.TextFragmentBox;
32 import dwtx.draw2d.text.CaretInfo;
33 import dwtx.draw2d.text.FlowUtilities;
34 import dwtx.draw2d.text.ParagraphTextLayout;
35 import dwtx.draw2d.text.BidiChars;
36 import dwtx.draw2d.text.FlowBorder;
37
38 import tango.text.convert.Format;
39
40 /**
41 * An inline flow figure that renders a string of text across one or more lines. A
42 * TextFlow cannot contain children. All <code>InlineFlow</code> figure's must be
43 * parented by a <code>FlowFigure</code>.
44 * <p>
45 * WARNING: This class is not intended to be subclassed by clients.
46 * @author hudsonr
47 * @author Pratik Shah
48 * @since 2.1
49 */
50 public class TextFlow
51 : InlineFlow
52 {
53
54 static final String ELLIPSIS = "..."; //$NON-NLS-1$
55 private BidiInfo bidiInfo;
56 private int selectionEnd = -1;
57 private String text;
58
59 /**
60 * Constructs a new TextFlow with the empty String.
61 * @see java.lang.Object#Object()
62 */
63 public this() {
64 this(""/+new String()+/);
65 }
66
67 /**
68 * Constructs a new TextFlow with the specified String.
69 * @param s the string
70 */
71 public this(String s) {
72 text = s;
73 }
74
75 /**
76 * Returns the width of the text until the first line-break.
77 * @see dwtx.draw2d.text.FlowFigure#addLeadingWordRequirements(int[])
78 */
79 public bool addLeadingWordRequirements(int[] width) {
80 return addLeadingWordWidth(getText(), width);
81 }
82
83 /**
84 * Calculates the width taken up by the given text before a line-break is encountered.
85 *
86 * @param text the text in which the break is to be found
87 * @param width the width before the next line-break (if one's found; the width of all
88 * the given text, otherwise) will be added on to the first int in the given array
89 * @return <code>true</code> if a line-break was found
90 * @since 3.1
91 */
92 bool addLeadingWordWidth(String text, int[] width) {
93 if (text.length is 0)
94 return false;
95 if (CharacterIsWhitespace(text.firstCodePoint()))
96 return true;
97
98 text = 'a' ~ text ~ 'a';
99 FlowUtilities.LINE_BREAK.setText(text);
100 int index = FlowUtilities.LINE_BREAK.next() - 1;
101 if (index is 0)
102 return true;
103 while (CharacterIsWhitespace(text[index..$].firstCodePoint()))
104 index--;
105 bool result = index < text.length - 1;
106 // index should point to the end of the actual text (not including the 'a' that was
107 // appended), if there were no breaks
108 if (index is text.length - 1)
109 index--;
110 text = text.substring(1, index + 1);
111
112 if (bidiInfo is null)
113 width[0] += getTextUtilities().getStringExtents(text, getFont()).width;
114 else {
115 TextLayout textLayout = FlowUtilities.getTextLayout();
116 textLayout.setFont(getFont());
117 textLayout.setText(text);
118 width[0] += textLayout.getBounds().width;
119 }
120 return result;
121 }
122
123 /**
124 * A TextFlow contributes its text.
125 * @see dwtx.draw2d.text.FlowFigure#contributeBidi(dwtx.draw2d.text.BidiProcessor)
126 */
127 protected void contributeBidi(BidiProcessor proc) {
128 bidiInfo = null;
129 proc.add(this, getText());
130 }
131
132 /**
133 * @see dwtx.draw2d.text.InlineFlow#createDefaultFlowLayout()
134 */
135 protected FlowFigureLayout createDefaultFlowLayout() {
136 return new ParagraphTextLayout(this);
137 }
138
139 private int findNextLineOffset(Point p, int[] trailing) {
140 if (getBounds().bottom() <= p.y)
141 return -1;
142
143 TextFragmentBox closestBox = null;
144 int index = 0;
145 List fragments = getFragmentsWithoutBorder();
146 for (int i = fragments.size() - 1; i >= 0; i--) {
147 TextFragmentBox box = cast(TextFragmentBox)fragments.get(i);
148 if (box.getBaseline() - box.getLineRoot().getAscent() > p.y
149 && (closestBox is null
150 || box.getBaseline() < closestBox.getBaseline()
151 || (box.getBaseline() is closestBox.getBaseline()
152 && hDistanceBetween(box, p.x) < hDistanceBetween(closestBox, p.x)))) {
153 closestBox = box;
154 index = i;
155 }
156 }
157 return findOffset(p, trailing, closestBox, index);
158 }
159
160 private int findOffset(Point p, int[] trailing, TextFragmentBox box, int boxIndex) {
161 if (box is null)
162 return -1;
163 TextLayout layout = FlowUtilities.getTextLayout();
164 layout.setFont(getFont());
165 layout.setText(getBidiSubstring(box, boxIndex));
166 int x = p.x - box.getX();
167 if (isMirrored())
168 x = box.getWidth() - x;
169 int layoutOffset = layout.getOffset(x, p.y - box.getTextTop(), trailing);
170 return box.offset + layoutOffset - getBidiPrefixLength(box, boxIndex);
171 }
172
173 private int findPreviousLineOffset(Point p, int[] trailing) {
174 if (getBounds().y > p.y)
175 return -1;
176
177 TextFragmentBox closestBox = null;
178 int index = 0;
179 List fragments = getFragmentsWithoutBorder();
180 for (int i = fragments.size() - 1; i >= 0; i--) {
181 TextFragmentBox box = cast(TextFragmentBox)fragments.get(i);
182 if (box.getBaseline() + box.getLineRoot().getDescent() < p.y
183 && (closestBox is null
184 || box.getBaseline() > closestBox.getBaseline()
185 || (box.getBaseline() is closestBox.getBaseline()
186 && hDistanceBetween(box, p.x) < hDistanceBetween(closestBox, p.x)))) {
187 closestBox = box;
188 index = i;
189 }
190 }
191 return findOffset(p, trailing, closestBox, index);
192 }
193
194 int getAscent() {
195 return getTextUtilities().getAscent(getFont());
196 }
197
198 /**
199 * Returns the BidiInfo for this figure or <code>null</code>.
200 * @return <code>null</code> or the info
201 * @since 3.1
202 */
203 public BidiInfo getBidiInfo() {
204 return bidiInfo;
205 }
206
207 private int getBidiPrefixLength(TextFragmentBox box, int index) {
208 if (box.getBidiLevel() < 1)
209 return 0;
210 if (index > 0 || !bidiInfo.leadingJoiner)
211 return 1;
212 return 2;
213 }
214
215 /**
216 * @param box which fragment
217 * @param index the fragment index
218 * @return the bidi string for that fragment
219 * @since 3.1
220 */
221 protected String getBidiSubstring(TextFragmentBox box, int index) {
222 if (box.getBidiLevel() < 1)
223 return getText().substring(box.offset, box.offset + box.length);
224
225 StringBuffer buffer = new StringBuffer(box.length + 3);
226 buffer.append( dcharToString( box.isRightToLeft() ? BidiChars.RLO : BidiChars.LRO ));
227 if (index is 0 && bidiInfo.leadingJoiner)
228 buffer.append(dcharToString(BidiChars.ZWJ));
229 buffer.append(getText().substring(box.offset, box.offset + box.length));
230 if (index is getFragmentsWithoutBorder().size() - 1 && bidiInfo.trailingJoiner)
231 buffer.append(dcharToString(BidiChars.ZWJ));
232 return buffer.toString();
233 }
234
235 /**
236 * Returns the CaretInfo in absolute coordinates. The offset must be between 0 and the
237 * length of the String being displayed.
238 * @since 3.1
239 * @param offset the location in this figure's text
240 * @param trailing true if the caret is being placed after the offset
241 * @exception IllegalArgumentException If the offset is not between <code>0</code> and the
242 * length of the string inclusively
243 * @return the caret bounds relative to this figure
244 */
245 public CaretInfo getCaretPlacement(int offset, bool trailing) {
246 if (offset < 0 || offset > getText().length)
247 throw new IllegalArgumentException(Format("Offset: {} is invalid", offset //$NON-NLS-1$
248 )); //$NON-NLS-1$
249
250 if (offset is getText().length)
251 trailing = false;
252
253 List fragments = getFragmentsWithoutBorder();
254 int i = fragments.size();
255 TextFragmentBox box;
256 do
257 box = cast(TextFragmentBox)fragments.get(--i);
258 while (offset < box.offset && i > 0);
259
260 // Cannot be trailing and after the last char, so go to first char in next box
261 if (trailing && box.offset + box.length <= offset) {
262 box = cast(TextFragmentBox)fragments.get(++i);
263 offset = box.offset;
264 trailing = false;
265 }
266
267 Point where = getPointInBox(box, offset, i, trailing);
268 CaretInfo info = new CaretInfo(where.x, where.y, box.getAscent(), box.getDescent(),
269 box.getLineRoot().getAscent(), box.getLineRoot().getDescent());
270 translateToAbsolute(info);
271 return info;
272 }
273
274 Point getPointInBox(TextFragmentBox box, int offset, int index, bool trailing) {
275 offset -= box.offset;
276 offset = Math.min(box.length, offset);
277 Point result = new Point(0, box.getTextTop());
278 if (bidiInfo is null) {
279 if (trailing && offset < box.length)
280 offset++;
281 String substring = getText().substring(box.offset, box.offset + offset);
282 result.x = getTextUtilities().getStringExtents(substring, getFont()).width;
283 } else {
284 TextLayout layout = FlowUtilities.getTextLayout();
285 layout.setFont(getFont());
286 String fragString = getBidiSubstring(box, index);
287 layout.setText(fragString);
288 offset += getBidiPrefixLength(box, index);
289 result.x = layout.getLocation(offset, trailing).x;
290 if (isMirrored())
291 result.x = box.width - result.x;
292 }
293 result.x += box.getX();
294 return result;
295 }
296
297 int getDescent() {
298 return getTextUtilities().getDescent(getFont());
299 }
300
301 /**
302 * Returns the minimum character offset which is on the given baseline y-coordinate. The y
303 * location should be relative to this figure. The return value will be between
304 * 0 and N-1. If no fragment is located on the baseline, <code>-1</code> is returned.
305 * @since 3.1
306 * @param baseline the relative baseline coordinate
307 * @return -1 or the lowest offset for the line
308 */
309 public int getFirstOffsetForLine(int baseline) {
310 TextFragmentBox box;
311 List fragments = getFragmentsWithoutBorder();
312 for (int i = 0; i < fragments.size(); i++) {
313 box = cast(TextFragmentBox)fragments.get(i);
314 if (baseline is box.getBaseline())
315 return box.offset;
316 }
317 return -1;
318 }
319
320 /**
321 * Returns the <code>TextFragmentBox</code> fragments contained in this TextFlow, not
322 * including the border fragments. The returned list should not be modified.
323 * @return list of fragments without the border fragments
324 * @since 3.4
325 */
326 protected List getFragmentsWithoutBorder() {
327 List fragments = getFragments();
328 if (getBorder() !is null)
329 fragments = fragments.subList(1, fragments.size() - 1);
330 return fragments;
331 }
332
333 /**
334 * Returns the maximum offset for a character which is on the given baseline y-coordinate.
335 * The y location should be relative to this figure. The return value will be between
336 * 0 and N-1. If no fragment is located on the baseline, <code>-1</code> is returned.
337 * @since 3.1
338 * @param baseline the relative baseline coordinate
339 * @return -1 or the highest offset at the given baseline
340 */
341 public int getLastOffsetForLine(int baseline) {
342 TextFragmentBox box;
343 List fragments = getFragmentsWithoutBorder();
344 for (int i = fragments.size() - 1; i >= 0; i--) {
345 box = cast(TextFragmentBox)fragments.get(i);
346 if (baseline is box.getBaseline())
347 return box.offset + box.length - 1;
348 }
349 return -1;
350 }
351
352 /**
353 * Returns the offset nearest the given point either up or down one line. If no offset
354 * is found, -1 is returned. <code>trailing[0]</code> will be set to 1 if the reference
355 * point is closer to the trailing edge of the offset than it is to the leading edge.
356 * @since 3.1
357 * @param p a reference point
358 * @param down <code>true</code> if the search is down
359 * @param trailing an int array
360 * @return the next offset or <code>-1</code>
361 */
362 public int getNextOffset(Point p, bool down, int[] trailing) {
363 return down ? findNextLineOffset(p, trailing) : findPreviousLineOffset(p, trailing);
364 }
365
366 /**
367 * Returns the next offset which is visible in at least one fragment or -1 if there is
368 * not one. A visible offset means that the character or the one preceding it is
369 * displayed, which implies that a caret can be positioned at such an offset. This is
370 * useful for advancing a caret past characters which resulted in a line wrap.
371 *
372 * @param offset the reference offset
373 * @return the next offset which is visible
374 * @since 3.1
375 */
376 public int getNextVisibleOffset(int offset) {
377 TextFragmentBox box;
378 List fragments = getFragmentsWithoutBorder();
379 for (int i = 0; i < fragments.size(); i++) {
380 box = cast(TextFragmentBox)fragments.get(i);
381 if (box.offset + box.length <= offset)
382 continue;
383 return Math.max(box.offset, offset + 1);
384 }
385 return -1;
386 }
387
388 /**
389 * Returns the offset of the character directly below or nearest the given location. The
390 * point must be relative to this figure. The return value will be between 0 and N-1. If
391 * the proximity argument is not <code>null</code>, the result may also be <code>-1</code>
392 * if no offset was found within the proximity.
393 * <P>
394 * For a typical character, the trailing argument will be filled in to indicate whether
395 * the point is closer to the leading edge (0) or the trailing edge (1). When the point
396 * is over a cluster composed of multiple characters, the trailing argument will be filled
397 * with the position of the character in the cluster that is closest to the point.
398 * <P>
399 * If the proximity argument is not <code>null</code>, then the location may be no further
400 * than the proximity given. Passing <code>null</code> is equivalent to passing <code>new
401 * Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE)</code>. The <code>width</code> field of
402 * the proximity will contain the horizontal distance, <code>height</code> will contain
403 * vertical. Vertical proximity is more important than horizontal. The returned offset is
404 * the lowest index with minimum vertical proximity not exceeding the given limit, with
405 * horizontal proximity not exceeding the given limit. If an offset that is within the
406 * proximity is found, then the given <code>Dimension</code> will be updated to reflect
407 * the new proximity.
408 *
409 *
410 * @since 3.1
411 * @param p the point relative to this figure
412 * @param trailing the trailing buffer
413 * @param proximity restricts and records the distance of the returned offset
414 * @return the nearest offset in this figure's text
415 */
416 public int getOffset(Point p, int trailing[], Dimension proximity) {
417 if (proximity is null)
418 proximity = new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
419 TextFragmentBox closestBox = null;
420 int index = 0;
421 int dy;
422 int dx;
423 int i = 0;
424 int size = fragments.size();
425 if (null !is cast(FlowBorder)getBorder() ) {
426 i++;
427 size--;
428 }
429 for (; i < size; i++) {
430 TextFragmentBox box = cast(TextFragmentBox)fragments.get(i);
431 dy = vDistanceBetween(box, p.y);
432 if (dy > proximity.height)
433 continue;
434 dx = hDistanceBetween(box, p.x);
435 if (dy is proximity.height && dx >= proximity.width)
436 continue;
437 proximity.height = dy;
438 proximity.width = dx;
439 closestBox = box;
440 index = i;
441 }
442 return findOffset(p, trailing, closestBox, index);
443 }
444
445 /**
446 * Returns the previous offset which is visible in at least one fragment or -1 if there
447 * is not one. See {@link #getNextVisibleOffset(int)} for more.
448 *
449 * @param offset a reference offset
450 * @return -1 or the previous offset which is visible
451 * @since 3.1
452 */
453
454 public int getPreviousVisibleOffset(int offset) {
455 TextFragmentBox box;
456 if (offset is -1)
457 offset = Integer.MAX_VALUE;
458 List fragments = getFragmentsWithoutBorder();
459 for (int i = fragments.size() - 1; i >= 0; i--) {
460 box = cast(TextFragmentBox)fragments.get(i);
461 if (box.offset >= offset)
462 continue;
463 return Math.min(box.offset + box.length, offset - 1);
464 }
465 return -1;
466 }
467
468 /**
469 * @return the String being displayed; will not be <code>null</code>
470 */
471 public String getText() {
472 return text;
473 }
474
475 int getVisibleAscent() {
476 if (null !is cast(FlowBorder)getBorder() ) {
477 FlowBorder border = cast(FlowBorder)getBorder();
478 return border.getInsets(this).top + getAscent();
479 }
480 return getAscent();
481 }
482
483 int getVisibleDescent() {
484 if (null !is cast(FlowBorder)getBorder() ) {
485 FlowBorder border = cast(FlowBorder)getBorder();
486 return border.getInsets(this).bottom + getDescent();
487 }
488 return getDescent();
489 }
490
491 private int hDistanceBetween(TextFragmentBox box, int x) {
492 if (x < box.getX())
493 return box.getX() - x;
494 return Math.max(0, x - (box.getX() + box.getWidth()));
495 }
496
497 /**
498 * Returns <code>true</code> if a portion if the text is truncated using ellipses ("...").
499 * @return <code>true</code> if the text is truncated with ellipses
500 */
501 public bool isTextTruncated() {
502 for (int i = 0; i < fragments.size(); i++) {
503 if ((cast(TextFragmentBox)fragments.get(i)).isTruncated())
504 return true;
505 }
506 return false;
507 }
508
509 /**
510 * @see dwtx.draw2d.Figure#paintFigure(Graphics)
511 */
512 protected void paintFigure(Graphics g) {
513 TextFragmentBox frag;
514 g.getClip(Rectangle.SINGLETON);
515 int yStart = Rectangle.SINGLETON.y;
516 int yEnd = Rectangle.SINGLETON.bottom();
517
518 for (int i = 0; i < fragments.size(); i++) {
519 frag = cast(TextFragmentBox)fragments.get(i);
520 // g.drawLine(frag.getX(), frag.getLineRoot().getVisibleTop(),
521 // frag.getWidth() + frag.getX(), frag.getLineRoot().getVisibleTop());
522 // g.drawLine(frag.getX(), frag.getBaseline(), frag.getWidth() + frag.getX(), frag.getBaseline());
523 if (frag.offset is -1)
524 continue;
525 //Loop until first visible fragment
526 if (yStart > frag.getLineRoot().getVisibleBottom() + 1)//The + 1 is for disabled text
527 continue;
528 //Break loop at first non-visible fragment
529 if (yEnd < frag.getLineRoot().getVisibleTop())
530 break;
531
532 String draw = getBidiSubstring(frag, i);
533
534 if (frag.isTruncated())
535 draw ~= ELLIPSIS;
536
537 if (!isEnabled()) {
538 Color fgColor = g.getForegroundColor();
539 g.setForegroundColor(ColorConstants.buttonLightest);
540 paintText(g, draw,
541 frag.getX() + 1,
542 frag.getBaseline() - getAscent() + 1,
543 frag.getBidiLevel());
544 g.setForegroundColor(ColorConstants.buttonDarker);
545 paintText(g, draw,
546 frag.getX(),
547 frag.getBaseline() - getAscent(),
548 frag.getBidiLevel());
549 g.setForegroundColor(fgColor);
550 } else {
551 paintText(g, draw,
552 frag.getX(),
553 frag.getBaseline() - getAscent(),
554 frag.getBidiLevel());
555 }
556 }
557 }
558
559 /**
560 * @see InlineFlow#paintSelection(dwtx.draw2d.Graphics)
561 */
562 protected void paintSelection(Graphics graphics) {
563 if (selectionStart is -1)
564 return;
565 graphics.setXORMode(true);
566 graphics.setBackgroundColor(ColorConstants.white);
567
568 TextFragmentBox frag;
569 for (int i = 0; i < fragments.size(); i++) {
570 frag = cast(TextFragmentBox)fragments.get(i);
571 //Loop until first visible fragment
572 if (frag.offset + frag.length <= selectionStart)
573 continue;
574 if (frag.offset > selectionEnd)
575 return;
576 if (selectionStart <= frag.offset && selectionEnd >= frag.offset + frag.length) {
577 int y = frag.getLineRoot().getVisibleTop();
578 int height = frag.getLineRoot().getVisibleBottom() - y;
579 graphics.fillRectangle(frag.getX(), y, frag.getWidth(), height);
580 } else if (selectionEnd > frag.offset && selectionStart < frag.offset + frag.length) {
581 Point p1 = getPointInBox(frag, Math.max(frag.offset, selectionStart), i, false);
582 Point p2 = getPointInBox(frag, Math.min(frag.offset + frag.length, selectionEnd) - 1, i, true);
583 Rectangle rect = new Rectangle(p1, p2);
584 rect.width--;
585 rect.y = frag.getLineRoot().getVisibleTop();
586 rect.height = frag.getLineRoot().getVisibleBottom() - rect.y;
587 graphics.fillRectangle(rect);
588 }
589 }
590 }
591
592 protected void paintText(Graphics g, String draw, int x, int y, int bidiLevel) {
593 if (bidiLevel is -1) {
594 g.drawString(draw, x, y);
595 } else {
596 TextLayout tl = FlowUtilities.getTextLayout();
597 if (isMirrored())
598 tl.setOrientation(DWT.RIGHT_TO_LEFT);
599 tl.setFont(g.getFont());
600 tl.setText(draw);
601 g.drawTextLayout(tl, x, y);
602 }
603 }
604
605 /**
606 * @see dwtx.draw2d.text.FlowFigure#setBidiInfo(dwtx.draw2d.text.BidiInfo)
607 */
608 public void setBidiInfo(BidiInfo info) {
609 this.bidiInfo = info;
610 }
611
612 /**
613 * Sets the extent of selection. The selection range is inclusive. For example, the
614 * range [0, 0] indicates that the first character is selected.
615 * @param start the start offset
616 * @param end the end offset
617 * @since 3.1
618 */
619 public void setSelection(int start, int end) {
620 bool repaint_ = false;
621
622 if (selectionStart is start) {
623 if (selectionEnd is end)
624 return;
625 repaint_ = true;
626 } else
627 repaint_ = selectionStart !is selectionEnd || start !is end;
628
629 selectionStart = start;
630 selectionEnd = end;
631 if (repaint_)
632 repaint();
633 }
634
635 /**
636 * Sets the text being displayed. The string may not be <code>null</code>.
637 * @param s The new text
638 */
639 public void setText(String s) {
640 if (s !is null && !s.equals(text)) {
641 text = s;
642 revalidateBidi(this);
643 repaint();
644 }
645 }
646
647 /**
648 * @see java.lang.Object#toString()
649 */
650 public String toString() {
651 return text;
652 }
653
654 private int vDistanceBetween(TextFragmentBox box, int y) {
655 int top = box.getBaseline() - box.getLineRoot().getAscent();
656 if (y < top)
657 return top - y;
658 return Math.max(0, y - (box.getBaseline() + box.getLineRoot().getDescent()));
659 }
660
661 /**
662 * Gets the <code>FlowUtilities</code> instance to be used in measurement
663 * calculations.
664 *
665 * @return a <code>FlowUtilities</code> instance
666 * @since 3.4
667 */
668 protected FlowUtilities getFlowUtilities() {
669 return FlowUtilities.INSTANCE;
670 }
671 package FlowUtilities getFlowUtilities_package() {
672 return getFlowUtilities();
673 }
674
675 /**
676 * Gets the <code>TextUtilities</code> instance to be used in measurement
677 * calculations.
678 *
679 * @return a <code>TextUtilities</code> instance
680 * @since 3.4
681 */
682 protected TextUtilities getTextUtilities() {
683 return TextUtilities.INSTANCE;
684 }
685
686 }