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.annotation.Nullable;
8import android.icu.text.BreakIterator;
9import android.text.Layout.BreakStrategy;
10import android.text.Layout.HyphenationFrequency;
11import android.text.Primitive.PrimitiveType;
12import android.text.StaticLayout.LineBreaks;
13
14import java.text.CharacterIterator;
15import java.util.ArrayList;
16import java.util.List;
17
18import javax.swing.text.Segment;
19
20/**
21 * Delegate that provides implementation for native methods in {@link android.text.StaticLayout}
22 * <p/>
23 * Through the layoutlib_create tool, selected methods of StaticLayout have been replaced
24 * by calls to methods of the same name in this delegate class.
25 *
26 */
27public class StaticLayout_Delegate {
28
29    private static final char CHAR_SPACE     = 0x20;
30    private static final char CHAR_TAB       = 0x09;
31    private static final char CHAR_NEWLINE   = 0x0A;
32    private static final char CHAR_ZWSP      = 0x200B;  // Zero width space.
33
34    // ---- Builder delegate manager ----
35    private static final DelegateManager<Builder> sBuilderManager =
36        new DelegateManager<Builder>(Builder.class);
37
38    @LayoutlibDelegate
39    /*package*/ static long nInit(
40            @BreakStrategy int breakStrategy,
41            @HyphenationFrequency int hyphenationFrequency,
42            boolean isJustified,
43            @Nullable int[] indents,
44            @Nullable int[] leftPaddings,
45            @Nullable int[] rightPaddings) {
46        Builder builder = new Builder();
47        builder.mBreakStrategy = breakStrategy;
48        return sBuilderManager.addNewDelegate(builder);
49    }
50
51    @LayoutlibDelegate
52    /*package*/ static void nFinish(long nativePtr) {
53        sBuilderManager.removeJavaReferenceFor(nativePtr);
54    }
55
56    @LayoutlibDelegate
57    /*package*/ static int nComputeLineBreaks(
58            /* non zero */ long nativePtr,
59
60            // Inputs
61            @NonNull char[] text,
62            long measuredTextPtr,
63            int length,
64            float firstWidth,
65            int firstWidthLineCount,
66            float restWidth,
67            @Nullable int[] variableTabStops,
68            int defaultTabStop,
69            int indentsOffset,
70
71            // Outputs
72            @NonNull LineBreaks recycle,
73            int recycleLength,
74            @NonNull int[] recycleBreaks,
75            @NonNull float[] recycleWidths,
76            @NonNull float[] recycleAscents,
77            @NonNull float[] recycleDescents,
78            @NonNull int[] recycleFlags,
79            @NonNull float[] charWidths) {
80        Builder builder = sBuilderManager.getDelegate(nativePtr);
81        if (builder == null) {
82            return 0;
83        }
84
85        builder.mText = text;
86        builder.mWidths = new float[length];
87        builder.mLineWidth = new LineWidth(firstWidth, firstWidthLineCount, restWidth);
88        builder.mTabStopCalculator = new TabStops(variableTabStops, defaultTabStop);
89
90        MeasuredParagraph_Delegate.computeRuns(measuredTextPtr, builder);
91
92        // compute all possible breakpoints.
93        BreakIterator it = BreakIterator.getLineInstance();
94        it.setText((CharacterIterator) new Segment(builder.mText, 0, length));
95
96        // average word length in english is 5. So, initialize the possible breaks with a guess.
97        List<Integer> breaks = new ArrayList<Integer>((int) Math.ceil(length / 5d));
98        int loc;
99        it.first();
100        while ((loc = it.next()) != BreakIterator.DONE) {
101            breaks.add(loc);
102        }
103
104        List<Primitive> primitives =
105                computePrimitives(builder.mText, builder.mWidths, length, breaks);
106        switch (builder.mBreakStrategy) {
107            case Layout.BREAK_STRATEGY_SIMPLE:
108                builder.mLineBreaker = new GreedyLineBreaker(primitives, builder.mLineWidth,
109                        builder.mTabStopCalculator);
110                break;
111            case Layout.BREAK_STRATEGY_HIGH_QUALITY:
112                // TODO
113//                break;
114            case Layout.BREAK_STRATEGY_BALANCED:
115                builder.mLineBreaker = new OptimizingLineBreaker(primitives, builder.mLineWidth,
116                        builder.mTabStopCalculator);
117                break;
118            default:
119                assert false : "Unknown break strategy: " + builder.mBreakStrategy;
120                builder.mLineBreaker = new GreedyLineBreaker(primitives, builder.mLineWidth,
121                        builder.mTabStopCalculator);
122        }
123        builder.mLineBreaker.computeBreaks(recycle);
124        System.arraycopy(builder.mWidths, 0, charWidths, 0, builder.mWidths.length);
125        return recycle.breaks.length;
126    }
127
128    /**
129     * Compute metadata each character - things which help in deciding if it's possible to break
130     * at a point or not.
131     */
132    @NonNull
133    private static List<Primitive> computePrimitives(@NonNull char[] text, @NonNull float[] widths,
134            int length, @NonNull List<Integer> breaks) {
135        // Initialize the list with a guess of the number of primitives:
136        // 2 Primitives per non-whitespace char and approx 5 chars per word (i.e. 83% chars)
137        List<Primitive> primitives = new ArrayList<Primitive>(((int) Math.ceil(length * 1.833)));
138        int breaksSize = breaks.size();
139        int breakIndex = 0;
140        for (int i = 0; i < length; i++) {
141            char c = text[i];
142            if (c == CHAR_SPACE || c == CHAR_ZWSP) {
143                primitives.add(PrimitiveType.GLUE.getNewPrimitive(i, widths[i]));
144            } else if (c == CHAR_TAB) {
145                primitives.add(PrimitiveType.VARIABLE.getNewPrimitive(i));
146            } else if (c != CHAR_NEWLINE) {
147                while (breakIndex < breaksSize && breaks.get(breakIndex) < i) {
148                    breakIndex++;
149                }
150                Primitive p;
151                if (widths[i] != 0) {
152                    if (breakIndex < breaksSize && breaks.get(breakIndex) == i) {
153                        p = PrimitiveType.PENALTY.getNewPrimitive(i, 0, 0);
154                    } else {
155                        p = PrimitiveType.WORD_BREAK.getNewPrimitive(i, 0);
156                    }
157                    primitives.add(p);
158                }
159
160                primitives.add(PrimitiveType.BOX.getNewPrimitive(i, widths[i]));
161            }
162        }
163        // final break at end of everything
164        primitives.add(
165                PrimitiveType.PENALTY.getNewPrimitive(length, 0, -PrimitiveType.PENALTY_INFINITY));
166        return primitives;
167    }
168
169    // TODO: Rename to LineBreakerRef and move everything other than LineBreaker to LineBreaker.
170    /**
171     * Java representation of the native Builder class.
172     */
173    public static class Builder {
174        char[] mText;
175        float[] mWidths;
176        private LineBreaker mLineBreaker;
177        private int mBreakStrategy;
178        private LineWidth mLineWidth;
179        private TabStops mTabStopCalculator;
180    }
181
182    public abstract static class Run {
183        int mStart;
184        int mEnd;
185
186        Run(int start, int end) {
187            mStart = start;
188            mEnd = end;
189        }
190
191        abstract void addTo(Builder builder);
192    }
193}
194