From 39c482f21fbfff39654c6f49e40105574be58377 Mon Sep 17 00:00:00 2001 From: Seigo Nonaka Date: Sat, 26 Sep 2020 23:53:02 -0700 Subject: [PATCH] Add Canvas#drawGlyphs Bug: 168048923 Test: atest CanvasDrawGlyphsTest Change-Id: I013335fa82883146044ea03fbbe67e1e26a35f6d --- api/current.txt | 1 + .../java/android/graphics/BaseCanvas.java | 61 +++++++++++++++++++ .../android/graphics/BaseRecordingCanvas.java | 38 ++++++++++++ graphics/java/android/graphics/Canvas.java | 39 ++++++++++++ libs/hwui/hwui/Canvas.cpp | 20 ++++++ libs/hwui/hwui/Canvas.h | 4 ++ libs/hwui/jni/android_graphics_Canvas.cpp | 17 ++++++ non-updatable-api/current.txt | 1 + 8 files changed, 181 insertions(+) diff --git a/api/current.txt b/api/current.txt index 94e90a75f644d..0497edf5295e3 100644 --- a/api/current.txt +++ b/api/current.txt @@ -14356,6 +14356,7 @@ package android.graphics { method public void drawColor(@ColorLong long, @NonNull android.graphics.BlendMode); method public void drawDoubleRoundRect(@NonNull android.graphics.RectF, float, float, @NonNull android.graphics.RectF, float, float, @NonNull android.graphics.Paint); method public void drawDoubleRoundRect(@NonNull android.graphics.RectF, @NonNull float[], @NonNull android.graphics.RectF, @NonNull float[], @NonNull android.graphics.Paint); + method public void drawGlyphs(@NonNull int[], @IntRange(from=0) int, @NonNull float[], @IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.graphics.fonts.Font, @NonNull android.graphics.Paint); method public void drawLine(float, float, float, float, @NonNull android.graphics.Paint); method public void drawLines(@NonNull @Size(multiple=4) float[], int, int, @NonNull android.graphics.Paint); method public void drawLines(@NonNull @Size(multiple=4) float[], @NonNull android.graphics.Paint); diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java index bee8d5efc933f..05df250fa6b9a 100644 --- a/graphics/java/android/graphics/BaseCanvas.java +++ b/graphics/java/android/graphics/BaseCanvas.java @@ -18,11 +18,13 @@ package android.graphics; import android.annotation.ColorInt; import android.annotation.ColorLong; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Canvas.VertexMode; +import android.graphics.fonts.Font; import android.graphics.text.MeasuredText; import android.text.GraphicsOperations; import android.text.MeasuredParagraph; @@ -31,6 +33,10 @@ import android.text.SpannableString; import android.text.SpannedString; import android.text.TextUtils; +import com.android.internal.util.Preconditions; + +import java.util.Objects; + /** * This class is a base class for Canvas's drawing operations. Any modifications here * should be accompanied by a similar modification to {@link BaseRecordingCanvas}. @@ -443,6 +449,58 @@ public abstract class BaseCanvas { paint.getNativeInstance()); } + /** + * Draw array of glyphs with specified font. + * + * @param glyphIds Array of glyph IDs. The length of array must be greater than or equal to + * {@code glyphStart + glyphCount}. + * @param glyphIdOffset Number of elements to skip before drawing in glyphIds + * array. + * @param positions A flattened X and Y position array. The first glyph X position must be + * stored at {@code positionOffset}. The first glyph Y position must be stored + * at {@code positionOffset + 1}, then the second glyph X position must be + * stored at {@code positionOffset + 2}. + * The length of array must be greater than or equal to + * {@code positionOffset + glyphCount * 2}. + * @param positionOffset Number of elements to skip before drawing in {@code positions}. + * The first glyph X position must be stored at {@code positionOffset}. + * The first glyph Y position must be stored at + * {@code positionOffset + 1}, then the second glyph X position must be + * stored at {@code positionOffset + 2}. + * @param glyphCount Number of glyphs to be drawn. + * @param font Font used for drawing. + * @param paint Paint used for drawing. The typeface set to this paint is ignored. + * + * @see android.graphics.text.TextShaper + * @see android.text.StyledTextShaper + */ + public void drawGlyphs( + @NonNull int[] glyphIds, + @IntRange(from = 0) int glyphIdOffset, + @NonNull float[] positions, + @IntRange(from = 0) int positionOffset, + @IntRange(from = 0) int glyphCount, + @NonNull Font font, + @NonNull Paint paint) { + Objects.requireNonNull(glyphIds, "glyphIds must not be null."); + Objects.requireNonNull(positions, "positions must not be null."); + Objects.requireNonNull(font, "font must not be null."); + Objects.requireNonNull(paint, "paint must not be null."); + Preconditions.checkArgumentNonnegative(glyphCount); + + if (glyphIdOffset < 0 || glyphIdOffset + glyphCount > glyphIds.length) { + throw new IndexOutOfBoundsException( + "glyphIds must have at least " + (glyphIdOffset + glyphCount) + " of elements"); + } + if (positionOffset < 0 || positionOffset + glyphCount * 2 > positions.length) { + throw new IndexOutOfBoundsException( + "positions must have at least " + (positionOffset + glyphCount * 2) + + " of elements"); + } + nDrawGlyphs(mNativeCanvasWrapper, glyphIds, positions, glyphIdOffset, positionOffset, + glyphCount, font.getNativePtr(), paint.getNativeInstance()); + } + public void drawText(@NonNull char[] text, int index, int count, float x, float y, @NonNull Paint paint) { if ((index | count | (index + count) | @@ -734,6 +792,9 @@ public abstract class BaseCanvas { int vertOffset, float[] texs, int texOffset, int[] colors, int colorOffset, short[] indices, int indexOffset, int indexCount, long nativePaint); + private static native void nDrawGlyphs(long nativeCanvas, int[] glyphIds, float[] positions, + int glyphIdStart, int positionStart, int glyphCount, long nativeFont, long nativePaint); + private static native void nDrawText(long nativeCanvas, char[] text, int index, int count, float x, float y, int flags, long nativePaint); diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java index 2f5214cb1df53..a8922e84514a2 100644 --- a/graphics/java/android/graphics/BaseRecordingCanvas.java +++ b/graphics/java/android/graphics/BaseRecordingCanvas.java @@ -18,9 +18,11 @@ package android.graphics; import android.annotation.ColorInt; import android.annotation.ColorLong; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; +import android.graphics.fonts.Font; import android.graphics.text.MeasuredText; import android.text.GraphicsOperations; import android.text.MeasuredParagraph; @@ -29,8 +31,12 @@ import android.text.SpannableString; import android.text.SpannedString; import android.text.TextUtils; +import com.android.internal.util.Preconditions; + import dalvik.annotation.optimization.FastNative; +import java.util.Objects; + /** * This class is a base class for canvases that defer drawing operations, so all * the draw operations can be marked @FastNative. It contains a re-implementation of @@ -408,6 +414,34 @@ public class BaseRecordingCanvas extends Canvas { paint.getNativeInstance()); } + @Override + public void drawGlyphs( + @NonNull int[] glyphIds, + @IntRange(from = 0) int glyphIdOffset, + @NonNull float[] positions, + @IntRange(from = 0) int positionOffset, + @IntRange(from = 0) int glyphCount, + @NonNull Font font, + @NonNull Paint paint) { + Objects.requireNonNull(glyphIds, "glyphIds must not be null."); + Objects.requireNonNull(positions, "positions must not be null."); + Objects.requireNonNull(font, "font must not be null."); + Objects.requireNonNull(paint, "paint must not be null."); + Preconditions.checkArgumentNonnegative(glyphCount); + + if (glyphIdOffset < 0 || glyphIdOffset + glyphCount > glyphIds.length) { + throw new IndexOutOfBoundsException( + "glyphIds must have at least " + (glyphIdOffset + glyphCount) + " of elements"); + } + if (positionOffset < 0 || positionOffset + glyphCount * 2 > positions.length) { + throw new IndexOutOfBoundsException( + "positions must have at least " + (positionOffset + glyphCount * 2) + + " of elements"); + } + nDrawGlyphs(mNativeCanvasWrapper, glyphIds, positions, glyphIdOffset, positionOffset, + glyphCount, font.getNativePtr(), paint.getNativeInstance()); + } + @Override public final void drawText(@NonNull char[] text, int index, int count, float x, float y, @NonNull Paint paint) { @@ -673,6 +707,10 @@ public class BaseRecordingCanvas extends Canvas { int vertOffset, float[] texs, int texOffset, int[] colors, int colorOffset, short[] indices, int indexOffset, int indexCount, long nativePaint); + @FastNative + private static native void nDrawGlyphs(long nativeCanvas, int[] glyphIds, float[] positions, + int glyphIdStart, int positionStart, int glyphCount, long nativeFont, long nativePaint); + @FastNative private static native void nDrawText(long nativeCanvas, char[] text, int index, int count, float x, float y, int flags, long nativePaint); diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index 559571cb02716..5e07d156a06af 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -19,10 +19,12 @@ package android.graphics; import android.annotation.ColorInt; import android.annotation.ColorLong; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; import android.compat.annotation.UnsupportedAppUsage; +import android.graphics.fonts.Font; import android.graphics.text.MeasuredText; import android.os.Build; @@ -2047,6 +2049,43 @@ public class Canvas extends BaseCanvas { super.drawDoubleRoundRect(outer, outerRadii, inner, innerRadii, paint); } + /** + * Draw array of glyphs with specified font. + * + * @param glyphIds Array of glyph IDs. The length of array must be greater than or equal to + * {@code glyphStart + glyphCount}. + * @param glyphIdOffset Number of elements to skip before drawing in glyphIds + * array. + * @param positions A flattened X and Y position array. The first glyph X position must be + * stored at {@code positionOffset}. The first glyph Y position must be stored + * at {@code positionOffset + 1}, then the second glyph X position must be + * stored at {@code positionOffset + 2}. + * The length of array must be greater than or equal to + * {@code positionOffset + glyphCount * 2}. + * @param positionOffset Number of elements to skip before drawing in {@code positions}. + * The first glyph X position must be stored at {@code positionOffset}. + * The first glyph Y position must be stored at + * {@code positionOffset + 1}, then the second glyph X position must be + * stored at {@code positionOffset + 2}. + * @param glyphCount Number of glyphs to be drawn. + * @param font Font used for drawing. + * @param paint Paint used for drawing. The typeface set to this paint is ignored. + * + * @see android.graphics.text.TextShaper + * @see android.text.StyledTextShaper + */ + public void drawGlyphs( + @NonNull int[] glyphIds, + @IntRange(from = 0) int glyphIdOffset, + @NonNull float[] positions, + @IntRange(from = 0) int positionOffset, + @IntRange(from = 0) int glyphCount, + @NonNull Font font, + @NonNull Paint paint) { + super.drawGlyphs(glyphIds, glyphIdOffset, positions, positionOffset, glyphCount, font, + paint); + } + /** * Draw the text, with origin at (x,y), using the specified paint. The origin is interpreted * based on the Align setting in the paint. diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp index 8df2770b01576..146bf283c58a8 100644 --- a/libs/hwui/hwui/Canvas.cpp +++ b/libs/hwui/hwui/Canvas.cpp @@ -135,6 +135,26 @@ private: float totalAdvance; }; +void Canvas::drawGlyphs(const minikin::Font& font, const int* glyphIds, const float* positions, + int glyphCount, const Paint& paint) { + // Minikin modify skFont for auto-fakebold/auto-fakeitalic. + Paint copied(paint); + + auto glyphFunc = [&](uint16_t* outGlyphIds, float* outPositions) { + for (uint32_t i = 0; i < glyphCount; ++i) { + outGlyphIds[i] = static_cast(glyphIds[i]); + } + memcpy(outPositions, positions, sizeof(float) * 2 * glyphCount); + }; + + const minikin::MinikinFont* minikinFont = font.typeface().get(); + SkFont* skfont = &copied.getSkFont(); + MinikinFontSkia::populateSkFont(skfont, minikinFont, minikin::FontFakery()); + + // total advance is used for drawing underline. We do not support underlyine by glyph drawing. + drawGlyphs(glyphFunc, glyphCount, copied, 0 /* x */, 0 /* y */, 0 /* total Advance */); +} + void Canvas::drawText(const uint16_t* text, int textSize, int start, int count, int contextStart, int contextCount, float x, float y, minikin::Bidi bidiFlags, const Paint& origPaint, const Typeface* typeface, minikin::MeasuredText* mt) { diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h index 817c7ee9077f6..772b7a28ef04b 100644 --- a/libs/hwui/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -32,6 +32,7 @@ class SkCanvasState; class SkVertices; namespace minikin { +class Font; class Layout; class MeasuredText; enum class Bidi : uint8_t; @@ -255,6 +256,9 @@ public: */ virtual void drawVectorDrawable(VectorDrawableRoot* tree) = 0; + void drawGlyphs(const minikin::Font& font, const int* glyphIds, const float* positions, + int glyphCount, const Paint& paint); + /** * Converts utf16 text to glyphs, calculating position and boundary, * and delegating the final draw to virtual drawGlyphs method. diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp index b6c6cd0b5c1c2..c04340c36511a 100644 --- a/libs/hwui/jni/android_graphics_Canvas.cpp +++ b/libs/hwui/jni/android_graphics_Canvas.cpp @@ -30,6 +30,7 @@ #include #include +#include "FontUtils.h" #include "Bitmap.h" #include "SkGraphics.h" #include "SkRegion.h" @@ -540,6 +541,21 @@ static void drawBitmapMesh(JNIEnv* env, jobject, jlong canvasHandle, jlong bitma colorA.ptr() + colorIndex, paint); } +static void drawGlyphs(JNIEnv* env, jobject, jlong canvasHandle, jintArray glyphIds, + jfloatArray positions, jint glyphOffset, jint positionOffset, + jint glyphCount, jlong fontHandle, jlong paintHandle) { + Paint* paint = reinterpret_cast(paintHandle); + FontWrapper* font = reinterpret_cast(fontHandle); + AutoJavaIntArray glyphIdArray(env, glyphIds); + AutoJavaFloatArray positionArray(env, positions); + get_canvas(canvasHandle)->drawGlyphs( + *font->font.get(), + glyphIdArray.ptr() + glyphOffset, + positionArray.ptr() + positionOffset, + glyphCount, + *paint); +} + static void drawTextChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray charArray, jint index, jint count, jfloat x, jfloat y, jint bidiFlags, jlong paintHandle) { @@ -719,6 +735,7 @@ static const JNINativeMethod gDrawMethods[] = { {"nDrawBitmap","(JJFFJIII)V", (void*) CanvasJNI::drawBitmap}, {"nDrawBitmap","(JJFFFFFFFFJII)V", (void*) CanvasJNI::drawBitmapRect}, {"nDrawBitmap", "(J[IIIFFIIZJ)V", (void*)CanvasJNI::drawBitmapArray}, + {"nDrawGlyphs", "(J[I[FIIIJJ)V", (void*)CanvasJNI::drawGlyphs}, {"nDrawText","(J[CIIFFIJ)V", (void*) CanvasJNI::drawTextChars}, {"nDrawText","(JLjava/lang/String;IIFFIJ)V", (void*) CanvasJNI::drawTextString}, {"nDrawTextRun","(J[CIIIIFFZJJ)V", (void*) CanvasJNI::drawTextRunChars}, diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt index 983f20aad2e3a..bb6597b7eca35 100644 --- a/non-updatable-api/current.txt +++ b/non-updatable-api/current.txt @@ -14356,6 +14356,7 @@ package android.graphics { method public void drawColor(@ColorLong long, @NonNull android.graphics.BlendMode); method public void drawDoubleRoundRect(@NonNull android.graphics.RectF, float, float, @NonNull android.graphics.RectF, float, float, @NonNull android.graphics.Paint); method public void drawDoubleRoundRect(@NonNull android.graphics.RectF, @NonNull float[], @NonNull android.graphics.RectF, @NonNull float[], @NonNull android.graphics.Paint); + method public void drawGlyphs(@NonNull int[], @IntRange(from=0) int, @NonNull float[], @IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.graphics.fonts.Font, @NonNull android.graphics.Paint); method public void drawLine(float, float, float, float, @NonNull android.graphics.Paint); method public void drawLines(@NonNull @Size(multiple=4) float[], int, int, @NonNull android.graphics.Paint); method public void drawLines(@NonNull @Size(multiple=4) float[], @NonNull android.graphics.Paint);