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