From c8abba490763142b12f034a7a8578ab33959794f Mon Sep 17 00:00:00 2001 From: Haoyu Zhang Date: Thu, 23 Jun 2022 10:29:24 -0700 Subject: [PATCH] Introduce TextLine#measureAllbounds This new method computes the horizontal character bounds by making use of the character advances returned from native layer. It'll be used in TextView to populate character bounds, and it's much efficent compared to calling getPrimaryHorizontal for each character. Bug: 233922052 Test: atest android.text.TextLineTest Change-Id: Icdd53e61e2d1513b2231affb19bb00ea5d938d48 --- core/api/current.txt | 2 + core/java/android/text/TextLine.java | 200 +++++++++++-- .../src/android/text/TextLineTest.java | 268 +++++++++++++++++- graphics/java/android/graphics/Paint.java | 125 ++++++++ libs/hwui/jni/Paint.cpp | 238 +++++++++------- 5 files changed, 693 insertions(+), 140 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index bb3264701ca5a..8d703d048e3b3 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -14928,6 +14928,8 @@ package android.graphics { method public android.graphics.PathEffect getPathEffect(); method public float getRunAdvance(char[], int, int, int, int, boolean, int); method public float getRunAdvance(CharSequence, int, int, int, int, boolean, int); + method public float getRunCharacterAdvance(@NonNull char[], int, int, int, int, boolean, int, @Nullable float[], int); + method public float getRunCharacterAdvance(@NonNull CharSequence, int, int, int, int, boolean, int, @Nullable float[], int); method public android.graphics.Shader getShader(); method @ColorInt public int getShadowLayerColor(); method @ColorLong public long getShadowLayerColorLong(); diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index e39231c9d64b8..7394c22ee93fe 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -408,14 +408,14 @@ public class TextLine { final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl; if (targetIsInThisSegment && sameDirection) { - return h + measureRun(segStart, offset, j, runIsRtl, fmi); + return h + measureRun(segStart, offset, j, runIsRtl, fmi, null, 0); } - final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi); + final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi, null, 0); h += sameDirection ? segmentWidth : -segmentWidth; if (targetIsInThisSegment) { - return h + measureRun(segStart, offset, j, runIsRtl, null); + return h + measureRun(segStart, offset, j, runIsRtl, null, null, 0); } if (j != runLimit) { // charAt(j) == TAB_CHAR @@ -436,6 +436,116 @@ public class TextLine { return h; } + /** + * Return the signed horizontal bounds of the characters in the line. + * + * The length of the returned array equals to 2 * mLen. The left bound of the i th character + * is stored at index 2 * i. And the right bound of the i th character is stored at index + * (2 * i + 1). + * + * Check the following examples. LX(e.g. L0, L1, ...) denotes a character which has LTR BiDi + * property. On the other hand, RX(e.g. R0, R1, ...) denotes a character which has RTL BiDi + * property. Assuming all character has 1em width. + * + * Example 1: All LTR chars within LTR context + * Input Text (logical) : L0 L1 L2 L3 + * Input Text (visual) : L0 L1 L2 L3 + * Output : [0em, 1em, 1em, 2em, 2em, 3em, 3em, 4em] + * + * Example 2: All RTL chars within RTL context. + * Input Text (logical) : R0 R1 R2 R3 + * Input Text (visual) : R3 R2 R1 R0 + * Output : [-1em, 0em, -2em, -1em, -3em, -2em, -4em, -3em] + + * + * Example 3: BiDi chars within LTR context. + * Input Text (logical) : L0 L1 R2 R3 L4 L5 + * Input Text (visual) : L0 L1 R3 R2 L4 L5 + * Output : [0em, 1em, 1em, 2em, 3em, 4em, 2em, 3em, 4em, 5em, 5em, 6em] + + * + * Example 4: BiDi chars within RTL context. + * Input Text (logical) : L0 L1 R2 R3 L4 L5 + * Input Text (visual) : L4 L5 R3 R2 L0 L1 + * Output : [-2em, -1em, -1em, 0em, -3em, -2em, -4em, -3em, -6em, -5em, -5em, -4em] + * + * @param bounds the array to receive the character bounds data. Its length should be at least + * 2 times of the line length. + * @param advances the array to receive the character advance data, nullable. If provided, its + * length should be equal or larger than the line length. + * + * @throws IllegalArgumentException if the given {@code bounds} is null. + * @throws IndexOutOfBoundsException if the given {@code bounds} or {@code advances} doesn't + * have enough space to hold the result. + */ + public void measureAllBounds(@NonNull float[] bounds, @Nullable float[] advances) { + if (bounds == null) { + throw new IllegalArgumentException("bounds can't be null"); + } + if (bounds.length < 2 * mLen) { + throw new IndexOutOfBoundsException("bounds doesn't have enough space to receive the " + + "result, needed: " + (2 * mLen) + " had: " + bounds.length); + } + if (advances == null) { + advances = new float[mLen]; + } + if (advances.length < mLen) { + throw new IndexOutOfBoundsException("advance doesn't have enough space to receive the " + + "result, needed: " + mLen + " had: " + advances.length); + } + float h = 0; + for (int runIndex = 0; runIndex < mDirections.getRunCount(); 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) { + final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl; + + final float segmentWidth = + measureRun(segStart, j, j, runIsRtl, null, advances, segStart); + + final float oldh = h; + h += sameDirection ? segmentWidth : -segmentWidth; + float currh = sameDirection ? oldh : h; + for (int offset = segStart; offset < j && offset < mLen; ++offset) { + if (runIsRtl) { + bounds[2 * offset + 1] = currh; + currh -= advances[offset]; + bounds[2 * offset] = currh; + } else { + bounds[2 * offset] = currh; + currh += advances[offset]; + bounds[2 * offset + 1] = currh; + } + } + + if (j != runLimit) { // charAt(j) == TAB_CHAR + final float leftX; + final float rightX; + if (runIsRtl) { + rightX = h; + h = mDir * nextTab(h * mDir); + leftX = h; + } else { + leftX = h; + h = mDir * nextTab(h * mDir); + rightX = h; + } + bounds[2 * j] = leftX; + bounds[2 * j + 1] = rightX; + advances[j] = rightX - leftX; + } + + segStart = j + 1; + } + } + } + } + /** * @see #measure(int, boolean, FontMetricsInt) * @return The measure results for all possible offsets @@ -464,15 +574,15 @@ public class TextLine { if (j == runLimit || charAt(j) == TAB_CHAR) { final float oldh = h; final boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl; - final float w = measureRun(segStart, j, j, runIsRtl, fmi); + final float w = measureRun(segStart, j, j, runIsRtl, fmi, null, 0); h += advance ? w : -w; final float baseh = advance ? oldh : h; FontMetricsInt crtfmi = advance ? fmi : null; for (int offset = segStart; offset <= j && offset <= mLen; ++offset) { if (target[offset] >= segStart && target[offset] < j) { - measurement[offset] = - baseh + measureRun(segStart, offset, j, runIsRtl, crtfmi); + measurement[offset] = baseh + + measureRun(segStart, offset, j, runIsRtl, crtfmi, null, 0); } } @@ -518,14 +628,14 @@ public class TextLine { boolean needWidth) { if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) { - float w = -measureRun(start, limit, limit, runIsRtl, null); + float w = -measureRun(start, limit, limit, runIsRtl, null, null, 0); handleRun(start, limit, limit, runIsRtl, c, null, x + w, top, - y, bottom, null, false); + y, bottom, null, false, null, 0); return w; } return handleRun(start, limit, limit, runIsRtl, c, null, x, top, - y, bottom, null, needWidth); + y, bottom, null, needWidth, null, 0); } /** @@ -538,12 +648,15 @@ public class TextLine { * @param runIsRtl true if the run is right-to-left * @param fmi receives metrics information about the requested * run, can be null. + * @param advances receives the advance information about the requested run, can be null. + * @param advancesIndex the start index to fill in the advance information. * @return the signed width from the start of the run to the leading edge * of the character at offset, based on the run (not paragraph) direction */ private float measureRun(int start, int offset, int limit, boolean runIsRtl, - FontMetricsInt fmi) { - return handleRun(start, offset, limit, runIsRtl, null, null, 0, 0, 0, 0, fmi, true); + @Nullable FontMetricsInt fmi, @Nullable float[] advances, int advancesIndex) { + return handleRun(start, offset, limit, runIsRtl, null, null, 0, 0, 0, 0, fmi, true, + advances, advancesIndex); } /** @@ -562,13 +675,14 @@ public class TextLine { 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, consumer, x + w, 0, 0, 0, null, false); + float w = -measureRun(start, limit, limit, runIsRtl, null, null, 0); + handleRun(start, limit, limit, runIsRtl, null, consumer, x + w, 0, 0, 0, null, + false, null, 0); return w; } return handleRun(start, limit, limit, runIsRtl, null, consumer, x, 0, 0, 0, null, - needWidth); + needWidth, null, 0); } @@ -908,14 +1022,16 @@ public class TextLine { } private float getRunAdvance(TextPaint wp, int start, int end, int contextStart, int contextEnd, - boolean runIsRtl, int offset) { + boolean runIsRtl, int offset, @Nullable float[] advances, int advancesIndex) { if (mCharsValid) { - return wp.getRunAdvance(mChars, start, end, contextStart, contextEnd, runIsRtl, offset); + return wp.getRunCharacterAdvance(mChars, start, end, contextStart, contextEnd, + runIsRtl, offset, advances, advancesIndex); } else { final int delta = mStart; - if (mComputed == null) { - return wp.getRunAdvance(mText, delta + start, delta + end, - delta + contextStart, delta + contextEnd, runIsRtl, delta + offset); + if (mComputed == null || advances != null) { + return wp.getRunCharacterAdvance(mText, delta + start, delta + end, + delta + contextStart, delta + contextEnd, runIsRtl, + delta + offset, advances, advancesIndex); } else { return mComputed.getWidth(start + delta, end + delta); } @@ -940,6 +1056,8 @@ public class TextLine { * @param needWidth true if the width of the run is needed * @param offset the offset for the purpose of measuring * @param decorations the list of locations and paremeters for drawing decorations + * @param advances receives the advance information about the requested run, can be null. + * @param advancesIndex the start index to fill in the advance information. * @return the signed width of the run based on the run direction; only * valid if needWidth is true */ @@ -947,7 +1065,8 @@ public class TextLine { int contextStart, int contextEnd, boolean runIsRtl, Canvas c, TextShaper.GlyphsConsumer consumer, float x, int top, int y, int bottom, FontMetricsInt fmi, boolean needWidth, int offset, - @Nullable ArrayList decorations) { + @Nullable ArrayList decorations, + @Nullable float[] advances, int advancesIndex) { if (mIsJustifying) { wp.setWordSpacing(mAddedWidthForJustify); @@ -967,7 +1086,8 @@ public class TextLine { final int numDecorations = decorations == null ? 0 : decorations.size(); if (needWidth || ((c != null || consumer != null) && (wp.bgColor != 0 || numDecorations != 0 || runIsRtl))) { - totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset); + totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset, + advances, advancesIndex); } final float leftX, rightX; @@ -1009,10 +1129,10 @@ public class TextLine { final int decorationStart = Math.max(info.start, start); final int decorationEnd = Math.min(info.end, offset); - float decorationStartAdvance = getRunAdvance( - wp, start, end, contextStart, contextEnd, runIsRtl, decorationStart); - float decorationEndAdvance = getRunAdvance( - wp, start, end, contextStart, contextEnd, runIsRtl, decorationEnd); + float decorationStartAdvance = getRunAdvance(wp, start, end, contextStart, + contextEnd, runIsRtl, decorationStart, null, 0); + float decorationEndAdvance = getRunAdvance(wp, start, end, contextStart, + contextEnd, runIsRtl, decorationEnd, null, 0); final float decorationXLeft, decorationXRight; if (runIsRtl) { decorationXLeft = rightX - decorationEndAdvance; @@ -1179,19 +1299,27 @@ public class TextLine { * @param bottom the bottom of the line * @param fmi receives metrics information, can be null * @param needWidth true if the width is required + * @param advances receives the advance information about the requested run, can be null. + * @param advancesIndex the start index to fill in the advance information. * @return the signed width of the run based on the run direction; only * valid if needWidth is true */ private float handleRun(int start, int measureLimit, int limit, boolean runIsRtl, Canvas c, TextShaper.GlyphsConsumer consumer, float x, int top, int y, - int bottom, FontMetricsInt fmi, boolean needWidth) { + int bottom, FontMetricsInt fmi, boolean needWidth, + @Nullable float[] advances, int advancesIndex) { if (measureLimit < start || measureLimit > limit) { throw new IndexOutOfBoundsException("measureLimit (" + measureLimit + ") is out of " + "start (" + start + ") and limit (" + limit + ") bounds"); } + if (advances != null && advances.length - advancesIndex < measureLimit - start) { + throw new IndexOutOfBoundsException("advances doesn't have enough space to receive the " + + "result"); + } + // Case of an empty line, make sure we update fmi according to mPaint if (start == measureLimit) { final TextPaint wp = mWorkPaint; @@ -1218,7 +1346,7 @@ public class TextLine { wp.setStartHyphenEdit(adjustStartHyphenEdit(start, wp.getStartHyphenEdit())); wp.setEndHyphenEdit(adjustEndHyphenEdit(limit, wp.getEndHyphenEdit())); return handleText(wp, start, limit, start, limit, runIsRtl, c, consumer, x, top, - y, bottom, fmi, needWidth, measureLimit, null); + y, bottom, fmi, needWidth, measureLimit, null, advances, advancesIndex); } // Shaping needs to take into account context up to metric boundaries, @@ -1257,8 +1385,16 @@ public class TextLine { } if (replacement != null) { - x += handleReplacement(replacement, wp, i, mlimit, runIsRtl, c, x, top, y, - bottom, fmi, needWidth || mlimit < measureLimit); + final float width = handleReplacement(replacement, wp, i, mlimit, runIsRtl, c, + x, top, y, bottom, fmi, needWidth || mlimit < measureLimit); + x += width; + if (advances != null) { + // For replacement, the entire width is assigned to the first character. + advances[advancesIndex + i - start] = runIsRtl ? -width : width; + for (int j = i + 1; j < mlimit; ++j) { + advances[advancesIndex + j - start] = 0.0f; + } + } continue; } @@ -1300,7 +1436,8 @@ public class TextLine { adjustEndHyphenEdit(activeEnd, mPaint.getEndHyphenEdit())); x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, consumer, x, top, y, bottom, fmi, needWidth || activeEnd < measureLimit, - Math.min(activeEnd, mlimit), mDecorations); + Math.min(activeEnd, mlimit), mDecorations, + advances, advancesIndex + activeStart - start); activeStart = j; activePaint.set(wp); @@ -1327,7 +1464,8 @@ public class TextLine { adjustEndHyphenEdit(activeEnd, mPaint.getEndHyphenEdit())); x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, consumer, x, top, y, bottom, fmi, needWidth || activeEnd < measureLimit, - Math.min(activeEnd, mlimit), mDecorations); + Math.min(activeEnd, mlimit), mDecorations, + advances, advancesIndex + activeStart - start); } return x - originalX; diff --git a/core/tests/coretests/src/android/text/TextLineTest.java b/core/tests/coretests/src/android/text/TextLineTest.java index 412d6ec975ac1..e3bcc8d2c2001 100644 --- a/core/tests/coretests/src/android/text/TextLineTest.java +++ b/core/tests/coretests/src/android/text/TextLineTest.java @@ -26,6 +26,7 @@ import android.graphics.Paint; import android.graphics.Typeface; import android.platform.test.annotations.Presubmit; import android.text.Layout.TabStops; +import android.text.style.AbsoluteSizeSpan; import android.text.style.ReplacementSpan; import android.text.style.TabStopSpan; @@ -97,7 +98,7 @@ public class TextLineTest { InstrumentationRegistry.getInstrumentation().getTargetContext().getAssets(), "fonts/StaticLayoutLineBreakingTestFont.ttf"); - private TextLine getTextLine(String str, TextPaint paint, TabStops tabStops) { + private TextLine getTextLine(CharSequence str, TextPaint paint, TabStops tabStops) { Layout layout = StaticLayout.Builder.obtain(str, 0, str.length(), paint, Integer.MAX_VALUE) .build(); @@ -109,7 +110,7 @@ public class TextLineTest { return tl; } - private TextLine getTextLine(String str, TextPaint paint) { + private TextLine getTextLine(CharSequence str, TextPaint paint) { return getTextLine(str, paint, null); } @@ -316,14 +317,275 @@ public class TextLineTest { assertTrue(span.mIsUsed); } + @Test + public void testMeasureAllBounds_LTR() { + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + TextLine tl = getTextLine("IIIIIV", paint); + float[] bounds = new float[12]; + float[] advances = new float[6]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 20.0f, 20.0f, 30.0f, 30.0f, 40.0f, + 40.0f, 50.0f, 50.0f, 100.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 10.0f, 10.0f, 10.0f, 10.0f, 50.0f}, advances, 0.0f); + } + + @Test + public void testMeasureAllBounds_LTR_StyledText() { + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + SpannableString text = new SpannableString("IIIIIV"); + text.setSpan(new AbsoluteSizeSpan(5), 1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + TextLine tl = getTextLine(text, paint); + float[] bounds = new float[12]; + float[] advances = new float[6]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 15.0f, 15.0f, 20.0f, 20.0f, 30.0f, + 30.0f, 40.0f, 40.0f, 90.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 5.0f, 5.0f, 10.0f, 10.0f, 50.0f}, advances, 0.0f); + } + + @Test + public void testMeasureAllBounds_RTL() { + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + TextLine tl = getTextLine("\u05D0\u05D0\u05D0\u05D0\u05D0\u05D1", paint); + float[] bounds = new float[12]; + float[] advances = new float[6]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {-10.0f, 0.0f, -20.0f, -10.0f, -30.0f, -20.0f, -40.0f, -30.0f, + -50.0f, -40.0f, -100.0f, -50.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 10.0f, 10.0f, 10.0f, 10.0f, 50.0f}, advances, 0.0f); + } + + + @Test + public void testMeasureAllBounds_RTL_StyledText() { + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + SpannableString text = new SpannableString("\u05D0\u05D0\u05D0\u05D0\u05D0\u05D1"); + text.setSpan(new AbsoluteSizeSpan(5), 1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + TextLine tl = getTextLine(text, paint); + float[] bounds = new float[12]; + float[] advances = new float[6]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {-10.0f, 0.0f, -15.0f, -10.0f, -20.0f, -15.0f, + -30.0f, -20.0f, -40.0f, -30.0f, -90.0f, -40.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 5.0f, 5.0f, 10.0f, 10.0f, 50.0f}, advances, 0.0f); + } + + @Test + public void testMeasureAllBounds_BiDi() { + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + TextLine tl = getTextLine("II\u05D0\u05D0II", paint); + float[] bounds = new float[12]; + float[] advances = new float[6]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 20.0f, 30.0f, 40.0f, 20.0f, 30.0f, + 40.0f, 50.0f, 50.0f, 60.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 10.0f, 10.0f, 10.0f, 10.0f, 10.0f}, advances, 0.0f); + } + + @Test + public void testMeasureAllBounds_BiDi2() { + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + TextLine tl = getTextLine("I" + RLI + "I\u05D0\u05D0" + PDI + "I", paint); + float[] bounds = new float[14]; + float[] advances = new float[7]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 10.0f, 30.0f, 40.0f, 20.0f, 30.0f, + 10.0f, 20.0f, 40.0f, 40.0f, 40.0f, 50.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 0.0f, 10.0f, 10.0f, 10.0f, 0.0f, 10.0f}, advances, + 0.0f); + } + + @Test + public void testMeasureAllBounds_BiDi3() { + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + TextLine tl = getTextLine("\u05D0" + LRI + "\u05D0II" + PDI + "\u05D0", paint); + float[] bounds = new float[14]; + float[] advances = new float[7]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {-10.0f, 0.0f, -10.0f, -10.0f, -40.0f, -30.0f, + -30.0f, -20.0f, -20.0f, -10.0f, -40.0f, -40.0f, -50.0f, -40.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 0.0f, 10.0f, 10.0f, 10.0f, 0.0f, 10.0f}, advances, + 0.0f); + } + + @Test + public void testMeasureAllBounds_styled_BiDi() { + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + SpannableString text = new SpannableString("II\u05D0\u05D0II"); + text.setSpan(new AbsoluteSizeSpan(5), 1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + TextLine tl = getTextLine(text, paint); + float[] bounds = new float[12]; + float[] advances = new float[6]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 15.0f, 25.0f, 30.0f, + 15.0f, 25.0f, 30.0f, 40.0f, 40.0f, 50.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 5.0f, 5.0f, 10.0f, 10.0f, 10.0f}, advances, 0.0f); + } + + @Test + public void testMeasureAllBounds_Tab_LTR() { + final Object[] spans = { new TabStopSpan.Standard(100) }; + final TabStops stops = new TabStops(100, spans); + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + TextLine tl = getTextLine("II\tII", paint, stops); + float[] bounds = new float[10]; + float[] advances = new float[5]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 20.0f, 20.0f, 100.0f, 100.0f, 110.0f, + 110.0f, 120.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 10.0f, 80.0f, 10.0f, 10.0f}, advances, 0.0f); + } + + @Test + public void testMeasureAllBounds_Tab_RTL() { + final Object[] spans = { new TabStopSpan.Standard(100) }; + final TabStops stops = new TabStops(100, spans); + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + TextLine tl = getTextLine("\u05D0\u05D0\t\u05D0\u05D0", paint, stops); + float[] bounds = new float[10]; + float[] advances = new float[5]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {-10.0f, 0.0f, -20.0f, -10.0f, -100.0f, -20.0f, + -110.0f, -100.0f, -120.0f, -110.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 10.0f, 80.0f, 10.0f, 10.0f}, advances, 0.0f); + } + + @Test + public void testMeasureAllBounds_Tab_BiDi() { + final Object[] spans = { new TabStopSpan.Standard(100) }; + final TabStops stops = new TabStops(100, spans); + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + TextLine tl = getTextLine("I\u05D0\tI\u05D0", paint, stops); + float[] bounds = new float[10]; + float[] advances = new float[5]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 20.0f, 20.0f, 100.0f, + 100.0f, 110.0f, 110.0f, 120.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 10.0f, 80.0f, 10.0f, 10.0f}, advances, 0.0f); + } + + @Test + public void testMeasureAllBounds_Tab_BiDi2() { + final Object[] spans = { new TabStopSpan.Standard(100) }; + final TabStops stops = new TabStops(100, spans); + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + TextLine tl = getTextLine("\u05D0I\t\u05D0I", paint, stops); + float[] bounds = new float[10]; + float[] advances = new float[5]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {-10.0f, 0.0f, -20.0f, -10.0f, -100.0f, -20.0f, + -110.0f, -100.0f, -120.0f, -110.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 10.0f, 80.0f, 10.0f, 10.0f}, advances, 0.0f); + } + + @Test + public void testMeasureAllBounds_replacement_LTR() { + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + SpannableString text = new SpannableString("IIIII"); + text.setSpan(new TestReplacementSpan(5), 1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + TextLine tl = getTextLine(text, paint); + float[] bounds = new float[10]; + float[] advances = new float[5]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 15.0f, 15.0f, 15.0f, + 15.0f, 25.0f, 25.0f, 35.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 5.0f, 0.0f, 10.0f, 10.0f}, advances, 0.0f); + } + + @Test + public void testMeasureAllBounds_replacement_RTL() { + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + SpannableString text = new SpannableString("\u05D0\u05D0\u05D0\u05D0\u05D0"); + text.setSpan(new TestReplacementSpan(5), 1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + TextLine tl = getTextLine(text, paint); + float[] bounds = new float[10]; + float[] advances = new float[5]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {-10.0f, 0.0f, -15.0f, -10.0f, -15.0f, -15.0f, + -25.0f, -15.0f, -35.0f, -25.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 5.0f, 0.0f, 10.0f, 10.0f}, advances, 0.0f); + } + + @Test + public void testMeasureAllBounds_replacement_BiDi() { + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + SpannableString text = new SpannableString("II\u05D0\u05D0II"); + text.setSpan(new TestReplacementSpan(5), 1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + TextLine tl = getTextLine(text, paint); + float[] bounds = new float[12]; + float[] advances = new float[6]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 15.0f, 15.0f, 15.0f, + 15.0f, 25.0f, 25.0f, 35.0f, 35.0f, 45.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 5.0f, 0.0f, 10.0f, 10.0f, 10.0f}, advances, 0.0f); + } + private static class TestReplacementSpan extends ReplacementSpan { boolean mIsUsed; + private final int mWidth; + + TestReplacementSpan() { + mWidth = 0; + } + + TestReplacementSpan(int width) { + mWidth = width; + } @Override public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { mIsUsed = true; - return 0; + return mWidth; } @Override diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 451b99ea7550c..1a80ab308bb5d 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -3136,6 +3136,128 @@ public class Paint { return result; } + + /** + * Measure the advance of each character within a run of text and also return the cursor + * position within the run. + * + * @see #getRunAdvance(char[], int, int, int, int, boolean, int) for more details. + * + * @param text the text to measure. Cannot be null. + * @param start the index of the start of the range to measure + * @param end the index + 1 of the end of the range to measure + * @param contextStart the index of the start of the shaping context + * @param contextEnd the index + 1 of the end of the shaping context + * @param isRtl whether the run is in RTL direction + * @param offset index of caret position + * @param advances the array that receives the computed character advances + * @param advancesIndex the start index from which the advances array is filled + * @return width measurement between start and offset + * @throws IndexOutOfBoundsException if a) contextStart or contextEnd is out of array's range + * or contextStart is larger than contextEnd, + * b) start or end is not within the range [contextStart, contextEnd), or start is larger than + * end, + * c) offset is not within the range [start, end), + * d) advances.length - advanceIndex is smaller than the length of the run, which equals to + * end - start. + * + */ + public float getRunCharacterAdvance(@NonNull char[] text, int start, int end, int contextStart, + int contextEnd, boolean isRtl, int offset, + @Nullable float[] advances, int advancesIndex) { + if (text == null) { + throw new IllegalArgumentException("text cannot be null"); + } + if (contextStart < 0 || contextEnd > text.length) { + throw new IndexOutOfBoundsException("Invalid Context Range: " + contextStart + ", " + + contextEnd + " must be in 0, " + text.length); + } + + if (start < contextStart || contextEnd < end) { + throw new IndexOutOfBoundsException("Invalid start/end range: " + start + ", " + end + + " must be in " + contextStart + ", " + contextEnd); + } + + if (offset < start || end < offset) { + throw new IndexOutOfBoundsException("Invalid offset position: " + offset + + " must be in " + start + ", " + end); + } + + if (advances != null && advances.length < advancesIndex - start + end) { + throw new IndexOutOfBoundsException("Given array doesn't have enough space to receive " + + "the result, advances.length: " + advances.length + " advanceIndex: " + + advancesIndex + " needed space: " + (offset - start)); + } + + if (end == start) { + return 0.0f; + } + + return nGetRunCharacterAdvance(mNativePaint, text, start, end, contextStart, contextEnd, + isRtl, offset, advances, advancesIndex); + } + + /** + * @see #getRunCharacterAdvance(char[], int, int, int, int, boolean, int, float[], int) + * + * @param text the text to measure. Cannot be null. + * @param start the index of the start of the range to measure + * @param end the index + 1 of the end of the range to measure + * @param contextStart the index of the start of the shaping context + * @param contextEnd the index + 1 of the end of the shaping context + * @param isRtl whether the run is in RTL direction + * @param offset index of caret position + * @param advances the array that receives the computed character advances + * @param advancesIndex the start index from which the advances array is filled + * @return width measurement between start and offset + * @throws IndexOutOfBoundsException if a) contextStart or contextEnd is out of array's range + * or contextStart is larger than contextEnd, + * b) start or end is not within the range [contextStart, contextEnd), or end is larger than + * start, + * c) offset is not within the range [start, end), + * d) advances.length - advanceIndex is smaller than the run length, which equals to + * end - start. + */ + public float getRunCharacterAdvance(@NonNull CharSequence text, int start, int end, + int contextStart, int contextEnd, boolean isRtl, int offset, + @Nullable float[] advances, int advancesIndex) { + if (text == null) { + throw new IllegalArgumentException("text cannot be null"); + } + if (contextStart < 0 || contextEnd > text.length()) { + throw new IndexOutOfBoundsException("Invalid Context Range: " + contextStart + ", " + + contextEnd + " must be in 0, " + text.length()); + } + + if (start < contextStart || contextEnd < end) { + throw new IndexOutOfBoundsException("Invalid start/end range: " + start + ", " + end + + " must be in " + contextStart + ", " + contextEnd); + } + + if (offset < start || end < offset) { + throw new IndexOutOfBoundsException("Invalid offset position: " + offset + + " must be in " + start + ", " + end); + } + + if (advances != null && advances.length < advancesIndex - start + end) { + throw new IndexOutOfBoundsException("Given array doesn't have enough space to receive " + + "the result, advances.length: " + advances.length + " advanceIndex: " + + advancesIndex + " needed space: " + (offset - start)); + } + + if (end == start) { + return 0.0f; + } + + char[] buf = TemporaryBuffer.obtain(contextEnd - contextStart); + TextUtils.getChars(text, contextStart, contextEnd, buf, 0); + final float result = getRunCharacterAdvance(buf, start - contextStart, end - contextStart, + 0, contextEnd - contextStart, isRtl, offset - contextStart, + advances, advancesIndex); + TemporaryBuffer.recycle(buf); + return result; + } + /** * Get the character offset within the string whose position is closest to the specified * horizontal position. @@ -3247,6 +3369,9 @@ public class Paint { private static native boolean nHasGlyph(long paintPtr, int bidiFlags, String string); private static native float nGetRunAdvance(long paintPtr, char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset); + private static native float nGetRunCharacterAdvance(long paintPtr, char[] text, int start, + int end, int contextStart, int contextEnd, boolean isRtl, int offset, float[] advances, + int advancesIndex); private static native int nGetOffsetForAdvance(long paintPtr, char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl, float advance); private static native void nGetFontMetricsIntForText(long paintPtr, char[] text, diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp index 0aa14655725c9..ed453b158579c 100644 --- a/libs/hwui/jni/Paint.cpp +++ b/libs/hwui/jni/Paint.cpp @@ -497,16 +497,29 @@ namespace PaintGlue { return true; } - static jfloat doRunAdvance(const Paint* paint, const Typeface* typeface, const jchar buf[], - jint start, jint count, jint bufSize, jboolean isRtl, jint offset) { + static jfloat doRunAdvance(JNIEnv* env, const Paint* paint, const Typeface* typeface, + const jchar buf[], jint start, jint count, jint bufSize, + jboolean isRtl, jint offset, jfloatArray advances, + jint advancesIndex) { + if (advances) { + size_t advancesLength = env->GetArrayLength(advances); + if ((size_t)(count + advancesIndex) > advancesLength) { + doThrowAIOOBE(env); + return 0; + } + } minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR; - if (offset == start + count) { + if (offset == start + count && advances == nullptr) { return MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize, nullptr); } std::unique_ptr advancesArray(new float[count]); MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize, advancesArray.get()); + + if (advances) { + env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray.get()); + } return minikin::getRunAdvance(advancesArray.get(), buf, start, count, offset); } @@ -515,9 +528,23 @@ namespace PaintGlue { const Paint* paint = reinterpret_cast(paintHandle); const Typeface* typeface = paint->getAndroidTypeface(); ScopedCharArrayRO textArray(env, text); - jfloat result = doRunAdvance(paint, typeface, textArray.get() + contextStart, - start - contextStart, end - start, contextEnd - contextStart, isRtl, - offset - contextStart); + jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart, + start - contextStart, end - start, contextEnd - contextStart, + isRtl, offset - contextStart, nullptr, 0); + return result; + } + + static jfloat getRunCharacterAdvance___CIIIIZI_FI_F(JNIEnv* env, jclass, jlong paintHandle, + jcharArray text, jint start, jint end, + jint contextStart, jint contextEnd, + jboolean isRtl, jint offset, + jfloatArray advances, jint advancesIndex) { + const Paint* paint = reinterpret_cast(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + ScopedCharArrayRO textArray(env, text); + jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart, + start - contextStart, end - start, contextEnd - contextStart, + isRtl, offset - contextStart, advances, advancesIndex); return result; } @@ -1034,113 +1061,112 @@ namespace PaintGlue { }; // namespace PaintGlue static const JNINativeMethod methods[] = { - {"nGetNativeFinalizer", "()J", (void*) PaintGlue::getNativeFinalizer}, - {"nInit","()J", (void*) PaintGlue::init}, - {"nInitWithPaint","(J)J", (void*) PaintGlue::initWithPaint}, - {"nBreakText","(J[CIIFI[F)I", (void*) PaintGlue::breakTextC}, - {"nBreakText","(JLjava/lang/String;ZFI[F)I", (void*) PaintGlue::breakTextS}, - {"nGetTextAdvances","(J[CIIIII[FI)F", - (void*) PaintGlue::getTextAdvances___CIIIII_FI}, - {"nGetTextAdvances","(JLjava/lang/String;IIIII[FI)F", - (void*) PaintGlue::getTextAdvances__StringIIIII_FI}, + {"nGetNativeFinalizer", "()J", (void*)PaintGlue::getNativeFinalizer}, + {"nInit", "()J", (void*)PaintGlue::init}, + {"nInitWithPaint", "(J)J", (void*)PaintGlue::initWithPaint}, + {"nBreakText", "(J[CIIFI[F)I", (void*)PaintGlue::breakTextC}, + {"nBreakText", "(JLjava/lang/String;ZFI[F)I", (void*)PaintGlue::breakTextS}, + {"nGetTextAdvances", "(J[CIIIII[FI)F", (void*)PaintGlue::getTextAdvances___CIIIII_FI}, + {"nGetTextAdvances", "(JLjava/lang/String;IIIII[FI)F", + (void*)PaintGlue::getTextAdvances__StringIIIII_FI}, - {"nGetTextRunCursor", "(J[CIIIII)I", (void*) PaintGlue::getTextRunCursor___C}, - {"nGetTextRunCursor", "(JLjava/lang/String;IIIII)I", - (void*) PaintGlue::getTextRunCursor__String}, - {"nGetTextPath", "(JI[CIIFFJ)V", (void*) PaintGlue::getTextPath___C}, - {"nGetTextPath", "(JILjava/lang/String;IIFFJ)V", (void*) PaintGlue::getTextPath__String}, - {"nGetStringBounds", "(JLjava/lang/String;IIILandroid/graphics/Rect;)V", - (void*) PaintGlue::getStringBounds }, - {"nGetCharArrayBounds", "(J[CIIILandroid/graphics/Rect;)V", - (void*) PaintGlue::getCharArrayBounds }, - {"nHasGlyph", "(JILjava/lang/String;)Z", (void*) PaintGlue::hasGlyph }, - {"nGetRunAdvance", "(J[CIIIIZI)F", (void*) PaintGlue::getRunAdvance___CIIIIZI_F}, - {"nGetOffsetForAdvance", "(J[CIIIIZF)I", - (void*) PaintGlue::getOffsetForAdvance___CIIIIZF_I}, - {"nGetFontMetricsIntForText", "(J[CIIIIZLandroid/graphics/Paint$FontMetricsInt;)V", - (void*)PaintGlue::getFontMetricsIntForText___C}, - {"nGetFontMetricsIntForText", - "(JLjava/lang/String;IIIIZLandroid/graphics/Paint$FontMetricsInt;)V", - (void*)PaintGlue::getFontMetricsIntForText___String}, + {"nGetTextRunCursor", "(J[CIIIII)I", (void*)PaintGlue::getTextRunCursor___C}, + {"nGetTextRunCursor", "(JLjava/lang/String;IIIII)I", + (void*)PaintGlue::getTextRunCursor__String}, + {"nGetTextPath", "(JI[CIIFFJ)V", (void*)PaintGlue::getTextPath___C}, + {"nGetTextPath", "(JILjava/lang/String;IIFFJ)V", (void*)PaintGlue::getTextPath__String}, + {"nGetStringBounds", "(JLjava/lang/String;IIILandroid/graphics/Rect;)V", + (void*)PaintGlue::getStringBounds}, + {"nGetCharArrayBounds", "(J[CIIILandroid/graphics/Rect;)V", + (void*)PaintGlue::getCharArrayBounds}, + {"nHasGlyph", "(JILjava/lang/String;)Z", (void*)PaintGlue::hasGlyph}, + {"nGetRunAdvance", "(J[CIIIIZI)F", (void*)PaintGlue::getRunAdvance___CIIIIZI_F}, + {"nGetRunCharacterAdvance", "(J[CIIIIZI[FI)F", + (void*)PaintGlue::getRunCharacterAdvance___CIIIIZI_FI_F}, + {"nGetOffsetForAdvance", "(J[CIIIIZF)I", (void*)PaintGlue::getOffsetForAdvance___CIIIIZF_I}, + {"nGetFontMetricsIntForText", "(J[CIIIIZLandroid/graphics/Paint$FontMetricsInt;)V", + (void*)PaintGlue::getFontMetricsIntForText___C}, + {"nGetFontMetricsIntForText", + "(JLjava/lang/String;IIIIZLandroid/graphics/Paint$FontMetricsInt;)V", + (void*)PaintGlue::getFontMetricsIntForText___String}, - // --------------- @FastNative ---------------------- + // --------------- @FastNative ---------------------- - {"nSetTextLocales","(JLjava/lang/String;)I", (void*) PaintGlue::setTextLocales}, - {"nSetFontFeatureSettings","(JLjava/lang/String;)V", - (void*) PaintGlue::setFontFeatureSettings}, - {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;)F", - (void*)PaintGlue::getFontMetrics}, - {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;)I", - (void*)PaintGlue::getFontMetricsInt}, + {"nSetTextLocales", "(JLjava/lang/String;)I", (void*)PaintGlue::setTextLocales}, + {"nSetFontFeatureSettings", "(JLjava/lang/String;)V", + (void*)PaintGlue::setFontFeatureSettings}, + {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;)F", + (void*)PaintGlue::getFontMetrics}, + {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;)I", + (void*)PaintGlue::getFontMetricsInt}, - // --------------- @CriticalNative ------------------ + // --------------- @CriticalNative ------------------ - {"nReset","(J)V", (void*) PaintGlue::reset}, - {"nSet","(JJ)V", (void*) PaintGlue::assign}, - {"nGetFlags","(J)I", (void*) PaintGlue::getFlags}, - {"nSetFlags","(JI)V", (void*) PaintGlue::setFlags}, - {"nGetHinting","(J)I", (void*) PaintGlue::getHinting}, - {"nSetHinting","(JI)V", (void*) PaintGlue::setHinting}, - {"nSetAntiAlias","(JZ)V", (void*) PaintGlue::setAntiAlias}, - {"nSetSubpixelText","(JZ)V", (void*) PaintGlue::setSubpixelText}, - {"nSetLinearText","(JZ)V", (void*) PaintGlue::setLinearText}, - {"nSetUnderlineText","(JZ)V", (void*) PaintGlue::setUnderlineText}, - {"nSetStrikeThruText","(JZ)V", (void*) PaintGlue::setStrikeThruText}, - {"nSetFakeBoldText","(JZ)V", (void*) PaintGlue::setFakeBoldText}, - {"nSetFilterBitmap","(JZ)V", (void*) PaintGlue::setFilterBitmap}, - {"nSetDither","(JZ)V", (void*) PaintGlue::setDither}, - {"nGetStyle","(J)I", (void*) PaintGlue::getStyle}, - {"nSetStyle","(JI)V", (void*) PaintGlue::setStyle}, - {"nSetColor","(JI)V", (void*) PaintGlue::setColor}, - {"nSetColor","(JJJ)V", (void*) PaintGlue::setColorLong}, - {"nSetAlpha","(JI)V", (void*) PaintGlue::setAlpha}, - {"nGetStrokeWidth","(J)F", (void*) PaintGlue::getStrokeWidth}, - {"nSetStrokeWidth","(JF)V", (void*) PaintGlue::setStrokeWidth}, - {"nGetStrokeMiter","(J)F", (void*) PaintGlue::getStrokeMiter}, - {"nSetStrokeMiter","(JF)V", (void*) PaintGlue::setStrokeMiter}, - {"nGetStrokeCap","(J)I", (void*) PaintGlue::getStrokeCap}, - {"nSetStrokeCap","(JI)V", (void*) PaintGlue::setStrokeCap}, - {"nGetStrokeJoin","(J)I", (void*) PaintGlue::getStrokeJoin}, - {"nSetStrokeJoin","(JI)V", (void*) PaintGlue::setStrokeJoin}, - {"nGetFillPath","(JJJ)Z", (void*) PaintGlue::getFillPath}, - {"nSetShader","(JJ)J", (void*) PaintGlue::setShader}, - {"nSetColorFilter","(JJ)J", (void*) PaintGlue::setColorFilter}, - {"nSetXfermode","(JI)V", (void*) PaintGlue::setXfermode}, - {"nSetPathEffect","(JJ)J", (void*) PaintGlue::setPathEffect}, - {"nSetMaskFilter","(JJ)J", (void*) PaintGlue::setMaskFilter}, - {"nSetTypeface","(JJ)V", (void*) PaintGlue::setTypeface}, - {"nGetTextAlign","(J)I", (void*) PaintGlue::getTextAlign}, - {"nSetTextAlign","(JI)V", (void*) PaintGlue::setTextAlign}, - {"nSetTextLocalesByMinikinLocaleListId","(JI)V", - (void*) PaintGlue::setTextLocalesByMinikinLocaleListId}, - {"nIsElegantTextHeight","(J)Z", (void*) PaintGlue::isElegantTextHeight}, - {"nSetElegantTextHeight","(JZ)V", (void*) PaintGlue::setElegantTextHeight}, - {"nGetTextSize","(J)F", (void*) PaintGlue::getTextSize}, - {"nSetTextSize","(JF)V", (void*) PaintGlue::setTextSize}, - {"nGetTextScaleX","(J)F", (void*) PaintGlue::getTextScaleX}, - {"nSetTextScaleX","(JF)V", (void*) PaintGlue::setTextScaleX}, - {"nGetTextSkewX","(J)F", (void*) PaintGlue::getTextSkewX}, - {"nSetTextSkewX","(JF)V", (void*) PaintGlue::setTextSkewX}, - {"nGetLetterSpacing","(J)F", (void*) PaintGlue::getLetterSpacing}, - {"nSetLetterSpacing","(JF)V", (void*) PaintGlue::setLetterSpacing}, - {"nGetWordSpacing","(J)F", (void*) PaintGlue::getWordSpacing}, - {"nSetWordSpacing","(JF)V", (void*) PaintGlue::setWordSpacing}, - {"nGetStartHyphenEdit", "(J)I", (void*) PaintGlue::getStartHyphenEdit}, - {"nGetEndHyphenEdit", "(J)I", (void*) PaintGlue::getEndHyphenEdit}, - {"nSetStartHyphenEdit", "(JI)V", (void*) PaintGlue::setStartHyphenEdit}, - {"nSetEndHyphenEdit", "(JI)V", (void*) PaintGlue::setEndHyphenEdit}, - {"nAscent","(J)F", (void*) PaintGlue::ascent}, - {"nDescent","(J)F", (void*) PaintGlue::descent}, - {"nGetUnderlinePosition","(J)F", (void*) PaintGlue::getUnderlinePosition}, - {"nGetUnderlineThickness","(J)F", (void*) PaintGlue::getUnderlineThickness}, - {"nGetStrikeThruPosition","(J)F", (void*) PaintGlue::getStrikeThruPosition}, - {"nGetStrikeThruThickness","(J)F", (void*) PaintGlue::getStrikeThruThickness}, - {"nSetShadowLayer", "(JFFFJJ)V", (void*)PaintGlue::setShadowLayer}, - {"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer}, - {"nEqualsForTextMeasurement", "(JJ)Z", (void*)PaintGlue::equalsForTextMeasurement}, + {"nReset", "(J)V", (void*)PaintGlue::reset}, + {"nSet", "(JJ)V", (void*)PaintGlue::assign}, + {"nGetFlags", "(J)I", (void*)PaintGlue::getFlags}, + {"nSetFlags", "(JI)V", (void*)PaintGlue::setFlags}, + {"nGetHinting", "(J)I", (void*)PaintGlue::getHinting}, + {"nSetHinting", "(JI)V", (void*)PaintGlue::setHinting}, + {"nSetAntiAlias", "(JZ)V", (void*)PaintGlue::setAntiAlias}, + {"nSetSubpixelText", "(JZ)V", (void*)PaintGlue::setSubpixelText}, + {"nSetLinearText", "(JZ)V", (void*)PaintGlue::setLinearText}, + {"nSetUnderlineText", "(JZ)V", (void*)PaintGlue::setUnderlineText}, + {"nSetStrikeThruText", "(JZ)V", (void*)PaintGlue::setStrikeThruText}, + {"nSetFakeBoldText", "(JZ)V", (void*)PaintGlue::setFakeBoldText}, + {"nSetFilterBitmap", "(JZ)V", (void*)PaintGlue::setFilterBitmap}, + {"nSetDither", "(JZ)V", (void*)PaintGlue::setDither}, + {"nGetStyle", "(J)I", (void*)PaintGlue::getStyle}, + {"nSetStyle", "(JI)V", (void*)PaintGlue::setStyle}, + {"nSetColor", "(JI)V", (void*)PaintGlue::setColor}, + {"nSetColor", "(JJJ)V", (void*)PaintGlue::setColorLong}, + {"nSetAlpha", "(JI)V", (void*)PaintGlue::setAlpha}, + {"nGetStrokeWidth", "(J)F", (void*)PaintGlue::getStrokeWidth}, + {"nSetStrokeWidth", "(JF)V", (void*)PaintGlue::setStrokeWidth}, + {"nGetStrokeMiter", "(J)F", (void*)PaintGlue::getStrokeMiter}, + {"nSetStrokeMiter", "(JF)V", (void*)PaintGlue::setStrokeMiter}, + {"nGetStrokeCap", "(J)I", (void*)PaintGlue::getStrokeCap}, + {"nSetStrokeCap", "(JI)V", (void*)PaintGlue::setStrokeCap}, + {"nGetStrokeJoin", "(J)I", (void*)PaintGlue::getStrokeJoin}, + {"nSetStrokeJoin", "(JI)V", (void*)PaintGlue::setStrokeJoin}, + {"nGetFillPath", "(JJJ)Z", (void*)PaintGlue::getFillPath}, + {"nSetShader", "(JJ)J", (void*)PaintGlue::setShader}, + {"nSetColorFilter", "(JJ)J", (void*)PaintGlue::setColorFilter}, + {"nSetXfermode", "(JI)V", (void*)PaintGlue::setXfermode}, + {"nSetPathEffect", "(JJ)J", (void*)PaintGlue::setPathEffect}, + {"nSetMaskFilter", "(JJ)J", (void*)PaintGlue::setMaskFilter}, + {"nSetTypeface", "(JJ)V", (void*)PaintGlue::setTypeface}, + {"nGetTextAlign", "(J)I", (void*)PaintGlue::getTextAlign}, + {"nSetTextAlign", "(JI)V", (void*)PaintGlue::setTextAlign}, + {"nSetTextLocalesByMinikinLocaleListId", "(JI)V", + (void*)PaintGlue::setTextLocalesByMinikinLocaleListId}, + {"nIsElegantTextHeight", "(J)Z", (void*)PaintGlue::isElegantTextHeight}, + {"nSetElegantTextHeight", "(JZ)V", (void*)PaintGlue::setElegantTextHeight}, + {"nGetTextSize", "(J)F", (void*)PaintGlue::getTextSize}, + {"nSetTextSize", "(JF)V", (void*)PaintGlue::setTextSize}, + {"nGetTextScaleX", "(J)F", (void*)PaintGlue::getTextScaleX}, + {"nSetTextScaleX", "(JF)V", (void*)PaintGlue::setTextScaleX}, + {"nGetTextSkewX", "(J)F", (void*)PaintGlue::getTextSkewX}, + {"nSetTextSkewX", "(JF)V", (void*)PaintGlue::setTextSkewX}, + {"nGetLetterSpacing", "(J)F", (void*)PaintGlue::getLetterSpacing}, + {"nSetLetterSpacing", "(JF)V", (void*)PaintGlue::setLetterSpacing}, + {"nGetWordSpacing", "(J)F", (void*)PaintGlue::getWordSpacing}, + {"nSetWordSpacing", "(JF)V", (void*)PaintGlue::setWordSpacing}, + {"nGetStartHyphenEdit", "(J)I", (void*)PaintGlue::getStartHyphenEdit}, + {"nGetEndHyphenEdit", "(J)I", (void*)PaintGlue::getEndHyphenEdit}, + {"nSetStartHyphenEdit", "(JI)V", (void*)PaintGlue::setStartHyphenEdit}, + {"nSetEndHyphenEdit", "(JI)V", (void*)PaintGlue::setEndHyphenEdit}, + {"nAscent", "(J)F", (void*)PaintGlue::ascent}, + {"nDescent", "(J)F", (void*)PaintGlue::descent}, + {"nGetUnderlinePosition", "(J)F", (void*)PaintGlue::getUnderlinePosition}, + {"nGetUnderlineThickness", "(J)F", (void*)PaintGlue::getUnderlineThickness}, + {"nGetStrikeThruPosition", "(J)F", (void*)PaintGlue::getStrikeThruPosition}, + {"nGetStrikeThruThickness", "(J)F", (void*)PaintGlue::getStrikeThruThickness}, + {"nSetShadowLayer", "(JFFFJJ)V", (void*)PaintGlue::setShadowLayer}, + {"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer}, + {"nEqualsForTextMeasurement", "(JJ)Z", (void*)PaintGlue::equalsForTextMeasurement}, }; - int register_android_graphics_Paint(JNIEnv* env) { return RegisterMethodsOrDie(env, "android/graphics/Paint", methods, NELEM(methods)); }