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