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