1ae0b1a2ebde9984c2e75650b7dcfa7449d1a8833Deepanshu Guptapackage android.text;
2ae0b1a2ebde9984c2e75650b7dcfa7449d1a8833Deepanshu Gupta
3e8685f6153d281f24d1d2cbfa5942f40a5b461b1Diego Perezimport com.android.layoutlib.bridge.impl.DelegateManager;
4ae0b1a2ebde9984c2e75650b7dcfa7449d1a8833Deepanshu Guptaimport com.android.tools.layoutlib.annotations.LayoutlibDelegate;
5ae0b1a2ebde9984c2e75650b7dcfa7449d1a8833Deepanshu Gupta
6442aee6bc1abfb143dcfa1ba60d696e576d066c4Deepanshu Guptaimport android.annotation.NonNull;
7345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillardimport android.annotation.Nullable;
89fe7fca9bcdceade9c654c6a8dcf0c48be16d78dDeepanshu Guptaimport android.icu.text.BreakIterator;
9345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillardimport android.text.Layout.BreakStrategy;
10345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillardimport android.text.Layout.HyphenationFrequency;
114ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Guptaimport android.text.Primitive.PrimitiveType;
129fe7fca9bcdceade9c654c6a8dcf0c48be16d78dDeepanshu Guptaimport android.text.StaticLayout.LineBreaks;
134ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta
146b06ef9725495cf071deec3bad76aff32c59b39eVictor Changimport java.text.CharacterIterator;
154ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Guptaimport java.util.ArrayList;
164ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Guptaimport java.util.List;
17ae0b1a2ebde9984c2e75650b7dcfa7449d1a8833Deepanshu Gupta
18ae0b1a2ebde9984c2e75650b7dcfa7449d1a8833Deepanshu Guptaimport javax.swing.text.Segment;
19ae0b1a2ebde9984c2e75650b7dcfa7449d1a8833Deepanshu Gupta
20ae0b1a2ebde9984c2e75650b7dcfa7449d1a8833Deepanshu Gupta/**
21ae0b1a2ebde9984c2e75650b7dcfa7449d1a8833Deepanshu Gupta * Delegate that provides implementation for native methods in {@link android.text.StaticLayout}
224ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta * <p/>
238a16d5d203a48e7b13761f329333b3dcb8f8210bDeepanshu Gupta * Through the layoutlib_create tool, selected methods of StaticLayout have been replaced
24ae0b1a2ebde9984c2e75650b7dcfa7449d1a8833Deepanshu Gupta * by calls to methods of the same name in this delegate class.
25ae0b1a2ebde9984c2e75650b7dcfa7449d1a8833Deepanshu Gupta *
26ae0b1a2ebde9984c2e75650b7dcfa7449d1a8833Deepanshu Gupta */
27ae0b1a2ebde9984c2e75650b7dcfa7449d1a8833Deepanshu Guptapublic class StaticLayout_Delegate {
28ae0b1a2ebde9984c2e75650b7dcfa7449d1a8833Deepanshu Gupta
294ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta    private static final char CHAR_SPACE     = 0x20;
304ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta    private static final char CHAR_TAB       = 0x09;
314ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta    private static final char CHAR_NEWLINE   = 0x0A;
324ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta    private static final char CHAR_ZWSP      = 0x200B;  // Zero width space.
334ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta
34e8685f6153d281f24d1d2cbfa5942f40a5b461b1Diego Perez    // ---- Builder delegate manager ----
35e8685f6153d281f24d1d2cbfa5942f40a5b461b1Diego Perez    private static final DelegateManager<Builder> sBuilderManager =
36e8685f6153d281f24d1d2cbfa5942f40a5b461b1Diego Perez        new DelegateManager<Builder>(Builder.class);
37e8685f6153d281f24d1d2cbfa5942f40a5b461b1Diego Perez
38ae0b1a2ebde9984c2e75650b7dcfa7449d1a8833Deepanshu Gupta    @LayoutlibDelegate
39345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard    /*package*/ static long nInit(
40345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            @BreakStrategy int breakStrategy,
41345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            @HyphenationFrequency int hyphenationFrequency,
42345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            boolean isJustified,
43345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            @Nullable int[] indents,
44345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            @Nullable int[] leftPaddings,
45345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            @Nullable int[] rightPaddings) {
46345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard        Builder builder = new Builder();
47345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard        builder.mBreakStrategy = breakStrategy;
48345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard        return sBuilderManager.addNewDelegate(builder);
499fe7fca9bcdceade9c654c6a8dcf0c48be16d78dDeepanshu Gupta    }
509fe7fca9bcdceade9c654c6a8dcf0c48be16d78dDeepanshu Gupta
519fe7fca9bcdceade9c654c6a8dcf0c48be16d78dDeepanshu Gupta    @LayoutlibDelegate
52345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard    /*package*/ static void nFinish(long nativePtr) {
53345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard        sBuilderManager.removeJavaReferenceFor(nativePtr);
549fe7fca9bcdceade9c654c6a8dcf0c48be16d78dDeepanshu Gupta    }
559fe7fca9bcdceade9c654c6a8dcf0c48be16d78dDeepanshu Gupta
569fe7fca9bcdceade9c654c6a8dcf0c48be16d78dDeepanshu Gupta    @LayoutlibDelegate
57345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard    /*package*/ static int nComputeLineBreaks(
58345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            /* non zero */ long nativePtr,
599fe7fca9bcdceade9c654c6a8dcf0c48be16d78dDeepanshu Gupta
60345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            // Inputs
61345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            @NonNull char[] text,
620b516c5c0cac7df4d540a9fbf0beab415837b42bJerome Gaillard            long measuredTextPtr,
63345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            int length,
64345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            float firstWidth,
65345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            int firstWidthLineCount,
66345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            float restWidth,
67345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            @Nullable int[] variableTabStops,
68345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            int defaultTabStop,
69345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            int indentsOffset,
70345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard
71345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            // Outputs
72345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            @NonNull LineBreaks recycle,
73345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            int recycleLength,
74345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            @NonNull int[] recycleBreaks,
75345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            @NonNull float[] recycleWidths,
76345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            @NonNull float[] recycleAscents,
77345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            @NonNull float[] recycleDescents,
78345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            @NonNull int[] recycleFlags,
79345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            @NonNull float[] charWidths) {
80345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard        Builder builder = sBuilderManager.getDelegate(nativePtr);
819fe7fca9bcdceade9c654c6a8dcf0c48be16d78dDeepanshu Gupta        if (builder == null) {
829fe7fca9bcdceade9c654c6a8dcf0c48be16d78dDeepanshu Gupta            return 0;
839fe7fca9bcdceade9c654c6a8dcf0c48be16d78dDeepanshu Gupta        }
847169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta
85345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard        builder.mText = text;
86345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard        builder.mWidths = new float[length];
87345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard        builder.mLineWidth = new LineWidth(firstWidth, firstWidthLineCount, restWidth);
88345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard        builder.mTabStopCalculator = new TabStops(variableTabStops, defaultTabStop);
89345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard
90f9ed408cb244e6924d51c50c99cdb5186a9c6ad4Jerome Gaillard        MeasuredParagraph_Delegate.computeRuns(measuredTextPtr, builder);
91345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard
927169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta        // compute all possible breakpoints.
93009184dd1182a3bcd25a18302ad34a07c3e6be42Jerome Gaillard        BreakIterator it = BreakIterator.getLineInstance();
946b06ef9725495cf071deec3bad76aff32c59b39eVictor Chang        it.setText((CharacterIterator) new Segment(builder.mText, 0, length));
957169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta
967169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta        // average word length in english is 5. So, initialize the possible breaks with a guess.
977169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta        List<Integer> breaks = new ArrayList<Integer>((int) Math.ceil(length / 5d));
987169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta        int loc;
997169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta        it.first();
1007169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta        while ((loc = it.next()) != BreakIterator.DONE) {
1017169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta            breaks.add(loc);
1027169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta        }
1037169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta
1047169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta        List<Primitive> primitives =
1057169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta                computePrimitives(builder.mText, builder.mWidths, length, breaks);
1067169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta        switch (builder.mBreakStrategy) {
1077169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta            case Layout.BREAK_STRATEGY_SIMPLE:
1087169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta                builder.mLineBreaker = new GreedyLineBreaker(primitives, builder.mLineWidth,
1097169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta                        builder.mTabStopCalculator);
1107169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta                break;
1117169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta            case Layout.BREAK_STRATEGY_HIGH_QUALITY:
1127169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta                // TODO
1137169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta//                break;
1147169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta            case Layout.BREAK_STRATEGY_BALANCED:
1157169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta                builder.mLineBreaker = new OptimizingLineBreaker(primitives, builder.mLineWidth,
1167169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta                        builder.mTabStopCalculator);
1177169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta                break;
1187169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta            default:
1190609785e6f839bfd27e33fa2630e6a1c12be9a20Diego Perez                assert false : "Unknown break strategy: " + builder.mBreakStrategy;
1200609785e6f839bfd27e33fa2630e6a1c12be9a20Diego Perez                builder.mLineBreaker = new GreedyLineBreaker(primitives, builder.mLineWidth,
1210609785e6f839bfd27e33fa2630e6a1c12be9a20Diego Perez                        builder.mTabStopCalculator);
1227169c14107fc4291342b6211fff01723996fd351Deepanshu Gupta        }
1239fe7fca9bcdceade9c654c6a8dcf0c48be16d78dDeepanshu Gupta        builder.mLineBreaker.computeBreaks(recycle);
124b28e813fd8bd9a9e330ea1803f5b153b6fc7d4e0Jerome Gaillard        System.arraycopy(builder.mWidths, 0, charWidths, 0, builder.mWidths.length);
1254ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta        return recycle.breaks.length;
126ae0b1a2ebde9984c2e75650b7dcfa7449d1a8833Deepanshu Gupta    }
127ae0b1a2ebde9984c2e75650b7dcfa7449d1a8833Deepanshu Gupta
1284ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta    /**
1294ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta     * Compute metadata each character - things which help in deciding if it's possible to break
1304ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta     * at a point or not.
1314ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta     */
1324ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta    @NonNull
1334ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta    private static List<Primitive> computePrimitives(@NonNull char[] text, @NonNull float[] widths,
1344ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta            int length, @NonNull List<Integer> breaks) {
1354ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta        // Initialize the list with a guess of the number of primitives:
1364ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta        // 2 Primitives per non-whitespace char and approx 5 chars per word (i.e. 83% chars)
1374ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta        List<Primitive> primitives = new ArrayList<Primitive>(((int) Math.ceil(length * 1.833)));
1384ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta        int breaksSize = breaks.size();
1394ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta        int breakIndex = 0;
1404ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta        for (int i = 0; i < length; i++) {
1414ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta            char c = text[i];
1424ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta            if (c == CHAR_SPACE || c == CHAR_ZWSP) {
1434ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta                primitives.add(PrimitiveType.GLUE.getNewPrimitive(i, widths[i]));
1444ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta            } else if (c == CHAR_TAB) {
1454ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta                primitives.add(PrimitiveType.VARIABLE.getNewPrimitive(i));
1464ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta            } else if (c != CHAR_NEWLINE) {
1474ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta                while (breakIndex < breaksSize && breaks.get(breakIndex) < i) {
1484ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta                    breakIndex++;
1494ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta                }
1504ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta                Primitive p;
1514ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta                if (widths[i] != 0) {
1524ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta                    if (breakIndex < breaksSize && breaks.get(breakIndex) == i) {
1534ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta                        p = PrimitiveType.PENALTY.getNewPrimitive(i, 0, 0);
1544ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta                    } else {
1554ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta                        p = PrimitiveType.WORD_BREAK.getNewPrimitive(i, 0);
1564ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta                    }
1574ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta                    primitives.add(p);
1584ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta                }
1594ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta
1604ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta                primitives.add(PrimitiveType.BOX.getNewPrimitive(i, widths[i]));
1614ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta            }
1624ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta        }
1634ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta        // final break at end of everything
1644ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta        primitives.add(
1654ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta                PrimitiveType.PENALTY.getNewPrimitive(length, 0, -PrimitiveType.PENALTY_INFINITY));
1664ef9b507c2c1b73805533a86d935d637493d5903Deepanshu Gupta        return primitives;
167ae0b1a2ebde9984c2e75650b7dcfa7449d1a8833Deepanshu Gupta    }
168e8685f6153d281f24d1d2cbfa5942f40a5b461b1Diego Perez
1699fe7fca9bcdceade9c654c6a8dcf0c48be16d78dDeepanshu Gupta    // TODO: Rename to LineBreakerRef and move everything other than LineBreaker to LineBreaker.
170e8685f6153d281f24d1d2cbfa5942f40a5b461b1Diego Perez    /**
171f3284cbd38989279a116e0082b7ab5b987c13388Diego Perez     * Java representation of the native Builder class.
172e8685f6153d281f24d1d2cbfa5942f40a5b461b1Diego Perez     */
1730b516c5c0cac7df4d540a9fbf0beab415837b42bJerome Gaillard    public static class Builder {
174f3284cbd38989279a116e0082b7ab5b987c13388Diego Perez        char[] mText;
175f3284cbd38989279a116e0082b7ab5b987c13388Diego Perez        float[] mWidths;
176345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard        private LineBreaker mLineBreaker;
177345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard        private int mBreakStrategy;
178345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard        private LineWidth mLineWidth;
179345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard        private TabStops mTabStopCalculator;
180345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard    }
181345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard
1820b516c5c0cac7df4d540a9fbf0beab415837b42bJerome Gaillard    public abstract static class Run {
183345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard        int mStart;
184345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard        int mEnd;
185345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard
186345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard        Run(int start, int end) {
187345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            mStart = start;
188345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard            mEnd = end;
189345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard        }
190345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard
191345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard        abstract void addTo(Builder builder);
192345d3cef8e40f59abeaba5402f51d3068b229bbcJerome Gaillard    }
193ae0b1a2ebde9984c2e75650b7dcfa7449d1a8833Deepanshu Gupta}
194