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