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