Home | History | Annotate | Download | only in text
      1 package android.text;
      2 
      3 import com.android.layoutlib.bridge.impl.DelegateManager;
      4 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
      5 
      6 import android.annotation.NonNull;
      7 import android.graphics.BidiRenderer;
      8 import android.graphics.Paint;
      9 import android.graphics.Paint_Delegate;
     10 import android.graphics.RectF;
     11 import android.icu.text.BreakIterator;
     12 import android.icu.util.ULocale;
     13 import android.text.Primitive.PrimitiveType;
     14 import android.text.StaticLayout.LineBreaks;
     15 
     16 import java.util.ArrayList;
     17 import java.util.Arrays;
     18 import java.util.List;
     19 
     20 import javax.swing.text.Segment;
     21 
     22 /**
     23  * Delegate that provides implementation for native methods in {@link android.text.StaticLayout}
     24  * <p/>
     25  * Through the layoutlib_create tool, selected methods of StaticLayout have been replaced
     26  * by calls to methods of the same name in this delegate class.
     27  *
     28  */
     29 public class StaticLayout_Delegate {
     30 
     31     private static final char CHAR_SPACE     = 0x20;
     32     private static final char CHAR_TAB       = 0x09;
     33     private static final char CHAR_NEWLINE   = 0x0A;
     34     private static final char CHAR_ZWSP      = 0x200B;  // Zero width space.
     35 
     36     // ---- Builder delegate manager ----
     37     private static final DelegateManager<Builder> sBuilderManager =
     38         new DelegateManager<Builder>(Builder.class);
     39 
     40     @LayoutlibDelegate
     41     /*package*/ static long nNewBuilder() {
     42         return sBuilderManager.addNewDelegate(new Builder());
     43     }
     44 
     45     @LayoutlibDelegate
     46     /*package*/ static void nFreeBuilder(long nativeBuilder) {
     47         sBuilderManager.removeJavaReferenceFor(nativeBuilder);
     48     }
     49 
     50     @LayoutlibDelegate
     51     /*package*/ static void nFinishBuilder(long nativeBuilder) {
     52     }
     53 
     54     @LayoutlibDelegate
     55     /*package*/ static long nLoadHyphenator(String patternData) {
     56         return Hyphenator_Delegate.loadHyphenator(patternData);
     57     }
     58 
     59     @LayoutlibDelegate
     60     /*package*/ static void nSetLocale(long nativeBuilder, String locale, long nativeHyphenator) {
     61         Builder builder = sBuilderManager.getDelegate(nativeBuilder);
     62         if (builder != null) {
     63             builder.mLocale = locale;
     64             builder.mNativeHyphenator = nativeHyphenator;
     65         }
     66     }
     67 
     68     @LayoutlibDelegate
     69     /*package*/ static void nSetIndents(long nativeBuilder, int[] indents) {
     70         // TODO.
     71     }
     72 
     73     @LayoutlibDelegate
     74     /*package*/ static void nSetupParagraph(long nativeBuilder, char[] text, int length,
     75             float firstWidth, int firstWidthLineCount, float restWidth,
     76             int[] variableTabStops, int defaultTabStop, int breakStrategy,
     77             int hyphenationFrequency) {
     78         Builder builder = sBuilderManager.getDelegate(nativeBuilder);
     79         if (builder == null) {
     80             return;
     81         }
     82 
     83         builder.mText = text;
     84         builder.mWidths = new float[length];
     85         builder.mLineWidth = new LineWidth(firstWidth, firstWidthLineCount, restWidth);
     86         builder.mTabStopCalculator = new TabStops(variableTabStops, defaultTabStop);
     87     }
     88 
     89     @LayoutlibDelegate
     90     /*package*/ static float nAddStyleRun(long nativeBuilder, long nativePaint, long nativeTypeface,
     91             int start, int end, boolean isRtl) {
     92         Builder builder = sBuilderManager.getDelegate(nativeBuilder);
     93 
     94         int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
     95         return builder == null ? 0 :
     96                 measureText(nativePaint, builder.mText, start, end - start, builder.mWidths,
     97                         bidiFlags);
     98     }
     99 
    100     @LayoutlibDelegate
    101     /*package*/ static void nAddMeasuredRun(long nativeBuilder, int start, int end, float[] widths) {
    102         Builder builder = sBuilderManager.getDelegate(nativeBuilder);
    103         if (builder != null) {
    104             System.arraycopy(widths, start, builder.mWidths, start, end - start);
    105         }
    106     }
    107 
    108     @LayoutlibDelegate
    109     /*package*/ static void nAddReplacementRun(long nativeBuilder, int start, int end, float width) {
    110         Builder builder = sBuilderManager.getDelegate(nativeBuilder);
    111         if (builder == null) {
    112             return;
    113         }
    114         builder.mWidths[start] = width;
    115         Arrays.fill(builder.mWidths, start + 1, end, 0.0f);
    116     }
    117 
    118     @LayoutlibDelegate
    119     /*package*/ static void nGetWidths(long nativeBuilder, float[] floatsArray) {
    120         Builder builder = sBuilderManager.getDelegate(nativeBuilder);
    121         if (builder != null) {
    122             System.arraycopy(builder.mWidths, 0, floatsArray, 0, builder.mWidths.length);
    123         }
    124     }
    125 
    126     @LayoutlibDelegate
    127     /*package*/ static int nComputeLineBreaks(long nativeBuilder,
    128             LineBreaks recycle, int[] recycleBreaks, float[] recycleWidths,
    129             int[] recycleFlags, int recycleLength) {
    130 
    131         Builder builder = sBuilderManager.getDelegate(nativeBuilder);
    132         if (builder == null) {
    133             return 0;
    134         }
    135 
    136         // compute all possible breakpoints.
    137         int length = builder.mWidths.length;
    138         BreakIterator it = BreakIterator.getLineInstance(new ULocale(builder.mLocale));
    139         it.setText(new Segment(builder.mText, 0, length));
    140 
    141         // average word length in english is 5. So, initialize the possible breaks with a guess.
    142         List<Integer> breaks = new ArrayList<Integer>((int) Math.ceil(length / 5d));
    143         int loc;
    144         it.first();
    145         while ((loc = it.next()) != BreakIterator.DONE) {
    146             breaks.add(loc);
    147         }
    148 
    149         List<Primitive> primitives =
    150                 computePrimitives(builder.mText, builder.mWidths, length, breaks);
    151         switch (builder.mBreakStrategy) {
    152             case Layout.BREAK_STRATEGY_SIMPLE:
    153                 builder.mLineBreaker = new GreedyLineBreaker(primitives, builder.mLineWidth,
    154                         builder.mTabStopCalculator);
    155                 break;
    156             case Layout.BREAK_STRATEGY_HIGH_QUALITY:
    157                 // TODO
    158 //                break;
    159             case Layout.BREAK_STRATEGY_BALANCED:
    160                 builder.mLineBreaker = new OptimizingLineBreaker(primitives, builder.mLineWidth,
    161                         builder.mTabStopCalculator);
    162                 break;
    163             default:
    164                 throw new AssertionError("Unknown break strategy: " + builder.mBreakStrategy);
    165         }
    166         builder.mLineBreaker.computeBreaks(recycle);
    167         return recycle.breaks.length;
    168     }
    169 
    170     /**
    171      * Compute metadata each character - things which help in deciding if it's possible to break
    172      * at a point or not.
    173      */
    174     @NonNull
    175     private static List<Primitive> computePrimitives(@NonNull char[] text, @NonNull float[] widths,
    176             int length, @NonNull List<Integer> breaks) {
    177         // Initialize the list with a guess of the number of primitives:
    178         // 2 Primitives per non-whitespace char and approx 5 chars per word (i.e. 83% chars)
    179         List<Primitive> primitives = new ArrayList<Primitive>(((int) Math.ceil(length * 1.833)));
    180         int breaksSize = breaks.size();
    181         int breakIndex = 0;
    182         for (int i = 0; i < length; i++) {
    183             char c = text[i];
    184             if (c == CHAR_SPACE || c == CHAR_ZWSP) {
    185                 primitives.add(PrimitiveType.GLUE.getNewPrimitive(i, widths[i]));
    186             } else if (c == CHAR_TAB) {
    187                 primitives.add(PrimitiveType.VARIABLE.getNewPrimitive(i));
    188             } else if (c != CHAR_NEWLINE) {
    189                 while (breakIndex < breaksSize && breaks.get(breakIndex) < i) {
    190                     breakIndex++;
    191                 }
    192                 Primitive p;
    193                 if (widths[i] != 0) {
    194                     if (breakIndex < breaksSize && breaks.get(breakIndex) == i) {
    195                         p = PrimitiveType.PENALTY.getNewPrimitive(i, 0, 0);
    196                     } else {
    197                         p = PrimitiveType.WORD_BREAK.getNewPrimitive(i, 0);
    198                     }
    199                     primitives.add(p);
    200                 }
    201 
    202                 primitives.add(PrimitiveType.BOX.getNewPrimitive(i, widths[i]));
    203             }
    204         }
    205         // final break at end of everything
    206         primitives.add(
    207                 PrimitiveType.PENALTY.getNewPrimitive(length, 0, -PrimitiveType.PENALTY_INFINITY));
    208         return primitives;
    209     }
    210 
    211     private static float measureText(long nativePaint, char []text, int index, int count,
    212             float[] widths, int bidiFlags) {
    213         Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint);
    214         RectF bounds = new BidiRenderer(null, paint, text)
    215             .renderText(index, index + count, bidiFlags, widths, 0, false);
    216         return bounds.right - bounds.left;
    217     }
    218 
    219     // TODO: Rename to LineBreakerRef and move everything other than LineBreaker to LineBreaker.
    220     /**
    221      * Java representation of the native Builder class.
    222      */
    223     private static class Builder {
    224         String mLocale;
    225         char[] mText;
    226         float[] mWidths;
    227         LineBreaker mLineBreaker;
    228         long mNativeHyphenator;
    229         int mBreakStrategy;
    230         LineWidth mLineWidth;
    231         TabStops mTabStopCalculator;
    232     }
    233 }
    234