| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.text; |
| |
| import android.annotation.NonNull; |
| import android.text.Primitive.PrimitiveType; |
| import android.text.StaticLayout.LineBreaks; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import static android.text.Primitive.PrimitiveType.PENALTY_INFINITY; |
| |
| // Based on the native implementation of GreedyLineBreaker in |
| // frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260 |
| public class GreedyLineBreaker extends LineBreaker { |
| |
| public GreedyLineBreaker(@NonNull List<Primitive> primitives, @NonNull LineWidth lineWidth, |
| @NonNull TabStops tabStops) { |
| super(primitives, lineWidth, tabStops); |
| } |
| |
| @Override |
| public void computeBreaks(@NonNull LineBreaks lineBreaks) { |
| BreakInfo breakInfo = new BreakInfo(); |
| int lineNum = 0; |
| float width = 0, printedWidth = 0; |
| boolean breakFound = false, goodBreakFound = false; |
| int breakIndex = 0, goodBreakIndex = 0; |
| float breakWidth = 0, goodBreakWidth = 0; |
| int firstTabIndex = Integer.MAX_VALUE; |
| |
| float maxWidth = mLineWidth.getLineWidth(lineNum); |
| |
| int numPrimitives = mPrimitives.size(); |
| // greedily fit as many characters as possible on each line |
| // loop over all primitives, and choose the best break point |
| // (if possible, a break point without splitting a word) |
| // after going over the maximum length |
| for (int i = 0; i < numPrimitives; i++) { |
| Primitive p = mPrimitives.get(i); |
| |
| // update the current line width |
| if (p.type == PrimitiveType.BOX || p.type == PrimitiveType.GLUE) { |
| width += p.width; |
| if (p.type == PrimitiveType.BOX) { |
| printedWidth = width; |
| } |
| } else if (p.type == PrimitiveType.VARIABLE) { |
| width = mTabStops.width(width); |
| // keep track of first tab character in the region we are examining |
| // so we can determine whether or not a line contains a tab |
| firstTabIndex = Math.min(firstTabIndex, i); |
| } |
| |
| // find the best break point for the characters examined so far |
| if (printedWidth > maxWidth) { |
| //noinspection StatementWithEmptyBody |
| if (breakFound || goodBreakFound) { |
| if (goodBreakFound) { |
| // a true line break opportunity existed in the characters examined so far, |
| // so there is no need to split a word |
| i = goodBreakIndex; // no +1 because of i++ |
| lineNum++; |
| maxWidth = mLineWidth.getLineWidth(lineNum); |
| breakInfo.mBreaksList.add(mPrimitives.get(goodBreakIndex).location); |
| breakInfo.mWidthsList.add(goodBreakWidth); |
| breakInfo.mFlagsList.add(firstTabIndex < goodBreakIndex); |
| firstTabIndex = Integer.MAX_VALUE; |
| } else { |
| // must split a word because there is no other option |
| i = breakIndex; // no +1 because of i++ |
| lineNum++; |
| maxWidth = mLineWidth.getLineWidth(lineNum); |
| breakInfo.mBreaksList.add(mPrimitives.get(breakIndex).location); |
| breakInfo.mWidthsList.add(breakWidth); |
| breakInfo.mFlagsList.add(firstTabIndex < breakIndex); |
| firstTabIndex = Integer.MAX_VALUE; |
| } |
| printedWidth = width = 0; |
| goodBreakFound = breakFound = false; |
| goodBreakWidth = breakWidth = 0; |
| continue; |
| } else { |
| // no choice, keep going... must make progress by putting at least one |
| // character on a line, even if part of that character is cut off -- |
| // there is no other option |
| } |
| } |
| |
| // update possible break points |
| if (p.type == PrimitiveType.PENALTY && |
| p.penalty < PENALTY_INFINITY) { |
| // this does not handle penalties with width |
| |
| // handle forced line break |
| if (p.penalty == -PENALTY_INFINITY) { |
| lineNum++; |
| maxWidth = mLineWidth.getLineWidth(lineNum); |
| breakInfo.mBreaksList.add(p.location); |
| breakInfo.mWidthsList.add(printedWidth); |
| breakInfo.mFlagsList.add(firstTabIndex < i); |
| firstTabIndex = Integer.MAX_VALUE; |
| printedWidth = width = 0; |
| goodBreakFound = breakFound = false; |
| goodBreakWidth = breakWidth = 0; |
| continue; |
| } |
| if (i > breakIndex && (printedWidth <= maxWidth || !breakFound)) { |
| breakFound = true; |
| breakIndex = i; |
| breakWidth = printedWidth; |
| } |
| if (i > goodBreakIndex && printedWidth <= maxWidth) { |
| goodBreakFound = true; |
| goodBreakIndex = i; |
| goodBreakWidth = printedWidth; |
| } |
| } else if (p.type == PrimitiveType.WORD_BREAK) { |
| // only do this if necessary -- we don't want to break words |
| // when possible, but sometimes it is unavoidable |
| if (i > breakIndex && (printedWidth <= maxWidth || !breakFound)) { |
| breakFound = true; |
| breakIndex = i; |
| breakWidth = printedWidth; |
| } |
| } |
| } |
| |
| if (breakFound || goodBreakFound) { |
| // output last break if there are more characters to output |
| if (goodBreakFound) { |
| breakInfo.mBreaksList.add(mPrimitives.get(goodBreakIndex).location); |
| breakInfo.mWidthsList.add(goodBreakWidth); |
| breakInfo.mFlagsList.add(firstTabIndex < goodBreakIndex); |
| } else { |
| breakInfo.mBreaksList.add(mPrimitives.get(breakIndex).location); |
| breakInfo.mWidthsList.add(breakWidth); |
| breakInfo.mFlagsList.add(firstTabIndex < breakIndex); |
| } |
| } |
| breakInfo.copyTo(lineBreaks); |
| } |
| |
| private static class BreakInfo { |
| List<Integer> mBreaksList = new ArrayList<Integer>(); |
| List<Float> mWidthsList = new ArrayList<Float>(); |
| List<Boolean> mFlagsList = new ArrayList<Boolean>(); |
| |
| public void copyTo(LineBreaks lineBreaks) { |
| if (lineBreaks.breaks.length != mBreaksList.size()) { |
| lineBreaks.breaks = new int[mBreaksList.size()]; |
| lineBreaks.widths = new float[mWidthsList.size()]; |
| lineBreaks.flags = new int[mFlagsList.size()]; |
| } |
| |
| int i = 0; |
| for (int b : mBreaksList) { |
| lineBreaks.breaks[i] = b; |
| i++; |
| } |
| i = 0; |
| for (float b : mWidthsList) { |
| lineBreaks.widths[i] = b; |
| i++; |
| } |
| i = 0; |
| for (boolean b : mFlagsList) { |
| lineBreaks.flags[i] = b ? TAB_MASK : 0; |
| i++; |
| } |
| |
| mBreaksList = null; |
| mWidthsList = null; |
| mFlagsList = null; |
| } |
| } |
| } |