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