OptimizingLineBreaker.java revision 7053919a3a55ad1b58e6db701afda18850037732
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; 26import java.util.ListIterator; 27 28import static android.text.Primitive.PrimitiveType.PENALTY_INFINITY; 29 30 31// Based on the native implementation of OptimizingLineBreaker in 32// frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260 33/** 34 * A more complex version of line breaking where we try to prevent the right edge from being too 35 * jagged. 36 */ 37public class OptimizingLineBreaker extends LineBreaker { 38 39 public OptimizingLineBreaker(@NonNull List<Primitive> primitives, @NonNull LineWidth lineWidth, 40 @NonNull TabStops tabStops) { 41 super(primitives, lineWidth, tabStops); 42 } 43 44 @Override 45 public void computeBreaks(@NonNull LineBreaks breakInfo) { 46 int numBreaks = mPrimitives.size(); 47 assert numBreaks > 0; 48 if (numBreaks == 1) { 49 // This can be true only if it's an empty paragraph. 50 Primitive p = mPrimitives.get(0); 51 assert p.type == PrimitiveType.PENALTY; 52 breakInfo.breaks = new int[]{0}; 53 breakInfo.widths = new float[]{p.width}; 54 breakInfo.flags = new boolean[]{false}; 55 return; 56 } 57 Node[] opt = new Node[numBreaks]; 58 opt[0] = new Node(-1, 0, 0, 0, false); 59 opt[numBreaks - 1] = new Node(-1, 0, 0, 0, false); 60 61 ArrayList<Integer> active = new ArrayList<Integer>(); 62 active.add(0); 63 int lastBreak = 0; 64 for (int i = 0; i < numBreaks; i++) { 65 Primitive p = mPrimitives.get(i); 66 if (p.type == PrimitiveType.PENALTY) { 67 boolean finalBreak = (i + 1 == numBreaks); 68 Node bestBreak = null; 69 70 for (ListIterator<Integer> it = active.listIterator(); it.hasNext(); 71 /* incrementing done in loop */) { 72 int pos = it.next(); 73 int lines = opt[pos].mPrevCount; 74 float maxWidth = mLineWidth.getLineWidth(lines); 75 // we have to compute metrics every time -- 76 // we can't really pre-compute this stuff and just deal with breaks 77 // because of the way tab characters work, this makes it computationally 78 // harder, but this way, we can still optimize while treating tab characters 79 // correctly 80 LineMetrics lineMetrics = computeMetrics(pos, i); 81 if (lineMetrics.mPrintedWidth <= maxWidth) { 82 float demerits = computeDemerits(maxWidth, lineMetrics.mPrintedWidth, 83 finalBreak, p.penalty) + opt[pos].mDemerits; 84 if (bestBreak == null || demerits < bestBreak.mDemerits) { 85 if (bestBreak == null) { 86 bestBreak = new Node(pos, opt[pos].mPrevCount + 1, demerits, 87 lineMetrics.mPrintedWidth, lineMetrics.mHasTabs); 88 } else { 89 bestBreak.mPrev = pos; 90 bestBreak.mPrevCount = opt[pos].mPrevCount + 1; 91 bestBreak.mDemerits = demerits; 92 bestBreak.mWidth = lineMetrics.mPrintedWidth; 93 bestBreak.mHasTabs = lineMetrics.mHasTabs; 94 } 95 } 96 } else { 97 it.remove(); 98 } 99 } 100 if (p.penalty == -PENALTY_INFINITY) { 101 active.clear(); 102 } 103 if (bestBreak != null) { 104 opt[i] = bestBreak; 105 active.add(i); 106 lastBreak = i; 107 } 108 if (active.isEmpty()) { 109 // we can't give up! 110 LineMetrics lineMetrics = new LineMetrics(); 111 int lines = opt[lastBreak].mPrevCount; 112 float maxWidth = mLineWidth.getLineWidth(lines); 113 int breakIndex = desperateBreak(lastBreak, numBreaks, maxWidth, lineMetrics); 114 opt[breakIndex] = new Node(lastBreak, lines + 1, 0 /*doesn't matter*/, 115 lineMetrics.mWidth, lineMetrics.mHasTabs); 116 active.add(breakIndex); 117 lastBreak = breakIndex; 118 i = breakIndex; // incremented by i++ 119 } 120 } 121 } 122 123 int idx = numBreaks - 1; 124 int count = opt[idx].mPrevCount; 125 resize(breakInfo, count); 126 while (opt[idx].mPrev != -1) { 127 count--; 128 assert count >=0; 129 130 breakInfo.breaks[count] = mPrimitives.get(idx).location; 131 breakInfo.widths[count] = opt[idx].mWidth; 132 breakInfo.flags [count] = opt[idx].mHasTabs; 133 idx = opt[idx].mPrev; 134 } 135 } 136 137 private static void resize(LineBreaks lineBreaks, int size) { 138 if (lineBreaks.breaks.length == size) { 139 return; 140 } 141 int[] breaks = new int[size]; 142 float[] widths = new float[size]; 143 boolean[] flags = new boolean[size]; 144 145 int toCopy = Math.min(size, lineBreaks.breaks.length); 146 System.arraycopy(lineBreaks.breaks, 0, breaks, 0, toCopy); 147 System.arraycopy(lineBreaks.widths, 0, widths, 0, toCopy); 148 System.arraycopy(lineBreaks.flags, 0, flags, 0, toCopy); 149 150 lineBreaks.breaks = breaks; 151 lineBreaks.widths = widths; 152 lineBreaks.flags = flags; 153 } 154 155 @NonNull 156 private LineMetrics computeMetrics(int start, int end) { 157 boolean f = false; 158 float w = 0, pw = 0; 159 for (int i = start; i < end; i++) { 160 Primitive p = mPrimitives.get(i); 161 if (p.type == PrimitiveType.BOX || p.type == PrimitiveType.GLUE) { 162 w += p.width; 163 if (p.type == PrimitiveType.BOX) { 164 pw = w; 165 } 166 } else if (p.type == PrimitiveType.VARIABLE) { 167 w = mTabStops.width(w); 168 f = true; 169 } 170 } 171 return new LineMetrics(w, pw, f); 172 } 173 174 private static float computeDemerits(float maxWidth, float width, boolean finalBreak, 175 float penalty) { 176 float deviation = finalBreak ? 0 : maxWidth - width; 177 return (deviation * deviation) + penalty; 178 } 179 180 /** 181 * @return the last break position or -1 if failed. 182 */ 183 @SuppressWarnings("ConstantConditions") // method too complex to be analyzed. 184 private int desperateBreak(int start, int limit, float maxWidth, 185 @NonNull LineMetrics lineMetrics) { 186 float w = 0, pw = 0; 187 boolean breakFound = false; 188 int breakIndex = 0, firstTabIndex = Integer.MAX_VALUE; 189 for (int i = start; i < limit; i++) { 190 Primitive p = mPrimitives.get(i); 191 192 if (p.type == PrimitiveType.BOX || p.type == PrimitiveType.GLUE) { 193 w += p.width; 194 if (p.type == PrimitiveType.BOX) { 195 pw = w; 196 } 197 } else if (p.type == PrimitiveType.VARIABLE) { 198 w = mTabStops.width(w); 199 firstTabIndex = Math.min(firstTabIndex, i); 200 } 201 202 if (pw > maxWidth && breakFound) { 203 break; 204 } 205 206 // must make progress 207 if (i > start && 208 (p.type == PrimitiveType.PENALTY || p.type == PrimitiveType.WORD_BREAK)) { 209 breakFound = true; 210 breakIndex = i; 211 } 212 } 213 214 if (breakFound) { 215 lineMetrics.mWidth = w; 216 lineMetrics.mPrintedWidth = pw; 217 lineMetrics.mHasTabs = (start <= firstTabIndex && firstTabIndex < breakIndex); 218 return breakIndex; 219 } else { 220 return -1; 221 } 222 } 223 224 private static class LineMetrics { 225 /** Actual width of the line. */ 226 float mWidth; 227 /** Width of the line minus trailing whitespace. */ 228 float mPrintedWidth; 229 boolean mHasTabs; 230 231 public LineMetrics() { 232 } 233 234 public LineMetrics(float width, float printedWidth, boolean hasTabs) { 235 mWidth = width; 236 mPrintedWidth = printedWidth; 237 mHasTabs = hasTabs; 238 } 239 } 240 241 /** 242 * A struct to store the info about a break. 243 */ 244 @SuppressWarnings("SpellCheckingInspection") // For the word struct. 245 private static class Node { 246 // -1 for the first node. 247 int mPrev; 248 // number of breaks so far. 249 int mPrevCount; 250 float mDemerits; 251 float mWidth; 252 boolean mHasTabs; 253 254 public Node(int prev, int prevCount, float demerits, float width, boolean hasTabs) { 255 mPrev = prev; 256 mPrevCount = prevCount; 257 mDemerits = demerits; 258 mWidth = width; 259 mHasTabs = hasTabs; 260 } 261 } 262} 263