1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17/**
18 * @author Oleg V. Khaschansky
19 * @version $Revision$
20 *
21 */
22
23package org.apache.harmony.awt.gl.font;
24
25
26import java.awt.geom.GeneralPath;
27import java.awt.geom.Rectangle2D;
28import java.awt.im.InputMethodHighlight;
29import java.awt.font.*;
30import java.awt.*;
31import java.text.AttributedCharacterIterator;
32import java.text.Annotation;
33import java.text.AttributedCharacterIterator.Attribute;
34import java.util.*;
35
36import org.apache.harmony.awt.gl.font.TextDecorator.Decoration;
37import org.apache.harmony.awt.internal.nls.Messages;
38import org.apache.harmony.misc.HashCode;
39// TODO - bidi not implemented yet
40
41/**
42 * This class is responsible for breaking the text into the run segments
43 * with constant font, style, other text attributes and direction.
44 * It also stores the created text run segments and covers functionality
45 * related to the operations on the set of segments, like calculating metrics,
46 * rendering, justification, hit testing, etc.
47 */
48public class TextRunBreaker implements Cloneable {
49    AttributedCharacterIterator aci;
50    FontRenderContext frc;
51
52    char[] text;
53
54    byte[] levels;
55
56    HashMap<Integer, Font> fonts;
57    HashMap<Integer, Decoration> decorations;
58
59    // Related to default font substitution
60    int forcedFontRunStarts[];
61
62    ArrayList<TextRunSegment> runSegments = new ArrayList<TextRunSegment>();
63
64    // For fast retrieving of the segment containing
65    // character with known logical index
66    int logical2segment[];
67    int segment2visual[]; // Visual order of segments TODO - implement
68    int visual2segment[];
69    int logical2visual[];
70    int visual2logical[];
71
72    SegmentsInfo storedSegments;
73    private boolean haveAllSegments = false;
74    int segmentsStart, segmentsEnd;
75
76    float justification = 1.0f;
77
78    public TextRunBreaker(AttributedCharacterIterator aci, FontRenderContext frc) {
79        this.aci = aci;
80        this.frc = frc;
81
82        segmentsStart = aci.getBeginIndex();
83        segmentsEnd = aci.getEndIndex();
84
85        int len = segmentsEnd - segmentsStart;
86        text = new char[len];
87        aci.setIndex(segmentsEnd);
88        while (len-- != 0) { // Going in backward direction is faster? Simplier checks here?
89            text[len] = aci.previous();
90        }
91
92        createStyleRuns();
93    }
94
95    /**
96     * Visual order of text segments may differ from the logical order.
97     * This method calculates visual position of the segment from its logical position.
98     * @param segmentNum - logical position of the segment
99     * @return visual position of the segment
100     */
101    int getVisualFromSegmentOrder(int segmentNum) {
102        return (segment2visual == null) ? segmentNum : segment2visual[segmentNum];
103    }
104
105    /**
106     * Visual order of text segments may differ from the logical order.
107     * This method calculates logical position of the segment from its visual position.
108     * @param visual - visual position of the segment
109     * @return logical position of the segment
110     */
111    int getSegmentFromVisualOrder(int visual) {
112        return (visual2segment == null) ? visual : visual2segment[visual];
113    }
114
115    /**
116     * Visual order of the characters may differ from the logical order.
117     * This method calculates visual position of the character from its logical position.
118     * @param logical - logical position of the character
119     * @return visual position
120     */
121    int getVisualFromLogical(int logical) {
122        return (logical2visual == null) ? logical : logical2visual[logical];
123    }
124
125    /**
126     * Visual order of the characters may differ from the logical order.
127     * This method calculates logical position of the character from its visual position.
128     * @param visual - visual position
129     * @return logical position
130     */
131    int getLogicalFromVisual(int visual) {
132        return (visual2logical == null) ? visual : visual2logical[visual];
133    }
134
135    /**
136     * Calculates the end index of the level run, limited by the given text run.
137     * @param runStart - run start
138     * @param runEnd - run end
139     * @return end index of the level run
140     */
141    int getLevelRunLimit(int runStart, int runEnd) {
142        if (levels == null) {
143            return runEnd;
144        }
145        int endLevelRun = runStart + 1;
146        byte level = levels[runStart];
147
148        while (endLevelRun <= runEnd && levels[endLevelRun] == level) {
149            endLevelRun++;
150        }
151
152        return endLevelRun;
153    }
154
155    /**
156     * Adds InputMethodHighlight to the attributes
157     * @param attrs - text attributes
158     * @return patched text attributes
159     */
160    Map<? extends Attribute, ?> unpackAttributes(Map<? extends Attribute, ?> attrs) {
161        if (attrs.containsKey(TextAttribute.INPUT_METHOD_HIGHLIGHT)) {
162            Map<TextAttribute, ?> styles = null;
163
164            Object val = attrs.get(TextAttribute.INPUT_METHOD_HIGHLIGHT);
165
166            if (val instanceof Annotation) {
167                val = ((Annotation) val).getValue();
168            }
169
170            if (val instanceof InputMethodHighlight) {
171                InputMethodHighlight ihl = ((InputMethodHighlight) val);
172                styles = ihl.getStyle();
173
174                if (styles == null) {
175                    Toolkit tk = Toolkit.getDefaultToolkit();
176                    styles = tk.mapInputMethodHighlight(ihl);
177                }
178            }
179
180            if (styles != null) {
181                HashMap<Attribute, Object> newAttrs = new HashMap<Attribute, Object>();
182                newAttrs.putAll(attrs);
183                newAttrs.putAll(styles);
184                return newAttrs;
185            }
186        }
187
188        return attrs;
189    }
190
191    /**
192     * Breaks the text into separate style runs.
193     */
194    void createStyleRuns() {
195        // TODO - implement fast and simple case
196        fonts = new HashMap<Integer, Font>();
197        decorations = new HashMap<Integer, Decoration>();
198        ////
199
200        ArrayList<Integer> forcedFontRunStartsList = null;
201
202        Map<? extends Attribute, ?> attributes = null;
203
204        // Check justification attribute
205        Object val = aci.getAttribute(TextAttribute.JUSTIFICATION);
206        if (val != null) {
207            justification = ((Float) val).floatValue();
208        }
209
210        for (
211            int index = segmentsStart, nextRunStart = segmentsStart;
212            index < segmentsEnd;
213            index = nextRunStart, aci.setIndex(index)
214           )  {
215            nextRunStart = aci.getRunLimit();
216            attributes = unpackAttributes(aci.getAttributes());
217
218            TextDecorator.Decoration d = TextDecorator.getDecoration(attributes);
219            decorations.put(new Integer(index), d);
220
221            // Find appropriate font or place GraphicAttribute there
222
223            // 1. Try to pick up CHAR_REPLACEMENT (compatibility)
224            Font value = (Font)attributes.get(TextAttribute.CHAR_REPLACEMENT);
225
226            if (value == null) {
227                // 2. Try to Get FONT
228                value = (Font)attributes.get(TextAttribute.FONT);
229
230                if (value == null) {
231                    // 3. Try to create font from FAMILY
232                    if (attributes.get(TextAttribute.FAMILY) != null) {
233                        value = Font.getFont(attributes);
234                    }
235
236                    if (value == null) {
237                        // 4. No attributes found, using default.
238                        if (forcedFontRunStartsList == null) {
239                            forcedFontRunStartsList = new ArrayList<Integer>();
240                        }
241                        FontFinder.findFonts(
242                                text,
243                                index,
244                                nextRunStart,
245                                forcedFontRunStartsList,
246                                fonts
247                        );
248                        value = fonts.get(new Integer(index));
249                    }
250                }
251            }
252
253            fonts.put(new Integer(index), value);
254        }
255
256        // We have added some default fonts, so we have some extra runs in text
257        if (forcedFontRunStartsList != null) {
258            forcedFontRunStarts = new int[forcedFontRunStartsList.size()];
259            for (int i=0; i<forcedFontRunStartsList.size(); i++) {
260                forcedFontRunStarts[i] =
261                        forcedFontRunStartsList.get(i).intValue();
262            }
263        }
264    }
265
266    /**
267     * Starting from the current position looks for the end of the text run with
268     * constant text attributes.
269     * @param runStart - start position
270     * @param maxPos - position where to stop if no run limit found
271     * @return style run limit
272     */
273    int getStyleRunLimit(int runStart, int maxPos) {
274        try {
275            aci.setIndex(runStart);
276        } catch(IllegalArgumentException e) { // Index out of bounds
277            if (runStart < segmentsStart) {
278                aci.first();
279            } else {
280                aci.last();
281            }
282        }
283
284        // If we have some extra runs we need to check for their limits
285        if (forcedFontRunStarts != null) {
286            for (int element : forcedFontRunStarts) {
287                if (element > runStart) {
288                    maxPos = Math.min(element, maxPos);
289                    break;
290                }
291            }
292        }
293
294        return Math.min(aci.getRunLimit(), maxPos);
295    }
296
297    /**
298     * Creates segments for the text run with
299     * constant decoration, font and bidi level
300     * @param runStart - run start
301     * @param runEnd - run end
302     */
303    public void createSegments(int runStart, int runEnd) {
304        int endStyleRun, endLevelRun;
305
306        // TODO - update levels
307
308        int pos = runStart, levelPos;
309
310        aci.setIndex(pos);
311        final int firstRunStart = aci.getRunStart();
312        Object tdd = decorations.get(new Integer(firstRunStart));
313        Object fontOrGAttr = fonts.get(new Integer(firstRunStart));
314
315        logical2segment = new int[runEnd - runStart];
316
317        do {
318            endStyleRun = getStyleRunLimit(pos, runEnd);
319
320            // runStart can be non-zero, but all arrays will be indexed from 0
321            int ajustedPos = pos - runStart;
322            int ajustedEndStyleRun = endStyleRun - runStart;
323            levelPos = ajustedPos;
324            do {
325                endLevelRun = getLevelRunLimit(levelPos, ajustedEndStyleRun);
326
327                if (fontOrGAttr instanceof GraphicAttribute) {
328                    runSegments.add(
329                        new TextRunSegmentImpl.TextRunSegmentGraphic(
330                                (GraphicAttribute)fontOrGAttr,
331                                endLevelRun - levelPos,
332                                levelPos + runStart)
333                    );
334                    Arrays.fill(logical2segment, levelPos, endLevelRun, runSegments.size()-1);
335                } else {
336                    TextRunSegmentImpl.TextSegmentInfo i =
337                            new TextRunSegmentImpl.TextSegmentInfo(
338                                    levels == null ? 0 : levels[ajustedPos],
339                                    (Font) fontOrGAttr,
340                                    frc,
341                                    text,
342                                    levelPos + runStart,
343                                    endLevelRun + runStart
344                            );
345
346                    runSegments.add(
347                            new TextRunSegmentImpl.TextRunSegmentCommon(
348                                    i,
349                                    (TextDecorator.Decoration) tdd
350                            )
351                    );
352                    Arrays.fill(logical2segment, levelPos, endLevelRun, runSegments.size()-1);
353                }
354
355                levelPos = endLevelRun;
356            } while (levelPos < ajustedEndStyleRun);
357
358            // Prepare next iteration
359            pos = endStyleRun;
360            tdd = decorations.get(new Integer(pos));
361            fontOrGAttr = fonts.get(new Integer(pos));
362        } while (pos < runEnd);
363    }
364
365    /**
366     * Checks if text run segments are up to date and creates the new segments if not.
367     */
368    public void createAllSegments() {
369        if ( !haveAllSegments &&
370            (logical2segment == null ||
371             logical2segment.length != segmentsEnd - segmentsStart)
372        ) { // Check if we don't have all segments yet
373            resetSegments();
374            createSegments(segmentsStart, segmentsEnd);
375        }
376
377        haveAllSegments = true;
378    }
379
380    /**
381     * Calculates position where line should be broken without
382     * taking into account word boundaries.
383     * @param start - start index
384     * @param maxAdvance - maximum advance, width of the line
385     * @return position where to break
386     */
387    public int getLineBreakIndex(int start, float maxAdvance) {
388        int breakIndex;
389        TextRunSegment s = null;
390
391        for (
392                int segmentIndex = logical2segment[start];
393                segmentIndex < runSegments.size();
394                segmentIndex++
395           ) {
396            s = runSegments.get(segmentIndex);
397            breakIndex = s.getCharIndexFromAdvance(maxAdvance, start);
398
399            if (breakIndex < s.getEnd()) {
400                return breakIndex;
401            }
402            maxAdvance -= s.getAdvanceDelta(start, s.getEnd());
403            start = s.getEnd();
404        }
405
406        return s.getEnd();
407    }
408
409    /**
410     * Inserts character into the managed text.
411     * @param newParagraph - new character iterator
412     * @param insertPos - insertion position
413     */
414    public void insertChar(AttributedCharacterIterator newParagraph, int insertPos) {
415        aci = newParagraph;
416
417        char insChar = aci.setIndex(insertPos);
418
419        Integer key = new Integer(insertPos);
420
421        insertPos -= aci.getBeginIndex();
422
423        char newText[] = new char[text.length + 1];
424        System.arraycopy(text, 0, newText, 0, insertPos);
425        newText[insertPos] = insChar;
426        System.arraycopy(text, insertPos, newText, insertPos+1, text.length - insertPos);
427        text = newText;
428
429        if (aci.getRunStart() == key.intValue() && aci.getRunLimit() == key.intValue() + 1) {
430            createStyleRuns(); // We have to create one new run, could be optimized
431        } else {
432            shiftStyleRuns(key, 1);
433        }
434
435        resetSegments();
436
437        segmentsEnd++;
438    }
439
440    /**
441     * Deletes character from the managed text.
442     * @param newParagraph - new character iterator
443     * @param deletePos - deletion position
444     */
445    public void deleteChar(AttributedCharacterIterator newParagraph, int deletePos) {
446        aci = newParagraph;
447
448        Integer key = new Integer(deletePos);
449
450        deletePos -= aci.getBeginIndex();
451
452        char newText[] = new char[text.length - 1];
453        System.arraycopy(text, 0, newText, 0, deletePos);
454        System.arraycopy(text, deletePos+1, newText, deletePos, newText.length - deletePos);
455        text = newText;
456
457        if (fonts.get(key) != null) {
458            fonts.remove(key);
459        }
460
461        shiftStyleRuns(key, -1);
462
463        resetSegments();
464
465        segmentsEnd--;
466    }
467
468    /**
469     * Shift all runs after specified position, needed to perfom insertion
470     * or deletion in the managed text
471     * @param pos - position where to start
472     * @param shift - shift, could be negative
473     */
474    private void shiftStyleRuns(Integer pos, final int shift) {
475        ArrayList<Integer> keys = new ArrayList<Integer>();
476
477        Integer key, oldkey;
478        for (Iterator<Integer> it = fonts.keySet().iterator(); it.hasNext(); ) {
479            oldkey = it.next();
480            if (oldkey.intValue() > pos.intValue()) {
481                keys.add(oldkey);
482            }
483        }
484
485        for (int i=0; i<keys.size(); i++) {
486            oldkey = keys.get(i);
487            key = new Integer(shift + oldkey.intValue());
488            fonts.put(key, fonts.remove(oldkey));
489            decorations.put(key, decorations.remove(oldkey));
490        }
491    }
492
493    /**
494     * Resets state of the class
495     */
496    private void resetSegments() {
497        runSegments = new ArrayList<TextRunSegment>();
498        logical2segment = null;
499        segment2visual = null;
500        visual2segment = null;
501        levels = null;
502        haveAllSegments = false;
503    }
504
505    private class SegmentsInfo {
506        ArrayList<TextRunSegment> runSegments;
507        int logical2segment[];
508        int segment2visual[];
509        int visual2segment[];
510        byte levels[];
511        int segmentsStart;
512        int segmentsEnd;
513    }
514
515    /**
516     * Saves the internal state of the class
517     * @param newSegStart - new start index in the text
518     * @param newSegEnd - new end index in the text
519     */
520    public void pushSegments(int newSegStart, int newSegEnd) {
521        storedSegments = new SegmentsInfo();
522        storedSegments.runSegments = this.runSegments;
523        storedSegments.logical2segment = this.logical2segment;
524        storedSegments.segment2visual = this.segment2visual;
525        storedSegments.visual2segment = this.visual2segment;
526        storedSegments.levels = this.levels;
527        storedSegments.segmentsStart = segmentsStart;
528        storedSegments.segmentsEnd = segmentsEnd;
529
530        resetSegments();
531
532        segmentsStart = newSegStart;
533        segmentsEnd = newSegEnd;
534    }
535
536    /**
537     * Restores the internal state of the class
538     */
539    public void popSegments() {
540        if (storedSegments == null) {
541            return;
542        }
543
544        this.runSegments = storedSegments.runSegments;
545        this.logical2segment = storedSegments.logical2segment;
546        this.segment2visual = storedSegments.segment2visual;
547        this.visual2segment = storedSegments.visual2segment;
548        this.levels = storedSegments.levels;
549        this.segmentsStart = storedSegments.segmentsStart;
550        this.segmentsEnd = storedSegments.segmentsEnd;
551        storedSegments = null;
552
553        if (runSegments.size() == 0 && logical2segment == null) {
554            haveAllSegments = false;
555        } else {
556            haveAllSegments = true;
557        }
558    }
559
560    @Override
561    public Object clone() {
562        try {
563            TextRunBreaker res = (TextRunBreaker) super.clone();
564            res.storedSegments = null;
565            ArrayList<TextRunSegment> newSegments = new ArrayList<TextRunSegment>(runSegments.size());
566            for (int i = 0; i < runSegments.size(); i++) {
567                TextRunSegment seg =  runSegments.get(i);
568                newSegments.add((TextRunSegment)seg.clone());
569            }
570            res.runSegments = newSegments;
571            return res;
572        } catch (CloneNotSupportedException e) {
573            // awt.3E=Clone not supported
574            throw new UnsupportedOperationException(Messages.getString("awt.3E")); //$NON-NLS-1$
575        }
576    }
577
578    @Override
579    public boolean equals(Object obj) {
580        if (!(obj instanceof TextRunBreaker)) {
581            return false;
582        }
583
584        TextRunBreaker br = (TextRunBreaker) obj;
585
586        if (br.getACI().equals(aci) && br.frc.equals(frc)) {
587            return true;
588        }
589
590        return false;
591    }
592
593    @Override
594    public int hashCode() {
595        return HashCode.combine(aci.hashCode(), frc.hashCode());
596    }
597
598    /**
599     * Renders the managed text
600     * @param g2d - graphics where to render
601     * @param xOffset - offset in X direction to the upper left corner
602     * of the layout from the origin of the graphics
603     * @param yOffset - offset in Y direction to the upper left corner
604     * of the layout from the origin of the graphics
605     */
606    public void drawSegments(Graphics2D g2d, float xOffset, float yOffset) {
607        for (int i=0; i<runSegments.size(); i++) {
608            runSegments.get(i).draw(g2d, xOffset, yOffset);
609        }
610    }
611
612    /**
613     * Creates the black box bounds shape
614     * @param firstEndpoint - start position
615     * @param secondEndpoint - end position
616     * @return black box bounds shape
617     */
618    public Shape getBlackBoxBounds(int firstEndpoint, int secondEndpoint) {
619        GeneralPath bounds = new GeneralPath();
620
621        TextRunSegment segment;
622
623        for (int idx = firstEndpoint; idx < secondEndpoint; idx=segment.getEnd()) {
624            segment = runSegments.get(logical2segment[idx]);
625            bounds.append(segment.getCharsBlackBoxBounds(idx, secondEndpoint), false);
626        }
627
628        return bounds;
629    }
630
631    /**
632     * Creates visual bounds shape
633     * @return visual bounds rectangle
634     */
635    public Rectangle2D getVisualBounds() {
636        Rectangle2D bounds = null;
637
638        for (int i=0; i<runSegments.size(); i++) {
639            TextRunSegment s = runSegments.get(i);
640            if (bounds != null) {
641                Rectangle2D.union(bounds, s.getVisualBounds(), bounds);
642            } else {
643                bounds = s.getVisualBounds();
644            }
645        }
646
647        return bounds;
648    }
649
650    /**
651     * Creates logical bounds shape
652     * @return logical bounds rectangle
653     */
654    public Rectangle2D getLogicalBounds() {
655        Rectangle2D bounds = null;
656
657        for (int i=0; i<runSegments.size(); i++) {
658            TextRunSegment s = runSegments.get(i);
659            if (bounds != null) {
660                Rectangle2D.union(bounds, s.getLogicalBounds(), bounds);
661            } else {
662                bounds = s.getLogicalBounds();
663            }
664        }
665
666        return bounds;
667    }
668
669    public int getCharCount() {
670        return segmentsEnd - segmentsStart;
671    }
672
673    public byte getLevel(int idx) {
674        if (levels == null) {
675            return 0;
676        }
677        return levels[idx];
678    }
679
680    public int getBaseLevel() {
681        return 0;
682    }
683
684    public boolean isLTR() {
685        return true;
686    }
687
688    public char getChar(int index) {
689        return text[index];
690    }
691
692    public AttributedCharacterIterator getACI() {
693        return aci;
694    }
695
696    /**
697     * Creates outline shape for the managed text
698     * @return outline
699     */
700    public GeneralPath getOutline() {
701        GeneralPath outline = new GeneralPath();
702
703        TextRunSegment segment;
704
705        for (int i = 0; i < runSegments.size(); i++) {
706            segment = runSegments.get(i);
707            outline.append(segment.getOutline(), false);
708        }
709
710        return outline;
711    }
712
713    /**
714     * Calculates text hit info from the screen coordinates.
715     * Current implementation totally ignores Y coordinate.
716     * If X coordinate is outside of the layout boundaries, this
717     * method returns leftmost or rightmost hit.
718     * @param x - x coordinate of the hit
719     * @param y - y coordinate of the hit
720     * @return hit info
721     */
722    public TextHitInfo hitTest(float x, float y) {
723        TextRunSegment segment;
724
725        double endOfPrevSeg = -1;
726        for (int i = 0; i < runSegments.size(); i++) {
727            segment = runSegments.get(i);
728            Rectangle2D bounds = segment.getVisualBounds();
729            if ((bounds.getMinX() <= x && bounds.getMaxX() >= x) || // We are in the segment
730               (endOfPrevSeg < x && bounds.getMinX() > x)) { // We are somewhere between the segments
731                return segment.hitTest(x,y);
732            }
733            endOfPrevSeg = bounds.getMaxX();
734        }
735
736        return isLTR() ? TextHitInfo.trailing(text.length) : TextHitInfo.leading(0);
737    }
738
739    public float getJustification() {
740        return justification;
741    }
742
743    /**
744     * Calculates position of the last non whitespace character
745     * in the managed text.
746     * @return position of the last non whitespace character
747     */
748    public int getLastNonWhitespace() {
749        int lastNonWhitespace = text.length;
750
751        while (lastNonWhitespace >= 0) {
752            lastNonWhitespace--;
753            if (!Character.isWhitespace(text[lastNonWhitespace])) {
754                break;
755            }
756        }
757
758        return lastNonWhitespace;
759    }
760
761    /**
762     * Performs justification of the managed text by changing segment positions
763     * and positions of the glyphs inside of the segments.
764     * @param gap - amount of space which should be compensated by justification
765     */
766    public void justify(float gap) {
767        // Ignore trailing logical whitespace
768        int firstIdx = segmentsStart;
769        int lastIdx = getLastNonWhitespace() + segmentsStart;
770        JustificationInfo jInfos[] = new JustificationInfo[5];
771        float gapLeft = gap;
772
773        int highestPriority = -1;
774        // GlyphJustificationInfo.PRIORITY_KASHIDA is 0
775        // GlyphJustificationInfo.PRIORITY_NONE is 3
776        for (int priority = 0; priority <= GlyphJustificationInfo.PRIORITY_NONE + 1; priority++) {
777            JustificationInfo jInfo = new JustificationInfo();
778            jInfo.lastIdx = lastIdx;
779            jInfo.firstIdx = firstIdx;
780            jInfo.grow = gap > 0;
781            jInfo.gapToFill = gapLeft;
782
783            if (priority <= GlyphJustificationInfo.PRIORITY_NONE) {
784                jInfo.priority = priority;
785            } else {
786                jInfo.priority = highestPriority; // Last pass
787            }
788
789            for (int i = 0; i < runSegments.size(); i++) {
790                TextRunSegment segment = runSegments.get(i);
791                if (segment.getStart() <= lastIdx) {
792                    segment.updateJustificationInfo(jInfo);
793                }
794            }
795
796            if (jInfo.priority == highestPriority) {
797                jInfo.absorb = true;
798                jInfo.absorbedWeight = jInfo.weight;
799            }
800
801            if (jInfo.weight != 0) {
802                if (highestPriority < 0) {
803                    highestPriority = priority;
804                }
805                jInfos[priority] = jInfo;
806            } else {
807                continue;
808            }
809
810            gapLeft -= jInfo.growLimit;
811
812            if (((gapLeft > 0) ^ jInfo.grow) || gapLeft == 0) {
813                gapLeft = 0;
814                jInfo.gapPerUnit = jInfo.gapToFill/jInfo.weight;
815                break;
816            }
817            jInfo.useLimits = true;
818
819            if (jInfo.absorbedWeight > 0) {
820                jInfo.absorb = true;
821                jInfo.absorbedGapPerUnit =
822                        (jInfo.gapToFill-jInfo.growLimit)/jInfo.absorbedWeight;
823                break;
824            }
825        }
826
827        float currJustificationOffset = 0;
828        for (int i = 0; i < runSegments.size(); i++) {
829            TextRunSegment segment =
830                    runSegments.get(getSegmentFromVisualOrder(i));
831            segment.x += currJustificationOffset;
832            currJustificationOffset += segment.doJustification(jInfos);
833        }
834
835        justification = -1; // Make further justification impossible
836    }
837
838    /**
839     * This class represents the information collected before the actual
840     * justification is started and needed to perform the justification.
841     * This information is closely related to the information stored in the
842     * GlyphJustificationInfo for the text represented by glyph vectors.
843     */
844    class JustificationInfo {
845        boolean grow;
846        boolean absorb = false;
847        boolean useLimits = false;
848        int priority = 0;
849        float weight = 0;
850        float absorbedWeight = 0;
851        float growLimit = 0;
852
853        int lastIdx;
854        int firstIdx;
855
856        float gapToFill;
857
858        float gapPerUnit = 0; // Precalculated value, gapToFill / weight
859        float absorbedGapPerUnit = 0; // Precalculated value, gapToFill / weight
860    }
861}
862