| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| /** |
| * @author Oleg V. Khaschansky |
| * @version $Revision$ |
| * |
| */ |
| |
| package org.apache.harmony.awt.gl.font; |
| |
| |
| import java.awt.geom.GeneralPath; |
| import java.awt.geom.Rectangle2D; |
| import java.awt.im.InputMethodHighlight; |
| import java.awt.font.*; |
| import java.awt.*; |
| import java.text.AttributedCharacterIterator; |
| import java.text.Annotation; |
| import java.text.AttributedCharacterIterator.Attribute; |
| import java.util.*; |
| |
| import org.apache.harmony.awt.gl.font.TextDecorator.Decoration; |
| import org.apache.harmony.awt.internal.nls.Messages; |
| import org.apache.harmony.misc.HashCode; |
| // TODO - bidi not implemented yet |
| |
| /** |
| * 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. |
| */ |
| public class TextRunBreaker implements Cloneable { |
| AttributedCharacterIterator aci; |
| FontRenderContext frc; |
| |
| char[] text; |
| |
| byte[] levels; |
| |
| HashMap<Integer, Font> fonts; |
| HashMap<Integer, Decoration> decorations; |
| |
| // Related to default font substitution |
| int forcedFontRunStarts[]; |
| |
| ArrayList<TextRunSegment> runSegments = new ArrayList<TextRunSegment>(); |
| |
| // For fast retrieving of the segment containing |
| // character with known logical index |
| int logical2segment[]; |
| int segment2visual[]; // Visual order of segments TODO - implement |
| int visual2segment[]; |
| int logical2visual[]; |
| int visual2logical[]; |
| |
| SegmentsInfo storedSegments; |
| private boolean haveAllSegments = false; |
| int segmentsStart, segmentsEnd; |
| |
| float justification = 1.0f; |
| |
| 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(); |
| } |
| |
| /** |
| * 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 |
| */ |
| int getVisualFromSegmentOrder(int segmentNum) { |
| return (segment2visual == null) ? segmentNum : segment2visual[segmentNum]; |
| } |
| |
| /** |
| * 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 |
| */ |
| int getSegmentFromVisualOrder(int visual) { |
| return (visual2segment == null) ? visual : visual2segment[visual]; |
| } |
| |
| /** |
| * 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 |
| */ |
| int getVisualFromLogical(int logical) { |
| return (logical2visual == null) ? logical : logical2visual[logical]; |
| } |
| |
| /** |
| * 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 |
| */ |
| int getLogicalFromVisual(int visual) { |
| return (visual2logical == null) ? visual : visual2logical[visual]; |
| } |
| |
| /** |
| * 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 |
| */ |
| int getLevelRunLimit(int runStart, int runEnd) { |
| if (levels == null) { |
| return runEnd; |
| } |
| int endLevelRun = runStart + 1; |
| byte level = levels[runStart]; |
| |
| while (endLevelRun <= runEnd && levels[endLevelRun] == level) { |
| endLevelRun++; |
| } |
| |
| return endLevelRun; |
| } |
| |
| /** |
| * Adds InputMethodHighlight to the attributes |
| * @param attrs - text attributes |
| * @return patched text attributes |
| */ |
| Map<? extends Attribute, ?> unpackAttributes(Map<? extends Attribute, ?> attrs) { |
| 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; |
| } |
| |
| /** |
| * Breaks the text into separate style runs. |
| */ |
| void createStyleRuns() { |
| // 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(); |
| } |
| } |
| } |
| |
| /** |
| * 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 |
| */ |
| int getStyleRunLimit(int runStart, int maxPos) { |
| 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); |
| } |
| |
| /** |
| * Creates segments for the text run with |
| * constant decoration, font and bidi level |
| * @param runStart - run start |
| * @param runEnd - run end |
| */ |
| public void createSegments(int runStart, int runEnd) { |
| 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); |
| } |
| |
| /** |
| * Checks if text run segments are up to date and creates the new segments if not. |
| */ |
| public void createAllSegments() { |
| if ( !haveAllSegments && |
| (logical2segment == null || |
| logical2segment.length != segmentsEnd - segmentsStart) |
| ) { // Check if we don't have all segments yet |
| resetSegments(); |
| createSegments(segmentsStart, segmentsEnd); |
| } |
| |
| haveAllSegments = true; |
| } |
| |
| /** |
| * 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 |
| */ |
| public int getLineBreakIndex(int start, float maxAdvance) { |
| 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(); |
| } |
| |
| /** |
| * Inserts character into the managed text. |
| * @param newParagraph - new character iterator |
| * @param insertPos - insertion position |
| */ |
| public void insertChar(AttributedCharacterIterator newParagraph, int insertPos) { |
| 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++; |
| } |
| |
| /** |
| * Deletes character from the managed text. |
| * @param newParagraph - new character iterator |
| * @param deletePos - deletion position |
| */ |
| public void deleteChar(AttributedCharacterIterator newParagraph, int deletePos) { |
| 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--; |
| } |
| |
| /** |
| * 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 |
| */ |
| private void shiftStyleRuns(Integer pos, final int shift) { |
| 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)); |
| } |
| } |
| |
| /** |
| * Resets state of the class |
| */ |
| private void resetSegments() { |
| runSegments = new ArrayList<TextRunSegment>(); |
| logical2segment = null; |
| segment2visual = null; |
| visual2segment = null; |
| levels = null; |
| haveAllSegments = false; |
| } |
| |
| private class SegmentsInfo { |
| ArrayList<TextRunSegment> runSegments; |
| int logical2segment[]; |
| int segment2visual[]; |
| int visual2segment[]; |
| byte levels[]; |
| int segmentsStart; |
| int segmentsEnd; |
| } |
| |
| /** |
| * Saves the internal state of the class |
| * @param newSegStart - new start index in the text |
| * @param newSegEnd - new end index in the text |
| */ |
| public void pushSegments(int newSegStart, int newSegEnd) { |
| 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; |
| } |
| |
| /** |
| * Restores the internal state of the class |
| */ |
| public void popSegments() { |
| 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; |
| } |
| } |
| |
| @Override |
| public Object clone() { |
| 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$ |
| } |
| } |
| |
| @Override |
| public boolean equals(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; |
| } |
| |
| @Override |
| public int hashCode() { |
| return HashCode.combine(aci.hashCode(), frc.hashCode()); |
| } |
| |
| /** |
| * 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 |
| */ |
| public void drawSegments(Graphics2D g2d, float xOffset, float yOffset) { |
| for (int i=0; i<runSegments.size(); i++) { |
| runSegments.get(i).draw(g2d, xOffset, yOffset); |
| } |
| } |
| |
| /** |
| * Creates the black box bounds shape |
| * @param firstEndpoint - start position |
| * @param secondEndpoint - end position |
| * @return black box bounds shape |
| */ |
| public Shape getBlackBoxBounds(int firstEndpoint, int secondEndpoint) { |
| 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; |
| } |
| |
| /** |
| * Creates visual bounds shape |
| * @return visual bounds rectangle |
| */ |
| public Rectangle2D getVisualBounds() { |
| 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; |
| } |
| |
| /** |
| * Creates logical bounds shape |
| * @return logical bounds rectangle |
| */ |
| public Rectangle2D getLogicalBounds() { |
| 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; |
| } |
| |
| public int getCharCount() { |
| return segmentsEnd - segmentsStart; |
| } |
| |
| public byte getLevel(int idx) { |
| if (levels == null) { |
| return 0; |
| } |
| return levels[idx]; |
| } |
| |
| public int getBaseLevel() { |
| return 0; |
| } |
| |
| public boolean isLTR() { |
| return true; |
| } |
| |
| public char getChar(int index) { |
| return text[index]; |
| } |
| |
| public AttributedCharacterIterator getACI() { |
| return aci; |
| } |
| |
| /** |
| * Creates outline shape for the managed text |
| * @return outline |
| */ |
| public GeneralPath getOutline() { |
| 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; |
| } |
| |
| /** |
| * 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 |
| */ |
| public TextHitInfo hitTest(float x, float y) { |
| 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 float getJustification() { |
| return justification; |
| } |
| |
| /** |
| * Calculates position of the last non whitespace character |
| * in the managed text. |
| * @return position of the last non whitespace character |
| */ |
| public int getLastNonWhitespace() { |
| int lastNonWhitespace = text.length; |
| |
| while (lastNonWhitespace >= 0) { |
| lastNonWhitespace--; |
| if (!Character.isWhitespace(text[lastNonWhitespace])) { |
| break; |
| } |
| } |
| |
| return lastNonWhitespace; |
| } |
| |
| /** |
| * 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 |
| */ |
| public void justify(float gap) { |
| // 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 |
| } |
| |
| /** |
| * This class represents the information collected before the actual |
| * justification is started and needed to perform the justification. |
| * This information is closely related to the information stored in the |
| * GlyphJustificationInfo for the text represented by glyph vectors. |
| */ |
| class JustificationInfo { |
| boolean grow; |
| boolean absorb = false; |
| boolean useLimits = false; |
| int priority = 0; |
| float weight = 0; |
| float absorbedWeight = 0; |
| float growLimit = 0; |
| |
| int lastIdx; |
| int firstIdx; |
| |
| float gapToFill; |
| |
| float gapPerUnit = 0; // Precalculated value, gapToFill / weight |
| float absorbedGapPerUnit = 0; // Precalculated value, gapToFill / weight |
| } |
| } |