Home | History | Annotate | Download | only in text
      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 
     32     public GreedyLineBreaker(@NonNull List<Primitive> primitives, @NonNull LineWidth lineWidth,
     33             @NonNull TabStops tabStops) {
     34         super(primitives, lineWidth, tabStops);
     35     }
     36 
     37     @Override
     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