| /******************************************************************************* |
| * Copyright (c) 2000, 2010 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.draw2d.text; |
| |
| import java.util.List; |
| |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.TextLayout; |
| |
| import org.eclipse.draw2d.ColorConstants; |
| import org.eclipse.draw2d.Graphics; |
| import org.eclipse.draw2d.TextUtilities; |
| import org.eclipse.draw2d.geometry.Dimension; |
| import org.eclipse.draw2d.geometry.Point; |
| import org.eclipse.draw2d.geometry.Rectangle; |
| import org.eclipse.draw2d.rap.swt.SWT; |
| |
| /** |
| * An inline flow figure that renders a string of text across one or more lines. |
| * A TextFlow cannot contain children. All <code>InlineFlow</code> figure's must |
| * be parented by a <code>FlowFigure</code>. |
| * <p> |
| * WARNING: This class is not intended to be subclassed by clients. |
| * |
| * @author hudsonr |
| * @author Pratik Shah |
| * @since 2.1 |
| */ |
| public class TextFlow extends InlineFlow { |
| |
| static final String ELLIPSIS = "..."; //$NON-NLS-1$ |
| private BidiInfo bidiInfo; |
| private int selectionEnd = -1; |
| private String text; |
| |
| /** |
| * Constructs a new TextFlow with the empty String. |
| * |
| * @see java.lang.Object#Object() |
| */ |
| public TextFlow() { |
| this(new String()); |
| } |
| |
| /** |
| * Constructs a new TextFlow with the specified String. |
| * |
| * @param s |
| * the string |
| */ |
| public TextFlow(String s) { |
| text = s; |
| } |
| |
| /** |
| * Returns the width of the text until the first line-break. |
| * |
| * @see org.eclipse.draw2d.text.FlowFigure#addLeadingWordRequirements(int[]) |
| */ |
| public boolean addLeadingWordRequirements(int[] width) { |
| return addLeadingWordWidth(getText(), width); |
| } |
| |
| /** |
| * Calculates the width taken up by the given text before a line-break is |
| * encountered. |
| * |
| * @param text |
| * the text in which the break is to be found |
| * @param width |
| * the width before the next line-break (if one's found; the |
| * width of all the given text, otherwise) will be added on to |
| * the first int in the given array |
| * @return <code>true</code> if a line-break was found |
| * @since 3.1 |
| */ |
| boolean addLeadingWordWidth(String text, int[] width) { |
| if (text.length() == 0) |
| return false; |
| if (Character.isWhitespace(text.charAt(0))) |
| return true; |
| |
| text = 'a' + text + 'a'; |
| FlowUtilities.LINE_BREAK.setText(text); |
| int index = FlowUtilities.LINE_BREAK.next() - 1; |
| if (index == 0) |
| return true; |
| while (Character.isWhitespace(text.charAt(index))) |
| index--; |
| boolean result = index < text.length() - 1; |
| // index should point to the end of the actual text (not including the |
| // 'a' that was |
| // appended), if there were no breaks |
| if (index == text.length() - 1) |
| index--; |
| text = text.substring(1, index + 1); |
| |
| if (bidiInfo == null) |
| width[0] += getTextUtilities().getTextExtents(text, getFont()).width; |
| else { |
| TextLayout textLayout = FlowUtilities.getTextLayout(); |
| textLayout.setFont(getFont()); |
| textLayout.setText(text); |
| width[0] += textLayout.getBounds().width; |
| } |
| return result; |
| } |
| |
| /** |
| * A TextFlow contributes its text. |
| * |
| * @see org.eclipse.draw2d.text.FlowFigure#contributeBidi(org.eclipse.draw2d.text.BidiProcessor) |
| */ |
| protected void contributeBidi(BidiProcessor proc) { |
| bidiInfo = null; |
| proc.add(this, getText()); |
| } |
| |
| /** |
| * @see org.eclipse.draw2d.text.InlineFlow#createDefaultFlowLayout() |
| */ |
| protected FlowFigureLayout createDefaultFlowLayout() { |
| return new ParagraphTextLayout(this); |
| } |
| |
| private int findNextLineOffset(Point p, int[] trailing) { |
| if (getBounds().bottom() <= p.y) |
| return -1; |
| |
| TextFragmentBox closestBox = null; |
| int index = 0; |
| List fragments = getFragmentsWithoutBorder(); |
| for (int i = fragments.size() - 1; i >= 0; i--) { |
| TextFragmentBox box = (TextFragmentBox) fragments.get(i); |
| if (box.getBaseline() - box.getLineRoot().getAscent() > p.y |
| && (closestBox == null |
| || box.getBaseline() < closestBox.getBaseline() || (box |
| .getBaseline() == closestBox.getBaseline() && hDistanceBetween( |
| box, p.x) < hDistanceBetween(closestBox, p.x)))) { |
| closestBox = box; |
| index = i; |
| } |
| } |
| return findOffset(p, trailing, closestBox, index); |
| } |
| |
| private int findOffset(Point p, int[] trailing, TextFragmentBox box, |
| int boxIndex) { |
| if (box == null) |
| return -1; |
| TextLayout layout = FlowUtilities.getTextLayout(); |
| layout.setFont(getFont()); |
| layout.setText(getBidiSubstring(box, boxIndex)); |
| int x = p.x - box.getX(); |
| if (isMirrored()) |
| x = box.getWidth() - x; |
| int layoutOffset = layout |
| .getOffset(x, p.y - box.getTextTop(), trailing); |
| return box.offset + layoutOffset - getBidiPrefixLength(box, boxIndex); |
| } |
| |
| private int findPreviousLineOffset(Point p, int[] trailing) { |
| if (getBounds().y > p.y) |
| return -1; |
| |
| TextFragmentBox closestBox = null; |
| int index = 0; |
| List fragments = getFragmentsWithoutBorder(); |
| for (int i = fragments.size() - 1; i >= 0; i--) { |
| TextFragmentBox box = (TextFragmentBox) fragments.get(i); |
| if (box.getBaseline() + box.getLineRoot().getDescent() < p.y |
| && (closestBox == null |
| || box.getBaseline() > closestBox.getBaseline() || (box |
| .getBaseline() == closestBox.getBaseline() && hDistanceBetween( |
| box, p.x) < hDistanceBetween(closestBox, p.x)))) { |
| closestBox = box; |
| index = i; |
| } |
| } |
| return findOffset(p, trailing, closestBox, index); |
| } |
| |
| int getAscent() { |
| return getTextUtilities().getAscent(getFont()); |
| } |
| |
| /** |
| * Returns the BidiInfo for this figure or <code>null</code>. |
| * |
| * @return <code>null</code> or the info |
| * @since 3.1 |
| */ |
| public BidiInfo getBidiInfo() { |
| return bidiInfo; |
| } |
| |
| private int getBidiPrefixLength(TextFragmentBox box, int index) { |
| if (box.getBidiLevel() < 1) |
| return 0; |
| if (index > 0 || !bidiInfo.leadingJoiner) |
| return 1; |
| return 2; |
| } |
| |
| /** |
| * @param box |
| * which fragment |
| * @param index |
| * the fragment index |
| * @return the bidi string for that fragment |
| * @since 3.1 |
| */ |
| protected String getBidiSubstring(TextFragmentBox box, int index) { |
| if (box.getBidiLevel() < 1) |
| return getText().substring(box.offset, box.offset + box.length); |
| |
| StringBuffer buffer = new StringBuffer(box.length + 3); |
| buffer.append(box.isRightToLeft() ? BidiChars.RLO : BidiChars.LRO); |
| if (index == 0 && bidiInfo.leadingJoiner) |
| buffer.append(BidiChars.ZWJ); |
| buffer.append(getText().substring(box.offset, box.offset + box.length)); |
| if (index == getFragmentsWithoutBorder().size() - 1 |
| && bidiInfo.trailingJoiner) |
| buffer.append(BidiChars.ZWJ); |
| return buffer.toString(); |
| } |
| |
| /** |
| * Returns the CaretInfo in absolute coordinates. The offset must be between |
| * 0 and the length of the String being displayed. |
| * |
| * @since 3.1 |
| * @param offset |
| * the location in this figure's text |
| * @param trailing |
| * true if the caret is being placed after the offset |
| * @exception IllegalArgumentException |
| * If the offset is not between <code>0</code> and the length |
| * of the string inclusively |
| * @return the caret bounds relative to this figure |
| */ |
| public CaretInfo getCaretPlacement(int offset, boolean trailing) { |
| if (offset < 0 || offset > getText().length()) |
| throw new IllegalArgumentException("Offset: " + offset //$NON-NLS-1$ |
| + " is invalid"); //$NON-NLS-1$ |
| |
| if (offset == getText().length()) |
| trailing = false; |
| |
| List fragments = getFragmentsWithoutBorder(); |
| int i = fragments.size(); |
| TextFragmentBox box; |
| do |
| box = (TextFragmentBox) fragments.get(--i); |
| while (offset < box.offset && i > 0); |
| |
| // Cannot be trailing and after the last char, so go to first char in |
| // next box |
| if (trailing && box.offset + box.length <= offset) { |
| box = (TextFragmentBox) fragments.get(++i); |
| offset = box.offset; |
| trailing = false; |
| } |
| |
| Point where = getPointInBox(box, offset, i, trailing); |
| CaretInfo info = new CaretInfo(where.x, where.y, box.getAscent(), |
| box.getDescent(), box.getLineRoot().getAscent(), box |
| .getLineRoot().getDescent()); |
| translateToAbsolute(info); |
| return info; |
| } |
| |
| Point getPointInBox(TextFragmentBox box, int offset, int index, |
| boolean trailing) { |
| offset -= box.offset; |
| offset = Math.min(box.length, offset); |
| Point result = new Point(0, box.getTextTop()); |
| if (bidiInfo == null) { |
| if (trailing && offset < box.length) |
| offset++; |
| String substring = getText().substring(box.offset, |
| box.offset + offset); |
| result.x = getTextUtilities().getTextExtents(substring, getFont()).width; |
| } else { |
| TextLayout layout = FlowUtilities.getTextLayout(); |
| layout.setFont(getFont()); |
| String fragString = getBidiSubstring(box, index); |
| layout.setText(fragString); |
| offset += getBidiPrefixLength(box, index); |
| result.x = layout.getLocation(offset, trailing).x; |
| if (isMirrored()) |
| result.x = box.width - result.x; |
| } |
| result.x += box.getX(); |
| return result; |
| } |
| |
| int getDescent() { |
| return getTextUtilities().getDescent(getFont()); |
| } |
| |
| /** |
| * Returns the minimum character offset which is on the given baseline |
| * y-coordinate. The y location should be relative to this figure. The |
| * return value will be between 0 and N-1. If no fragment is located on the |
| * baseline, <code>-1</code> is returned. |
| * |
| * @since 3.1 |
| * @param baseline |
| * the relative baseline coordinate |
| * @return -1 or the lowest offset for the line |
| */ |
| public int getFirstOffsetForLine(int baseline) { |
| TextFragmentBox box; |
| List fragments = getFragmentsWithoutBorder(); |
| for (int i = 0; i < fragments.size(); i++) { |
| box = (TextFragmentBox) fragments.get(i); |
| if (baseline == box.getBaseline()) |
| return box.offset; |
| } |
| return -1; |
| } |
| |
| /** |
| * Returns the <code>TextFragmentBox</code> fragments contained in this |
| * TextFlow, not including the border fragments. The returned list should |
| * not be modified. |
| * |
| * @return list of fragments without the border fragments |
| * @since 3.4 |
| */ |
| protected List getFragmentsWithoutBorder() { |
| List fragments = getFragments(); |
| if (getBorder() != null) |
| fragments = fragments.subList(1, fragments.size() - 1); |
| return fragments; |
| } |
| |
| /** |
| * Returns the maximum offset for a character which is on the given baseline |
| * y-coordinate. The y location should be relative to this figure. The |
| * return value will be between 0 and N-1. If no fragment is located on the |
| * baseline, <code>-1</code> is returned. |
| * |
| * @since 3.1 |
| * @param baseline |
| * the relative baseline coordinate |
| * @return -1 or the highest offset at the given baseline |
| */ |
| public int getLastOffsetForLine(int baseline) { |
| TextFragmentBox box; |
| List fragments = getFragmentsWithoutBorder(); |
| for (int i = fragments.size() - 1; i >= 0; i--) { |
| box = (TextFragmentBox) fragments.get(i); |
| if (baseline == box.getBaseline()) |
| return box.offset + box.length - 1; |
| } |
| return -1; |
| } |
| |
| /** |
| * Returns the offset nearest the given point either up or down one line. If |
| * no offset is found, -1 is returned. <code>trailing[0]</code> will be set |
| * to 1 if the reference point is closer to the trailing edge of the offset |
| * than it is to the leading edge. |
| * |
| * @since 3.1 |
| * @param p |
| * a reference point |
| * @param down |
| * <code>true</code> if the search is down |
| * @param trailing |
| * an int array |
| * @return the next offset or <code>-1</code> |
| */ |
| public int getNextOffset(Point p, boolean down, int[] trailing) { |
| return down ? findNextLineOffset(p, trailing) : findPreviousLineOffset( |
| p, trailing); |
| } |
| |
| /** |
| * Returns the next offset which is visible in at least one fragment or -1 |
| * if there is not one. A visible offset means that the character or the one |
| * preceding it is displayed, which implies that a caret can be positioned |
| * at such an offset. This is useful for advancing a caret past characters |
| * which resulted in a line wrap. |
| * |
| * @param offset |
| * the reference offset |
| * @return the next offset which is visible |
| * @since 3.1 |
| */ |
| public int getNextVisibleOffset(int offset) { |
| TextFragmentBox box; |
| List fragments = getFragmentsWithoutBorder(); |
| for (int i = 0; i < fragments.size(); i++) { |
| box = (TextFragmentBox) fragments.get(i); |
| if (box.offset + box.length <= offset) |
| continue; |
| return Math.max(box.offset, offset + 1); |
| } |
| return -1; |
| } |
| |
| /** |
| * Returns the offset of the character directly below or nearest the given |
| * location. The point must be relative to this figure. The return value |
| * will be between 0 and N-1. If the proximity argument is not |
| * <code>null</code>, the result may also be <code>-1</code> if no offset |
| * was found within the proximity. |
| * <P> |
| * For a typical character, the trailing argument will be filled in to |
| * indicate whether the point is closer to the leading edge (0) or the |
| * trailing edge (1). When the point is over a cluster composed of multiple |
| * characters, the trailing argument will be filled with the position of the |
| * character in the cluster that is closest to the point. |
| * <P> |
| * If the proximity argument is not <code>null</code>, then the location may |
| * be no further than the proximity given. Passing <code>null</code> is |
| * equivalent to passing <code>new |
| * Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE)</code>. The |
| * <code>width</code> field of the proximity will contain the horizontal |
| * distance, <code>height</code> will contain vertical. Vertical proximity |
| * is more important than horizontal. The returned offset is the lowest |
| * index with minimum vertical proximity not exceeding the given limit, with |
| * horizontal proximity not exceeding the given limit. If an offset that is |
| * within the proximity is found, then the given <code>Dimension</code> will |
| * be updated to reflect the new proximity. |
| * |
| * |
| * @since 3.1 |
| * @param p |
| * the point relative to this figure |
| * @param trailing |
| * the trailing buffer |
| * @param proximity |
| * restricts and records the distance of the returned offset |
| * @return the nearest offset in this figure's text |
| */ |
| public int getOffset(Point p, int trailing[], Dimension proximity) { |
| if (proximity == null) |
| proximity = new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); |
| TextFragmentBox closestBox = null; |
| int index = 0; |
| int dy; |
| int dx; |
| int i = 0; |
| int size = fragments.size(); |
| if (getBorder() instanceof FlowBorder) { |
| i++; |
| size--; |
| } |
| for (; i < size; i++) { |
| TextFragmentBox box = (TextFragmentBox) fragments.get(i); |
| dy = vDistanceBetween(box, p.y); |
| if (dy > proximity.height) |
| continue; |
| dx = hDistanceBetween(box, p.x); |
| if (dy == proximity.height && dx >= proximity.width) |
| continue; |
| proximity.height = dy; |
| proximity.width = dx; |
| closestBox = box; |
| index = i; |
| } |
| return findOffset(p, trailing, closestBox, index); |
| } |
| |
| /** |
| * Returns the previous offset which is visible in at least one fragment or |
| * -1 if there is not one. See {@link #getNextVisibleOffset(int)} for more. |
| * |
| * @param offset |
| * a reference offset |
| * @return -1 or the previous offset which is visible |
| * @since 3.1 |
| */ |
| |
| public int getPreviousVisibleOffset(int offset) { |
| TextFragmentBox box; |
| if (offset == -1) |
| offset = Integer.MAX_VALUE; |
| List fragments = getFragmentsWithoutBorder(); |
| for (int i = fragments.size() - 1; i >= 0; i--) { |
| box = (TextFragmentBox) fragments.get(i); |
| if (box.offset >= offset) |
| continue; |
| return Math.min(box.offset + box.length, offset - 1); |
| } |
| return -1; |
| } |
| |
| /** |
| * @return the String being displayed; will not be <code>null</code> |
| */ |
| public String getText() { |
| return text; |
| } |
| |
| int getVisibleAscent() { |
| if (getBorder() instanceof FlowBorder) { |
| FlowBorder border = (FlowBorder) getBorder(); |
| return border.getInsets(this).top + getAscent(); |
| } |
| return getAscent(); |
| } |
| |
| int getVisibleDescent() { |
| if (getBorder() instanceof FlowBorder) { |
| FlowBorder border = (FlowBorder) getBorder(); |
| return border.getInsets(this).bottom + getDescent(); |
| } |
| return getDescent(); |
| } |
| |
| private int hDistanceBetween(TextFragmentBox box, int x) { |
| if (x < box.getX()) |
| return box.getX() - x; |
| return Math.max(0, x - (box.getX() + box.getWidth())); |
| } |
| |
| /** |
| * Returns <code>true</code> if a portion if the text is truncated using |
| * ellipses ("..."). |
| * |
| * @return <code>true</code> if the text is truncated with ellipses |
| */ |
| public boolean isTextTruncated() { |
| for (int i = 0; i < fragments.size(); i++) { |
| if (((TextFragmentBox) fragments.get(i)).isTruncated()) |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * @see org.eclipse.draw2d.Figure#paintFigure(Graphics) |
| */ |
| protected void paintFigure(Graphics g) { |
| TextFragmentBox frag; |
| g.getClip(Rectangle.SINGLETON); |
| int yStart = Rectangle.SINGLETON.y; |
| int yEnd = Rectangle.SINGLETON.bottom(); |
| |
| for (int i = 0; i < fragments.size(); i++) { |
| frag = (TextFragmentBox) fragments.get(i); |
| // g.drawLine(frag.getX(), frag.getLineRoot().getVisibleTop(), |
| // frag.getWidth() + frag.getX(), |
| // frag.getLineRoot().getVisibleTop()); |
| // g.drawLine(frag.getX(), frag.getBaseline(), frag.getWidth() + |
| // frag.getX(), frag.getBaseline()); |
| if (frag.offset == -1) |
| continue; |
| // Loop until first visible fragment |
| if (yStart > frag.getLineRoot().getVisibleBottom() + 1)// The + 1 is |
| // for |
| // disabled |
| // text |
| continue; |
| // Break loop at first non-visible fragment |
| if (yEnd < frag.getLineRoot().getVisibleTop()) |
| break; |
| |
| String draw = getBidiSubstring(frag, i); |
| |
| if (frag.isTruncated()) |
| draw += ELLIPSIS; |
| |
| if (!isEnabled()) { |
| Color fgColor = g.getForegroundColor(); |
| g.setForegroundColor(ColorConstants.buttonLightest); |
| paintText(g, draw, frag.getX() + 1, frag.getBaseline() |
| - getAscent() + 1, frag.getBidiLevel()); |
| g.setForegroundColor(ColorConstants.buttonDarker); |
| paintText(g, draw, frag.getX(), frag.getBaseline() |
| - getAscent(), frag.getBidiLevel()); |
| g.setForegroundColor(fgColor); |
| } else { |
| paintText(g, draw, frag.getX(), frag.getBaseline() |
| - getAscent(), frag.getBidiLevel()); |
| } |
| } |
| } |
| |
| /** |
| * @see InlineFlow#paintSelection(org.eclipse.draw2d.Graphics) |
| */ |
| protected void paintSelection(Graphics graphics) { |
| if (selectionStart == -1) |
| return; |
| graphics.setXORMode(true); |
| graphics.setBackgroundColor(ColorConstants.white); |
| |
| TextFragmentBox frag; |
| for (int i = 0; i < fragments.size(); i++) { |
| frag = (TextFragmentBox) fragments.get(i); |
| // Loop until first visible fragment |
| if (frag.offset + frag.length <= selectionStart) |
| continue; |
| if (frag.offset > selectionEnd) |
| return; |
| if (selectionStart <= frag.offset |
| && selectionEnd >= frag.offset + frag.length) { |
| int y = frag.getLineRoot().getVisibleTop(); |
| int height = frag.getLineRoot().getVisibleBottom() - y; |
| graphics.fillRectangle(frag.getX(), y, frag.getWidth(), height); |
| } else if (selectionEnd > frag.offset |
| && selectionStart < frag.offset + frag.length) { |
| Point p1 = getPointInBox(frag, |
| Math.max(frag.offset, selectionStart), i, false); |
| Point p2 = getPointInBox(frag, |
| Math.min(frag.offset + frag.length, selectionEnd) - 1, |
| i, true); |
| Rectangle rect = new Rectangle(p1, p2); |
| rect.width--; |
| rect.y = frag.getLineRoot().getVisibleTop(); |
| rect.height = frag.getLineRoot().getVisibleBottom() - rect.y; |
| graphics.fillRectangle(rect); |
| } |
| } |
| } |
| |
| protected void paintText(Graphics g, String draw, int x, int y, |
| int bidiLevel) { |
| if (bidiLevel == -1) { |
| g.drawText(draw, x, y); |
| } else { |
| TextLayout tl = FlowUtilities.getTextLayout(); |
| if (isMirrored()) |
| tl.setOrientation(SWT.RIGHT_TO_LEFT); |
| tl.setFont(g.getFont()); |
| tl.setText(draw); |
| g.drawTextLayout(tl, x, y); |
| } |
| } |
| |
| /** |
| * @see org.eclipse.draw2d.text.FlowFigure#setBidiInfo(org.eclipse.draw2d.text.BidiInfo) |
| */ |
| public void setBidiInfo(BidiInfo info) { |
| this.bidiInfo = info; |
| } |
| |
| /** |
| * Sets the extent of selection. The selection range is inclusive. For |
| * example, the range [0, 0] indicates that the first character is selected. |
| * |
| * @param start |
| * the start offset |
| * @param end |
| * the end offset |
| * @since 3.1 |
| */ |
| public void setSelection(int start, int end) { |
| boolean repaint = false; |
| |
| if (selectionStart == start) { |
| if (selectionEnd == end) |
| return; |
| repaint = true; |
| } else |
| repaint = selectionStart != selectionEnd || start != end; |
| |
| selectionStart = start; |
| selectionEnd = end; |
| if (repaint) |
| repaint(); |
| } |
| |
| /** |
| * Sets the text being displayed. The string may not be <code>null</code>. |
| * |
| * @param s |
| * The new text |
| */ |
| public void setText(String s) { |
| if (s != null && !s.equals(text)) { |
| text = s; |
| revalidateBidi(this); |
| repaint(); |
| } |
| } |
| |
| /** |
| * @see java.lang.Object#toString() |
| */ |
| public String toString() { |
| return text; |
| } |
| |
| private int vDistanceBetween(TextFragmentBox box, int y) { |
| int top = box.getBaseline() - box.getLineRoot().getAscent(); |
| if (y < top) |
| return top - y; |
| return Math.max(0, y |
| - (box.getBaseline() + box.getLineRoot().getDescent())); |
| } |
| |
| /** |
| * Gets the <code>FlowUtilities</code> instance to be used in measurement |
| * calculations. |
| * |
| * @return a <code>FlowUtilities</code> instance |
| * @since 3.4 |
| */ |
| protected FlowUtilities getFlowUtilities() { |
| return FlowUtilities.INSTANCE; |
| } |
| |
| /** |
| * Gets the <code>TextUtilities</code> instance to be used in measurement |
| * calculations. |
| * |
| * @return a <code>TextUtilities</code> instance |
| * @since 3.4 |
| */ |
| protected TextUtilities getTextUtilities() { |
| return TextUtilities.INSTANCE; |
| } |
| |
| } |