StaticLayout_Delegate.java revision fe12aa77743c4a4a98409418a6cbb0be11b8fee5
1package android.text;
2
3import com.android.annotations.NonNull;
4import com.android.layoutlib.bridge.impl.DelegateManager;
5import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
6
7import android.text.StaticLayout.LineBreaks;
8import android.text.Primitive.PrimitiveType;
9
10import java.util.ArrayList;
11import java.util.List;
12
13import com.ibm.icu.text.BreakIterator;
14import com.ibm.icu.util.ULocale;
15import javax.swing.text.Segment;
16
17/**
18 * Delegate that provides implementation for native methods in {@link android.text.StaticLayout}
19 * <p/>
20 * Through the layoutlib_create tool, selected methods of StaticLayout have been replaced
21 * by calls to methods of the same name in this delegate class.
22 *
23 */
24public class StaticLayout_Delegate {
25
26    private static final char CHAR_SPACE     = 0x20;
27    private static final char CHAR_TAB       = 0x09;
28    private static final char CHAR_NEWLINE   = 0x0A;
29    private static final char CHAR_ZWSP      = 0x200B;  // Zero width space.
30
31    // ---- Builder delegate manager ----
32    private static final DelegateManager<Builder> sBuilderManager =
33        new DelegateManager<Builder>(Builder.class);
34
35    @LayoutlibDelegate
36    /*package*/ static int nComputeLineBreaks(long nativeBuilder, char[] inputText, float[] widths,
37            int length, float firstWidth, int firstWidthLineCount, float restWidth,
38            int[] variableTabStops, int defaultTabStop, boolean optimize, LineBreaks recycle,
39            int[] recycleBreaks, float[] recycleWidths, boolean[] recycleFlags, int recycleLength) {
40
41        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
42        // compute all possible breakpoints.
43        BreakIterator it = BreakIterator.getLineInstance(new ULocale(builder.mLocale));
44        it.setText(new Segment(inputText, 0, length));
45        // average word length in english is 5. So, initialize the possible breaks with a guess.
46        List<Integer> breaks = new ArrayList<Integer>((int) Math.ceil(length / 5d));
47        int loc;
48        it.first();
49        while ((loc = it.next()) != BreakIterator.DONE) {
50            breaks.add(loc);
51        }
52
53        LineWidth lineWidth = new LineWidth(firstWidth, firstWidthLineCount, restWidth);
54        TabStops tabStopCalculator = new TabStops(variableTabStops, defaultTabStop);
55        List<Primitive> primitives = computePrimitives(inputText, widths, length, breaks);
56        LineBreaker lineBreaker;
57        if (optimize) {
58            lineBreaker = new OptimizingLineBreaker(primitives, lineWidth, tabStopCalculator);
59        } else {
60            lineBreaker = new GreedyLineBreaker(primitives, lineWidth, tabStopCalculator);
61        }
62        lineBreaker.computeBreaks(recycle);
63        return recycle.breaks.length;
64    }
65
66    /**
67     * Compute metadata each character - things which help in deciding if it's possible to break
68     * at a point or not.
69     */
70    @NonNull
71    private static List<Primitive> computePrimitives(@NonNull char[] text, @NonNull float[] widths,
72            int length, @NonNull List<Integer> breaks) {
73        // Initialize the list with a guess of the number of primitives:
74        // 2 Primitives per non-whitespace char and approx 5 chars per word (i.e. 83% chars)
75        List<Primitive> primitives = new ArrayList<Primitive>(((int) Math.ceil(length * 1.833)));
76        int breaksSize = breaks.size();
77        int breakIndex = 0;
78        for (int i = 0; i < length; i++) {
79            char c = text[i];
80            if (c == CHAR_SPACE || c == CHAR_ZWSP) {
81                primitives.add(PrimitiveType.GLUE.getNewPrimitive(i, widths[i]));
82            } else if (c == CHAR_TAB) {
83                primitives.add(PrimitiveType.VARIABLE.getNewPrimitive(i));
84            } else if (c != CHAR_NEWLINE) {
85                while (breakIndex < breaksSize && breaks.get(breakIndex) < i) {
86                    breakIndex++;
87                }
88                Primitive p;
89                if (widths[i] != 0) {
90                    if (breakIndex < breaksSize && breaks.get(breakIndex) == i) {
91                        p = PrimitiveType.PENALTY.getNewPrimitive(i, 0, 0);
92                    } else {
93                        p = PrimitiveType.WORD_BREAK.getNewPrimitive(i, 0);
94                    }
95                    primitives.add(p);
96                }
97
98                primitives.add(PrimitiveType.BOX.getNewPrimitive(i, widths[i]));
99            }
100        }
101        // final break at end of everything
102        primitives.add(
103                PrimitiveType.PENALTY.getNewPrimitive(length, 0, -PrimitiveType.PENALTY_INFINITY));
104        return primitives;
105    }
106
107    @LayoutlibDelegate
108    /*package*/ static long nNewBuilder() {
109        return sBuilderManager.addNewDelegate(new Builder());
110    }
111
112    @LayoutlibDelegate
113    /*package*/ static void nFinishBuilder(long nativeBuilder) {
114    }
115
116    @LayoutlibDelegate
117    /*package*/ static void nFreeBuilder(long nativeBuilder) {
118        sBuilderManager.removeJavaReferenceFor(nativeBuilder);
119    }
120
121    @LayoutlibDelegate
122    /*package*/ static void nBuilderSetLocale(long nativeBuilder, String locale) {
123        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
124        builder.mLocale = locale;
125    }
126
127    /**
128     * Java representation of the native Builder class. It currently only stores the locale
129     * set by nBuilderSetLocale.
130     */
131    static class Builder {
132        String mLocale;
133    }
134}
135