comparison org.eclipse.draw2d/src/org/eclipse/draw2d/text/TextFlow.d @ 12:bc29606a740c

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