diff options
author | 2023-12-18 16:27:37 +0900 | |
---|---|---|
committer | 2024-01-18 10:27:02 +0900 | |
commit | d27b2f207a19f15c94f232a1db524364efd68ddf (patch) | |
tree | 8d8c635b423d26cf5d45135560d388b5bc0f9de2 | |
parent | 6b2c10dbc55b99c08723cbe4e1b2f212db262773 (diff) |
Remove unnecessary letter spaceing from the left and right of line
This CL is a groundwork for the inter character justification.
The letter spacing should not be added to the left most character
and right most character.
Bug: 283193133
Test: atest FrameworksCoreTests:android.text.TextLineLetterSpacingTest
Test: atest CtsTextTestCases
Change-Id: I28ac4f9f3b7520d7518d34f06ea9c05907cad9a6
-rw-r--r-- | core/api/current.txt | 2 | ||||
-rw-r--r-- | core/java/android/text/MeasuredParagraph.java | 28 | ||||
-rw-r--r-- | core/java/android/text/TextLine.java | 184 | ||||
-rw-r--r-- | core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt | 224 | ||||
-rw-r--r-- | graphics/java/android/graphics/Paint.java | 169 | ||||
-rw-r--r-- | graphics/java/android/graphics/text/MeasuredText.java | 4 | ||||
-rw-r--r-- | libs/hwui/FeatureFlags.h | 8 | ||||
-rw-r--r-- | libs/hwui/hwui/MinikinUtils.cpp | 10 | ||||
-rw-r--r-- | libs/hwui/hwui/Paint.h | 5 | ||||
-rw-r--r-- | libs/hwui/hwui/PaintImpl.cpp | 36 | ||||
-rw-r--r-- | libs/hwui/jni/android_graphics_Canvas.cpp | 26 |
11 files changed, 607 insertions, 89 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 15c054f0b5e8..5fa0c66161c7 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -16400,6 +16400,8 @@ package android.graphics { field public static final int START_HYPHEN_EDIT_NO_EDIT = 0; // 0x0 field public static final int STRIKE_THRU_TEXT_FLAG = 16; // 0x10 field public static final int SUBPIXEL_TEXT_FLAG = 128; // 0x80 + field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_LEFT_EDGE = 8192; // 0x2000 + field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 16384; // 0x4000 field public static final int UNDERLINE_TEXT_FLAG = 8; // 0x8 } diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java index ac5eb3cbeeaa..b268c2edd9a7 100644 --- a/core/java/android/text/MeasuredParagraph.java +++ b/core/java/android/text/MeasuredParagraph.java @@ -671,9 +671,18 @@ public class MeasuredParagraph { if (mLtrWithoutBidi) { // If the whole text is LTR direction, just apply whole region. if (builder == null) { - mWholeWidth += paint.getTextRunAdvances( - mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */, - mWidths.getRawArray(), start); + // For the compatibility reasons, the letter spacing should not be dropped at the + // left and right edge. + int oldFlag = paint.getFlags(); + paint.setFlags(paint.getFlags() + | (Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE)); + try { + mWholeWidth += paint.getTextRunAdvances( + mCopiedBuffer, start, end - start, start, end - start, + false /* isRtl */, mWidths.getRawArray(), start); + } finally { + paint.setFlags(oldFlag); + } } else { builder.appendStyleRun(paint, config, end - start, false /* isRtl */); } @@ -690,9 +699,16 @@ public class MeasuredParagraph { final boolean isRtl = (level & 0x1) != 0; if (builder == null) { final int levelLength = levelEnd - levelStart; - mWholeWidth += paint.getTextRunAdvances( - mCopiedBuffer, levelStart, levelLength, levelStart, levelLength, - isRtl, mWidths.getRawArray(), levelStart); + int oldFlag = paint.getFlags(); + paint.setFlags(paint.getFlags() + | (Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE)); + try { + mWholeWidth += paint.getTextRunAdvances( + mCopiedBuffer, levelStart, levelLength, levelStart, levelLength, + isRtl, mWidths.getRawArray(), levelStart); + } finally { + paint.setFlags(oldFlag); + } } else { builder.appendStyleRun(paint, config, levelEnd - levelStart, isRtl); } diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index 135935cb0632..2175b47e149e 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -291,6 +291,97 @@ public class TextLine { } /** + * Returns the run flag of at the given BiDi run. + * + * @param bidiRunIndex a BiDi run index. + * @return a run flag of the given BiDi run. + */ + @VisibleForTesting + public static int calculateRunFlag(int bidiRunIndex, int bidiRunCount, int lineDirection) { + if (bidiRunCount == 1) { + // Easy case. If there is only single run, it is most left and most right run. + return Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE; + } + if (bidiRunIndex != 0 && bidiRunIndex != (bidiRunCount - 1)) { + // Easy case. If the given run is the middle of the line, it is not the most left or + // the most right run. + return 0; + } + + int runFlag = 0; + // For the historical reasons, the BiDi implementation of Android works differently + // from the Java BiDi APIs. The mDirections holds the BiDi runs in visual order, but + // it is reversed order if the paragraph direction is RTL. So, the first BiDi run of + // mDirections is located the most left of the line if the paragraph direction is LTR. + // If the paragraph direction is RTL, the first BiDi run is located the most right of + // the line. + if (bidiRunIndex == 0) { + if (lineDirection == Layout.DIR_LEFT_TO_RIGHT) { + runFlag |= Paint.TEXT_RUN_FLAG_LEFT_EDGE; + } else { + runFlag |= Paint.TEXT_RUN_FLAG_RIGHT_EDGE; + } + } + if (bidiRunIndex == (bidiRunCount - 1)) { + if (lineDirection == Layout.DIR_LEFT_TO_RIGHT) { + runFlag |= Paint.TEXT_RUN_FLAG_RIGHT_EDGE; + } else { + runFlag |= Paint.TEXT_RUN_FLAG_LEFT_EDGE; + } + } + return runFlag; + } + + /** + * Resolve the runFlag for the inline span range. + * + * @param runFlag the runFlag of the current BiDi run. + * @param isRtlRun true for RTL run, false for LTR run. + * @param runStart the inclusive BiDi run start offset. + * @param runEnd the exclusive BiDi run end offset. + * @param spanStart the inclusive span start offset. + * @param spanEnd the exclusive span end offset. + * @return the resolved runFlag. + */ + @VisibleForTesting + public static int resolveRunFlagForSubSequence(int runFlag, boolean isRtlRun, int runStart, + int runEnd, int spanStart, int spanEnd) { + if (runFlag == 0) { + // Easy case. If the run is in the middle of the line, any inline span is also in the + // middle of the line. + return 0; + } + int localRunFlag = runFlag; + if ((runFlag & Paint.TEXT_RUN_FLAG_LEFT_EDGE) != 0) { + if (isRtlRun) { + if (spanEnd != runEnd) { + // In the RTL context, the last run is the most left run. + localRunFlag &= ~Paint.TEXT_RUN_FLAG_LEFT_EDGE; + } + } else { // LTR + if (spanStart != runStart) { + // In the LTR context, the first run is the most left run. + localRunFlag &= ~Paint.TEXT_RUN_FLAG_LEFT_EDGE; + } + } + } + if ((runFlag & Paint.TEXT_RUN_FLAG_RIGHT_EDGE) != 0) { + if (isRtlRun) { + if (spanStart != runStart) { + // In the RTL context, the start of the run is the most right run. + localRunFlag &= ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE; + } + } else { // LTR + if (spanEnd != runEnd) { + // In the LTR context, the last run is the most right position. + localRunFlag &= ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE; + } + } + } + return localRunFlag; + } + + /** * Renders the TextLine. * * @param c the canvas to render on @@ -308,11 +399,13 @@ public class TextLine { final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen); final boolean runIsRtl = mDirections.isRunRtl(runIndex); + final int runFlag = calculateRunFlag(runIndex, runCount, mDir); + int segStart = runStart; for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) { if (j == runLimit || charAt(j) == TAB_CHAR) { h += drawRun(c, segStart, j, runIsRtl, x + h, top, y, bottom, - runIndex != (runCount - 1) || j != mLen); + runIndex != (runCount - 1) || j != mLen, runFlag); if (j != runLimit) { // charAt(j) == TAB_CHAR h = mDir * nextTab(h * mDir); @@ -371,11 +464,12 @@ public class TextLine { final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen); final boolean runIsRtl = mDirections.isRunRtl(runIndex); + final int runFlag = calculateRunFlag(runIndex, runCount, mDir); int segStart = runStart; for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) { if (j == runLimit || charAt(j) == TAB_CHAR) { horizontal += shapeRun(consumer, segStart, j, runIsRtl, x + horizontal, - runIndex != (runCount - 1) || j != mLen); + runIndex != (runCount - 1) || j != mLen, runFlag); if (j != runLimit) { // charAt(j) == TAB_CHAR horizontal = mDir * nextTab(horizontal * mDir); @@ -441,11 +535,13 @@ public class TextLine { } float h = 0; - for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) { + 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); + final int runFlag = calculateRunFlag(runIndex, runCount, mDir); int segStart = runStart; for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) { @@ -455,16 +551,16 @@ public class TextLine { if (targetIsInThisSegment && sameDirection) { return h + measureRun(segStart, offset, j, runIsRtl, fmi, drawBounds, null, - 0, h, lineInfo); + 0, h, lineInfo, runFlag); } final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi, drawBounds, - null, 0, h, lineInfo); + null, 0, h, lineInfo, runFlag); h += sameDirection ? segmentWidth : -segmentWidth; if (targetIsInThisSegment) { return h + measureRun(segStart, offset, j, runIsRtl, null, null, null, 0, - h, lineInfo); + h, lineInfo, runFlag); } if (j != runLimit) { // charAt(j) == TAB_CHAR @@ -543,20 +639,21 @@ public class TextLine { + "result, needed: " + mLen + " had: " + advances.length); } float h = 0; - for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) { + 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); + final int runFlag = calculateRunFlag(runIndex, runCount, mDir); 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, null, advances, segStart, 0, - null); + null, runFlag); final float oldh = h; h += sameDirection ? segmentWidth : -segmentWidth; @@ -608,11 +705,13 @@ public class TextLine { } float horizontal = 0; - for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) { + 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); + final int runFlag = calculateRunFlag(runIndex, runCount, mDir); int segStart = runStart; for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; ++j) { @@ -629,7 +728,7 @@ public class TextLine { final float previousSegEndHorizontal = measurement[segStart]; final float width = measureRun(segStart, j, j, runIsRtl, fmi, null, measurement, segStart, - 0, null); + 0, null, runFlag); horizontal += sameDirection ? width : -width; float currHorizontal = sameDirection ? oldHorizontal : horizontal; @@ -686,22 +785,24 @@ public class TextLine { * @param y the baseline * @param bottom the bottom of the line * @param needWidth true if the width value is required. + * @param runFlag the run flag to be applied for this run. * @return the signed width of the run, based on the paragraph direction. * Only valid if needWidth is true. */ private float drawRun(Canvas c, int start, int limit, boolean runIsRtl, float x, int top, int y, int bottom, - boolean needWidth) { + boolean needWidth, int runFlag) { if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) { - float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null); + float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null, + runFlag); handleRun(start, limit, limit, runIsRtl, c, null, x + w, top, - y, bottom, null, null, false, null, 0, null); + y, bottom, null, null, false, null, 0, null, runFlag); return w; } return handleRun(start, limit, limit, runIsRtl, c, null, x, top, - y, bottom, null, null, needWidth, null, 0, null); + y, bottom, null, null, needWidth, null, 0, null, runFlag); } /** @@ -718,19 +819,21 @@ public class TextLine { * @param advancesIndex the start index to fill in the advance information. * @param x horizontal offset of the run. * @param lineInfo an optional output parameter for filling line information. + * @param runFlag the run flag to be applied for this run. * @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, @Nullable FontMetricsInt fmi, @Nullable RectF drawBounds, @Nullable float[] advances, - int advancesIndex, float x, @Nullable LineInfo lineInfo) { + int advancesIndex, float x, @Nullable LineInfo lineInfo, int runFlag) { if (drawBounds != null && (mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) { - float w = -measureRun(start, offset, limit, runIsRtl, null, null, null, 0, 0, null); + float w = -measureRun(start, offset, limit, runIsRtl, null, null, null, 0, 0, null, + runFlag); return handleRun(start, offset, limit, runIsRtl, null, null, x + w, 0, 0, 0, fmi, - drawBounds, true, advances, advancesIndex, lineInfo); + drawBounds, true, advances, advancesIndex, lineInfo, runFlag); } return handleRun(start, offset, limit, runIsRtl, null, null, x, 0, 0, 0, fmi, drawBounds, - true, advances, advancesIndex, lineInfo); + true, advances, advancesIndex, lineInfo, runFlag); } /** @@ -742,21 +845,23 @@ public class TextLine { * @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. + * @param runFlag the run flag to be applied for this run. * @return the signed width of the run, based on the paragraph direction. * Only valid if needWidth is true. */ private float shapeRun(TextShaper.GlyphsConsumer consumer, int start, - int limit, boolean runIsRtl, float x, boolean needWidth) { + int limit, boolean runIsRtl, float x, boolean needWidth, int runFlag) { if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) { - float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null); + float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null, + runFlag); handleRun(start, limit, limit, runIsRtl, null, consumer, x + w, 0, 0, 0, null, null, - false, null, 0, null); + false, null, 0, null, runFlag); return w; } return handleRun(start, limit, limit, runIsRtl, null, consumer, x, 0, 0, 0, null, null, - needWidth, null, 0, null); + needWidth, null, 0, null, runFlag); } @@ -1160,6 +1265,7 @@ public class TextLine { * @param advances receives the advance information about the requested run, can be null. * @param advancesIndex the start index to fill in the advance information. * @param lineInfo an optional output parameter for filling line information. + * @param runFlag the run flag to be applied for this run. * @return the signed width of the run based on the run direction; only * valid if needWidth is true */ @@ -1168,8 +1274,8 @@ public class TextLine { Canvas c, TextShaper.GlyphsConsumer consumer, float x, int top, int y, int bottom, FontMetricsInt fmi, RectF drawBounds, boolean needWidth, int offset, @Nullable ArrayList<DecorationInfo> decorations, - @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo) { - + @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo, + int runFlag) { if (mIsJustifying) { wp.setWordSpacing(mAddedWidthForJustify); } @@ -1187,7 +1293,16 @@ public class TextLine { } float totalWidth = 0; - + if ((runFlag & Paint.TEXT_RUN_FLAG_LEFT_EDGE) == Paint.TEXT_RUN_FLAG_LEFT_EDGE) { + wp.setFlags(wp.getFlags() | Paint.TEXT_RUN_FLAG_LEFT_EDGE); + } else { + wp.setFlags(wp.getFlags() & ~Paint.TEXT_RUN_FLAG_LEFT_EDGE); + } + if ((runFlag & Paint.TEXT_RUN_FLAG_RIGHT_EDGE) == Paint.TEXT_RUN_FLAG_RIGHT_EDGE) { + wp.setFlags(wp.getFlags() | Paint.TEXT_RUN_FLAG_RIGHT_EDGE); + } else { + wp.setFlags(wp.getFlags() & ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE); + } final int numDecorations = decorations == null ? 0 : decorations.size(); if (needWidth || ((c != null || consumer != null) && (wp.bgColor != 0 || numDecorations != 0 || runIsRtl))) { @@ -1419,6 +1534,7 @@ public class TextLine { * @param advances receives the advance information about the requested run, can be null. * @param advancesIndex the start index to fill in the advance information. * @param lineInfo an optional output parameter for filling line information. + * @param runFlag the run flag to be applied for this run. * @return the signed width of the run based on the run direction; only * valid if needWidth is true */ @@ -1426,7 +1542,8 @@ public class TextLine { int limit, boolean runIsRtl, Canvas c, TextShaper.GlyphsConsumer consumer, float x, int top, int y, int bottom, FontMetricsInt fmi, RectF drawBounds, boolean needWidth, - @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo) { + @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo, + int runFlag) { if (measureLimit < start || measureLimit > limit) { throw new IndexOutOfBoundsException("measureLimit (" + measureLimit + ") is out of " @@ -1473,7 +1590,7 @@ public class TextLine { wp.setEndHyphenEdit(adjustEndHyphenEdit(limit, wp.getEndHyphenEdit())); return handleText(wp, start, limit, start, limit, runIsRtl, c, consumer, x, top, y, bottom, fmi, drawBounds, needWidth, measureLimit, null, advances, - advancesIndex, lineInfo); + advancesIndex, lineInfo, runFlag); } // Shaping needs to take into account context up to metric boundaries, @@ -1554,6 +1671,9 @@ public class TextLine { // and use. activePaint.set(wp); } else if (!equalAttributes(wp, activePaint)) { + final int spanRunFlag = resolveRunFlagForSubSequence( + runFlag, runIsRtl, start, measureLimit, activeStart, activeEnd); + // The style of the present chunk of text is substantially different from the // style of the previous chunk. We need to handle the active piece of text // and restart with the present chunk. @@ -1565,7 +1685,7 @@ public class TextLine { consumer, x, top, y, bottom, fmi, drawBounds, needWidth || activeEnd < measureLimit, Math.min(activeEnd, mlimit), mDecorations, - advances, advancesIndex + activeStart - start, lineInfo); + advances, advancesIndex + activeStart - start, lineInfo, spanRunFlag); activeStart = j; activePaint.set(wp); @@ -1585,6 +1705,9 @@ public class TextLine { mDecorations.add(copy); } } + + final int spanRunFlag = resolveRunFlagForSubSequence( + runFlag, runIsRtl, start, measureLimit, activeStart, activeEnd); // Handle the final piece of text. activePaint.setStartHyphenEdit( adjustStartHyphenEdit(activeStart, mPaint.getStartHyphenEdit())); @@ -1593,7 +1716,7 @@ public class TextLine { x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, consumer, x, top, y, bottom, fmi, drawBounds, needWidth || activeEnd < measureLimit, Math.min(activeEnd, mlimit), mDecorations, - advances, advancesIndex + activeStart - start, lineInfo); + advances, advancesIndex + activeStart - start, lineInfo, spanRunFlag); } return x - originalX; @@ -1614,7 +1737,6 @@ public class TextLine { */ private void drawTextRun(Canvas c, TextPaint wp, int start, int end, int contextStart, int contextEnd, boolean runIsRtl, float x, int y) { - if (mCharsValid) { int count = end - start; int contextCount = contextEnd - contextStart; diff --git a/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt new file mode 100644 index 000000000000..27869bbf5392 --- /dev/null +++ b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2024 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.graphics.Paint +import android.platform.test.annotations.RequiresFlagsEnabled +import android.platform.test.flag.junit.DeviceFlagsValueProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +const val LEFT_EDGE = Paint.TEXT_RUN_FLAG_LEFT_EDGE +const val RIGHT_EDGE = Paint.TEXT_RUN_FLAG_RIGHT_EDGE +const val MIDDLE_OF_LINE = 0 +const val WHOLE_LINE = LEFT_EDGE or RIGHT_EDGE + +@SmallTest +@RunWith(AndroidJUnit4::class) +class TextLineLetterSpacingTest { + + @Rule + @JvmField + val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() + + @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION) + @Test + fun calculateRunFlagTest() { + // Only one Bidi run + assertThat(TextLine.calculateRunFlag(0, 1, Layout.DIR_LEFT_TO_RIGHT)) + .isEqualTo(WHOLE_LINE) + assertThat(TextLine.calculateRunFlag(0, 1, Layout.DIR_RIGHT_TO_LEFT)) + .isEqualTo(WHOLE_LINE) + + // Two BiDi Runs. + // If the layout is LTR, the first run is the left most run. + assertThat(TextLine.calculateRunFlag(0, 2, Layout.DIR_LEFT_TO_RIGHT)) + .isEqualTo(LEFT_EDGE) + // If the layout is LTR, the last run is the right most run. + assertThat(TextLine.calculateRunFlag(1, 2, Layout.DIR_LEFT_TO_RIGHT)) + .isEqualTo(RIGHT_EDGE) + // If the layout is RTL, the first run is the right most run. + assertThat(TextLine.calculateRunFlag(0, 2, Layout.DIR_RIGHT_TO_LEFT)) + .isEqualTo(RIGHT_EDGE) + // If the layout is RTL, the last run is the left most run. + assertThat(TextLine.calculateRunFlag(1, 2, Layout.DIR_RIGHT_TO_LEFT)) + .isEqualTo(LEFT_EDGE) + + // Three BiDi Runs. + // If the layout is LTR, the first run is the left most run. + assertThat(TextLine.calculateRunFlag(0, 3, Layout.DIR_LEFT_TO_RIGHT)) + .isEqualTo(LEFT_EDGE) + // Regardless of the context direction, the middle run must not have any flags. + assertThat(TextLine.calculateRunFlag(1, 3, Layout.DIR_LEFT_TO_RIGHT)) + .isEqualTo(MIDDLE_OF_LINE) + // If the layout is LTR, the last run is the right most run. + assertThat(TextLine.calculateRunFlag(2, 3, Layout.DIR_LEFT_TO_RIGHT)) + .isEqualTo(RIGHT_EDGE) + // If the layout is RTL, the first run is the right most run. + assertThat(TextLine.calculateRunFlag(0, 3, Layout.DIR_RIGHT_TO_LEFT)) + .isEqualTo(RIGHT_EDGE) + // Regardless of the context direction, the middle run must not have any flags. + assertThat(TextLine.calculateRunFlag(1, 3, Layout.DIR_RIGHT_TO_LEFT)) + .isEqualTo(MIDDLE_OF_LINE) + // If the layout is RTL, the last run is the left most run. + assertThat(TextLine.calculateRunFlag(2, 3, Layout.DIR_RIGHT_TO_LEFT)) + .isEqualTo(LEFT_EDGE) + } + + @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION) + @Test + fun resolveRunFlagForSubSequenceTest() { + val runStart = 5 + val runEnd = 15 + // Regardless of the run directions, if the span covers entire Bidi run, the same runFlag + // should be returned. + assertThat(TextLine.resolveRunFlagForSubSequence( + LEFT_EDGE, false, runStart, runEnd, runStart, runEnd)) + .isEqualTo(LEFT_EDGE) + assertThat(TextLine.resolveRunFlagForSubSequence( + LEFT_EDGE, true, runStart, runEnd, runStart, runEnd)) + .isEqualTo(LEFT_EDGE) + assertThat(TextLine.resolveRunFlagForSubSequence( + RIGHT_EDGE, false, runStart, runEnd, runStart, runEnd)) + .isEqualTo(RIGHT_EDGE) + assertThat(TextLine.resolveRunFlagForSubSequence( + RIGHT_EDGE, true, runStart, runEnd, runStart, runEnd)) + .isEqualTo(RIGHT_EDGE) + assertThat(TextLine.resolveRunFlagForSubSequence( + WHOLE_LINE, false, runStart, runEnd, runStart, runEnd)) + .isEqualTo(WHOLE_LINE) + assertThat(TextLine.resolveRunFlagForSubSequence( + WHOLE_LINE, true, runStart, runEnd, runStart, runEnd)) + .isEqualTo(WHOLE_LINE) + assertThat(TextLine.resolveRunFlagForSubSequence( + MIDDLE_OF_LINE, false, runStart, runEnd, runStart, runEnd)) + .isEqualTo(MIDDLE_OF_LINE) + assertThat(TextLine.resolveRunFlagForSubSequence( + MIDDLE_OF_LINE, true, runStart, runEnd, runStart, runEnd)) + .isEqualTo(MIDDLE_OF_LINE) + + + + // Left edge of LTR text, span start from run start offset but not cover the run. + assertThat(TextLine.resolveRunFlagForSubSequence( + LEFT_EDGE, false, runStart, runEnd, runStart, runEnd - 1)) + .isEqualTo(LEFT_EDGE) + // Left edge of RTL text, span start from run start offset but not cover the run. + assertThat(TextLine.resolveRunFlagForSubSequence( + LEFT_EDGE, true, runStart, runEnd, runStart, runEnd - 1)) + .isEqualTo(MIDDLE_OF_LINE) + // Right edge of LTR text, span start from run start offset but not cover the run. + assertThat(TextLine.resolveRunFlagForSubSequence( + RIGHT_EDGE, false, runStart, runEnd, runStart, runEnd - 1)) + .isEqualTo(MIDDLE_OF_LINE) + // Right edge of RTL text, span start from run start offset but not cover the run. + assertThat(TextLine.resolveRunFlagForSubSequence( + RIGHT_EDGE, true, runStart, runEnd, runStart, runEnd - 1)) + .isEqualTo(RIGHT_EDGE) + // Whole line of LTR text, span start from run start offset but not cover the run. + assertThat(TextLine.resolveRunFlagForSubSequence( + WHOLE_LINE, false, runStart, runEnd, runStart, runEnd - 1)) + .isEqualTo(LEFT_EDGE) + // Whole line of RTL text, span start from run start offset but not cover the run. + assertThat(TextLine.resolveRunFlagForSubSequence( + WHOLE_LINE, true, runStart, runEnd, runStart, runEnd - 1)) + .isEqualTo(RIGHT_EDGE) + // Middle of LTR text, span start from run start offset but not cover the run. + assertThat(TextLine.resolveRunFlagForSubSequence( + MIDDLE_OF_LINE, false, runStart, runEnd, runStart, runEnd - 1)) + .isEqualTo(MIDDLE_OF_LINE) + // Middle of RTL text, span start from run start offset but not cover the run. + assertThat(TextLine.resolveRunFlagForSubSequence( + MIDDLE_OF_LINE, true, runStart, runEnd, runStart, runEnd - 1)) + .isEqualTo(MIDDLE_OF_LINE) + + + + // Left edge of LTR text, span start from middle of run start offset until end of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + LEFT_EDGE, false, runStart, runEnd, runStart + 1, runEnd)) + .isEqualTo(MIDDLE_OF_LINE) + // Left edge of RTL text, span start from middle of run start offset until end of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + LEFT_EDGE, true, runStart, runEnd, runStart + 1, runEnd)) + .isEqualTo(LEFT_EDGE) + // Right edge of LTR text, span start from middle of run start offset until end of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + RIGHT_EDGE, false, runStart, runEnd, runStart + 1, runEnd)) + .isEqualTo(RIGHT_EDGE) + // Right edge of RTL text, span start from middle of run start offset until end of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + RIGHT_EDGE, true, runStart, runEnd, runStart + 1, runEnd)) + .isEqualTo(MIDDLE_OF_LINE) + // Whole line of LTR text, span start from middle of run start offset until end of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + WHOLE_LINE, false, runStart, runEnd, runStart + 1, runEnd)) + .isEqualTo(RIGHT_EDGE) + // Whole line of RTL text, span start from middle of run start offset until end of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + WHOLE_LINE, true, runStart, runEnd, runStart + 1, runEnd)) + .isEqualTo(LEFT_EDGE) + // Middle of LTR text, span start from middle of run start offset until end of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + MIDDLE_OF_LINE, false, runStart, runEnd, runStart + 1, runEnd)) + .isEqualTo(MIDDLE_OF_LINE) + // Middle of RTL text, span start from middle of run start offset until end of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + MIDDLE_OF_LINE, true, runStart, runEnd, runStart + 1, runEnd)) + .isEqualTo(MIDDLE_OF_LINE) + + + + // Left edge of LTR text, span start from middle of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + LEFT_EDGE, false, runStart, runEnd, runStart + 1, runEnd - 1)) + .isEqualTo(MIDDLE_OF_LINE) + // Left edge of RTL text, span start from middle of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + LEFT_EDGE, true, runStart, runEnd, runStart + 1, runEnd - 1)) + .isEqualTo(MIDDLE_OF_LINE) + // Right edge of LTR text, span start from middle of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + RIGHT_EDGE, false, runStart, runEnd, runStart + 1, runEnd - 1)) + .isEqualTo(MIDDLE_OF_LINE) + // Right edge of RTL text, span start from middle of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + RIGHT_EDGE, true, runStart, runEnd, runStart + 1, runEnd - 1)) + .isEqualTo(MIDDLE_OF_LINE) + // Whole line of LTR text, span start from middle of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + WHOLE_LINE, false, runStart, runEnd, runStart + 1, runEnd - 1)) + .isEqualTo(MIDDLE_OF_LINE) + // Whole line of RTL text, span start from middle of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + WHOLE_LINE, true, runStart, runEnd, runStart + 1, runEnd - 1)) + .isEqualTo(MIDDLE_OF_LINE) + // Middle of LTR text, span start from middle of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + MIDDLE_OF_LINE, false, runStart, runEnd, runStart + 1, runEnd - 1)) + .isEqualTo(MIDDLE_OF_LINE) + // Middle of RTL text, span start from middle of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + MIDDLE_OF_LINE, true, runStart, runEnd, runStart + 1, runEnd - 1)) + .isEqualTo(MIDDLE_OF_LINE) + } +}
\ No newline at end of file diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index f6ba103f6f05..ae61a2d811d2 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -17,6 +17,7 @@ package android.graphics; import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE; +import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION; import android.annotation.ColorInt; import android.annotation.ColorLong; @@ -133,7 +134,9 @@ public class Paint { FAKE_BOLD_TEXT_FLAG, LINEAR_TEXT_FLAG, SUBPIXEL_TEXT_FLAG, - EMBEDDED_BITMAP_TEXT_FLAG + EMBEDDED_BITMAP_TEXT_FLAG, + TEXT_RUN_FLAG_LEFT_EDGE, + TEXT_RUN_FLAG_RIGHT_EDGE }) public @interface PaintFlag{} @@ -264,6 +267,66 @@ public class Paint { /** @hide bit mask for the flag enabling vertical rendering for text */ public static final int VERTICAL_TEXT_FLAG = 0x1000; + /** + * A text run flag that indicates the run is located the visually most left segment of the line. + * <p> + * This flag is used for telling the underlying text layout engine that the text is located at + * the most left of the line. This flag is used for controlling the amount letter spacing + * added. If the text is in the middle of the line, the text layout engine assigns additional + * letter spacing to the both side of each letter. On the other hand, the letter spacing should + * not be added to the visually most left and right of the line. By setting this flag, text + * layout engine calculates the layout as it is located at the most visually left of the line + * and doesn't add letter spacing to the left of this run. + * <p> + * Note that the caller must resolve BiDi runs and reorder them visually and set this flag only + * if the target run is located visually most left position. This left does not always mean the + * beginning of the text. + * <p> + * If the run covers entire line, caller should set {@link #TEXT_RUN_FLAG_RIGHT_EDGE} as well. + * <p> + * Note that this flag is only effective for run based APIs. For example, this flag works for + * {@link Canvas#drawTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)} + * and + * {@link Paint#getRunCharacterAdvance(char[], int, int, int, int, boolean, int, float[], int)}. + * However, this doesn't work for + * {@link Canvas#drawText(CharSequence, int, int, float, float, Paint)} or + * {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both + * {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified. + */ + @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION) + public static final int TEXT_RUN_FLAG_LEFT_EDGE = 0x2000; + + + /** + * A text run flag that indicates the run is located the visually most right segment of the + * line. + * <p> + * This flag is used for telling the underlying text layout engine that the text is located at + * the most right of the line. This flag is used for controlling the amount letter spacing + * added. If the text is in the middle of the line, the text layout engine assigns additional + * letter spacing to the both side of each letter. On the other hand, the letter spacing should + * not be added to the visually most left and right of the line. By setting this flag, text + * layout engine calculates the layout as it is located at the most visually left of the line + * and doesn't add letter spacing to the left of this run. + * <p> + * Note that the caller must resolve BiDi runs and reorder them visually and set this flag only + * if the target run is located visually most right position. This right does not always mean + * the end of the text. + * <p> + * If the run covers entire line, caller should set {@link #TEXT_RUN_FLAG_LEFT_EDGE} as well. + * <p> + * Note that this flag is only effective for run based APIs. For example, this flag works for + * {@link Canvas#drawTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)} + * and + * {@link Paint#getRunCharacterAdvance(char[], int, int, int, int, boolean, int, float[], int)}. + * However, this doesn't work for + * {@link Canvas#drawText(CharSequence, int, int, float, float, Paint)} or + * {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both + * {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified. + */ + @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION) + public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 0x4000; + // These flags are always set on a new/reset paint, even if flags 0 is passed. static final int HIDDEN_DEFAULT_PAINT_FLAGS = DEV_KERN_TEXT_FLAG | EMBEDDED_BITMAP_TEXT_FLAG | FILTER_BITMAP_FLAG; @@ -2520,17 +2583,24 @@ public class Paint { if (text.length == 0 || count == 0) { return 0f; } - if (!mHasCompatScaling) { - return (float) Math.ceil(nGetTextAdvances(mNativePaint, text, - index, count, index, count, mBidiFlags, null, 0)); - } + int oldFlag = getFlags(); + setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE)); + try { - final float oldSize = getTextSize(); - setTextSize(oldSize * mCompatScaling); - final float w = nGetTextAdvances(mNativePaint, text, index, count, index, count, - mBidiFlags, null, 0); - setTextSize(oldSize); - return (float) Math.ceil(w*mInvCompatScaling); + if (!mHasCompatScaling) { + return (float) Math.ceil(nGetTextAdvances(mNativePaint, text, + index, count, index, count, mBidiFlags, null, 0)); + } + + final float oldSize = getTextSize(); + setTextSize(oldSize * mCompatScaling); + final float w = nGetTextAdvances(mNativePaint, text, index, count, index, count, + mBidiFlags, null, 0); + setTextSize(oldSize); + return (float) Math.ceil(w * mInvCompatScaling); + } finally { + setFlags(oldFlag); + } } /** @@ -2552,16 +2622,22 @@ public class Paint { if (text.length() == 0 || start == end) { return 0f; } - if (!mHasCompatScaling) { - return (float) Math.ceil(nGetTextAdvances(mNativePaint, text, - start, end, start, end, mBidiFlags, null, 0)); + int oldFlag = getFlags(); + setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE)); + try { + if (!mHasCompatScaling) { + return (float) Math.ceil(nGetTextAdvances(mNativePaint, text, + start, end, start, end, mBidiFlags, null, 0)); + } + final float oldSize = getTextSize(); + setTextSize(oldSize * mCompatScaling); + final float w = nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, + null, 0); + setTextSize(oldSize); + return (float) Math.ceil(w * mInvCompatScaling); + } finally { + setFlags(oldFlag); } - final float oldSize = getTextSize(); - setTextSize(oldSize * mCompatScaling); - final float w = nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, - null, 0); - setTextSize(oldSize); - return (float) Math.ceil(w * mInvCompatScaling); } /** @@ -2766,19 +2842,26 @@ public class Paint { if (text.length == 0 || count == 0) { return 0; } - if (!mHasCompatScaling) { + int oldFlag = getFlags(); + setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE)); + try { + if (!mHasCompatScaling) { + nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, + 0); + return count; + } + + final float oldSize = getTextSize(); + setTextSize(oldSize * mCompatScaling); nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0); + setTextSize(oldSize); + for (int i = 0; i < count; i++) { + widths[i] *= mInvCompatScaling; + } return count; + } finally { + setFlags(oldFlag); } - - final float oldSize = getTextSize(); - setTextSize(oldSize * mCompatScaling); - nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0); - setTextSize(oldSize); - for (int i = 0; i < count; i++) { - widths[i] *= mInvCompatScaling; - } - return count; } /** @@ -2849,19 +2932,25 @@ public class Paint { if (text.length() == 0 || start == end) { return 0; } - if (!mHasCompatScaling) { + int oldFlag = getFlags(); + setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE)); + try { + if (!mHasCompatScaling) { + nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0); + return end - start; + } + + final float oldSize = getTextSize(); + setTextSize(oldSize * mCompatScaling); nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0); + setTextSize(oldSize); + for (int i = 0; i < end - start; i++) { + widths[i] *= mInvCompatScaling; + } return end - start; + } finally { + setFlags(oldFlag); } - - final float oldSize = getTextSize(); - setTextSize(oldSize * mCompatScaling); - nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0); - setTextSize(oldSize); - for (int i = 0; i < end - start; i++) { - widths[i] *= mInvCompatScaling; - } - return end - start; } /** diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java index 2d33e8d24ece..6da07198c3ad 100644 --- a/graphics/java/android/graphics/text/MeasuredText.java +++ b/graphics/java/android/graphics/text/MeasuredText.java @@ -269,6 +269,10 @@ public class MeasuredText { * offset is zero. After the style is applied the internal offset is moved to {@code offset * + length}, and next call will start from this new position. * + * <p> + * {@link Paint#TEXT_RUN_FLAG_RIGHT_EDGE} and {@link Paint#TEXT_RUN_FLAG_LEFT_EDGE} are + * ignored and treated as both of them are set. + * * @param paint a paint * @param length a length to be applied with a given paint, can not exceed the length of the * text diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h index 00d049cde925..6ebfc63bdf26 100644 --- a/libs/hwui/FeatureFlags.h +++ b/libs/hwui/FeatureFlags.h @@ -41,6 +41,14 @@ inline bool deprecate_ui_fonts() { #endif // __ANDROID__ } +inline bool inter_character_justification() { +#ifdef __ANDROID__ + return com_android_text_flags_inter_character_justification(); +#else + return true; +#endif // __ANDROID__ +} + } // namespace text_feature } // namespace android diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp index 833069f363c8..56133699d5f5 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -72,10 +72,13 @@ minikin::Layout MinikinUtils::doLayout(const Paint* paint, minikin::Bidi bidiFla const minikin::Range contextRange(contextStart, contextStart + contextCount); const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit(); const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit(); + const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification() + ? paint->getRunFlag() + : minikin::RunFlag::NONE; if (mt == nullptr) { return minikin::Layout(textBuf.substr(contextRange), range - contextStart, bidiFlags, - minikinPaint, startHyphen, endHyphen); + minikinPaint, startHyphen, endHyphen, minikinRunFlag); } else { return mt->buildLayout(textBuf, range, contextRange, minikinPaint, startHyphen, endHyphen); } @@ -102,9 +105,12 @@ float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags, const minikin::Range range(start, start + count); const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit(); const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit(); + const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification() + ? paint->getRunFlag() + : minikin::RunFlag::NONE; return minikin::Layout::measureText(textBuf, range, bidiFlags, minikinPaint, startHyphen, - endHyphen, advances, bounds, clusterCount); + endHyphen, advances, bounds, clusterCount, minikinRunFlag); } minikin::MinikinExtent MinikinUtils::getFontExtent(const Paint* paint, minikin::Bidi bidiFlags, diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h index ef4dce57bf46..708f96e5a070 100644 --- a/libs/hwui/hwui/Paint.h +++ b/libs/hwui/hwui/Paint.h @@ -25,6 +25,7 @@ #include <minikin/FontFamily.h> #include <minikin/FontFeature.h> #include <minikin/Hyphenator.h> +#include <minikin/Layout.h> #include <string> @@ -144,6 +145,9 @@ public: bool isDevKern() const { return mDevKern; } void setDevKern(bool d) { mDevKern = d; } + minikin::RunFlag getRunFlag() const { return mRunFlag; } + void setRunFlag(minikin::RunFlag runFlag) { mRunFlag = runFlag; } + // Deprecated -- bitmapshaders will be taking this flag explicitly bool isFilterBitmap() const { return mFilterBitmap; } void setFilterBitmap(bool filter) { mFilterBitmap = filter; } @@ -188,6 +192,7 @@ private: bool mStrikeThru = false; bool mUnderline = false; bool mDevKern = false; + minikin::RunFlag mRunFlag = minikin::RunFlag::NONE; }; } // namespace android diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp index aac928f85924..c32ea01db7b7 100644 --- a/libs/hwui/hwui/PaintImpl.cpp +++ b/libs/hwui/hwui/PaintImpl.cpp @@ -47,8 +47,8 @@ Paint::Paint(const Paint& paint) , mFilterBitmap(paint.mFilterBitmap) , mStrikeThru(paint.mStrikeThru) , mUnderline(paint.mUnderline) - , mDevKern(paint.mDevKern) {} - + , mDevKern(paint.mDevKern) + , mRunFlag(paint.mRunFlag) {} Paint::~Paint() {} @@ -68,21 +68,19 @@ Paint& Paint::operator=(const Paint& other) { mStrikeThru = other.mStrikeThru; mUnderline = other.mUnderline; mDevKern = other.mDevKern; + mRunFlag = other.mRunFlag; return *this; } bool operator==(const Paint& a, const Paint& b) { - return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) && - a.mFont == b.mFont && - a.mLooper == b.mLooper && - a.mLetterSpacing == b.mLetterSpacing && a.mWordSpacing == b.mWordSpacing && - a.mFontFeatureSettings == b.mFontFeatureSettings && + return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) && a.mFont == b.mFont && + a.mLooper == b.mLooper && a.mLetterSpacing == b.mLetterSpacing && + a.mWordSpacing == b.mWordSpacing && a.mFontFeatureSettings == b.mFontFeatureSettings && a.mMinikinLocaleListId == b.mMinikinLocaleListId && a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit && a.mTypeface == b.mTypeface && a.mAlign == b.mAlign && - a.mFilterBitmap == b.mFilterBitmap && - a.mStrikeThru == b.mStrikeThru && a.mUnderline == b.mUnderline && - a.mDevKern == b.mDevKern; + a.mFilterBitmap == b.mFilterBitmap && a.mStrikeThru == b.mStrikeThru && + a.mUnderline == b.mUnderline && a.mDevKern == b.mDevKern && a.mRunFlag == b.mRunFlag; } void Paint::reset() { @@ -96,6 +94,7 @@ void Paint::reset() { mStrikeThru = false; mUnderline = false; mDevKern = false; + mRunFlag = minikin::RunFlag::NONE; } void Paint::setLooper(sk_sp<BlurDrawLooper> looper) { @@ -133,6 +132,8 @@ static const uint32_t sForceAutoHinting = 0x800; // flags related to minikin::Paint static const uint32_t sUnderlineFlag = 0x08; static const uint32_t sStrikeThruFlag = 0x10; +static const uint32_t sTextRunLeftEdge = 0x2000; +static const uint32_t sTextRunRightEdge = 0x4000; // flags no longer supported on native side (but mirrored for compatibility) static const uint32_t sDevKernFlag = 0x100; @@ -186,6 +187,12 @@ uint32_t Paint::getJavaFlags() const { flags |= -(int)mUnderline & sUnderlineFlag; flags |= -(int)mDevKern & sDevKernFlag; flags |= -(int)mFilterBitmap & sFilterBitmapFlag; + if (mRunFlag & minikin::RunFlag::LEFT_EDGE) { + flags |= sTextRunLeftEdge; + } + if (mRunFlag & minikin::RunFlag::RIGHT_EDGE) { + flags |= sTextRunRightEdge; + } return flags; } @@ -196,6 +203,15 @@ void Paint::setJavaFlags(uint32_t flags) { mUnderline = (flags & sUnderlineFlag) != 0; mDevKern = (flags & sDevKernFlag) != 0; mFilterBitmap = (flags & sFilterBitmapFlag) != 0; + + std::underlying_type<minikin::RunFlag>::type rawFlag = minikin::RunFlag::NONE; + if (flags & sTextRunLeftEdge) { + rawFlag |= minikin::RunFlag::LEFT_EDGE; + } + if (flags & sTextRunRightEdge) { + rawFlag |= minikin::RunFlag::RIGHT_EDGE; + } + mRunFlag = static_cast<minikin::RunFlag>(rawFlag); } } // namespace android diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp index 8ba750372d18..d5725935551a 100644 --- a/libs/hwui/jni/android_graphics_Canvas.cpp +++ b/libs/hwui/jni/android_graphics_Canvas.cpp @@ -613,6 +613,12 @@ static void drawTextChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray c Paint* paint = reinterpret_cast<Paint*>(paintHandle); const Typeface* typeface = paint->getAndroidTypeface(); ScopedCharArrayRO text(env, charArray); + + // The drawText API is designed to draw entire line, so ignore the text run flag and draw the + // text as entire line mode. + const minikin::RunFlag originalRunFlag = paint->getRunFlag(); + paint->setRunFlag(minikin::RunFlag::WHOLE_LINE); + // drawTextString and drawTextChars doesn't use context info get_canvas(canvasHandle)->drawText( text.get() + index, count, // text buffer @@ -620,6 +626,7 @@ static void drawTextChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray c 0, count, // context range x, y, // draw position static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr /* measured text */); + paint->setRunFlag(originalRunFlag); } static void drawTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring strObj, @@ -629,6 +636,12 @@ static void drawTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring str Paint* paint = reinterpret_cast<Paint*>(paintHandle); const Typeface* typeface = paint->getAndroidTypeface(); const int count = end - start; + + // The drawText API is designed to draw entire line, so ignore the text run flag and draw the + // text as entire line mode. + const minikin::RunFlag originalRunFlag = paint->getRunFlag(); + paint->setRunFlag(minikin::RunFlag::WHOLE_LINE); + // drawTextString and drawTextChars doesn't use context info get_canvas(canvasHandle)->drawText( text.get() + start, count, // text buffer @@ -636,6 +649,7 @@ static void drawTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring str 0, count, // context range x, y, // draw position static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr /* measured text */); + paint->setRunFlag(originalRunFlag); } static void drawTextRunChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray charArray, @@ -681,9 +695,15 @@ static void drawTextOnPathChars(JNIEnv* env, jobject, jlong canvasHandle, jcharA jchar* jchars = env->GetCharArrayElements(text, NULL); + // The drawText API is designed to draw entire line, so ignore the text run flag and draw the + // text as entire line mode. + const minikin::RunFlag originalRunFlag = paint->getRunFlag(); + paint->setRunFlag(minikin::RunFlag::WHOLE_LINE); + get_canvas(canvasHandle)->drawTextOnPath(jchars + index, count, static_cast<minikin::Bidi>(bidiFlags), *path, hOffset, vOffset, *paint, typeface); + paint->setRunFlag(originalRunFlag); env->ReleaseCharArrayElements(text, jchars, 0); } @@ -697,9 +717,15 @@ static void drawTextOnPathString(JNIEnv* env, jobject, jlong canvasHandle, jstri const jchar* jchars = env->GetStringChars(text, NULL); int count = env->GetStringLength(text); + // The drawText API is designed to draw entire line, so ignore the text run flag and draw the + // text as entire line mode. + const minikin::RunFlag originalRunFlag = paint->getRunFlag(); + paint->setRunFlag(minikin::RunFlag::WHOLE_LINE); + get_canvas(canvasHandle)->drawTextOnPath(jchars, count, static_cast<minikin::Bidi>(bidiFlags), *path, hOffset, vOffset, *paint, typeface); + paint->setRunFlag(originalRunFlag); env->ReleaseStringChars(text, jchars); } |