| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.graphics.text; |
| |
| import com.android.layoutlib.bridge.impl.DelegateManager; |
| import com.android.tools.layoutlib.annotations.LayoutlibDelegate; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.icu.text.BreakIterator; |
| import android.text.Layout; |
| import android.text.Layout.BreakStrategy; |
| import android.text.Layout.HyphenationFrequency; |
| import android.graphics.text.Primitive.PrimitiveType; |
| |
| import java.text.CharacterIterator; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import javax.swing.text.Segment; |
| import libcore.util.NativeAllocationRegistry_Delegate; |
| |
| /** |
| * Delegate that provides implementation for native methods in {@link android.text.StaticLayout} |
| * <p/> |
| * Through the layoutlib_create tool, selected methods of StaticLayout have been replaced |
| * by calls to methods of the same name in this delegate class. |
| * |
| */ |
| public class LineBreaker_Delegate { |
| |
| private static final char CHAR_SPACE = 0x20; |
| private static final char CHAR_TAB = 0x09; |
| private static final char CHAR_NEWLINE = 0x0A; |
| private static final char CHAR_ZWSP = 0x200B; // Zero width space. |
| |
| // ---- Builder delegate manager ---- |
| private static final DelegateManager<Builder> sBuilderManager = |
| new DelegateManager<>(Builder.class); |
| private static long sFinalizer = -1; |
| |
| // ---- Result delegate manager ---- |
| private static final DelegateManager<Result> sResultManager = |
| new DelegateManager<>(Result.class); |
| private static long sResultFinalizer = -1; |
| |
| @LayoutlibDelegate |
| /*package*/ static long nInit( |
| @BreakStrategy int breakStrategy, |
| @HyphenationFrequency int hyphenationFrequency, |
| boolean isJustified, |
| @Nullable int[] indents) { |
| Builder builder = new Builder(); |
| builder.mBreakStrategy = breakStrategy; |
| return sBuilderManager.addNewDelegate(builder); |
| } |
| |
| @LayoutlibDelegate |
| /*package*/ static long nGetReleaseFunc() { |
| synchronized (MeasuredText_Delegate.class) { |
| if (sFinalizer == -1) { |
| sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer( |
| sBuilderManager::removeJavaReferenceFor); |
| } |
| } |
| return sFinalizer; |
| } |
| |
| @LayoutlibDelegate |
| /*package*/ static long nComputeLineBreaks( |
| /* non zero */ long nativePtr, |
| |
| // Inputs |
| @NonNull char[] text, |
| long measuredTextPtr, |
| int length, |
| float firstWidth, |
| int firstWidthLineCount, |
| float restWidth, |
| @Nullable float[] variableTabStops, |
| float defaultTabStop, |
| int indentsOffset) { |
| Builder builder = sBuilderManager.getDelegate(nativePtr); |
| if (builder == null) { |
| return 0; |
| } |
| |
| builder.mText = text; |
| builder.mWidths = new float[length]; |
| builder.mLineWidth = new LineWidth(firstWidth, firstWidthLineCount, restWidth); |
| builder.mTabStopCalculator = new TabStops(variableTabStops, defaultTabStop); |
| |
| MeasuredText_Delegate.computeRuns(measuredTextPtr, builder); |
| |
| // compute all possible breakpoints. |
| BreakIterator it = BreakIterator.getLineInstance(); |
| it.setText((CharacterIterator) new Segment(builder.mText, 0, length)); |
| |
| // average word length in english is 5. So, initialize the possible breaks with a guess. |
| List<Integer> breaks = new ArrayList<Integer>((int) Math.ceil(length / 5d)); |
| int loc; |
| it.first(); |
| while ((loc = it.next()) != BreakIterator.DONE) { |
| breaks.add(loc); |
| } |
| |
| List<Primitive> primitives = |
| computePrimitives(builder.mText, builder.mWidths, length, breaks); |
| switch (builder.mBreakStrategy) { |
| case Layout.BREAK_STRATEGY_SIMPLE: |
| builder.mLineBreaker = new GreedyLineBreaker(primitives, builder.mLineWidth, |
| builder.mTabStopCalculator); |
| break; |
| case Layout.BREAK_STRATEGY_HIGH_QUALITY: |
| // TODO |
| // break; |
| case Layout.BREAK_STRATEGY_BALANCED: |
| builder.mLineBreaker = new OptimizingLineBreaker(primitives, builder.mLineWidth, |
| builder.mTabStopCalculator); |
| break; |
| default: |
| assert false : "Unknown break strategy: " + builder.mBreakStrategy; |
| builder.mLineBreaker = new GreedyLineBreaker(primitives, builder.mLineWidth, |
| builder.mTabStopCalculator); |
| } |
| Result result = new Result(builder.mLineBreaker.computeBreaks()); |
| return sResultManager.addNewDelegate(result); |
| } |
| |
| @LayoutlibDelegate |
| /*package*/ static int nGetLineCount(long ptr) { |
| Result result = sResultManager.getDelegate(ptr); |
| return result.mResult.mLineBreakOffset.size(); |
| } |
| |
| @LayoutlibDelegate |
| /*package*/ static int nGetLineBreakOffset(long ptr, int idx) { |
| Result result = sResultManager.getDelegate(ptr); |
| return result.mResult.mLineBreakOffset.get(idx); |
| } |
| |
| @LayoutlibDelegate |
| /*package*/ static float nGetLineWidth(long ptr, int idx) { |
| Result result = sResultManager.getDelegate(ptr); |
| return result.mResult.mLineWidths.get(idx); |
| } |
| |
| @LayoutlibDelegate |
| /*package*/ static float nGetLineAscent(long ptr, int idx) { |
| Result result = sResultManager.getDelegate(ptr); |
| return result.mResult.mLineAscents.get(idx); |
| } |
| |
| @LayoutlibDelegate |
| /*package*/ static float nGetLineDescent(long ptr, int idx) { |
| Result result = sResultManager.getDelegate(ptr); |
| return result.mResult.mLineDescents.get(idx); |
| } |
| |
| @LayoutlibDelegate |
| /*package*/ static int nGetLineFlag(long ptr, int idx) { |
| Result result = sResultManager.getDelegate(ptr); |
| return result.mResult.mLineFlags.get(idx); |
| } |
| |
| @LayoutlibDelegate |
| /*package*/ static long nGetReleaseResultFunc() { |
| synchronized (MeasuredText_Delegate.class) { |
| if (sResultFinalizer == -1) { |
| sResultFinalizer = NativeAllocationRegistry_Delegate.createFinalizer( |
| sBuilderManager::removeJavaReferenceFor); |
| } |
| } |
| return sResultFinalizer; |
| } |
| |
| /** |
| * Compute metadata each character - things which help in deciding if it's possible to break |
| * at a point or not. |
| */ |
| @NonNull |
| private static List<Primitive> computePrimitives(@NonNull char[] text, @NonNull float[] widths, |
| int length, @NonNull List<Integer> breaks) { |
| // Initialize the list with a guess of the number of primitives: |
| // 2 Primitives per non-whitespace char and approx 5 chars per word (i.e. 83% chars) |
| List<Primitive> primitives = new ArrayList<Primitive>(((int) Math.ceil(length * 1.833))); |
| int breaksSize = breaks.size(); |
| int breakIndex = 0; |
| for (int i = 0; i < length; i++) { |
| char c = text[i]; |
| if (c == CHAR_SPACE || c == CHAR_ZWSP) { |
| primitives.add(PrimitiveType.GLUE.getNewPrimitive(i, widths[i])); |
| } else if (c == CHAR_TAB) { |
| primitives.add(PrimitiveType.VARIABLE.getNewPrimitive(i)); |
| } else if (c != CHAR_NEWLINE) { |
| while (breakIndex < breaksSize && breaks.get(breakIndex) < i) { |
| breakIndex++; |
| } |
| Primitive p; |
| if (widths[i] != 0) { |
| if (breakIndex < breaksSize && breaks.get(breakIndex) == i) { |
| p = PrimitiveType.PENALTY.getNewPrimitive(i, 0, 0); |
| } else { |
| p = PrimitiveType.WORD_BREAK.getNewPrimitive(i, 0); |
| } |
| primitives.add(p); |
| } |
| |
| primitives.add(PrimitiveType.BOX.getNewPrimitive(i, widths[i])); |
| } |
| } |
| // final break at end of everything |
| primitives.add( |
| PrimitiveType.PENALTY.getNewPrimitive(length, 0, -PrimitiveType.PENALTY_INFINITY)); |
| return primitives; |
| } |
| |
| // TODO: Rename to LineBreakerRef and move everything other than LineBreaker to LineBreaker. |
| /** |
| * Java representation of the native Builder class. |
| */ |
| public static class Builder { |
| char[] mText; |
| float[] mWidths; |
| private BaseLineBreaker mLineBreaker; |
| private int mBreakStrategy; |
| private LineWidth mLineWidth; |
| private TabStops mTabStopCalculator; |
| } |
| |
| public abstract static class Run { |
| int mStart; |
| int mEnd; |
| |
| Run(int start, int end) { |
| mStart = start; |
| mEnd = end; |
| } |
| |
| abstract void addTo(Builder builder); |
| } |
| |
| public static class Result { |
| final BaseLineBreaker.Result mResult; |
| public Result(BaseLineBreaker.Result result) { |
| mResult = result; |
| } |
| } |
| } |