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