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