StaticLayout_Delegate.java revision f3284cbd38989279a116e0082b7ab5b987c13388
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.graphics.BidiRenderer;
8import android.graphics.Paint;
9import android.graphics.Paint_Delegate;
10import android.graphics.RectF;
11import android.text.StaticLayout.LineBreaks;
12import android.text.Primitive.PrimitiveType;
13
14import java.util.ArrayList;
15import java.util.Arrays;
16import java.util.List;
17
18import com.ibm.icu.text.BreakIterator;
19import com.ibm.icu.util.ULocale;
20import 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 */
29public 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 int nComputeLineBreaks(long nativeBuilder,
42            int length, float firstWidth, int firstWidthLineCount, float restWidth,
43            int[] variableTabStops, int defaultTabStop, boolean optimize, LineBreaks recycle,
44            int[] recycleBreaks, float[] recycleWidths, boolean[] recycleFlags, int recycleLength) {
45
46        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
47        // compute all possible breakpoints.
48        BreakIterator it = BreakIterator.getLineInstance(new ULocale(builder.mLocale));
49        it.setText(new Segment(builder.mText, 0, length));
50        // average word length in english is 5. So, initialize the possible breaks with a guess.
51        List<Integer> breaks = new ArrayList<Integer>((int) Math.ceil(length / 5d));
52        int loc;
53        it.first();
54        while ((loc = it.next()) != BreakIterator.DONE) {
55            breaks.add(loc);
56        }
57
58        LineWidth lineWidth = new LineWidth(firstWidth, firstWidthLineCount, restWidth);
59        TabStops tabStopCalculator = new TabStops(variableTabStops, defaultTabStop);
60        List<Primitive> primitives = computePrimitives(builder.mText, builder.mWidths, length, breaks);
61        LineBreaker lineBreaker;
62        if (optimize) {
63            lineBreaker = new OptimizingLineBreaker(primitives, lineWidth, tabStopCalculator);
64        } else {
65            lineBreaker = new GreedyLineBreaker(primitives, lineWidth, tabStopCalculator);
66        }
67        lineBreaker.computeBreaks(recycle);
68        return recycle.breaks.length;
69    }
70
71    /**
72     * Compute metadata each character - things which help in deciding if it's possible to break
73     * at a point or not.
74     */
75    @NonNull
76    private static List<Primitive> computePrimitives(@NonNull char[] text, @NonNull float[] widths,
77            int length, @NonNull List<Integer> breaks) {
78        // Initialize the list with a guess of the number of primitives:
79        // 2 Primitives per non-whitespace char and approx 5 chars per word (i.e. 83% chars)
80        List<Primitive> primitives = new ArrayList<Primitive>(((int) Math.ceil(length * 1.833)));
81        int breaksSize = breaks.size();
82        int breakIndex = 0;
83        for (int i = 0; i < length; i++) {
84            char c = text[i];
85            if (c == CHAR_SPACE || c == CHAR_ZWSP) {
86                primitives.add(PrimitiveType.GLUE.getNewPrimitive(i, widths[i]));
87            } else if (c == CHAR_TAB) {
88                primitives.add(PrimitiveType.VARIABLE.getNewPrimitive(i));
89            } else if (c != CHAR_NEWLINE) {
90                while (breakIndex < breaksSize && breaks.get(breakIndex) < i) {
91                    breakIndex++;
92                }
93                Primitive p;
94                if (widths[i] != 0) {
95                    if (breakIndex < breaksSize && breaks.get(breakIndex) == i) {
96                        p = PrimitiveType.PENALTY.getNewPrimitive(i, 0, 0);
97                    } else {
98                        p = PrimitiveType.WORD_BREAK.getNewPrimitive(i, 0);
99                    }
100                    primitives.add(p);
101                }
102
103                primitives.add(PrimitiveType.BOX.getNewPrimitive(i, widths[i]));
104            }
105        }
106        // final break at end of everything
107        primitives.add(
108                PrimitiveType.PENALTY.getNewPrimitive(length, 0, -PrimitiveType.PENALTY_INFINITY));
109        return primitives;
110    }
111
112    @LayoutlibDelegate
113    /*package*/ static long nNewBuilder() {
114        return sBuilderManager.addNewDelegate(new Builder());
115    }
116
117    @LayoutlibDelegate
118    /*package*/ static void nFinishBuilder(long nativeBuilder) {
119    }
120
121    @LayoutlibDelegate
122    /*package*/ static void nFreeBuilder(long nativeBuilder) {
123        sBuilderManager.removeJavaReferenceFor(nativeBuilder);
124    }
125
126    @LayoutlibDelegate
127    /*package*/ static void nSetLocale(long nativeBuilder, String locale) {
128        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
129        builder.mLocale = locale;
130    }
131
132    @LayoutlibDelegate
133    /*package*/ static void nSetText(long nativeBuilder, char[] text, int length) {
134        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
135        builder.mText = text;
136        builder.mWidths = new float[length];
137    }
138
139
140    @LayoutlibDelegate
141    /*package*/ static float nAddStyleRun(long nativeBuilder, long nativePaint, long nativeTypeface,
142            int start, int end, boolean isRtl) {
143        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
144
145        int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
146        return measureText(nativePaint, builder.mText, start, end - start, builder.mWidths, bidiFlags);
147    }
148
149
150    @LayoutlibDelegate
151    /*package*/ static void nAddMeasuredRun(long nativeBuilder, int start, int end, float[] widths) {
152        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
153        System.arraycopy(widths, start, builder.mWidths, start, end - start);
154    }
155
156    @LayoutlibDelegate
157    /*package*/ static void nAddReplacementRun(long nativeBuilder, int start, int end, float width) {
158        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
159        builder.mWidths[start] = width;
160        Arrays.fill(builder.mWidths, start + 1, end, 0.0f);
161    }
162
163    @LayoutlibDelegate
164    /*package*/ static void nGetWidths(long nativeBuilder, float[] floatsArray) {
165        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
166        System.arraycopy(builder.mWidths, 0, floatsArray, 0, builder.mWidths.length);
167    }
168
169    private static float measureText(long nativePaint, char []text, int index, int count,
170            float[] widths, int bidiFlags) {
171        Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint);
172        RectF bounds = new BidiRenderer(null, paint, text)
173            .renderText(index, index + count, bidiFlags, widths, 0, false);
174        return bounds.right - bounds.left;
175    }
176
177    /**
178     * Java representation of the native Builder class.
179     */
180    static class Builder {
181        String mLocale;
182        char[] mText;
183        float[] mWidths;
184    }
185}
186