StaticLayout_Delegate.java revision d77b9ed7dcc42efca33b225c4594a30aab9e709c
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.icu.text.BreakIterator;
12import android.icu.util.ULocale;
13import android.text.Primitive.PrimitiveType;
14import android.text.StaticLayout.LineBreaks;
15
16import java.util.ArrayList;
17import java.util.Arrays;
18import java.util.List;
19
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 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
86        // compute all possible breakpoints.
87        BreakIterator it = BreakIterator.getLineInstance(new ULocale(builder.mLocale));
88        it.setText(new Segment(builder.mText, 0, length));
89
90        // average word length in english is 5. So, initialize the possible breaks with a guess.
91        List<Integer> breaks = new ArrayList<Integer>((int) Math.ceil(length / 5d));
92        int loc;
93        it.first();
94        while ((loc = it.next()) != BreakIterator.DONE) {
95            breaks.add(loc);
96        }
97        LineWidth lineWidth = new LineWidth(firstWidth, firstWidthLineCount, restWidth);
98        TabStops tabStopCalculator = new TabStops(variableTabStops, defaultTabStop);
99        List<Primitive> primitives =
100                computePrimitives(builder.mText, builder.mWidths, length, breaks);
101        BreakStrategy strategy = BreakStrategy.getStrategy(breakStrategy);
102        switch (strategy) {
103            case GREEDY:
104                builder.mLineBreaker =
105                        new GreedyLineBreaker(primitives, lineWidth, tabStopCalculator);
106                break;
107            case HIGH_QUALITY:
108                // TODO
109                break;
110            case BALANCED:
111                builder.mLineBreaker = new OptimizingLineBreaker(primitives, lineWidth,
112                        tabStopCalculator);
113                break;
114        }
115    }
116
117    @LayoutlibDelegate
118    /*package*/ static float nAddStyleRun(long nativeBuilder, long nativePaint, long nativeTypeface,
119            int start, int end, boolean isRtl) {
120        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
121
122        int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
123        return builder == null ? 0 :
124                measureText(nativePaint, builder.mText, start, end - start, builder.mWidths,
125                        bidiFlags);
126    }
127
128    @LayoutlibDelegate
129    /*package*/ static void nAddMeasuredRun(long nativeBuilder, int start, int end, float[] widths) {
130        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
131        if (builder != null) {
132            System.arraycopy(widths, start, builder.mWidths, start, end - start);
133        }
134    }
135
136    @LayoutlibDelegate
137    /*package*/ static void nAddReplacementRun(long nativeBuilder, int start, int end, float width) {
138        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
139        if (builder == null) {
140            return;
141        }
142        builder.mWidths[start] = width;
143        Arrays.fill(builder.mWidths, start + 1, end, 0.0f);
144    }
145
146    @LayoutlibDelegate
147    /*package*/ static void nGetWidths(long nativeBuilder, float[] floatsArray) {
148        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
149        if (builder != null) {
150            System.arraycopy(builder.mWidths, 0, floatsArray, 0, builder.mWidths.length);
151        }
152    }
153
154    @LayoutlibDelegate
155    /*package*/ static int nComputeLineBreaks(long nativeBuilder,
156            LineBreaks recycle, int[] recycleBreaks, float[] recycleWidths,
157            int[] recycleFlags, int recycleLength) {
158
159        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
160        if (builder == null) {
161            return 0;
162        }
163        builder.mLineBreaker.computeBreaks(recycle);
164        return recycle.breaks.length;
165    }
166
167    /**
168     * Compute metadata each character - things which help in deciding if it's possible to break
169     * at a point or not.
170     */
171    @NonNull
172    private static List<Primitive> computePrimitives(@NonNull char[] text, @NonNull float[] widths,
173            int length, @NonNull List<Integer> breaks) {
174        // Initialize the list with a guess of the number of primitives:
175        // 2 Primitives per non-whitespace char and approx 5 chars per word (i.e. 83% chars)
176        List<Primitive> primitives = new ArrayList<Primitive>(((int) Math.ceil(length * 1.833)));
177        int breaksSize = breaks.size();
178        int breakIndex = 0;
179        for (int i = 0; i < length; i++) {
180            char c = text[i];
181            if (c == CHAR_SPACE || c == CHAR_ZWSP) {
182                primitives.add(PrimitiveType.GLUE.getNewPrimitive(i, widths[i]));
183            } else if (c == CHAR_TAB) {
184                primitives.add(PrimitiveType.VARIABLE.getNewPrimitive(i));
185            } else if (c != CHAR_NEWLINE) {
186                while (breakIndex < breaksSize && breaks.get(breakIndex) < i) {
187                    breakIndex++;
188                }
189                Primitive p;
190                if (widths[i] != 0) {
191                    if (breakIndex < breaksSize && breaks.get(breakIndex) == i) {
192                        p = PrimitiveType.PENALTY.getNewPrimitive(i, 0, 0);
193                    } else {
194                        p = PrimitiveType.WORD_BREAK.getNewPrimitive(i, 0);
195                    }
196                    primitives.add(p);
197                }
198
199                primitives.add(PrimitiveType.BOX.getNewPrimitive(i, widths[i]));
200            }
201        }
202        // final break at end of everything
203        primitives.add(
204                PrimitiveType.PENALTY.getNewPrimitive(length, 0, -PrimitiveType.PENALTY_INFINITY));
205        return primitives;
206    }
207
208    private static float measureText(long nativePaint, char []text, int index, int count,
209            float[] widths, int bidiFlags) {
210        Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint);
211        RectF bounds = new BidiRenderer(null, paint, text)
212            .renderText(index, index + count, bidiFlags, widths, 0, false);
213        return bounds.right - bounds.left;
214    }
215
216    // TODO: Rename to LineBreakerRef and move everything other than LineBreaker to LineBreaker.
217    /**
218     * Java representation of the native Builder class.
219     */
220    private static class Builder {
221        String mLocale;
222        char[] mText;
223        float[] mWidths;
224        LineBreaker mLineBreaker;
225        long mNativeHyphenator;
226    }
227
228    private enum BreakStrategy {
229        GREEDY, HIGH_QUALITY, BALANCED;
230
231        static BreakStrategy getStrategy(int strategy) {
232            switch (strategy) {
233                case 0:
234                    return GREEDY;
235                case 1:
236                    return HIGH_QUALITY;
237                case 2:
238                    return BALANCED;
239                default:
240                    throw new AssertionError("Unknown break strategy: " + strategy);
241            }
242        }
243    }
244}
245