diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/draw2d/text/TextFlow.d	Sun Aug 03 00:52:14 2008 +0200
@@ -0,0 +1,686 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2008 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
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.draw2d.text.TextFlow;
+
+import dwt.dwthelper.utils;
+import dwtx.dwtxhelper.Collection;
+
+import dwt.DWT;
+import dwt.graphics.Color;
+import dwt.graphics.TextLayout;
+import dwtx.draw2d.ColorConstants;
+import dwtx.draw2d.Graphics;
+import dwtx.draw2d.TextUtilities;
+import dwtx.draw2d.geometry.Dimension;
+import dwtx.draw2d.geometry.Point;
+import dwtx.draw2d.geometry.Rectangle;
+import dwtx.draw2d.text.InlineFlow;
+import dwtx.draw2d.text.BidiInfo;
+import dwtx.draw2d.text.BidiProcessor;
+import dwtx.draw2d.text.FlowFigureLayout;
+import dwtx.draw2d.text.TextFragmentBox;
+import dwtx.draw2d.text.CaretInfo;
+import dwtx.draw2d.text.FlowUtilities;
+import dwtx.draw2d.text.ParagraphTextLayout;
+import dwtx.draw2d.text.BidiChars;
+import dwtx.draw2d.text.FlowBorder;
+
+import tango.text.convert.Format;
+
+/**
+ * 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
+    : 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 this() {
+    this(""/+new String()+/);
+}
+
+/**
+ * Constructs a new TextFlow with the specified String.
+ * @param s the string
+ */
+public this(String s) {
+    text = s;
+}
+
+/**
+ * Returns the width of the text until the first line-break.
+ * @see dwtx.draw2d.text.FlowFigure#addLeadingWordRequirements(int[])
+ */
+public bool 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
+ */
+bool addLeadingWordWidth(String text, int[] width) {
+    if (text.length is 0)
+        return false;
+    if (CharacterIsWhitespace(text.firstCodePoint()))
+        return true;
+
+    text = 'a' ~ text ~ 'a';
+    FlowUtilities.LINE_BREAK.setText(text);
+    int index = FlowUtilities.LINE_BREAK.next() - 1;
+    if (index is 0)
+        return true;
+    while (CharacterIsWhitespace(text[index..$].firstCodePoint()))
+        index--;
+    bool 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 is text.length - 1)
+        index--;
+    text = text.substring(1, index + 1);
+
+    if (bidiInfo is null)
+        width[0] += getTextUtilities().getStringExtents(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 dwtx.draw2d.text.FlowFigure#contributeBidi(dwtx.draw2d.text.BidiProcessor)
+ */
+protected void contributeBidi(BidiProcessor proc) {
+    bidiInfo = null;
+    proc.add(this, getText());
+}
+
+/**
+ * @see dwtx.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 = cast(TextFragmentBox)fragments.get(i);
+        if (box.getBaseline() - box.getLineRoot().getAscent() > p.y
+                && (closestBox is null
+                || box.getBaseline() < closestBox.getBaseline()
+                || (box.getBaseline() is 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 is 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 = cast(TextFragmentBox)fragments.get(i);
+        if (box.getBaseline() + box.getLineRoot().getDescent() < p.y
+                && (closestBox is null
+                || box.getBaseline() > closestBox.getBaseline()
+                || (box.getBaseline() is 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( dcharToString( box.isRightToLeft() ? BidiChars.RLO : BidiChars.LRO ));
+    if (index is 0 && bidiInfo.leadingJoiner)
+        buffer.append(dcharToString(BidiChars.ZWJ));
+    buffer.append(getText().substring(box.offset, box.offset + box.length));
+    if (index is getFragmentsWithoutBorder().size() - 1 && bidiInfo.trailingJoiner)
+        buffer.append(dcharToString(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, bool trailing) {
+    if (offset < 0 || offset > getText().length)
+        throw new IllegalArgumentException(Format("Offset: {} is invalid", offset //$NON-NLS-1$
+                )); //$NON-NLS-1$
+
+    if (offset is getText().length)
+        trailing = false;
+
+    List fragments = getFragmentsWithoutBorder();
+    int i = fragments.size();
+    TextFragmentBox box;
+    do
+        box = cast(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 = cast(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, bool trailing) {
+    offset -= box.offset;
+    offset = Math.min(box.length, offset);
+    Point result = new Point(0, box.getTextTop());
+    if (bidiInfo is null) {
+        if (trailing && offset < box.length)
+            offset++;
+        String substring = getText().substring(box.offset, box.offset + offset);
+        result.x = getTextUtilities().getStringExtents(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 = cast(TextFragmentBox)fragments.get(i);
+        if (baseline is 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() !is 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 = cast(TextFragmentBox)fragments.get(i);
+        if (baseline is 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, bool 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 = cast(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 is 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 (null !is cast(FlowBorder)getBorder() ) {
+        i++;
+        size--;
+    }
+    for (; i < size; i++) {
+        TextFragmentBox box = cast(TextFragmentBox)fragments.get(i);
+        dy = vDistanceBetween(box, p.y);
+        if (dy > proximity.height)
+            continue;
+        dx = hDistanceBetween(box, p.x);
+        if (dy is 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 is -1)
+        offset = Integer.MAX_VALUE;
+    List fragments = getFragmentsWithoutBorder();
+    for (int i = fragments.size() - 1; i >= 0; i--) {
+        box = cast(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 (null !is cast(FlowBorder)getBorder() ) {
+        FlowBorder border = cast(FlowBorder)getBorder();
+        return border.getInsets(this).top + getAscent();
+    }
+    return getAscent();
+}
+
+int getVisibleDescent() {
+    if (null !is cast(FlowBorder)getBorder() ) {
+        FlowBorder border = cast(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 bool isTextTruncated() {
+    for (int i = 0; i < fragments.size(); i++) {
+        if ((cast(TextFragmentBox)fragments.get(i)).isTruncated())
+            return true;
+    }
+    return false;
+}
+
+/**
+ * @see dwtx.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 = cast(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 is -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(dwtx.draw2d.Graphics)
+ */
+protected void paintSelection(Graphics graphics) {
+    if (selectionStart is -1)
+        return;
+    graphics.setXORMode(true);
+    graphics.setBackgroundColor(ColorConstants.white);
+
+    TextFragmentBox frag;
+    for (int i = 0; i < fragments.size(); i++) {
+        frag = cast(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 is -1) {
+        g.drawString(draw, x, y);
+    } else {
+        TextLayout tl = FlowUtilities.getTextLayout();
+        if (isMirrored())
+            tl.setOrientation(DWT.RIGHT_TO_LEFT);
+        tl.setFont(g.getFont());
+        tl.setText(draw);
+        g.drawTextLayout(tl, x, y);
+    }
+}
+
+/**
+ * @see dwtx.draw2d.text.FlowFigure#setBidiInfo(dwtx.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) {
+    bool repaint_ = false;
+
+    if (selectionStart is start) {
+        if (selectionEnd is end)
+            return;
+        repaint_ = true;
+    } else
+        repaint_ = selectionStart !is selectionEnd || start !is 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 !is 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;
+}
+package FlowUtilities getFlowUtilities_package() {
+    return getFlowUtilities();
+}
+
+/**
+ * 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;
+}
+
+}