Home | History | Annotate | Download | only in text
      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 com.android.layoutlib.bridge.impl.DelegateManager;
     20 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
     21 
     22 import android.annotation.NonNull;
     23 import android.annotation.Nullable;
     24 import android.icu.text.BreakIterator;
     25 import android.text.Layout;
     26 import android.text.Layout.BreakStrategy;
     27 import android.text.Layout.HyphenationFrequency;
     28 import android.graphics.text.Primitive.PrimitiveType;
     29 
     30 import java.text.CharacterIterator;
     31 import java.util.ArrayList;
     32 import java.util.List;
     33 
     34 import javax.swing.text.Segment;
     35 import libcore.util.NativeAllocationRegistry_Delegate;
     36 
     37 /**
     38  * Delegate that provides implementation for native methods in {@link android.text.StaticLayout}
     39  * <p/>
     40  * Through the layoutlib_create tool, selected methods of StaticLayout have been replaced
     41  * by calls to methods of the same name in this delegate class.
     42  *
     43  */
     44 public class LineBreaker_Delegate {
     45 
     46     private static final char CHAR_SPACE     = 0x20;
     47     private static final char CHAR_TAB       = 0x09;
     48     private static final char CHAR_NEWLINE   = 0x0A;
     49     private static final char CHAR_ZWSP      = 0x200B;  // Zero width space.
     50 
     51     // ---- Builder delegate manager ----
     52     private static final DelegateManager<Builder> sBuilderManager =
     53         new DelegateManager<>(Builder.class);
     54     private static long sFinalizer = -1;
     55 
     56     // ---- Result delegate manager ----
     57     private static final DelegateManager<Result> sResultManager =
     58         new DelegateManager<>(Result.class);
     59     private static long sResultFinalizer = -1;
     60 
     61     @LayoutlibDelegate
     62     /*package*/ static long nInit(
     63             @BreakStrategy int breakStrategy,
     64             @HyphenationFrequency int hyphenationFrequency,
     65             boolean isJustified,
     66             @Nullable int[] indents) {
     67         Builder builder = new Builder();
     68         builder.mBreakStrategy = breakStrategy;
     69         return sBuilderManager.addNewDelegate(builder);
     70     }
     71 
     72     @LayoutlibDelegate
     73     /*package*/ static long nGetReleaseFunc() {
     74         synchronized (MeasuredText_Delegate.class) {
     75             if (sFinalizer == -1) {
     76                 sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
     77                         sBuilderManager::removeJavaReferenceFor);
     78             }
     79         }
     80         return sFinalizer;
     81     }
     82 
     83     @LayoutlibDelegate
     84     /*package*/ static long nComputeLineBreaks(
     85             /* non zero */ long nativePtr,
     86 
     87             // Inputs
     88             @NonNull char[] text,
     89             long measuredTextPtr,
     90             int length,
     91             float firstWidth,
     92             int firstWidthLineCount,
     93             float restWidth,
     94             @Nullable float[] variableTabStops,
     95             float defaultTabStop,
     96             int indentsOffset) {
     97         Builder builder = sBuilderManager.getDelegate(nativePtr);
     98         if (builder == null) {
     99             return 0;
    100         }
    101 
    102         builder.mText = text;
    103         builder.mWidths = new float[length];
    104         builder.mLineWidth = new LineWidth(firstWidth, firstWidthLineCount, restWidth);
    105         builder.mTabStopCalculator = new TabStops(variableTabStops, defaultTabStop);
    106 
    107         MeasuredText_Delegate.computeRuns(measuredTextPtr, builder);
    108 
    109         // compute all possible breakpoints.
    110         BreakIterator it = BreakIterator.getLineInstance();
    111         it.setText((CharacterIterator) new Segment(builder.mText, 0, length));
    112 
    113         // average word length in english is 5. So, initialize the possible breaks with a guess.
    114         List<Integer> breaks = new ArrayList<Integer>((int) Math.ceil(length / 5d));
    115         int loc;
    116         it.first();
    117         while ((loc = it.next()) != BreakIterator.DONE) {
    118             breaks.add(loc);
    119         }
    120 
    121         List<Primitive> primitives =
    122                 computePrimitives(builder.mText, builder.mWidths, length, breaks);
    123         switch (builder.mBreakStrategy) {
    124             case Layout.BREAK_STRATEGY_SIMPLE:
    125                 builder.mLineBreaker = new GreedyLineBreaker(primitives, builder.mLineWidth,
    126                         builder.mTabStopCalculator);
    127                 break;
    128             case Layout.BREAK_STRATEGY_HIGH_QUALITY:
    129                 // TODO
    130 //                break;
    131             case Layout.BREAK_STRATEGY_BALANCED:
    132                 builder.mLineBreaker = new OptimizingLineBreaker(primitives, builder.mLineWidth,
    133                         builder.mTabStopCalculator);
    134                 break;
    135             default:
    136                 assert false : "Unknown break strategy: " + builder.mBreakStrategy;
    137                 builder.mLineBreaker = new GreedyLineBreaker(primitives, builder.mLineWidth,
    138                         builder.mTabStopCalculator);
    139         }
    140         Result result = new Result(builder.mLineBreaker.computeBreaks());
    141         return sResultManager.addNewDelegate(result);
    142     }
    143 
    144     @LayoutlibDelegate
    145     /*package*/ static int nGetLineCount(long ptr) {
    146         Result result = sResultManager.getDelegate(ptr);
    147         return result.mResult.mLineBreakOffset.size();
    148     }
    149 
    150     @LayoutlibDelegate
    151     /*package*/ static int nGetLineBreakOffset(long ptr, int idx) {
    152         Result result = sResultManager.getDelegate(ptr);
    153         return result.mResult.mLineBreakOffset.get(idx);
    154     }
    155 
    156     @LayoutlibDelegate
    157     /*package*/ static float nGetLineWidth(long ptr, int idx) {
    158         Result result = sResultManager.getDelegate(ptr);
    159         return result.mResult.mLineWidths.get(idx);
    160     }
    161 
    162     @LayoutlibDelegate
    163     /*package*/ static float nGetLineAscent(long ptr, int idx) {
    164         Result result = sResultManager.getDelegate(ptr);
    165         return result.mResult.mLineAscents.get(idx);
    166     }
    167 
    168     @LayoutlibDelegate
    169     /*package*/ static float nGetLineDescent(long ptr, int idx) {
    170         Result result = sResultManager.getDelegate(ptr);
    171         return result.mResult.mLineDescents.get(idx);
    172     }
    173 
    174     @LayoutlibDelegate
    175     /*package*/ static int nGetLineFlag(long ptr, int idx) {
    176         Result result = sResultManager.getDelegate(ptr);
    177         return result.mResult.mLineFlags.get(idx);
    178     }
    179 
    180     @LayoutlibDelegate
    181     /*package*/ static long nGetReleaseResultFunc() {
    182         synchronized (MeasuredText_Delegate.class) {
    183             if (sResultFinalizer == -1) {
    184                 sResultFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
    185                         sBuilderManager::removeJavaReferenceFor);
    186             }
    187         }
    188         return sResultFinalizer;
    189     }
    190 
    191     /**
    192      * Compute metadata each character - things which help in deciding if it's possible to break
    193      * at a point or not.
    194      */
    195     @NonNull
    196     private static List<Primitive> computePrimitives(@NonNull char[] text, @NonNull float[] widths,
    197             int length, @NonNull List<Integer> breaks) {
    198         // Initialize the list with a guess of the number of primitives:
    199         // 2 Primitives per non-whitespace char and approx 5 chars per word (i.e. 83% chars)
    200         List<Primitive> primitives = new ArrayList<Primitive>(((int) Math.ceil(length * 1.833)));
    201         int breaksSize = breaks.size();
    202         int breakIndex = 0;
    203         for (int i = 0; i < length; i++) {
    204             char c = text[i];
    205             if (c == CHAR_SPACE || c == CHAR_ZWSP) {
    206                 primitives.add(PrimitiveType.GLUE.getNewPrimitive(i, widths[i]));
    207             } else if (c == CHAR_TAB) {
    208                 primitives.add(PrimitiveType.VARIABLE.getNewPrimitive(i));
    209             } else if (c != CHAR_NEWLINE) {
    210                 while (breakIndex < breaksSize && breaks.get(breakIndex) < i) {
    211                     breakIndex++;
    212                 }
    213                 Primitive p;
    214                 if (widths[i] != 0) {
    215                     if (breakIndex < breaksSize && breaks.get(breakIndex) == i) {
    216                         p = PrimitiveType.PENALTY.getNewPrimitive(i, 0, 0);
    217                     } else {
    218                         p = PrimitiveType.WORD_BREAK.getNewPrimitive(i, 0);
    219                     }
    220                     primitives.add(p);
    221                 }
    222 
    223                 primitives.add(PrimitiveType.BOX.getNewPrimitive(i, widths[i]));
    224             }
    225         }
    226         // final break at end of everything
    227         primitives.add(
    228                 PrimitiveType.PENALTY.getNewPrimitive(length, 0, -PrimitiveType.PENALTY_INFINITY));
    229         return primitives;
    230     }
    231 
    232     // TODO: Rename to LineBreakerRef and move everything other than LineBreaker to LineBreaker.
    233     /**
    234      * Java representation of the native Builder class.
    235      */
    236     public static class Builder {
    237         char[] mText;
    238         float[] mWidths;
    239         private BaseLineBreaker mLineBreaker;
    240         private int mBreakStrategy;
    241         private LineWidth mLineWidth;
    242         private TabStops mTabStopCalculator;
    243     }
    244 
    245     public abstract static class Run {
    246         int mStart;
    247         int mEnd;
    248 
    249         Run(int start, int end) {
    250             mStart = start;
    251             mEnd = end;
    252         }
    253 
    254         abstract void addTo(Builder builder);
    255     }
    256 
    257     public static class Result {
    258         final BaseLineBreaker.Result mResult;
    259         public Result(BaseLineBreaker.Result result) {
    260             mResult = result;
    261         }
    262     }
    263 }
    264