GreedyLineBreaker.java revision 26d443aee4ee5a8791417b4ca09e8c78ba8dc78b
1/* 2 * Copyright (C) 2014 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 17package android.text; 18 19import com.android.annotations.NonNull; 20 21import android.text.Primitive.PrimitiveType; 22import android.text.StaticLayout.LineBreaks; 23 24import java.util.ArrayList; 25import java.util.List; 26 27import static android.text.Primitive.PrimitiveType.PENALTY_INFINITY; 28 29// Based on the native implementation of GreedyLineBreaker in 30// frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260 31public class GreedyLineBreaker extends LineBreaker { 32 33 public GreedyLineBreaker(@NonNull List<Primitive> primitives, @NonNull LineWidth lineWidth, 34 @NonNull TabStops tabStops) { 35 super(primitives, lineWidth, tabStops); 36 } 37 38 @Override 39 public void computeBreaks(LineBreaks lineBreaks) { 40 BreakInfo breakInfo = new BreakInfo(); 41 int lineNum = 0; 42 float width = 0, printedWidth = 0; 43 boolean breakFound = false, goodBreakFound = false; 44 int breakIndex = 0, goodBreakIndex = 0; 45 float breakWidth = 0, goodBreakWidth = 0; 46 int firstTabIndex = Integer.MAX_VALUE; 47 48 float maxWidth = mLineWidth.getLineWidth(lineNum); 49 50 int numPrimitives = mPrimitives.size(); 51 // greedily fit as many characters as possible on each line 52 // loop over all primitives, and choose the best break point 53 // (if possible, a break point without splitting a word) 54 // after going over the maximum length 55 for (int i = 0; i < numPrimitives; i++) { 56 Primitive p = mPrimitives.get(i); 57 58 // update the current line width 59 if (p.type == PrimitiveType.BOX || p.type == PrimitiveType.GLUE) { 60 width += p.width; 61 if (p.type == PrimitiveType.BOX) { 62 printedWidth = width; 63 } 64 } else if (p.type == PrimitiveType.VARIABLE) { 65 width = mTabStops.width(width); 66 // keep track of first tab character in the region we are examining 67 // so we can determine whether or not a line contains a tab 68 firstTabIndex = Math.min(firstTabIndex, i); 69 } 70 71 // find the best break point for the characters examined so far 72 if (printedWidth > maxWidth) { 73 //noinspection StatementWithEmptyBody 74 if (breakFound || goodBreakFound) { 75 if (goodBreakFound) { 76 // a true line break opportunity existed in the characters examined so far, 77 // so there is no need to split a word 78 i = goodBreakIndex; // no +1 because of i++ 79 lineNum++; 80 maxWidth = mLineWidth.getLineWidth(lineNum); 81 breakInfo.mBreaksList.add(mPrimitives.get(goodBreakIndex).location); 82 breakInfo.mWidthsList.add(goodBreakWidth); 83 breakInfo.mFlagsList.add(firstTabIndex < goodBreakIndex); 84 firstTabIndex = Integer.MAX_VALUE; 85 } else { 86 // must split a word because there is no other option 87 i = breakIndex; // no +1 because of i++ 88 lineNum++; 89 maxWidth = mLineWidth.getLineWidth(lineNum); 90 breakInfo.mBreaksList.add(mPrimitives.get(breakIndex).location); 91 breakInfo.mWidthsList.add(breakWidth); 92 breakInfo.mFlagsList.add(firstTabIndex < breakIndex); 93 firstTabIndex = Integer.MAX_VALUE; 94 } 95 printedWidth = width = 0; 96 goodBreakFound = breakFound = false; 97 goodBreakWidth = breakWidth = 0; 98 continue; 99 } else { 100 // no choice, keep going... must make progress by putting at least one 101 // character on a line, even if part of that character is cut off -- 102 // there is no other option 103 } 104 } 105 106 // update possible break points 107 if (p.type == PrimitiveType.PENALTY && 108 p.penalty < PENALTY_INFINITY) { 109 // this does not handle penalties with width 110 111 // handle forced line break 112 if (p.penalty == -PENALTY_INFINITY) { 113 lineNum++; 114 maxWidth = mLineWidth.getLineWidth(lineNum); 115 breakInfo.mBreaksList.add(p.location); 116 breakInfo.mWidthsList.add(printedWidth); 117 breakInfo.mFlagsList.add(firstTabIndex < i); 118 firstTabIndex = Integer.MAX_VALUE; 119 printedWidth = width = 0; 120 goodBreakFound = breakFound = false; 121 goodBreakWidth = breakWidth = 0; 122 continue; 123 } 124 if (i > breakIndex && (printedWidth <= maxWidth || !breakFound)) { 125 breakFound = true; 126 breakIndex = i; 127 breakWidth = printedWidth; 128 } 129 if (i > goodBreakIndex && printedWidth <= maxWidth) { 130 goodBreakFound = true; 131 goodBreakIndex = i; 132 goodBreakWidth = printedWidth; 133 } 134 } else if (p.type == PrimitiveType.WORD_BREAK) { 135 // only do this if necessary -- we don't want to break words 136 // when possible, but sometimes it is unavoidable 137 if (i > breakIndex && (printedWidth <= maxWidth || !breakFound)) { 138 breakFound = true; 139 breakIndex = i; 140 breakWidth = printedWidth; 141 } 142 } 143 } 144 145 if (breakFound || goodBreakFound) { 146 // output last break if there are more characters to output 147 if (goodBreakFound) { 148 breakInfo.mBreaksList.add(mPrimitives.get(goodBreakIndex).location); 149 breakInfo.mWidthsList.add(goodBreakWidth); 150 breakInfo.mFlagsList.add(firstTabIndex < goodBreakIndex); 151 } else { 152 breakInfo.mBreaksList.add(mPrimitives.get(breakIndex).location); 153 breakInfo.mWidthsList.add(breakWidth); 154 breakInfo.mFlagsList.add(firstTabIndex < breakIndex); 155 } 156 } 157 breakInfo.copyTo(lineBreaks); 158 } 159 160 private static class BreakInfo { 161 List<Integer> mBreaksList = new ArrayList<Integer>(); 162 List<Float> mWidthsList = new ArrayList<Float>(); 163 List<Boolean> mFlagsList = new ArrayList<Boolean>(); 164 165 public void copyTo(LineBreaks lineBreaks) { 166 if (lineBreaks.breaks.length != mBreaksList.size()) { 167 lineBreaks.breaks = new int[mBreaksList.size()]; 168 lineBreaks.widths = new float[mWidthsList.size()]; 169 lineBreaks.flags = new int[mFlagsList.size()]; 170 } 171 172 int i = 0; 173 for (int b : mBreaksList) { 174 lineBreaks.breaks[i] = b; 175 i++; 176 } 177 i = 0; 178 for (float b : mWidthsList) { 179 lineBreaks.widths[i] = b; 180 i++; 181 } 182 i = 0; 183 for (boolean b : mFlagsList) { 184 lineBreaks.flags[i] = b ? TAB_MASK : 0; 185 i++; 186 } 187 188 mBreaksList = null; 189 mWidthsList = null; 190 mFlagsList = null; 191 } 192 } 193} 194