1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.graphics.text; 18 19 import android.annotation.NonNull; 20 import android.graphics.text.Primitive.PrimitiveType; 21 22 import java.util.List; 23 24 import static android.graphics.text.Primitive.PrimitiveType.PENALTY_INFINITY; 25 26 // Based on the native implementation of GreedyLineBreaker in 27 // frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260 28 public class GreedyLineBreaker extends BaseLineBreaker { 29 GreedyLineBreaker(@onNull List<Primitive> primitives, @NonNull LineWidth lineWidth, @NonNull TabStops tabStops)30 public GreedyLineBreaker(@NonNull List<Primitive> primitives, @NonNull LineWidth lineWidth, 31 @NonNull TabStops tabStops) { 32 super(primitives, lineWidth, tabStops); 33 } 34 35 @Override computeBreaks()36 public Result computeBreaks() { 37 int lineNum = 0; 38 float width = 0, printedWidth = 0; 39 boolean breakFound = false, goodBreakFound = false; 40 int breakIndex = 0, goodBreakIndex = 0; 41 float breakWidth = 0, goodBreakWidth = 0; 42 int firstTabIndex = Integer.MAX_VALUE; 43 44 float maxWidth = mLineWidth.getLineWidth(lineNum); 45 46 int numPrimitives = mPrimitives.size(); 47 Result result = new Result(); 48 // greedily fit as many characters as possible on each line 49 // loop over all primitives, and choose the best break point 50 // (if possible, a break point without splitting a word) 51 // after going over the maximum length 52 for (int i = 0; i < numPrimitives; i++) { 53 Primitive p = mPrimitives.get(i); 54 55 // update the current line width 56 if (p.type == PrimitiveType.BOX || p.type == PrimitiveType.GLUE) { 57 width += p.width; 58 if (p.type == PrimitiveType.BOX) { 59 printedWidth = width; 60 } 61 } else if (p.type == PrimitiveType.VARIABLE) { 62 width = mTabStops.width(width); 63 // keep track of first tab character in the region we are examining 64 // so we can determine whether or not a line contains a tab 65 firstTabIndex = Math.min(firstTabIndex, i); 66 } 67 68 // find the best break point for the characters examined so far 69 if (printedWidth > maxWidth) { 70 //noinspection StatementWithEmptyBody 71 if (breakFound || goodBreakFound) { 72 if (goodBreakFound) { 73 // a true line break opportunity existed in the characters examined so far, 74 // so there is no need to split a word 75 i = goodBreakIndex; // no +1 because of i++ 76 lineNum++; 77 maxWidth = mLineWidth.getLineWidth(lineNum); 78 result.mLineBreakOffset.add(mPrimitives.get(goodBreakIndex).location); 79 result.mLineWidths.add(goodBreakWidth); 80 result.mLineAscents.add(0f); 81 result.mLineDescents.add(0f); 82 result.mLineFlags.add(firstTabIndex < goodBreakIndex ? TAB_MASK : 0); 83 firstTabIndex = Integer.MAX_VALUE; 84 } else { 85 // must split a word because there is no other option 86 i = breakIndex; // no +1 because of i++ 87 lineNum++; 88 maxWidth = mLineWidth.getLineWidth(lineNum); 89 result.mLineBreakOffset.add(mPrimitives.get(breakIndex).location); 90 result.mLineWidths.add(breakWidth); 91 result.mLineAscents.add(0f); 92 result.mLineDescents.add(0f); 93 result.mLineFlags.add(firstTabIndex < breakIndex ? TAB_MASK : 0); 94 firstTabIndex = Integer.MAX_VALUE; 95 } 96 printedWidth = width = 0; 97 goodBreakFound = breakFound = false; 98 goodBreakWidth = breakWidth = 0; 99 continue; 100 } else { 101 // no choice, keep going... must make progress by putting at least one 102 // character on a line, even if part of that character is cut off -- 103 // there is no other option 104 } 105 } 106 107 // update possible break points 108 if (p.type == PrimitiveType.PENALTY && 109 p.penalty < PENALTY_INFINITY) { 110 // this does not handle penalties with width 111 112 // handle forced line break 113 if (p.penalty == -PENALTY_INFINITY) { 114 lineNum++; 115 maxWidth = mLineWidth.getLineWidth(lineNum); 116 result.mLineBreakOffset.add(p.location); 117 result.mLineWidths.add(printedWidth); 118 result.mLineAscents.add(0f); 119 result.mLineDescents.add(0f); 120 result.mLineFlags.add(firstTabIndex < i ? TAB_MASK : 0); 121 firstTabIndex = Integer.MAX_VALUE; 122 printedWidth = width = 0; 123 goodBreakFound = breakFound = false; 124 goodBreakWidth = breakWidth = 0; 125 continue; 126 } 127 if (i > breakIndex && (printedWidth <= maxWidth || !breakFound)) { 128 breakFound = true; 129 breakIndex = i; 130 breakWidth = printedWidth; 131 } 132 if (i > goodBreakIndex && printedWidth <= maxWidth) { 133 goodBreakFound = true; 134 goodBreakIndex = i; 135 goodBreakWidth = printedWidth; 136 } 137 } else if (p.type == PrimitiveType.WORD_BREAK) { 138 // only do this if necessary -- we don't want to break words 139 // when possible, but sometimes it is unavoidable 140 if (i > breakIndex && (printedWidth <= maxWidth || !breakFound)) { 141 breakFound = true; 142 breakIndex = i; 143 breakWidth = printedWidth; 144 } 145 } 146 } 147 148 if (breakFound || goodBreakFound) { 149 // output last break if there are more characters to output 150 if (goodBreakFound) { 151 result.mLineBreakOffset.add(mPrimitives.get(goodBreakIndex).location); 152 result.mLineWidths.add(goodBreakWidth); 153 result.mLineAscents.add(0f); 154 result.mLineDescents.add(0f); 155 result.mLineFlags.add(firstTabIndex < goodBreakIndex ? TAB_MASK : 0); 156 } else { 157 result.mLineBreakOffset.add(mPrimitives.get(breakIndex).location); 158 result.mLineWidths.add(breakWidth); 159 result.mLineAscents.add(0f); 160 result.mLineDescents.add(0f); 161 result.mLineFlags.add(firstTabIndex < breakIndex ? TAB_MASK : 0); 162 } 163 } 164 return result; 165 } 166 } 167