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

Commit 703427e5 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add TextShaper API"

parents d2cfc483 f3a1915b
Loading
Loading
Loading
Loading
+40 −0
Original line number Diff line number Diff line
@@ -16510,6 +16510,23 @@ package android.graphics.pdf {
package android.graphics.text {
  public class GlyphStyle {
    ctor public GlyphStyle(@ColorInt int, @FloatRange(from=0) float, @FloatRange(from=0) float, @FloatRange(from=0) float, int);
    ctor public GlyphStyle(@NonNull android.graphics.Paint);
    method public void applyToPaint(@NonNull android.graphics.Paint);
    method @ColorInt public int getColor();
    method public int getFlags();
    method @FloatRange(from=0) public float getFontSize();
    method @FloatRange(from=0) public float getScaleX();
    method @FloatRange(from=0) public float getSkewX();
    method public void setColor(@ColorInt int);
    method public void setFlags(int);
    method public void setFontSize(@FloatRange(from=0) float);
    method public void setFromPaint(@NonNull android.graphics.Paint);
    method public void setScaleX(@FloatRange(from=0) float);
    method public void setSkewX(@FloatRange(from=0) float);
  }
  public class LineBreaker {
    method @NonNull public android.graphics.text.LineBreaker.Result computeLineBreaks(@NonNull android.graphics.text.MeasuredText, @NonNull android.graphics.text.LineBreaker.ParagraphConstraints, @IntRange(from=0) int);
    field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2
@@ -16570,6 +16587,25 @@ package android.graphics.text {
    method @NonNull public android.graphics.text.MeasuredText.Builder setComputeLayout(boolean);
  }
  public final class PositionedGlyphs {
    method public float getAscent();
    method public float getDescent();
    method @NonNull public android.graphics.fonts.Font getFont(@IntRange(from=0) int);
    method @IntRange(from=0) public int getGlyphId(@IntRange(from=0) int);
    method public float getOriginX();
    method public float getOriginY();
    method public float getPositionX(@IntRange(from=0) int);
    method public float getPositionY(@IntRange(from=0) int);
    method @NonNull public android.graphics.text.GlyphStyle getStyle();
    method public float getTotalAdvance();
    method @IntRange(from=0) public int glyphCount();
  }
  public class TextShaper {
    method @NonNull public static android.graphics.text.PositionedGlyphs shapeTextRun(@NonNull char[], int, int, int, int, float, float, boolean, @NonNull android.graphics.Paint);
    method @NonNull public static android.graphics.text.PositionedGlyphs shapeTextRun(@NonNull CharSequence, int, int, int, int, float, float, boolean, @NonNull android.graphics.Paint);
  }
}
package android.hardware {
@@ -50000,6 +50036,10 @@ package android.text {
    method @NonNull public android.text.StaticLayout.Builder setUseLineSpacingFromFallbacks(boolean);
  }
  public class StyledTextShaper {
    method @NonNull public static java.util.List<android.graphics.text.PositionedGlyphs> shapeText(@NonNull CharSequence, int, int, @NonNull android.text.TextDirectionHeuristic, @NonNull android.text.TextPaint);
  }
  public interface TextDirectionHeuristic {
    method public boolean isRtl(char[], int, int);
    method public boolean isRtl(CharSequence, int, int);
+67 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.annotation.NonNull;
import android.graphics.Paint;
import android.graphics.text.PositionedGlyphs;
import android.graphics.text.TextShaper;

import java.util.List;

/**
 * Provides text shaping for multi-styled text.
 *
 * @see TextShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint)
 * @see TextShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)
 * @see StyledTextShaper#shapeText(CharSequence, int, int, TextDirectionHeuristic, TextPaint)
 */
public class StyledTextShaper {
    private StyledTextShaper() {}


    /**
     * Shape multi-styled text.
     *
     * @param text a styled text.
     * @param start a start index of shaping target in the text.
     * @param count a length of shaping target in the text.
     * @param dir a text direction.
     * @param paint a paint
     * @return a shape result.
     */
    public static @NonNull List<PositionedGlyphs> shapeText(
            @NonNull CharSequence text, int start, int count,
            @NonNull TextDirectionHeuristic dir, @NonNull TextPaint paint) {
        MeasuredParagraph mp = MeasuredParagraph.buildForBidi(
                text, start, start + count, dir, null);
        TextLine tl = TextLine.obtain();
        try {
            tl.set(paint, text, start, start + count,
                    mp.getParagraphDir(),
                    mp.getDirections(start, start + count),
                    false /* tabstop is not supported */,
                    null,
                    -1, -1 // ellipsis is not supported.
            );
            return tl.shape();
        } finally {
            TextLine.recycle(tl);
        }
    }

}
+122 −18
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.text.PositionedGlyphs;
import android.graphics.text.TextShaper;
import android.os.Build;
import android.text.Layout.Directions;
import android.text.Layout.TabStops;
@@ -35,6 +37,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * Represents a line of styled text, for measuring in visual order and
@@ -306,6 +309,36 @@ public class TextLine {
        return measure(mLen, false, fmi);
    }

    /**
     * Shape the TextLine.
     */
    List<PositionedGlyphs> shape() {
        List<PositionedGlyphs> glyphs = new ArrayList<>();
        float horizontal = 0;
        float x = 0;
        final int runCount = mDirections.getRunCount();
        for (int runIndex = 0; runIndex < runCount; runIndex++) {
            final int runStart = mDirections.getRunStart(runIndex);
            if (runStart > mLen) break;
            final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
            final boolean runIsRtl = mDirections.isRunRtl(runIndex);

            int segStart = runStart;
            for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
                if (j == runLimit || charAt(j) == TAB_CHAR) {
                    horizontal += shapeRun(glyphs, segStart, j, runIsRtl, x + horizontal,
                            runIndex != (runCount - 1) || j != mLen);

                    if (j != runLimit) {  // charAt(j) == TAB_CHAR
                        horizontal = mDir * nextTab(horizontal * mDir);
                    }
                    segStart = j + 1;
                }
            }
        }
        return glyphs;
    }

    /**
     * Returns the signed graphical offset from the leading margin.
     *
@@ -483,12 +516,12 @@ public class TextLine {

        if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
            float w = -measureRun(start, limit, limit, runIsRtl, null);
            handleRun(start, limit, limit, runIsRtl, c, x + w, top,
            handleRun(start, limit, limit, runIsRtl, c, null, x + w, top,
                    y, bottom, null, false);
            return w;
        }

        return handleRun(start, limit, limit, runIsRtl, c, x, top,
        return handleRun(start, limit, limit, runIsRtl, c, null, x, top,
                y, bottom, null, needWidth);
    }

@@ -507,9 +540,34 @@ public class TextLine {
     */
    private float measureRun(int start, int offset, int limit, boolean runIsRtl,
            FontMetricsInt fmi) {
        return handleRun(start, offset, limit, runIsRtl, null, 0, 0, 0, 0, fmi, true);
        return handleRun(start, offset, limit, runIsRtl, null, null, 0, 0, 0, 0, fmi, true);
    }

    /**
     * Shape a unidirectional (but possibly multi-styled) run of text.
     *
     * @param glyphs the output positioned glyphs list
     * @param start the line-relative start
     * @param limit the line-relative limit
     * @param runIsRtl true if the run is right-to-left
     * @param x the position of the run that is closest to the leading margin
     * @param needWidth true if the width value is required.
     * @return the signed width of the run, based on the paragraph direction.
     * Only valid if needWidth is true.
     */
    private float shapeRun(List<PositionedGlyphs> glyphs, int start,
            int limit, boolean runIsRtl, float x, boolean needWidth) {

        if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
            float w = -measureRun(start, limit, limit, runIsRtl, null);
            handleRun(start, limit, limit, runIsRtl, null, glyphs, x + w, 0, 0, 0, null, false);
            return w;
        }

        return handleRun(start, limit, limit, runIsRtl, null, glyphs, x, 0, 0, 0, null, needWidth);
    }


    /**
     * Walk the cursor through this line, skipping conjuncts and
     * zero-width characters.
@@ -841,6 +899,7 @@ public class TextLine {
     * @param end the end of the text
     * @param runIsRtl true if the run is right-to-left
     * @param c the canvas, can be null if rendering is not needed
     * @param glyphs the output positioned glyph list, can be null if not necessary
     * @param x the edge of the run closest to the leading margin
     * @param top the top of the line
     * @param y the baseline
@@ -854,7 +913,7 @@ public class TextLine {
     */
    private float handleText(TextPaint wp, int start, int end,
            int contextStart, int contextEnd, boolean runIsRtl,
            Canvas c, float x, int top, int y, int bottom,
            Canvas c, List<PositionedGlyphs> glyphs, float x, int top, int y, int bottom,
            FontMetricsInt fmi, boolean needWidth, int offset,
            @Nullable ArrayList<DecorationInfo> decorations) {

@@ -878,7 +937,6 @@ public class TextLine {
            totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset);
        }

        if (c != null) {
        final float leftX, rightX;
        if (runIsRtl) {
            leftX = x - totalWidth;
@@ -888,6 +946,11 @@ public class TextLine {
            rightX = x + totalWidth;
        }

        if (glyphs != null) {
            shapeTextRun(glyphs, wp, start, end, contextStart, contextEnd, runIsRtl, leftX);
        }

        if (c != null) {
            if (wp.bgColor != 0) {
                int previousColor = wp.getColor();
                Paint.Style previousStyle = wp.getStyle();
@@ -1072,6 +1135,7 @@ public class TextLine {
     * @param limit the limit of the run
     * @param runIsRtl true if the run is right-to-left
     * @param c the canvas, can be null
     * @param glyphs the output positioned glyphs, can be null
     * @param x the end of the run closest to the leading margin
     * @param top the top of the line
     * @param y the baseline
@@ -1082,7 +1146,8 @@ public class TextLine {
     * valid if needWidth is true
     */
    private float handleRun(int start, int measureLimit,
            int limit, boolean runIsRtl, Canvas c, float x, int top, int y,
            int limit, boolean runIsRtl, Canvas c,
            List<PositionedGlyphs> glyphs, float x, int top, int y,
            int bottom, FontMetricsInt fmi, boolean needWidth) {

        if (measureLimit < start || measureLimit > limit) {
@@ -1115,7 +1180,7 @@ public class TextLine {
            wp.set(mPaint);
            wp.setStartHyphenEdit(adjustStartHyphenEdit(start, wp.getStartHyphenEdit()));
            wp.setEndHyphenEdit(adjustEndHyphenEdit(limit, wp.getEndHyphenEdit()));
            return handleText(wp, start, limit, start, limit, runIsRtl, c, x, top,
            return handleText(wp, start, limit, start, limit, runIsRtl, c, glyphs, x, top,
                    y, bottom, fmi, needWidth, measureLimit, null);
        }

@@ -1196,8 +1261,8 @@ public class TextLine {
                            adjustStartHyphenEdit(activeStart, mPaint.getStartHyphenEdit()));
                    activePaint.setEndHyphenEdit(
                            adjustEndHyphenEdit(activeEnd, mPaint.getEndHyphenEdit()));
                    x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x,
                            top, y, bottom, fmi, needWidth || activeEnd < measureLimit,
                    x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c,
                            glyphs, x, top, y, bottom, fmi, needWidth || activeEnd < measureLimit,
                            Math.min(activeEnd, mlimit), mDecorations);

                    activeStart = j;
@@ -1223,7 +1288,7 @@ public class TextLine {
                    adjustStartHyphenEdit(activeStart, mPaint.getStartHyphenEdit()));
            activePaint.setEndHyphenEdit(
                    adjustEndHyphenEdit(activeEnd, mPaint.getEndHyphenEdit()));
            x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x,
            x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, glyphs, x,
                    top, y, bottom, fmi, needWidth || activeEnd < measureLimit,
                    Math.min(activeEnd, mlimit), mDecorations);
        }
@@ -1259,6 +1324,45 @@ public class TextLine {
        }
    }

    /**
     * Shape a text run with the set-up paint.
     *
     * @param glyphs the output positioned glyphs list
     * @param paint the paint used to render the text
     * @param start the start of the run
     * @param end the end of the run
     * @param contextStart the start of context for the run
     * @param contextEnd the end of the context for the run
     * @param runIsRtl true if the run is right-to-left
     * @param x the x position of the left edge of the run
     */
    private void shapeTextRun(List<PositionedGlyphs> glyphs, TextPaint paint,
            int start, int end, int contextStart, int contextEnd, boolean runIsRtl, float x) {

        int count = end - start;
        int contextCount = contextEnd - contextStart;
        if (mCharsValid) {
            glyphs.add(TextShaper.shapeTextRun(
                    mChars,
                    start, count,
                    contextStart, contextCount,
                    x, 0f,
                    runIsRtl,
                    paint
            ));
        } else {
            glyphs.add(TextShaper.shapeTextRun(
                    mText,
                    mStart + start, count,
                    mStart + contextStart, contextCount,
                    x, 0f,
                    runIsRtl,
                    paint
            ));
        }
    }


    /**
     * Returns the next tab position.
     *
+74 −0
Original line number Diff line number Diff line
@@ -26,8 +26,11 @@ import android.graphics.Paint;
import android.graphics.RectF;
import android.os.LocaleList;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.TypedValue;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;

import dalvik.annotation.optimization.CriticalNative;
@@ -40,6 +43,7 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
@@ -56,6 +60,15 @@ public final class Font {
    private static final int STYLE_ITALIC = 1;
    private static final int STYLE_NORMAL = 0;

    private static final Object MAP_LOCK = new Object();
    // We need to have mapping from native ptr to Font object for later accessing from TextShape
    // result since Typeface doesn't have reference to Font object and it is not always created from
    // Font object. Sometimes Typeface is created in native layer only and there might not be Font
    // object in Java layer. So, if not found in this cache, create new Font object for API user.
    @GuardedBy("MAP_LOCK")
    private static final LongSparseArray<WeakReference<Font>> FONT_PTR_MAP =
            new LongSparseArray<>();

    /**
     * A builder class for creating new Font.
     */
@@ -501,6 +514,10 @@ public final class Font {
        mTtcIndex = ttcIndex;
        mAxes = axes;
        mLocaleList = localeList;

        synchronized (MAP_LOCK) {
            FONT_PTR_MAP.append(mNativePtr, new WeakReference<>(this));
        }
    }

    /**
@@ -637,6 +654,63 @@ public final class Font {
            + "}";
    }

    /**
     * Lookup Font object from native pointer or create new one if not found.
     * @hide
     */
    public static Font findOrCreateFontFromNativePtr(long ptr) {
        // First, lookup from known mapps.
        synchronized (MAP_LOCK) {
            WeakReference<Font> fontRef = FONT_PTR_MAP.get(ptr);
            if (fontRef != null) {
                Font font = fontRef.get();
                if (font != null) {
                    return font;
                }
            }

            // If not found, create Font object from native object for Java API users.
            ByteBuffer buffer = NativeFontBufferHelper.refByteBuffer(ptr);
            long packed = nGetFontInfo(ptr);
            int weight = (int) (packed & 0x0000_0000_0000_FFFFL);
            boolean italic = (packed & 0x0000_0000_0001_0000L) != 0;
            int ttcIndex = (int) ((packed & 0x0000_FFFF_0000_0000L) >> 32);
            int axisCount = (int) ((packed & 0xFFFF_0000_0000_0000L) >> 48);
            FontVariationAxis[] axes = new FontVariationAxis[axisCount];
            char[] charBuffer = new char[4];
            for (int i = 0; i < axisCount; ++i) {
                long packedAxis = nGetAxisInfo(ptr, i);
                float value = Float.intBitsToFloat((int) (packedAxis & 0x0000_0000_FFFF_FFFFL));
                charBuffer[0] = (char) ((packedAxis & 0xFF00_0000_0000_0000L) >> 56);
                charBuffer[1] = (char) ((packedAxis & 0x00FF_0000_0000_0000L) >> 48);
                charBuffer[2] = (char) ((packedAxis & 0x0000_FF00_0000_0000L) >> 40);
                charBuffer[3] = (char) ((packedAxis & 0x0000_00FF_0000_0000L) >> 32);
                axes[i] = new FontVariationAxis(new String(charBuffer), value);
            }
            Font.Builder builder = new Font.Builder(buffer)
                    .setWeight(weight)
                    .setSlant(italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT)
                    .setTtcIndex(ttcIndex)
                    .setFontVariationSettings(axes);

            Font newFont = null;
            try {
                newFont = builder.build();
                FONT_PTR_MAP.append(ptr, new WeakReference<>(newFont));
            } catch (IOException e) {
                // This must not happen since the buffer was already created once.
                Log.e("Font", "Failed to create font object from existing buffer.", e);
            }
            return newFont;
        }
    }

    @CriticalNative
    private static native long nGetFontInfo(long ptr);

    @CriticalNative
    private static native long nGetAxisInfo(long ptr, int i);

    @FastNative
    private static native float nGetGlyphBounds(long font, int glyphId, long paint, RectF rect);

+62 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.fonts;

import android.annotation.NonNull;

import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;

import libcore.util.NativeAllocationRegistry;

import java.nio.ByteBuffer;

/**
 * This is a helper class for showing native allocated buffer in Java API.
 *
 * @hide
 */
public class NativeFontBufferHelper {
    private NativeFontBufferHelper() {}

    private static final NativeAllocationRegistry REGISTRY =
            NativeAllocationRegistry.createMalloced(
                    ByteBuffer.class.getClassLoader(), nGetReleaseFunc());

    /**
     * Wrap native buffer with ByteBuffer with adding reference to it.
     */
    public static @NonNull ByteBuffer refByteBuffer(long fontPtr) {
        long refPtr = nRefFontBuffer(fontPtr);
        ByteBuffer buffer = nWrapByteBuffer(refPtr);

        // Releasing native object so that decreasing shared pointer ref count when the byte buffer
        // is GCed.
        REGISTRY.registerNativeAllocation(buffer, refPtr);

        return buffer;
    }

    @CriticalNative
    private static native long nRefFontBuffer(long fontPtr);

    @FastNative
    private static native ByteBuffer nWrapByteBuffer(long refPtr);

    @CriticalNative
    private static native long nGetReleaseFunc();
}
Loading