Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 71cbc72e authored by Raph Levien's avatar Raph Levien
Browse files

Record hyphens from Minikin and draw them

This patch plumbs up hyphens computed in Minikin's LineBreaker,
records them in a new column in StaticLayout, and draws them.
DynamicLayout mirrors the new column, and TextLine is also changed to
make sure the hyphen is only drawn for the last run in a line.

There is a rather primitive mechanism for loading hyphenation
patterns, for testing only at this point.

Change-Id: Ib208568c0f6cff12cf834047500ec1da9ea9f430
parent c94f742f
Loading
Loading
Loading
Loading
+12 −4
Original line number Diff line number Diff line
@@ -356,6 +356,8 @@ public class DynamicLayout extends Layout
            ints[DESCENT] = desc;
            objects[0] = reflowed.getLineDirections(i);

            ints[HYPHEN] = reflowed.getHyphen(i);

            if (mEllipsize) {
                ints[ELLIPSIS_START] = reflowed.getEllipsisStart(i);
                ints[ELLIPSIS_COUNT] = reflowed.getEllipsisCount(i);
@@ -631,6 +633,11 @@ public class DynamicLayout extends Layout
        return mBottomPadding;
    }

    @Override
    public int getHyphen(int line) {
        return mInts.getValue(line, HYPHEN);
    }

    @Override
    public int getEllipsizedWidth() {
        return mEllipsizedWidth;
@@ -739,11 +746,12 @@ public class DynamicLayout extends Layout
    private static final int TAB = START;
    private static final int TOP = 1;
    private static final int DESCENT = 2;
    private static final int COLUMNS_NORMAL = 3;
    private static final int HYPHEN = 3;
    private static final int COLUMNS_NORMAL = 4;

    private static final int ELLIPSIS_START = 3;
    private static final int ELLIPSIS_COUNT = 4;
    private static final int COLUMNS_ELLIPSIZE = 5;
    private static final int ELLIPSIS_START = 4;
    private static final int ELLIPSIS_COUNT = 5;
    private static final int COLUMNS_ELLIPSIZE = 6;

    private static final int START_MASK = 0x1FFFFFFF;
    private static final int DIR_SHIFT  = 30;
+76 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 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.text;

import android.util.Log;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.HashMap;
import java.util.Locale;

/**
 * Hyphenator is a wrapper class for a native implementation of automatic hyphenation,
 * in essence finding valid hyphenation opportunities in a word.
 *
 * @hide
 */
/* package */ class Hyphenator {
    // This class has deliberately simple lifetime management (no finalizer) because in
    // the common case a process will use a very small number of locales.

    private static String TAG = "Hyphenator";

    static HashMap<Locale, Hyphenator> sMap = new HashMap<Locale, Hyphenator>();

    private long mNativePtr;

    private Hyphenator(long nativePtr) {
        mNativePtr = nativePtr;
    }

    public static long get(Locale locale) {
        synchronized (sMap) {
            Hyphenator result = sMap.get(locale);
            if (result == null) {
                result = loadHyphenator(locale);
                sMap.put(locale, result);
            }
            return result == null ? 0 : result.mNativePtr;
        }
    }

    private static Hyphenator loadHyphenator(Locale locale) {
        // TODO: find pattern dictionary (from system location) that best matches locale
        if (Locale.US.equals(locale)) {
            File f = new File("/data/local/tmp/hyph-en-us.pat.txt");
            try {
                RandomAccessFile rf = new RandomAccessFile(f, "r");
                byte[] buf = new byte[(int)rf.length()];
                rf.read(buf);
                rf.close();
                String patternData = new String(buf);
                long nativePtr = StaticLayout.nLoadHyphenator(patternData);
                return new Hyphenator(nativePtr);
            } catch (IOException e) {
                Log.e(TAG, "error loading hyphenation " + f);
            }
        }
        return null;
    }
}
+21 −11
Original line number Diff line number Diff line
@@ -225,17 +225,17 @@ public abstract class Layout {

        // Draw the lines, one at a time.
        // The baseline is the top of the following line minus the current line's descent.
        for (int i = firstLine; i <= lastLine; i++) {
        for (int lineNum = firstLine; lineNum <= lastLine; lineNum++) {
            int start = previousLineEnd;
            previousLineEnd = getLineStart(i + 1);
            int end = getLineVisibleEnd(i, start, previousLineEnd);
            previousLineEnd = getLineStart(lineNum + 1);
            int end = getLineVisibleEnd(lineNum, start, previousLineEnd);

            int ltop = previousLineBottom;
            int lbottom = getLineTop(i+1);
            int lbottom = getLineTop(lineNum + 1);
            previousLineBottom = lbottom;
            int lbaseline = lbottom - getLineDescent(i);
            int lbaseline = lbottom - getLineDescent(lineNum);

            int dir = getParagraphDirection(i);
            int dir = getParagraphDirection(lineNum);
            int left = 0;
            int right = mWidth;

@@ -254,7 +254,7 @@ public abstract class Layout {
                // just collect the ones present at the start of the paragraph.
                // If spanEnd is before the end of the paragraph, that's not
                // our problem.
                if (start >= spanEnd && (i == firstLine || isFirstParaLine)) {
                if (start >= spanEnd && (lineNum == firstLine || isFirstParaLine)) {
                    spanEnd = sp.nextSpanTransition(start, textLength,
                                                    ParagraphStyle.class);
                    spans = getParagraphSpans(sp, start, spanEnd, ParagraphStyle.class);
@@ -280,7 +280,7 @@ public abstract class Layout {
                        int startLine = getLineForOffset(sp.getSpanStart(spans[n]));
                        // if there is more than one LeadingMarginSpan2, use
                        // the count that is greatest
                        if (i < startLine + count) {
                        if (lineNum < startLine + count) {
                            useFirstLineMargin = true;
                            break;
                        }
@@ -304,7 +304,7 @@ public abstract class Layout {
                }
            }

            boolean hasTabOrEmoji = getLineContainsTab(i);
            boolean hasTabOrEmoji = getLineContainsTab(lineNum);
            // Can't tell if we have tabs for sure, currently
            if (hasTabOrEmoji && !tabStopsIsInitialized) {
                if (tabStops == null) {
@@ -333,7 +333,7 @@ public abstract class Layout {
                    x = right;
                }
            } else {
                int max = (int)getLineExtent(i, tabStops, false);
                int max = (int)getLineExtent(lineNum, tabStops, false);
                if (align == Alignment.ALIGN_OPPOSITE) {
                    if (dir == DIR_LEFT_TO_RIGHT) {
                        x = right - max;
@@ -346,7 +346,8 @@ public abstract class Layout {
                }
            }

            Directions directions = getLineDirections(i);
            paint.setHyphenEdit(getHyphen(lineNum));
            Directions directions = getLineDirections(lineNum);
            if (directions == DIRS_ALL_LEFT_TO_RIGHT && !mSpannedText && !hasTabOrEmoji) {
                // XXX: assumes there's nothing additional to be done
                canvas.drawText(buf, start, end, x, lbaseline, paint);
@@ -677,6 +678,15 @@ public abstract class Layout {
     */
    public abstract int getBottomPadding();

    /**
     * Returns the hyphen edit for a line.
     *
     * @hide
     */
    public int getHyphen(int line) {
        return 0;
    }


    /**
     * Returns true if the character at offset and the preceding character
+26 −14
Original line number Diff line number Diff line
@@ -170,7 +170,8 @@ public class StaticLayout extends Layout {
         * Measurement and break iteration is done in native code. The protocol for using
         * the native code is as follows.
         *
         * For each paragraph, do a nSetText of the paragraph text. Also do nSetLineWidth.
         * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab
         * stops, break strategy (and possibly other parameters in the future).
         *
         * Then, for each run within the paragraph:
         *  - setLocale (this must be done at least for the first run, optional afterwards)
@@ -187,7 +188,7 @@ public class StaticLayout extends Layout {

        private void setLocale(Locale locale) {
            if (!locale.equals(mLocale)) {
                nSetLocale(mNativePtr, locale.toLanguageTag());
                nSetLocale(mNativePtr, locale.toLanguageTag(), Hyphenator.get(locale));
                mLocale = locale;
            }
        }
@@ -531,7 +532,7 @@ public class StaticLayout extends Layout {

            int[] breaks = lineBreaks.breaks;
            float[] lineWidths = lineBreaks.widths;
            boolean[] flags = lineBreaks.flags;
            int[] flags = lineBreaks.flags;

            // here is the offset of the starting character of the line we are currently measuring
            int here = paraStart;
@@ -617,7 +618,7 @@ public class StaticLayout extends Layout {
                    fm.top, fm.bottom,
                    v,
                    spacingmult, spacingadd, null,
                    null, fm, false,
                    null, fm, 0,
                    needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
                    includepad, trackpad, null,
                    null, bufStart, ellipsize,
@@ -629,7 +630,7 @@ public class StaticLayout extends Layout {
                      int above, int below, int top, int bottom, int v,
                      float spacingmult, float spacingadd,
                      LineHeightSpan[] chooseHt, int[] chooseHtv,
                      Paint.FontMetricsInt fm, boolean hasTabOrEmoji,
                      Paint.FontMetricsInt fm, int flags,
                      boolean needMultiply, byte[] chdirs, int dir,
                      boolean easy, int bufEnd, boolean includePad,
                      boolean trackPad, char[] chs,
@@ -722,8 +723,10 @@ public class StaticLayout extends Layout {
        lines[off + mColumns + START] = end;
        lines[off + mColumns + TOP] = v;

        if (hasTabOrEmoji)
            lines[off + TAB] |= TAB_MASK;
        // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
        // one bit for start field
        lines[off + TAB] |= flags & TAB_MASK;
        lines[off + HYPHEN] = flags;

        lines[off + DIR] |= dir << DIR_SHIFT;
        Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
@@ -942,6 +945,11 @@ public class StaticLayout extends Layout {
        return mBottomPadding;
    }

    @Override
    public int getHyphen(int line) {
        return mLines[mColumns * line + HYPHEN] & 0xff;
    }

    @Override
    public int getEllipsisCount(int line) {
        if (mColumns < COLUMNS_ELLIPSIZE) {
@@ -968,7 +976,10 @@ public class StaticLayout extends Layout {
    private static native long nNewBuilder();
    private static native void nFreeBuilder(long nativePtr);
    private static native void nFinishBuilder(long nativePtr);
    private static native void nSetLocale(long nativePtr, String locale);

    /* package */ static native long nLoadHyphenator(String patternData);

    private static native void nSetLocale(long nativePtr, String locale, long nativeHyphenator);

    // Set up paragraph text and settings; done as one big method to minimize jni crossings
    private static native void nSetupParagraph(long nativePtr, char[] text, int length,
@@ -991,22 +1002,23 @@ public class StaticLayout extends Layout {
    // to reduce the number of JNI calls in the common case where the
    // arrays do not have to be resized
    private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
            int[] recycleBreaks, float[] recycleWidths, boolean[] recycleFlags, int recycleLength);
            int[] recycleBreaks, float[] recycleWidths, int[] recycleFlags, int recycleLength);

    private int mLineCount;
    private int mTopPadding, mBottomPadding;
    private int mColumns;
    private int mEllipsizedWidth;

    private static final int COLUMNS_NORMAL = 3;
    private static final int COLUMNS_ELLIPSIZE = 5;
    private static final int COLUMNS_NORMAL = 4;
    private static final int COLUMNS_ELLIPSIZE = 6;
    private static final int START = 0;
    private static final int DIR = START;
    private static final int TAB = START;
    private static final int TOP = 1;
    private static final int DESCENT = 2;
    private static final int ELLIPSIS_START = 3;
    private static final int ELLIPSIS_COUNT = 4;
    private static final int HYPHEN = 3;
    private static final int ELLIPSIS_START = 4;
    private static final int ELLIPSIS_COUNT = 5;

    private int[] mLines;
    private Directions[] mLineDirections;
@@ -1028,7 +1040,7 @@ public class StaticLayout extends Layout {
        private static final int INITIAL_SIZE = 16;
        public int[] breaks = new int[INITIAL_SIZE];
        public float[] widths = new float[INITIAL_SIZE];
        public boolean[] flags = new boolean[INITIAL_SIZE]; // hasTabOrEmoji
        public int[] flags = new int[INITIAL_SIZE]; // hasTabOrEmoji
        // breaks, widths, and flags should all have the same length
    }

+4 −0
Original line number Diff line number Diff line
@@ -955,6 +955,10 @@ class TextLine {
                    span.updateDrawState(wp);
                }

                // Only draw hyphen on last run in line
                if (jnext < mLen) {
                    wp.setHyphenEdit(0);
                }
                x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x,
                        top, y, bottom, fmi, needWidth || jnext < measureLimit);
            }
Loading