1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.graphics.text; 18 19 import com.android.layoutlib.bridge.impl.DelegateManager; 20 import com.android.tools.layoutlib.annotations.LayoutlibDelegate; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.icu.text.BreakIterator; 25 import android.text.Layout; 26 import android.text.Layout.BreakStrategy; 27 import android.text.Layout.HyphenationFrequency; 28 import android.graphics.text.Primitive.PrimitiveType; 29 30 import java.text.CharacterIterator; 31 import java.util.ArrayList; 32 import java.util.List; 33 34 import javax.swing.text.Segment; 35 import libcore.util.NativeAllocationRegistry_Delegate; 36 37 /** 38 * Delegate that provides implementation for native methods in {@link android.text.StaticLayout} 39 * <p/> 40 * Through the layoutlib_create tool, selected methods of StaticLayout have been replaced 41 * by calls to methods of the same name in this delegate class. 42 * 43 */ 44 public class LineBreaker_Delegate { 45 46 private static final char CHAR_SPACE = 0x20; 47 private static final char CHAR_TAB = 0x09; 48 private static final char CHAR_NEWLINE = 0x0A; 49 private static final char CHAR_ZWSP = 0x200B; // Zero width space. 50 51 // ---- Builder delegate manager ---- 52 private static final DelegateManager<Builder> sBuilderManager = 53 new DelegateManager<>(Builder.class); 54 private static long sFinalizer = -1; 55 56 // ---- Result delegate manager ---- 57 private static final DelegateManager<Result> sResultManager = 58 new DelegateManager<>(Result.class); 59 private static long sResultFinalizer = -1; 60 61 @LayoutlibDelegate nInit( @reakStrategy int breakStrategy, @HyphenationFrequency int hyphenationFrequency, boolean isJustified, @Nullable int[] indents)62 /*package*/ static long nInit( 63 @BreakStrategy int breakStrategy, 64 @HyphenationFrequency int hyphenationFrequency, 65 boolean isJustified, 66 @Nullable int[] indents) { 67 Builder builder = new Builder(); 68 builder.mBreakStrategy = breakStrategy; 69 return sBuilderManager.addNewDelegate(builder); 70 } 71 72 @LayoutlibDelegate nGetReleaseFunc()73 /*package*/ static long nGetReleaseFunc() { 74 synchronized (MeasuredText_Delegate.class) { 75 if (sFinalizer == -1) { 76 sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer( 77 sBuilderManager::removeJavaReferenceFor); 78 } 79 } 80 return sFinalizer; 81 } 82 83 @LayoutlibDelegate nComputeLineBreaks( long nativePtr, @NonNull char[] text, long measuredTextPtr, int length, float firstWidth, int firstWidthLineCount, float restWidth, @Nullable float[] variableTabStops, float defaultTabStop, int indentsOffset)84 /*package*/ static long nComputeLineBreaks( 85 /* non zero */ long nativePtr, 86 87 // Inputs 88 @NonNull char[] text, 89 long measuredTextPtr, 90 int length, 91 float firstWidth, 92 int firstWidthLineCount, 93 float restWidth, 94 @Nullable float[] variableTabStops, 95 float defaultTabStop, 96 int indentsOffset) { 97 Builder builder = sBuilderManager.getDelegate(nativePtr); 98 if (builder == null) { 99 return 0; 100 } 101 102 builder.mText = text; 103 builder.mWidths = new float[length]; 104 builder.mLineWidth = new LineWidth(firstWidth, firstWidthLineCount, restWidth); 105 builder.mTabStopCalculator = new TabStops(variableTabStops, defaultTabStop); 106 107 MeasuredText_Delegate.computeRuns(measuredTextPtr, builder); 108 109 // compute all possible breakpoints. 110 BreakIterator it = BreakIterator.getLineInstance(); 111 it.setText((CharacterIterator) new Segment(builder.mText, 0, length)); 112 113 // average word length in english is 5. So, initialize the possible breaks with a guess. 114 List<Integer> breaks = new ArrayList<Integer>((int) Math.ceil(length / 5d)); 115 int loc; 116 it.first(); 117 while ((loc = it.next()) != BreakIterator.DONE) { 118 breaks.add(loc); 119 } 120 121 List<Primitive> primitives = 122 computePrimitives(builder.mText, builder.mWidths, length, breaks); 123 switch (builder.mBreakStrategy) { 124 case Layout.BREAK_STRATEGY_SIMPLE: 125 builder.mLineBreaker = new GreedyLineBreaker(primitives, builder.mLineWidth, 126 builder.mTabStopCalculator); 127 break; 128 case Layout.BREAK_STRATEGY_HIGH_QUALITY: 129 // TODO 130 // break; 131 case Layout.BREAK_STRATEGY_BALANCED: 132 builder.mLineBreaker = new OptimizingLineBreaker(primitives, builder.mLineWidth, 133 builder.mTabStopCalculator); 134 break; 135 default: 136 assert false : "Unknown break strategy: " + builder.mBreakStrategy; 137 builder.mLineBreaker = new GreedyLineBreaker(primitives, builder.mLineWidth, 138 builder.mTabStopCalculator); 139 } 140 Result result = new Result(builder.mLineBreaker.computeBreaks()); 141 return sResultManager.addNewDelegate(result); 142 } 143 144 @LayoutlibDelegate nGetLineCount(long ptr)145 /*package*/ static int nGetLineCount(long ptr) { 146 Result result = sResultManager.getDelegate(ptr); 147 return result.mResult.mLineBreakOffset.size(); 148 } 149 150 @LayoutlibDelegate nGetLineBreakOffset(long ptr, int idx)151 /*package*/ static int nGetLineBreakOffset(long ptr, int idx) { 152 Result result = sResultManager.getDelegate(ptr); 153 return result.mResult.mLineBreakOffset.get(idx); 154 } 155 156 @LayoutlibDelegate nGetLineWidth(long ptr, int idx)157 /*package*/ static float nGetLineWidth(long ptr, int idx) { 158 Result result = sResultManager.getDelegate(ptr); 159 return result.mResult.mLineWidths.get(idx); 160 } 161 162 @LayoutlibDelegate nGetLineAscent(long ptr, int idx)163 /*package*/ static float nGetLineAscent(long ptr, int idx) { 164 Result result = sResultManager.getDelegate(ptr); 165 return result.mResult.mLineAscents.get(idx); 166 } 167 168 @LayoutlibDelegate nGetLineDescent(long ptr, int idx)169 /*package*/ static float nGetLineDescent(long ptr, int idx) { 170 Result result = sResultManager.getDelegate(ptr); 171 return result.mResult.mLineDescents.get(idx); 172 } 173 174 @LayoutlibDelegate nGetLineFlag(long ptr, int idx)175 /*package*/ static int nGetLineFlag(long ptr, int idx) { 176 Result result = sResultManager.getDelegate(ptr); 177 return result.mResult.mLineFlags.get(idx); 178 } 179 180 @LayoutlibDelegate nGetReleaseResultFunc()181 /*package*/ static long nGetReleaseResultFunc() { 182 synchronized (MeasuredText_Delegate.class) { 183 if (sResultFinalizer == -1) { 184 sResultFinalizer = NativeAllocationRegistry_Delegate.createFinalizer( 185 sBuilderManager::removeJavaReferenceFor); 186 } 187 } 188 return sResultFinalizer; 189 } 190 191 /** 192 * Compute metadata each character - things which help in deciding if it's possible to break 193 * at a point or not. 194 */ 195 @NonNull computePrimitives(@onNull char[] text, @NonNull float[] widths, int length, @NonNull List<Integer> breaks)196 private static List<Primitive> computePrimitives(@NonNull char[] text, @NonNull float[] widths, 197 int length, @NonNull List<Integer> breaks) { 198 // Initialize the list with a guess of the number of primitives: 199 // 2 Primitives per non-whitespace char and approx 5 chars per word (i.e. 83% chars) 200 List<Primitive> primitives = new ArrayList<Primitive>(((int) Math.ceil(length * 1.833))); 201 int breaksSize = breaks.size(); 202 int breakIndex = 0; 203 for (int i = 0; i < length; i++) { 204 char c = text[i]; 205 if (c == CHAR_SPACE || c == CHAR_ZWSP) { 206 primitives.add(PrimitiveType.GLUE.getNewPrimitive(i, widths[i])); 207 } else if (c == CHAR_TAB) { 208 primitives.add(PrimitiveType.VARIABLE.getNewPrimitive(i)); 209 } else if (c != CHAR_NEWLINE) { 210 while (breakIndex < breaksSize && breaks.get(breakIndex) < i) { 211 breakIndex++; 212 } 213 Primitive p; 214 if (widths[i] != 0) { 215 if (breakIndex < breaksSize && breaks.get(breakIndex) == i) { 216 p = PrimitiveType.PENALTY.getNewPrimitive(i, 0, 0); 217 } else { 218 p = PrimitiveType.WORD_BREAK.getNewPrimitive(i, 0); 219 } 220 primitives.add(p); 221 } 222 223 primitives.add(PrimitiveType.BOX.getNewPrimitive(i, widths[i])); 224 } 225 } 226 // final break at end of everything 227 primitives.add( 228 PrimitiveType.PENALTY.getNewPrimitive(length, 0, -PrimitiveType.PENALTY_INFINITY)); 229 return primitives; 230 } 231 232 // TODO: Rename to LineBreakerRef and move everything other than LineBreaker to LineBreaker. 233 /** 234 * Java representation of the native Builder class. 235 */ 236 public static class Builder { 237 char[] mText; 238 float[] mWidths; 239 private BaseLineBreaker mLineBreaker; 240 private int mBreakStrategy; 241 private LineWidth mLineWidth; 242 private TabStops mTabStopCalculator; 243 } 244 245 public abstract static class Run { 246 int mStart; 247 int mEnd; 248 Run(int start, int end)249 Run(int start, int end) { 250 mStart = start; 251 mEnd = end; 252 } 253 addTo(Builder builder)254 abstract void addTo(Builder builder); 255 } 256 257 public static class Result { 258 final BaseLineBreaker.Result mResult; Result(BaseLineBreaker.Result result)259 public Result(BaseLineBreaker.Result result) { 260 mResult = result; 261 } 262 } 263 } 264