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