FileDocCategorySizeDatePackage
CaretManager.javaAPI DocAndroid 1.5 API18890Wed May 06 22:41:54 BST 2009org.apache.harmony.awt.gl.font

CaretManager

public class CaretManager extends Object
This class provides functionality for creating caret and highlight shapes (bidirectional text is also supported, but, unfortunately, not tested yet).

Fields Summary
private TextRunBreaker
breaker
Constructors Summary
public CaretManager(TextRunBreaker breaker)

        this.breaker = breaker;
    
Methods Summary
private voidcheckHit(java.awt.font.TextHitInfo info)
Checks if TextHitInfo is not out of the text range and throws the IllegalArgumentException if it is.

param
info - text hit info

        int idx = info.getInsertionIndex();

        if (idx < 0 || idx > breaker.getCharCount()) {
            // awt.42=TextHitInfo out of range
            throw new IllegalArgumentException(Messages.getString("awt.42")); //$NON-NLS-1$
        }
    
java.awt.geom.GeneralPathconnectCarets(java.awt.geom.Line2D caret1, java.awt.geom.Line2D caret2)
Connects two carets to produce a highlight shape.

param
caret1 - 1st caret
param
caret2 - 2nd caret
return
highlight shape

        GeneralPath path = new GeneralPath(GeneralPath.WIND_NON_ZERO);
        path.moveTo((float) caret1.getX1(), (float) caret1.getY1());
        path.lineTo((float) caret2.getX1(), (float) caret2.getY1());
        path.lineTo((float) caret2.getX2(), (float) caret2.getY2());
        path.lineTo((float) caret1.getX2(), (float) caret1.getY2());

        path.closePath();

        return path;
    
public float[]getCaretInfo(java.awt.font.TextHitInfo hitInfo)
Creates caret info. Required for the getCaretInfo methods of the TextLayout

param
hitInfo - specifies caret position
return
caret info, see TextLayout.getCaretInfo documentation

        checkHit(hitInfo);
        float res[] = new float[2];

        int visual = getVisualFromHitInfo(hitInfo);
        float advance, angle;
        TextRunSegment seg;

        if (visual < breaker.getCharCount()) {
            int logIdx = breaker.getLogicalFromVisual(visual);
            int segmentIdx = breaker.logical2segment[logIdx];
            seg = breaker.runSegments.get(segmentIdx);
            advance = seg.x + seg.getAdvanceDelta(seg.getStart(), logIdx);
            angle = seg.metrics.italicAngle;

        } else { // Last character
            int logIdx = breaker.getLogicalFromVisual(visual-1);
            int segmentIdx = breaker.logical2segment[logIdx];
            seg = breaker.runSegments.get(segmentIdx);
            advance = seg.x + seg.getAdvanceDelta(seg.getStart(), logIdx+1);
        }

        angle = seg.metrics.italicAngle;

        res[0] = advance;
        res[1] = angle;

        return res;
    
public java.awt.geom.Line2DgetCaretShape(java.awt.font.TextHitInfo hitInfo, java.awt.font.TextLayout layout, boolean useItalic, boolean useBounds, java.awt.geom.Rectangle2D bounds)
Creates a caret shape.

param
hitInfo - hit where to place a caret
param
layout - text layout
param
useItalic - unused for now, was used to create slanted carets for italic text
param
useBounds - true if the cared should fit into the provided bounds
param
bounds - bounds for the caret
return
caret shape

        checkHit(hitInfo);

        float x1, x2, y1, y2;

        int charIdx = hitInfo.getCharIndex();

        if (charIdx >= 0 && charIdx < breaker.getCharCount()) {
            TextRunSegment segment = breaker.runSegments.get(breaker.logical2segment[charIdx]);
            y1 = segment.metrics.descent;
            y2 = - segment.metrics.ascent - segment.metrics.leading;

            x1 = x2 = segment.getCharPosition(charIdx) + (hitInfo.isLeadingEdge() ?
                    0 : segment.getCharAdvance(charIdx));
            // Decided that straight cursor looks better even for italic fonts,
            // especially combined with highlighting
            /*
            // Not graphics, need to check italic angle and baseline
            if (layout.getBaseline() >= 0) {
                if (segment.metrics.italicAngle != 0 && useItalic) {
                    x1 -= segment.metrics.italicAngle * segment.metrics.descent;
                    x2 += segment.metrics.italicAngle *
                        (segment.metrics.ascent + segment.metrics.leading);

                    float baselineOffset =
                        layout.getBaselineOffsets()[layout.getBaseline()];
                    y1 += baselineOffset;
                    y2 += baselineOffset;
                }
            }
            */
        } else {
            y1 = layout.getDescent();
            y2 = - layout.getAscent() - layout.getLeading();
            x1 = x2 = ((breaker.getBaseLevel() & 0x1) == 0 ^ charIdx < 0) ?
                    layout.getAdvance() : 0;
        }

        if (useBounds) {
            y1 = (float) bounds.getMaxY();
            y2 = (float) bounds.getMinY();

            if (x2 > bounds.getMaxX()) {
                x1 = x2 = (float) bounds.getMaxX();
            }
            if (x1 < bounds.getMinX()) {
                x1 = x2 = (float) bounds.getMinX();
            }
        }

        return new Line2D.Float(x1, y1, x2, y2);
    
public java.awt.geom.Line2DgetCaretShape(java.awt.font.TextHitInfo hitInfo, java.awt.font.TextLayout layout)

        return getCaretShape(hitInfo, layout, true, false, null);
    
public java.awt.Shape[]getCaretShapes(int offset, java.awt.geom.Rectangle2D bounds, java.awt.font.TextLayout$CaretPolicy policy, java.awt.font.TextLayout layout)
Creates caret shapes for the specified offset. On the boundaries where the text is changing its direction this method may return two shapes for the strong and the weak carets, in other cases it would return one.

param
offset - offset in the text.
param
bounds - bounds to fit the carets into
param
policy - caret policy
param
layout - text layout
return
one or two caret shapes

        TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
        TextHitInfo hit2 = getVisualOtherHit(hit1);

        Shape caret1 = getCaretShape(hit1, layout);

        if (getVisualFromHitInfo(hit1) == getVisualFromHitInfo(hit2)) {
            return new Shape[] {caret1, null};
        }
        Shape caret2 = getCaretShape(hit2, layout);

        TextHitInfo strongHit = policy.getStrongCaret(hit1, hit2, layout);
        return strongHit.equals(hit1) ?
                new Shape[] {caret1, caret2} :
                new Shape[] {caret2, caret1};
    
private java.awt.font.TextHitInfogetHitInfoFromVisual(int visual)
Calculates text hit info from the visual position

param
visual - visual position
return
text hit info

        final boolean first = visual == 0;

        if (!(first || visual == breaker.getCharCount())) {
            int logical = breaker.getLogicalFromVisual(visual);
            return (breaker.getLevel(logical) & 0x1) == 0x0 ?
                    TextHitInfo.leading(logical) : // LTR
                    TextHitInfo.trailing(logical); // RTL
        } else if (first) {
            return breaker.isLTR() ?
                    TextHitInfo.trailing(-1) :
                    TextHitInfo.leading(breaker.getCharCount());
        } else { // Last
            return breaker.isLTR() ?
                    TextHitInfo.leading(breaker.getCharCount()) :
                    TextHitInfo.trailing(-1);
        }
    
public java.awt.ShapegetLogicalHighlightShape(int firstEndpoint, int secondEndpoint, java.awt.geom.Rectangle2D bounds, java.awt.font.TextLayout layout)
Creates a highlight shape from given two endpoints in the logical representation. This shape is not always visually contiguous

param
firstEndpoint - 1st logical endpoint
param
secondEndpoint - 2nd logical endpoint
param
bounds - bounds to fit the shape into
param
layout - text layout
return
highlight shape

        GeneralPath res = new GeneralPath();

        for (int i=firstEndpoint; i<=secondEndpoint; i++) {
            int endRun = breaker.getLevelRunLimit(i, secondEndpoint);
            TextHitInfo hit1 = TextHitInfo.leading(i);
            TextHitInfo hit2 = TextHitInfo.trailing(endRun-1);

            Line2D caret1 = getCaretShape(hit1, layout, false, true, bounds);
            Line2D caret2 = getCaretShape(hit2, layout, false, true, bounds);

            res.append(connectCarets(caret1, caret2), false);

            i = endRun;
        }

        return res;
    
public int[]getLogicalRangesForVisualSelection(java.awt.font.TextHitInfo hit1, java.awt.font.TextHitInfo hit2)
Suppose that the user visually selected a block of text which has several different levels (mixed RTL and LTR), so, in the logical representation of the text this selection may be not contigous. This methods returns a set of logical ranges for the arbitrary visual selection represented by two hits.

param
hit1 - 1st hit
param
hit2 - 2nd hit
return
logical ranges for the selection

        checkHit(hit1);
        checkHit(hit2);

        int visual1 = getVisualFromHitInfo(hit1);
        int visual2 = getVisualFromHitInfo(hit2);

        if (visual1 > visual2) {
            int tmp = visual2;
            visual2 = visual1;
            visual1 = tmp;
        }

        // Max level is 255, so we don't need more than 512 entries
        int results[] = new int[512];

        int prevLogical, logical, runStart, numRuns = 0;

        logical = runStart = prevLogical = breaker.getLogicalFromVisual(visual1);

        // Get all the runs. We use the fact that direction is constant in all runs.
        for (int i=visual1+1; i<=visual2; i++) {
            logical = breaker.getLogicalFromVisual(i);
            int diff = logical-prevLogical;

            // Start of the next run encountered
            if (diff > 1 || diff < -1) {
                results[(numRuns)*2] = Math.min(runStart, prevLogical);
                results[(numRuns)*2 + 1] = Math.max(runStart, prevLogical);
                numRuns++;
                runStart = logical;
            }

            prevLogical = logical;
        }

        // The last unsaved run
        results[(numRuns)*2] = Math.min(runStart, logical);
        results[(numRuns)*2 + 1] = Math.max(runStart, logical);
        numRuns++;

        int retval[] = new int[numRuns*2];
        System.arraycopy(results, 0, retval, 0, numRuns*2);
        return retval;
    
public java.awt.font.TextHitInfogetNextLeftHit(java.awt.font.TextHitInfo hitInfo)
Returns the next position to the left from the current caret position

param
hitInfo - current position
return
next position to the left

        checkHit(hitInfo);
        int visual = getVisualFromHitInfo(hitInfo);

        if (visual == 0) {
            return null;
        }

        TextHitInfo newInfo;

        while(visual >= 0) {
            visual--;
            newInfo = getHitInfoFromVisual(visual);

            if (newInfo.getCharIndex() < 0) {
                return newInfo;
            }

            // Don't check for rightmost info
            if (hitInfo.getCharIndex() < breaker.logical2segment.length) {
                if (
                        breaker.logical2segment[newInfo.getCharIndex()] !=
                        breaker.logical2segment[hitInfo.getCharIndex()]
                ) {
                    return newInfo; // We crossed segment boundary
                }
            }

            TextRunSegment seg = breaker.runSegments.get(breaker.logical2segment[newInfo
                    .getCharIndex()]);
            if (!seg.charHasZeroAdvance(newInfo.getCharIndex())) {
                return newInfo;
            }
        }

        return null;
    
public java.awt.font.TextHitInfogetNextRightHit(java.awt.font.TextHitInfo hitInfo)
Returns the next position to the right from the current caret position

param
hitInfo - current position
return
next position to the right

        checkHit(hitInfo);
        int visual = getVisualFromHitInfo(hitInfo);

        if (visual == breaker.getCharCount()) {
            return null;
        }

        TextHitInfo newInfo;

        while(visual <= breaker.getCharCount()) {
            visual++;
            newInfo = getHitInfoFromVisual(visual);

            if (newInfo.getCharIndex() >= breaker.logical2segment.length) {
                return newInfo;
            }

            if (hitInfo.getCharIndex() >= 0) { // Don't check for leftmost info
                if (
                        breaker.logical2segment[newInfo.getCharIndex()] !=
                        breaker.logical2segment[hitInfo.getCharIndex()]
                ) {
                    return newInfo; // We crossed segment boundary
                }
            }

            TextRunSegment seg = breaker.runSegments.get(breaker.logical2segment[newInfo
                    .getCharIndex()]);
            if (!seg.charHasZeroAdvance(newInfo.getCharIndex())) {
                return newInfo;
            }
        }

        return null;
    
private intgetVisualFromHitInfo(java.awt.font.TextHitInfo hitInfo)
Calculates and returns visual position from the text hit info.

param
hitInfo - text hit info
return
visual index

        final int idx = hitInfo.getCharIndex();

        if (idx >= 0 && idx < breaker.getCharCount()) {
            int visual = breaker.getVisualFromLogical(idx);
            // We take next character for (LTR char + TRAILING info) and (RTL + LEADING)
            if (hitInfo.isLeadingEdge() ^ ((breaker.getLevel(idx) & 0x1) == 0x0)) {
                visual++;
            }
            return visual;
        } else if (idx < 0) {
            return breaker.isLTR() ? 0: breaker.getCharCount();
        } else {
            return breaker.isLTR() ? breaker.getCharCount() : 0;
        }
    
public java.awt.ShapegetVisualHighlightShape(java.awt.font.TextHitInfo hit1, java.awt.font.TextHitInfo hit2, java.awt.geom.Rectangle2D bounds, java.awt.font.TextLayout layout)
Creates a highlight shape from given two hits. This shape will always be visually contiguous

param
hit1 - 1st hit
param
hit2 - 2nd hit
param
bounds - bounds to fit the shape into
param
layout - text layout
return
highlight shape

        checkHit(hit1);
        checkHit(hit2);

        Line2D caret1 = getCaretShape(hit1, layout, false, true, bounds);
        Line2D caret2 = getCaretShape(hit2, layout, false, true, bounds);

        return connectCarets(caret1, caret2);
    
public java.awt.font.TextHitInfogetVisualOtherHit(java.awt.font.TextHitInfo hitInfo)
For each visual caret position there are two hits. For the simple LTR text one is a trailing of the previous char and another is the leading of the next char. This method returns the opposite hit for the given hit.

param
hitInfo - given hit
return
opposite hit

        checkHit(hitInfo);

        int idx = hitInfo.getCharIndex();

        int resIdx;
        boolean resIsLeading;

        if (idx >= 0 && idx < breaker.getCharCount()) { // Hit info in the middle
            int visual = breaker.getVisualFromLogical(idx);

            // Char is LTR + LEADING info
            if (((breaker.getLevel(idx) & 0x1) == 0x0) ^ hitInfo.isLeadingEdge()) {
                visual++;
                if (visual == breaker.getCharCount()) {
                    if (breaker.isLTR()) {
                        resIdx = breaker.getCharCount();
                        resIsLeading = true;
                    } else {
                        resIdx = -1;
                        resIsLeading = false;
                    }
                } else {
                    resIdx = breaker.getLogicalFromVisual(visual);
                    if ((breaker.getLevel(resIdx) & 0x1) == 0x0) {
                        resIsLeading = true;
                    } else {
                        resIsLeading = false;
                    }
                }
            } else {
                visual--;
                if (visual == -1) {
                    if (breaker.isLTR()) {
                        resIdx = -1;
                        resIsLeading = false;
                    } else {
                        resIdx = breaker.getCharCount();
                        resIsLeading = true;
                    }
                } else {
                    resIdx = breaker.getLogicalFromVisual(visual);
                    if ((breaker.getLevel(resIdx) & 0x1) == 0x0) {
                        resIsLeading = false;
                    } else {
                        resIsLeading = true;
                    }
                }
            }
        } else if (idx < 0) { // before "start"
            if (breaker.isLTR()) {
                resIdx = breaker.getLogicalFromVisual(0);
                resIsLeading = (breaker.getLevel(resIdx) & 0x1) == 0x0; // LTR char?
            } else {
                resIdx = breaker.getLogicalFromVisual(breaker.getCharCount() - 1);
                resIsLeading = (breaker.getLevel(resIdx) & 0x1) != 0x0; // RTL char?
            }
        } else { // idx == breaker.getCharCount()
            if (breaker.isLTR()) {
                resIdx = breaker.getLogicalFromVisual(breaker.getCharCount() - 1);
                resIsLeading = (breaker.getLevel(resIdx) & 0x1) != 0x0; // LTR char?
            } else {
                resIdx = breaker.getLogicalFromVisual(0);
                resIsLeading = (breaker.getLevel(resIdx) & 0x1) == 0x0; // RTL char?
            }
        }

        return resIsLeading ? TextHitInfo.leading(resIdx) : TextHitInfo.trailing(resIdx);