Add TextShaper API

TextShaper API provides a primitive text layout result, e.g. what glyph is used etc.

Here is the new APIs and its description

- PositionedGlyphs
This is a native instance backed object that gives layout information to developers.

- GlyphStyle
The glyph style is an object that holds the parameters that will be used for drawing.
This object is a subset of the Paint object for avoiding creating lots of Paint copy.

- TextShaper#shapeText
This does a text shaping and gives TextShaper.Result. This is a native backed primitive
shaping API.

- StyledTextShaper
This is a text shaper for a styled text. This will be the top-level developer facing
API for shaping text.

Bug: 168048923
Test: atest FontTest TextShaperTest StyledTextShaperTest
Change-Id: I2e91d1ef8503b25b28efc94da7de7cad49c4c1a9
This commit is contained in:
Seigo Nonaka
2020-09-14 15:29:42 -07:00
parent 08348e26e2
commit f3a1915b2e
15 changed files with 1323 additions and 20 deletions

View File

@@ -16508,6 +16508,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
@@ -16568,6 +16585,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 {
@@ -49990,6 +50026,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);

View File

@@ -0,0 +1,67 @@
/*
* 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);
}
}
}

View File

@@ -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,16 +937,20 @@ public class TextLine {
totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset);
}
if (c != null) {
final float leftX, rightX;
if (runIsRtl) {
leftX = x - totalWidth;
rightX = x;
} else {
leftX = x;
rightX = x + totalWidth;
}
final float leftX, rightX;
if (runIsRtl) {
leftX = x - totalWidth;
rightX = x;
} else {
leftX = x;
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.
*

View File

@@ -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);

View File

@@ -0,0 +1,62 @@
/*
* 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();
}

View File

@@ -0,0 +1,234 @@
/*
* 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.text;
import android.annotation.ColorInt;
import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.graphics.Paint;
import java.util.Objects;
/**
* Represents subset of Paint parameters such as font size, scaleX that is used to draw a glyph.
*
* Glyph is a most primitive unit of text drawing.
*
*/
public class GlyphStyle {
private @ColorInt int mColor;
private float mFontSize;
private float mScaleX;
private float mSkewX;
private int mFlags;
/**
* @param color a color.
* @param fontSize a font size in pixels.
* @param scaleX a horizontal scale factor.
* @param skewX a horizontal skew factor
* @param flags paint flags
*
* @see Paint#getFlags()
* @see Paint#setFlags(int)
*/
public GlyphStyle(
@ColorInt int color,
@FloatRange(from = 0) float fontSize,
@FloatRange(from = 0) float scaleX,
@FloatRange(from = 0) float skewX,
int flags) {
mColor = color;
mFontSize = fontSize;
mScaleX = scaleX;
mSkewX = skewX;
mFlags = flags;
}
/**
* Create glyph style from Paint
*
* @param paint a paint
*/
public GlyphStyle(@NonNull Paint paint) {
setFromPaint(paint);
}
/**
* Gets the color.
*
* @return a color
* @see Paint#getColor()
* @see Paint#setColor(int)
*/
public @ColorInt int getColor() {
return mColor;
}
/**
* Sets the color.
*
* @param color a color
* @see Paint#getColor()
* @see Paint#setColor(int)
*/
public void setColor(@ColorInt int color) {
mColor = color;
}
/**
* Gets the font size in pixels.
*
* @return font size
* @see Paint#getTextSize()
* @see Paint#setTextSize(float)
*/
public @FloatRange(from = 0) float getFontSize() {
return mFontSize;
}
/**
* Sets the font size in pixels.
*
* @param fontSize font size in pixel
* @see Paint#getTextSize()
* @see Paint#setTextSize(float)
*/
public void setFontSize(@FloatRange(from = 0) float fontSize) {
mFontSize = fontSize;
}
/**
* Return the horizontal scale factor
*
* @return a horizontal scale factor
* @see Paint#getTextScaleX()
* @see Paint#setTextScaleX(float)
*/
public @FloatRange(from = 0) float getScaleX() {
return mScaleX;
}
/**
* Set the horizontal scale factor
*
* @param scaleX a horizontal scale factor
* @see Paint#getTextScaleX()
* @see Paint#setTextScaleX(float)
*/
public void setScaleX(@FloatRange(from = 0) float scaleX) {
mScaleX = scaleX;
}
/**
* Return the horizontal skew factor
*
* @return a horizontal skew factor
* @see Paint#getTextSkewX()
* @see Paint#setTextSkewX(float)
*/
public @FloatRange(from = 0) float getSkewX() {
return mSkewX;
}
/**
* Set the horizontal skew factor
*
* @param skewX a horizontal skew factor
* @see Paint#getTextSkewX()
* @see Paint#setTextSkewX(float)
*/
public void setSkewX(@FloatRange(from = 0) float skewX) {
mSkewX = skewX;
}
/**
* Returns the Paint flags.
*
* @return a paint flags
* @see Paint#getFlags()
* @see Paint#setFlags(int)
*/
public int getFlags() {
return mFlags;
}
/**
* Set the Paint flags.
*
* @param flags a paint flags
* @see Paint#getFlags()
* @see Paint#setFlags(int)
*/
public void setFlags(int flags) {
mFlags = flags;
}
/**
* Applies glyph style to the paint object.
*
* @param paint a paint object
*/
public void applyToPaint(@NonNull Paint paint) {
paint.setColor(mColor);
paint.setTextSize(mFontSize);
paint.setTextScaleX(mScaleX);
paint.setTextSkewX(mSkewX);
paint.setFlags(mFlags);
}
/**
* Copy parameters from a Paint object.
*
* @param paint a paint object
*/
public void setFromPaint(@NonNull Paint paint) {
mColor = paint.getColor();
mFontSize = paint.getTextSize();
mScaleX = paint.getTextScaleX();
mSkewX = paint.getTextSkewX();
mFlags = paint.getFlags();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof GlyphStyle)) return false;
GlyphStyle that = (GlyphStyle) o;
return that.mColor == mColor
&& Float.compare(that.mFontSize, mFontSize) == 0
&& Float.compare(that.mScaleX, mScaleX) == 0
&& Float.compare(that.mSkewX, mSkewX) == 0
&& mFlags == that.mFlags;
}
@Override
public int hashCode() {
return Objects.hash(mColor, mFontSize, mScaleX, mSkewX, mFlags);
}
@Override
public String toString() {
return "GlyphStyle{"
+ "mColor=" + mColor
+ ", mFontSize=" + mFontSize
+ ", mScaleX=" + mScaleX
+ ", mSkewX=" + mSkewX
+ ", mFlags=" + mFlags
+ '}';
}
}

View File

@@ -0,0 +1,277 @@
/*
* 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.text;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.graphics.fonts.Font;
import com.android.internal.util.Preconditions;
import dalvik.annotation.optimization.CriticalNative;
import libcore.util.NativeAllocationRegistry;
import java.util.ArrayList;
import java.util.Objects;
/**
* Text shaping result object for single style text.
*
* You can get text shaping result by
* {@link TextShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint)} and
* {@link TextShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)}.
*
* @see TextShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint)
* @see TextShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)
*/
public final class PositionedGlyphs {
private static final NativeAllocationRegistry REGISTRY =
NativeAllocationRegistry.createMalloced(
Typeface.class.getClassLoader(), nReleaseFunc());
private final long mLayoutPtr;
private final float mXOffset;
private final float mYOffset;
private final GlyphStyle mGlyphStyle;
private final ArrayList<Font> mFonts;
/**
* Returns the total amount of advance consumed by this positioned glyphs.
*
* The advance is an amount of width consumed by the glyph. The total amount of advance is
* a total amount of advance consumed by this series of glyphs. In other words, if another
* glyph is placed next to this series of glyphs, it's X offset should be shifted this amount
* of width.
*
* @return total amount of advance
*/
public float getTotalAdvance() {
return nGetTotalAdvance(mLayoutPtr);
}
/**
* Effective ascent value of this positioned glyphs.
*
* If two or more font files are used in this series of glyphs, the effective ascent will be
* the minimum ascent value across the all font files.
*
* @return effective ascent value
*/
public float getAscent() {
return nGetAscent(mLayoutPtr);
}
/**
* Effective descent value of this positioned glyphs.
*
* If two or more font files are used in this series of glyphs, the effective descent will be
* the maximum descent value across the all font files.
*
* @return effective descent value
*/
public float getDescent() {
return nGetDescent(mLayoutPtr);
}
/**
* Returns the glyph style used for drawing the glyph at the given index.
*
* @return A glyph style
*/
@NonNull
public GlyphStyle getStyle() {
return mGlyphStyle;
}
/**
* Returns the amount of X offset added to glyph position.
*
* @return The X offset added to glyph position.
*/
public float getOriginX() {
return mXOffset;
}
/**
* Returns the amount of Y offset added to glyph position.
*
* @return The Y offset added to glyph position.
*/
public float getOriginY() {
return mYOffset;
}
/**
* Returns the number of glyphs stored.
*
* @return the number of glyphs
*/
@IntRange(from = 0)
public int glyphCount() {
return nGetGlyphCount(mLayoutPtr);
}
/**
* Returns the font object used for drawing the glyph at the given index.
*
* @param index the glyph index
* @return the font object used for drawing the glyph at the given index
*/
@NonNull
public Font getFont(@IntRange(from = 0) int index) {
Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
return mFonts.get(index);
}
/**
* Returns the glyph ID used for drawing the glyph at the given index.
*
* @param index the glyph index
* @return A font object
*/
@IntRange(from = 0)
public int getGlyphId(@IntRange(from = 0) int index) {
Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
return nGetGlyphId(mLayoutPtr, index);
}
/**
* Returns the x coordinate of the glyph position at the given index.
*
* @param index the glyph index
* @return A X offset in pixels
*/
public float getPositionX(@IntRange(from = 0) int index) {
Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
return nGetX(mLayoutPtr, index) + mXOffset;
}
/**
* Returns the y coordinate of the glyph position at the given index.
*
* @param index the glyph index
* @return A Y offset in pixels.
*/
public float getPositionY(@IntRange(from = 0) int index) {
Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
return nGetY(mLayoutPtr, index) + mYOffset;
}
/**
* Create single style layout from native result.
*
* @hide
*
* @param layoutPtr the address of native layout object.
* @param paint a paint object
*/
public PositionedGlyphs(long layoutPtr, @NonNull Paint paint, float xOffset, float yOffset) {
mLayoutPtr = layoutPtr;
mGlyphStyle = new GlyphStyle(paint);
int glyphCount = nGetGlyphCount(layoutPtr);
mFonts = new ArrayList<>(glyphCount);
mXOffset = xOffset;
mYOffset = yOffset;
long prevPtr = 0;
Font prevFont = null;
for (int i = 0; i < glyphCount; ++i) {
long ptr = nGetFont(layoutPtr, i);
if (prevPtr != ptr) {
prevPtr = ptr;
prevFont = Font.findOrCreateFontFromNativePtr(ptr);
}
mFonts.add(prevFont);
}
REGISTRY.registerNativeAllocation(this, layoutPtr);
}
@CriticalNative
private static native int nGetGlyphCount(long minikinLayout);
@CriticalNative
private static native float nGetTotalAdvance(long minikinLayout);
@CriticalNative
private static native float nGetAscent(long minikinLayout);
@CriticalNative
private static native float nGetDescent(long minikinLayout);
@CriticalNative
private static native int nGetGlyphId(long minikinLayout, int i);
@CriticalNative
private static native float nGetX(long minikinLayout, int i);
@CriticalNative
private static native float nGetY(long minikinLayout, int i);
@CriticalNative
private static native long nGetFont(long minikinLayout, int i);
@CriticalNative
private static native long nReleaseFunc();
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PositionedGlyphs)) return false;
PositionedGlyphs that = (PositionedGlyphs) o;
if (!mGlyphStyle.equals(that.mGlyphStyle)) return false;
if (mXOffset != that.mXOffset || mYOffset != that.mYOffset) return false;
if (glyphCount() != that.glyphCount()) return false;
for (int i = 0; i < glyphCount(); ++i) {
if (getGlyphId(i) != that.getGlyphId(i)) return false;
if (getPositionX(i) != that.getPositionX(i)) return false;
if (getPositionY(i) != that.getPositionY(i)) return false;
// Intentionally using reference equality since font equality is heavy due to buffer
// compare.
if (getFont(i) != that.getFont(i)) return false;
}
return true;
}
@Override
public int hashCode() {
int hashCode = Objects.hash(mXOffset, mYOffset, mGlyphStyle);
for (int i = 0; i < glyphCount(); ++i) {
hashCode = Objects.hash(hashCode,
getGlyphId(i), getPositionX(i), getPositionY(i), getFont(i));
}
return hashCode;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < glyphCount(); ++i) {
if (i != 0) {
sb.append(", ");
}
sb.append("[ ID = " + getGlyphId(i) + ","
+ " pos = (" + getPositionX(i) + "," + getPositionY(i) + ")"
+ " font = " + getFont(i) + " ]");
}
sb.append("]");
return "PositionedGlyphs{"
+ "glyphs = " + sb.toString()
+ ", mXOffset=" + mXOffset
+ ", mYOffset=" + mYOffset
+ ", mGlyphStyle=" + mGlyphStyle
+ '}';
}
}

View File

@@ -0,0 +1,125 @@
/*
* 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.text;
import android.annotation.NonNull;
import android.graphics.Paint;
import android.text.TextDirectionHeuristic;
import android.text.TextUtils;
import com.android.internal.util.Preconditions;
import dalvik.annotation.optimization.FastNative;
/**
* Provides conversion from a text into glyph array.
*
* Text shaping is a preprocess for drawing text into canvas with glyphs. The glyph is a most
* primitive unit of the text drawing, consist of glyph identifier in the font file and its position
* and style. You can draw the shape result to Canvas by calling Canvas#drawGlyphs.
*
* @see TextShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)
* @see TextShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint)
* @see android.text.StyledTextShaper#shapeText(CharSequence, int, int, TextDirectionHeuristic,
* TextPaint)
*/
public class TextShaper {
private TextShaper() {} // Do not instantiate
/**
* Shape non-styled text.
*
* This function shapes the text of the given range under the context of given context range.
* Some script, e.g. Arabic or Devanagari, changes letter shape based on its location or
* surrounding characters.
*
* @param text a text buffer to be shaped
* @param start a start index of shaping target in the buffer.
* @param count a length of shaping target in the buffer.
* @param contextStart a start index of context used for shaping in the buffer.
* @param contextCount a length of context used for shaping in the buffer.
* @param xOffset an additional amount of x offset of the result glyphs.
* @param yOffset an additional amount of y offset of the result glyphs.
* @param isRtl true if this text is shaped for RTL direction, false otherwise.
* @param paint a paint used for shaping text.
* @return a shape result.
*/
@NonNull
public static PositionedGlyphs shapeTextRun(
@NonNull char[] text, int start, int count, int contextStart, int contextCount,
float xOffset, float yOffset, boolean isRtl, @NonNull Paint paint) {
Preconditions.checkNotNull(text);
Preconditions.checkNotNull(paint);
return new PositionedGlyphs(
nativeShapeTextRun(text, start, count, contextStart, contextCount, isRtl,
paint.getNativeInstance()),
paint, xOffset, yOffset);
}
/**
* Shape non-styled text.
*
* This function shapes the text of the given range under the context of given context range.
* Some script, e.g. Arabic or Devanagari, changes letter shape based on its location or
* surrounding characters.
*
* @param text a text buffer to be shaped. Any styled spans stored in this text are ignored.
* @param start a start index of shaping target in the buffer.
* @param count a length of shaping target in the buffer.
* @param contextStart a start index of context used for shaping in the buffer.
* @param contextCount a length of context used for shaping in the buffer.
* @param xOffset an additional amount of x offset of the result glyphs.
* @param yOffset an additional amount of y offset of the result glyphs.
* @param isRtl true if this text is shaped for RTL direction, false otherwise.
* @param paint a paint used for shaping text.
* @return a shape result
*/
@NonNull
public static PositionedGlyphs shapeTextRun(
@NonNull CharSequence text, int start, int count, int contextStart, int contextCount,
float xOffset, float yOffset, boolean isRtl, @NonNull Paint paint) {
Preconditions.checkNotNull(text);
Preconditions.checkNotNull(paint);
if (text instanceof String) {
return new PositionedGlyphs(
nativeShapeTextRun(
(String) text, start, count, contextStart, contextCount, isRtl,
paint.getNativeInstance()),
paint, xOffset, yOffset);
} else {
char[] buf = new char[contextCount];
TextUtils.getChars(text, contextStart, contextStart + contextCount, buf, 0);
return new PositionedGlyphs(
nativeShapeTextRun(
buf, start - contextStart, count,
0, contextCount, isRtl, paint.getNativeInstance()),
paint, xOffset, yOffset);
}
}
@FastNative
private static native long nativeShapeTextRun(
char[] text, int start, int count, int contextStart, int contextCount,
boolean isRtl, long nativePaint);
@FastNative
private static native long nativeShapeTextRun(
String text, int start, int count, int contextStart, int contextCount,
boolean isRtl, long nativePaint);
}

View File

@@ -334,6 +334,7 @@ cc_defaults {
"jni/fonts/FontFamily.cpp",
"jni/text/LineBreaker.cpp",
"jni/text/MeasuredText.cpp",
"jni/text/TextShaper.cpp",
],
header_libs: [ "android_graphics_jni_headers" ],

View File

@@ -73,6 +73,7 @@ extern int register_android_graphics_pdf_PdfEditor(JNIEnv* env);
extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env);
extern int register_android_graphics_text_MeasuredText(JNIEnv* env);
extern int register_android_graphics_text_LineBreaker(JNIEnv *env);
extern int register_android_graphics_text_TextShaper(JNIEnv *env);
extern int register_android_util_PathParser(JNIEnv* env);
extern int register_android_view_DisplayListCanvas(JNIEnv* env);
@@ -137,6 +138,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_graphics_pdf_PdfRenderer),
REG_JNI(register_android_graphics_text_MeasuredText),
REG_JNI(register_android_graphics_text_LineBreaker),
REG_JNI(register_android_graphics_text_TextShaper),
REG_JNI(register_android_util_PathParser),
REG_JNI(register_android_view_RenderNode),

View File

@@ -72,7 +72,7 @@ public:
size_t start = 0;
size_t nGlyphs = layout.nGlyphs();
for (size_t i = 0; i < nGlyphs; i++) {
const minikin::MinikinFont* nextFont = layout.getFont(i);
const minikin::MinikinFont* nextFont = layout.getFont(i)->typeface().get();
if (i > 0 && nextFont != curFont) {
SkFont* skfont = &paint->getSkFont();
MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));

View File

@@ -38,6 +38,14 @@ struct FontWrapper {
minikin::Font font;
};
// We assume FontWrapper's address is the same as underlying Font's address.
// This assumption is used for looking up Java font object from native address.
// The Font object can be created without Java's Font object but all Java's Font objects point to
// the native FontWrapper. So when looking up Java object from minikin::Layout which gives us Font
// address, we lookup Font Java object from Font address with assumption that it is the same as
// FontWrapper address.
static_assert(offsetof(FontWrapper, font) == 0);
// Utility wrapper for java.util.List
class ListHelper {
public:

View File

@@ -198,6 +198,59 @@ static jfloat Font_getFontMetrics(JNIEnv* env, jobject, jlong fontHandle, jlong
return spacing;
}
// Critical Native
static jlong Font_getFontInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle);
MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font.typeface().get());
uint64_t result = font->font.style().weight();
result |= font->font.style().slant() == minikin::FontStyle::Slant::ITALIC ? 0x10000 : 0x00000;
result |= ((static_cast<uint64_t>(minikinSkia->GetFontIndex())) << 32);
result |= ((static_cast<uint64_t>(minikinSkia->GetAxes().size())) << 48);
return result;
}
// Critical Native
static jlong Font_getAxisInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle, jint index) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle);
MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font.typeface().get());
const minikin::FontVariation& var = minikinSkia->GetAxes().at(index);
uint32_t floatBinary = *reinterpret_cast<const uint32_t*>(&var.value);
return (static_cast<uint64_t>(var.axisTag) << 32) | static_cast<uint64_t>(floatBinary);
}
///////////////////////////////////////////////////////////////////////////////
struct FontBufferWrapper {
FontBufferWrapper(const std::shared_ptr<minikin::MinikinFont>& font) : minikinFont(font) {}
// MinikinFont holds a shared pointer of SkTypeface which has reference to font data.
std::shared_ptr<minikin::MinikinFont> minikinFont;
};
static void unrefBuffer(jlong nativePtr) {
FontBufferWrapper* wrapper = reinterpret_cast<FontBufferWrapper*>(nativePtr);
delete wrapper;
}
// Critical Native
static jlong FontBufferHelper_refFontBuffer(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle);
return reinterpret_cast<jlong>(new FontBufferWrapper(font->font.typeface()));
}
// Fast Native
static jobject FontBufferHelper_wrapByteBuffer(JNIEnv* env, jobject, jlong nativePtr) {
FontBufferWrapper* wrapper = reinterpret_cast<FontBufferWrapper*>(nativePtr);
return env->NewDirectByteBuffer(
const_cast<void*>(wrapper->minikinFont->GetFontData()),
wrapper->minikinFont->GetFontSize());
}
// Critical Native
static jlong FontBufferHelper_getReleaseFunc(CRITICAL_JNI_PARAMS) {
return reinterpret_cast<jlong>(unrefBuffer);
}
///////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gFontBuilderMethods[] = {
@@ -211,13 +264,23 @@ static const JNINativeMethod gFontBuilderMethods[] = {
static const JNINativeMethod gFontMethods[] = {
{ "nGetGlyphBounds", "(JIJLandroid/graphics/RectF;)F", (void*) Font_getGlyphBounds },
{ "nGetFontMetrics", "(JJLandroid/graphics/Paint$FontMetrics;)F", (void*) Font_getFontMetrics },
{ "nGetFontInfo", "(J)J", (void*) Font_getFontInfo },
{ "nGetAxisInfo", "(JI)J", (void*) Font_getAxisInfo },
};
static const JNINativeMethod gFontBufferHelperMethods[] = {
{ "nRefFontBuffer", "(J)J", (void*) FontBufferHelper_refFontBuffer },
{ "nWrapByteBuffer", "(J)Ljava/nio/ByteBuffer;", (void*) FontBufferHelper_wrapByteBuffer },
{ "nGetReleaseFunc", "()J", (void*) FontBufferHelper_getReleaseFunc },
};
int register_android_graphics_fonts_Font(JNIEnv* env) {
return RegisterMethodsOrDie(env, "android/graphics/fonts/Font$Builder", gFontBuilderMethods,
NELEM(gFontBuilderMethods)) +
RegisterMethodsOrDie(env, "android/graphics/fonts/Font", gFontMethods,
NELEM(gFontMethods));
NELEM(gFontMethods)) +
RegisterMethodsOrDie(env, "android/graphics/fonts/NativeFontBufferHelper",
gFontBufferHelperMethods, NELEM(gFontBufferHelperMethods));
}
}

View File

@@ -0,0 +1,206 @@
/*
* 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.
*/
#undef LOG_TAG
#define LOG_TAG "TextShaper"
#include "graphics_jni_helpers.h"
#include <nativehelper/ScopedStringChars.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <set>
#include <algorithm>
#include "SkPaint.h"
#include "SkTypeface.h"
#include <hwui/MinikinSkia.h>
#include <hwui/MinikinUtils.h>
#include <hwui/Paint.h>
#include <minikin/MinikinPaint.h>
#include <minikin/MinikinFont.h>
namespace android {
struct LayoutWrapper {
LayoutWrapper(minikin::Layout&& layout, float ascent, float descent)
: layout(std::move(layout)), ascent(ascent), descent(descent) {}
minikin::Layout layout;
float ascent;
float descent;
};
static void releaseLayout(jlong ptr) {
delete reinterpret_cast<LayoutWrapper*>(ptr);
}
static jlong shapeTextRun(const uint16_t* text, int textSize, int start, int count,
int contextStart, int contextCount, minikin::Bidi bidiFlags,
const Paint& paint, const Typeface* typeface) {
minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(&paint, typeface);
minikin::Layout layout = MinikinUtils::doLayout(&paint, bidiFlags, typeface,
text, textSize, start, count, contextStart, contextCount, nullptr);
std::set<const minikin::Font*> seenFonts;
float overallAscent = 0;
float overallDescent = 0;
for (int i = 0; i < layout.nGlyphs(); ++i) {
const minikin::Font* font = layout.getFont(i);
if (seenFonts.find(font) != seenFonts.end()) continue;
minikin::MinikinExtent extent = {};
font->typeface()->GetFontExtent(&extent, minikinPaint, layout.getFakery(i));
overallAscent = std::min(overallAscent, extent.ascent);
overallDescent = std::max(overallDescent, extent.descent);
}
std::unique_ptr<LayoutWrapper> ptr = std::make_unique<LayoutWrapper>(
std::move(layout), overallAscent, overallDescent
);
return reinterpret_cast<jlong>(ptr.release());
}
static jlong TextShaper_shapeTextRunChars(JNIEnv *env, jobject, jcharArray charArray,
jint start, jint count, jint contextStart, jint contextCount, jboolean isRtl,
jlong paintPtr) {
ScopedCharArrayRO text(env, charArray);
Paint* paint = reinterpret_cast<Paint*>(paintPtr);
const Typeface* typeface = paint->getAndroidTypeface();
const minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
return shapeTextRun(
text.get(), text.size(),
start, count,
contextStart, contextCount,
bidiFlags,
*paint, typeface);
}
static jlong TextShaper_shapeTextRunString(JNIEnv *env, jobject, jstring string,
jint start, jint count, jint contextStart, jint contextCount, jboolean isRtl,
jlong paintPtr) {
ScopedStringChars text(env, string);
Paint* paint = reinterpret_cast<Paint*>(paintPtr);
const Typeface* typeface = paint->getAndroidTypeface();
const minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
return shapeTextRun(
text.get(), text.size(),
start, count,
contextStart, contextCount,
bidiFlags,
*paint, typeface);
}
// CriticalNative
static jint TextShaper_Result_getGlyphCount(CRITICAL_JNI_PARAMS_COMMA jlong ptr) {
const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
return layout->layout.nGlyphs();
}
// CriticalNative
static jfloat TextShaper_Result_getTotalAdvance(CRITICAL_JNI_PARAMS_COMMA jlong ptr) {
const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
return layout->layout.getAdvance();
}
// CriticalNative
static jfloat TextShaper_Result_getAscent(CRITICAL_JNI_PARAMS_COMMA jlong ptr) {
const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
return layout->ascent;
}
// CriticalNative
static jfloat TextShaper_Result_getDescent(CRITICAL_JNI_PARAMS_COMMA jlong ptr) {
const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
return layout->descent;
}
// CriticalNative
static jint TextShaper_Result_getGlyphId(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
return layout->layout.getGlyphId(i);
}
// CriticalNative
static jfloat TextShaper_Result_getX(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
return layout->layout.getX(i);
}
// CriticalNative
static jfloat TextShaper_Result_getY(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
return layout->layout.getY(i);
}
// CriticalNative
static jlong TextShaper_Result_getFont(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
return reinterpret_cast<jlong>(layout->layout.getFont(i));
}
// CriticalNative
static jlong TextShaper_Result_nReleaseFunc(CRITICAL_JNI_PARAMS) {
return reinterpret_cast<jlong>(releaseLayout);
}
static const JNINativeMethod gMethods[] = {
// Fast Natives
{"nativeShapeTextRun", "("
"[C" // text
"I" // start
"I" // count
"I" // contextStart
"I" // contextCount
"Z" // isRtl
"J)" // paint
"J", // LayoutPtr
(void*) TextShaper_shapeTextRunChars},
{"nativeShapeTextRun", "("
"Ljava/lang/String;" // text
"I" // start
"I" // count
"I" // contextStart
"I" // contextCount
"Z" // isRtl
"J)" // paint
"J", // LayoutPtr
(void*) TextShaper_shapeTextRunString},
};
static const JNINativeMethod gResultMethods[] = {
{ "nGetGlyphCount", "(J)I", (void*)TextShaper_Result_getGlyphCount },
{ "nGetTotalAdvance", "(J)F", (void*)TextShaper_Result_getTotalAdvance },
{ "nGetAscent", "(J)F", (void*)TextShaper_Result_getAscent },
{ "nGetDescent", "(J)F", (void*)TextShaper_Result_getDescent },
{ "nGetGlyphId", "(JI)I", (void*)TextShaper_Result_getGlyphId },
{ "nGetX", "(JI)F", (void*)TextShaper_Result_getX },
{ "nGetY", "(JI)F", (void*)TextShaper_Result_getY },
{ "nGetFont", "(JI)J", (void*)TextShaper_Result_getFont },
{ "nReleaseFunc", "()J", (void*)TextShaper_Result_nReleaseFunc },
};
int register_android_graphics_text_TextShaper(JNIEnv* env) {
return RegisterMethodsOrDie(env, "android/graphics/text/TextShaper", gMethods,
NELEM(gMethods))
+ RegisterMethodsOrDie(env, "android/graphics/text/PositionedGlyphs",
gResultMethods, NELEM(gResultMethods));
}
}

View File

@@ -16490,6 +16490,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
@@ -16550,6 +16567,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 {
@@ -48127,6 +48163,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);