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