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

TextRunBreaker

public class TextRunBreaker extends Object implements Cloneable
This class is responsible for breaking the text into the run segments with constant font, style, other text attributes and direction. It also stores the created text run segments and covers functionality related to the operations on the set of segments, like calculating metrics, rendering, justification, hit testing, etc.

Fields Summary
AttributedCharacterIterator
aci
FontRenderContext
frc
char[]
text
byte[]
levels
HashMap
fonts
HashMap
decorations
int[]
forcedFontRunStarts
ArrayList
runSegments
int[]
logical2segment
int[]
segment2visual
int[]
visual2segment
int[]
logical2visual
int[]
visual2logical
SegmentsInfo
storedSegments
private boolean
haveAllSegments
int
segmentsStart
int
segmentsEnd
float
justification
Constructors Summary
public TextRunBreaker(AttributedCharacterIterator aci, FontRenderContext frc)


         
        this.aci = aci;
        this.frc = frc;

        segmentsStart = aci.getBeginIndex();
        segmentsEnd = aci.getEndIndex();

        int len = segmentsEnd - segmentsStart;
        text = new char[len];
        aci.setIndex(segmentsEnd);
        while (len-- != 0) { // Going in backward direction is faster? Simplier checks here?
            text[len] = aci.previous();
        }

        createStyleRuns();
    
Methods Summary
public java.lang.Objectclone()

        try {
            TextRunBreaker res = (TextRunBreaker) super.clone();
            res.storedSegments = null;
            ArrayList<TextRunSegment> newSegments = new ArrayList<TextRunSegment>(runSegments.size());
            for (int i = 0; i < runSegments.size(); i++) {
                TextRunSegment seg =  runSegments.get(i);
                newSegments.add((TextRunSegment)seg.clone());
            }
            res.runSegments = newSegments;
            return res;
        } catch (CloneNotSupportedException e) {
            // awt.3E=Clone not supported
            throw new UnsupportedOperationException(Messages.getString("awt.3E")); //$NON-NLS-1$
        }
    
public voidcreateAllSegments()
Checks if text run segments are up to date and creates the new segments if not.

        if ( !haveAllSegments &&
            (logical2segment == null ||
             logical2segment.length != segmentsEnd - segmentsStart)
        ) { // Check if we don't have all segments yet
            resetSegments();
            createSegments(segmentsStart, segmentsEnd);
        }

        haveAllSegments = true;
    
public voidcreateSegments(int runStart, int runEnd)
Creates segments for the text run with constant decoration, font and bidi level

param
runStart - run start
param
runEnd - run end

        int endStyleRun, endLevelRun;

        // TODO - update levels

        int pos = runStart, levelPos;

        aci.setIndex(pos);
        final int firstRunStart = aci.getRunStart();
        Object tdd = decorations.get(new Integer(firstRunStart));
        Object fontOrGAttr = fonts.get(new Integer(firstRunStart));

        logical2segment = new int[runEnd - runStart];

        do {
            endStyleRun = getStyleRunLimit(pos, runEnd);

            // runStart can be non-zero, but all arrays will be indexed from 0
            int ajustedPos = pos - runStart;
            int ajustedEndStyleRun = endStyleRun - runStart;
            levelPos = ajustedPos;
            do {
                endLevelRun = getLevelRunLimit(levelPos, ajustedEndStyleRun);

                if (fontOrGAttr instanceof GraphicAttribute) {
                    runSegments.add(
                        new TextRunSegmentImpl.TextRunSegmentGraphic(
                                (GraphicAttribute)fontOrGAttr,
                                endLevelRun - levelPos,
                                levelPos + runStart)
                    );
                    Arrays.fill(logical2segment, levelPos, endLevelRun, runSegments.size()-1);
                } else {
                    TextRunSegmentImpl.TextSegmentInfo i =
                            new TextRunSegmentImpl.TextSegmentInfo(
                                    levels == null ? 0 : levels[ajustedPos],
                                    (Font) fontOrGAttr,
                                    frc,
                                    text,
                                    levelPos + runStart,
                                    endLevelRun + runStart
                            );

                    runSegments.add(
                            new TextRunSegmentImpl.TextRunSegmentCommon(
                                    i,
                                    (TextDecorator.Decoration) tdd
                            )
                    );
                    Arrays.fill(logical2segment, levelPos, endLevelRun, runSegments.size()-1);
                }

                levelPos = endLevelRun;
            } while (levelPos < ajustedEndStyleRun);

            // Prepare next iteration
            pos = endStyleRun;
            tdd = decorations.get(new Integer(pos));
            fontOrGAttr = fonts.get(new Integer(pos));
        } while (pos < runEnd);
    
voidcreateStyleRuns()
Breaks the text into separate style runs.

        // TODO - implement fast and simple case
        fonts = new HashMap<Integer, Font>();
        decorations = new HashMap<Integer, Decoration>();
        ////

        ArrayList<Integer> forcedFontRunStartsList = null;

        Map<? extends Attribute, ?> attributes = null;

        // Check justification attribute
        Object val = aci.getAttribute(TextAttribute.JUSTIFICATION);
        if (val != null) {
            justification = ((Float) val).floatValue();
        }

        for (
            int index = segmentsStart, nextRunStart = segmentsStart;
            index < segmentsEnd;
            index = nextRunStart, aci.setIndex(index)
           )  {
            nextRunStart = aci.getRunLimit();
            attributes = unpackAttributes(aci.getAttributes());

            TextDecorator.Decoration d = TextDecorator.getDecoration(attributes);
            decorations.put(new Integer(index), d);

            // Find appropriate font or place GraphicAttribute there

            // 1. Try to pick up CHAR_REPLACEMENT (compatibility)
            Font value = (Font)attributes.get(TextAttribute.CHAR_REPLACEMENT);

            if (value == null) {
                // 2. Try to Get FONT
                value = (Font)attributes.get(TextAttribute.FONT);

                if (value == null) {
                    // 3. Try to create font from FAMILY
                    if (attributes.get(TextAttribute.FAMILY) != null) {
                        value = Font.getFont(attributes);
                    }

                    if (value == null) {
                        // 4. No attributes found, using default.
                        if (forcedFontRunStartsList == null) {
                            forcedFontRunStartsList = new ArrayList<Integer>();
                        }
                        FontFinder.findFonts(
                                text,
                                index,
                                nextRunStart,
                                forcedFontRunStartsList,
                                fonts
                        );
                        value = fonts.get(new Integer(index));
                    }
                }
            }

            fonts.put(new Integer(index), value);
        }

        // We have added some default fonts, so we have some extra runs in text
        if (forcedFontRunStartsList != null) {
            forcedFontRunStarts = new int[forcedFontRunStartsList.size()];
            for (int i=0; i<forcedFontRunStartsList.size(); i++) {
                forcedFontRunStarts[i] =
                        forcedFontRunStartsList.get(i).intValue();
            }
        }
    
public voiddeleteChar(java.text.AttributedCharacterIterator newParagraph, int deletePos)
Deletes character from the managed text.

param
newParagraph - new character iterator
param
deletePos - deletion position

        aci = newParagraph;

        Integer key = new Integer(deletePos);

        deletePos -= aci.getBeginIndex();

        char newText[] = new char[text.length - 1];
        System.arraycopy(text, 0, newText, 0, deletePos);
        System.arraycopy(text, deletePos+1, newText, deletePos, newText.length - deletePos);
        text = newText;

        if (fonts.get(key) != null) {
            fonts.remove(key);
        }

        shiftStyleRuns(key, -1);

        resetSegments();

        segmentsEnd--;
    
public voiddrawSegments(java.awt.Graphics2D g2d, float xOffset, float yOffset)
Renders the managed text

param
g2d - graphics where to render
param
xOffset - offset in X direction to the upper left corner of the layout from the origin of the graphics
param
yOffset - offset in Y direction to the upper left corner of the layout from the origin of the graphics

        for (int i=0; i<runSegments.size(); i++) {
            runSegments.get(i).draw(g2d, xOffset, yOffset);
        }
    
public booleanequals(java.lang.Object obj)

        if (!(obj instanceof TextRunBreaker)) {
            return false;
        }

        TextRunBreaker br = (TextRunBreaker) obj;

        if (br.getACI().equals(aci) && br.frc.equals(frc)) {
            return true;
        }

        return false;
    
public java.text.AttributedCharacterIteratorgetACI()

        return aci;
    
public intgetBaseLevel()

        return 0;
    
public java.awt.ShapegetBlackBoxBounds(int firstEndpoint, int secondEndpoint)
Creates the black box bounds shape

param
firstEndpoint - start position
param
secondEndpoint - end position
return
black box bounds shape

        GeneralPath bounds = new GeneralPath();

        TextRunSegment segment;

        for (int idx = firstEndpoint; idx < secondEndpoint; idx=segment.getEnd()) {
            segment = runSegments.get(logical2segment[idx]);
            bounds.append(segment.getCharsBlackBoxBounds(idx, secondEndpoint), false);
        }

        return bounds;
    
public chargetChar(int index)

        return text[index];
    
public intgetCharCount()

        return segmentsEnd - segmentsStart;
    
public floatgetJustification()

        return justification;
    
public intgetLastNonWhitespace()
Calculates position of the last non whitespace character in the managed text.

return
position of the last non whitespace character

        int lastNonWhitespace = text.length;

        while (lastNonWhitespace >= 0) {
            lastNonWhitespace--;
            if (!Character.isWhitespace(text[lastNonWhitespace])) {
                break;
            }
        }

        return lastNonWhitespace;
    
public bytegetLevel(int idx)

        if (levels == null) {
            return 0;
        }
        return levels[idx];
    
intgetLevelRunLimit(int runStart, int runEnd)
Calculates the end index of the level run, limited by the given text run.

param
runStart - run start
param
runEnd - run end
return
end index of the level run

        if (levels == null) {
            return runEnd;
        }
        int endLevelRun = runStart + 1;
        byte level = levels[runStart];

        while (endLevelRun <= runEnd && levels[endLevelRun] == level) {
            endLevelRun++;
        }

        return endLevelRun;
    
public intgetLineBreakIndex(int start, float maxAdvance)
Calculates position where line should be broken without taking into account word boundaries.

param
start - start index
param
maxAdvance - maximum advance, width of the line
return
position where to break

        int breakIndex;
        TextRunSegment s = null;

        for (
                int segmentIndex = logical2segment[start];
                segmentIndex < runSegments.size();
                segmentIndex++
           ) {
            s = runSegments.get(segmentIndex);
            breakIndex = s.getCharIndexFromAdvance(maxAdvance, start);

            if (breakIndex < s.getEnd()) {
                return breakIndex;
            }
            maxAdvance -= s.getAdvanceDelta(start, s.getEnd());
            start = s.getEnd();
        }

        return s.getEnd();
    
public java.awt.geom.Rectangle2DgetLogicalBounds()
Creates logical bounds shape

return
logical bounds rectangle

        Rectangle2D bounds = null;

        for (int i=0; i<runSegments.size(); i++) {
            TextRunSegment s = runSegments.get(i);
            if (bounds != null) {
                Rectangle2D.union(bounds, s.getLogicalBounds(), bounds);
            } else {
                bounds = s.getLogicalBounds();
            }
        }

        return bounds;
    
intgetLogicalFromVisual(int visual)
Visual order of the characters may differ from the logical order. This method calculates logical position of the character from its visual position.

param
visual - visual position
return
logical position

        return (visual2logical == null) ? visual : visual2logical[visual];
    
public java.awt.geom.GeneralPathgetOutline()
Creates outline shape for the managed text

return
outline

        GeneralPath outline = new GeneralPath();

        TextRunSegment segment;

        for (int i = 0; i < runSegments.size(); i++) {
            segment = runSegments.get(i);
            outline.append(segment.getOutline(), false);
        }

        return outline;
    
intgetSegmentFromVisualOrder(int visual)
Visual order of text segments may differ from the logical order. This method calculates logical position of the segment from its visual position.

param
visual - visual position of the segment
return
logical position of the segment

        return (visual2segment == null) ? visual : visual2segment[visual];
    
intgetStyleRunLimit(int runStart, int maxPos)
Starting from the current position looks for the end of the text run with constant text attributes.

param
runStart - start position
param
maxPos - position where to stop if no run limit found
return
style run limit

        try {
            aci.setIndex(runStart);
        } catch(IllegalArgumentException e) { // Index out of bounds
            if (runStart < segmentsStart) {
                aci.first();
            } else {
                aci.last();
            }
        }

        // If we have some extra runs we need to check for their limits
        if (forcedFontRunStarts != null) {
            for (int element : forcedFontRunStarts) {
                if (element > runStart) {
                    maxPos = Math.min(element, maxPos);
                    break;
                }
            }
        }

        return Math.min(aci.getRunLimit(), maxPos);
    
public java.awt.geom.Rectangle2DgetVisualBounds()
Creates visual bounds shape

return
visual bounds rectangle

        Rectangle2D bounds = null;

        for (int i=0; i<runSegments.size(); i++) {
            TextRunSegment s = runSegments.get(i);
            if (bounds != null) {
                Rectangle2D.union(bounds, s.getVisualBounds(), bounds);
            } else {
                bounds = s.getVisualBounds();
            }
        }

        return bounds;
    
intgetVisualFromLogical(int logical)
Visual order of the characters may differ from the logical order. This method calculates visual position of the character from its logical position.

param
logical - logical position of the character
return
visual position

        return (logical2visual == null) ? logical : logical2visual[logical];
    
intgetVisualFromSegmentOrder(int segmentNum)
Visual order of text segments may differ from the logical order. This method calculates visual position of the segment from its logical position.

param
segmentNum - logical position of the segment
return
visual position of the segment

        return (segment2visual == null) ? segmentNum : segment2visual[segmentNum];
    
public inthashCode()

        return HashCode.combine(aci.hashCode(), frc.hashCode());
    
public java.awt.font.TextHitInfohitTest(float x, float y)
Calculates text hit info from the screen coordinates. Current implementation totally ignores Y coordinate. If X coordinate is outside of the layout boundaries, this method returns leftmost or rightmost hit.

param
x - x coordinate of the hit
param
y - y coordinate of the hit
return
hit info

        TextRunSegment segment;

        double endOfPrevSeg = -1;
        for (int i = 0; i < runSegments.size(); i++) {
            segment = runSegments.get(i);
            Rectangle2D bounds = segment.getVisualBounds();
            if ((bounds.getMinX() <= x && bounds.getMaxX() >= x) || // We are in the segment
               (endOfPrevSeg < x && bounds.getMinX() > x)) { // We are somewhere between the segments
                return segment.hitTest(x,y);
            }
            endOfPrevSeg = bounds.getMaxX();
        }

        return isLTR() ? TextHitInfo.trailing(text.length) : TextHitInfo.leading(0);
    
public voidinsertChar(java.text.AttributedCharacterIterator newParagraph, int insertPos)
Inserts character into the managed text.

param
newParagraph - new character iterator
param
insertPos - insertion position

        aci = newParagraph;

        char insChar = aci.setIndex(insertPos);

        Integer key = new Integer(insertPos);

        insertPos -= aci.getBeginIndex();

        char newText[] = new char[text.length + 1];
        System.arraycopy(text, 0, newText, 0, insertPos);
        newText[insertPos] = insChar;
        System.arraycopy(text, insertPos, newText, insertPos+1, text.length - insertPos);
        text = newText;

        if (aci.getRunStart() == key.intValue() && aci.getRunLimit() == key.intValue() + 1) {
            createStyleRuns(); // We have to create one new run, could be optimized
        } else {
            shiftStyleRuns(key, 1);
        }

        resetSegments();

        segmentsEnd++;
    
public booleanisLTR()

        return true;
    
public voidjustify(float gap)
Performs justification of the managed text by changing segment positions and positions of the glyphs inside of the segments.

param
gap - amount of space which should be compensated by justification

        // Ignore trailing logical whitespace
        int firstIdx = segmentsStart;
        int lastIdx = getLastNonWhitespace() + segmentsStart;
        JustificationInfo jInfos[] = new JustificationInfo[5];
        float gapLeft = gap;

        int highestPriority = -1;
        // GlyphJustificationInfo.PRIORITY_KASHIDA is 0
        // GlyphJustificationInfo.PRIORITY_NONE is 3
        for (int priority = 0; priority <= GlyphJustificationInfo.PRIORITY_NONE + 1; priority++) {
            JustificationInfo jInfo = new JustificationInfo();
            jInfo.lastIdx = lastIdx;
            jInfo.firstIdx = firstIdx;
            jInfo.grow = gap > 0;
            jInfo.gapToFill = gapLeft;

            if (priority <= GlyphJustificationInfo.PRIORITY_NONE) {
                jInfo.priority = priority;
            } else {
                jInfo.priority = highestPriority; // Last pass
            }

            for (int i = 0; i < runSegments.size(); i++) {
                TextRunSegment segment = runSegments.get(i);
                if (segment.getStart() <= lastIdx) {
                    segment.updateJustificationInfo(jInfo);
                }
            }

            if (jInfo.priority == highestPriority) {
                jInfo.absorb = true;
                jInfo.absorbedWeight = jInfo.weight;
            }

            if (jInfo.weight != 0) {
                if (highestPriority < 0) {
                    highestPriority = priority;
                }
                jInfos[priority] = jInfo;
            } else {
                continue;
            }

            gapLeft -= jInfo.growLimit;

            if (((gapLeft > 0) ^ jInfo.grow) || gapLeft == 0) {
                gapLeft = 0;
                jInfo.gapPerUnit = jInfo.gapToFill/jInfo.weight;
                break;
            }
            jInfo.useLimits = true;

            if (jInfo.absorbedWeight > 0) {
                jInfo.absorb = true;
                jInfo.absorbedGapPerUnit =
                        (jInfo.gapToFill-jInfo.growLimit)/jInfo.absorbedWeight;
                break;
            }
        }

        float currJustificationOffset = 0;
        for (int i = 0; i < runSegments.size(); i++) {
            TextRunSegment segment =
                    runSegments.get(getSegmentFromVisualOrder(i));
            segment.x += currJustificationOffset;
            currJustificationOffset += segment.doJustification(jInfos);
        }

        justification = -1; // Make further justification impossible
    
public voidpopSegments()
Restores the internal state of the class

        if (storedSegments == null) {
            return;
        }

        this.runSegments = storedSegments.runSegments;
        this.logical2segment = storedSegments.logical2segment;
        this.segment2visual = storedSegments.segment2visual;
        this.visual2segment = storedSegments.visual2segment;
        this.levels = storedSegments.levels;
        this.segmentsStart = storedSegments.segmentsStart;
        this.segmentsEnd = storedSegments.segmentsEnd;
        storedSegments = null;

        if (runSegments.size() == 0 && logical2segment == null) {
            haveAllSegments = false;
        } else {
            haveAllSegments = true;
        }
    
public voidpushSegments(int newSegStart, int newSegEnd)
Saves the internal state of the class

param
newSegStart - new start index in the text
param
newSegEnd - new end index in the text

        storedSegments = new SegmentsInfo();
        storedSegments.runSegments = this.runSegments;
        storedSegments.logical2segment = this.logical2segment;
        storedSegments.segment2visual = this.segment2visual;
        storedSegments.visual2segment = this.visual2segment;
        storedSegments.levels = this.levels;
        storedSegments.segmentsStart = segmentsStart;
        storedSegments.segmentsEnd = segmentsEnd;

        resetSegments();

        segmentsStart = newSegStart;
        segmentsEnd = newSegEnd;
    
private voidresetSegments()
Resets state of the class

        runSegments = new ArrayList<TextRunSegment>();
        logical2segment = null;
        segment2visual = null;
        visual2segment = null;
        levels = null;
        haveAllSegments = false;
    
private voidshiftStyleRuns(java.lang.Integer pos, int shift)
Shift all runs after specified position, needed to perfom insertion or deletion in the managed text

param
pos - position where to start
param
shift - shift, could be negative

        ArrayList<Integer> keys = new ArrayList<Integer>();

        Integer key, oldkey;
        for (Iterator<Integer> it = fonts.keySet().iterator(); it.hasNext(); ) {
            oldkey = it.next();
            if (oldkey.intValue() > pos.intValue()) {
                keys.add(oldkey);
            }
        }

        for (int i=0; i<keys.size(); i++) {
            oldkey = keys.get(i);
            key = new Integer(shift + oldkey.intValue());
            fonts.put(key, fonts.remove(oldkey));
            decorations.put(key, decorations.remove(oldkey));
        }
    
java.util.MapunpackAttributes(java.util.Map attrs)
Adds InputMethodHighlight to the attributes

param
attrs - text attributes
return
patched text attributes

        if (attrs.containsKey(TextAttribute.INPUT_METHOD_HIGHLIGHT)) {
            Map<TextAttribute, ?> styles = null;

            Object val = attrs.get(TextAttribute.INPUT_METHOD_HIGHLIGHT);

            if (val instanceof Annotation) {
                val = ((Annotation) val).getValue();
            }

            if (val instanceof InputMethodHighlight) {
                InputMethodHighlight ihl = ((InputMethodHighlight) val);
                styles = ihl.getStyle();

                if (styles == null) {
                    Toolkit tk = Toolkit.getDefaultToolkit();
                    styles = tk.mapInputMethodHighlight(ihl);
                }
            }

            if (styles != null) {
                HashMap<Attribute, Object> newAttrs = new HashMap<Attribute, Object>();
                newAttrs.putAll(attrs);
                newAttrs.putAll(styles);
                return newAttrs;
            }
        }

        return attrs;