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
diff --git a/core/api/current.txt b/core/api/current.txt
index 15c054f..5fa0c66 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -16400,6 +16400,8 @@
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 ac5eb3c..b268c2e 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -671,9 +671,18 @@
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 @@
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 135935c..2175b47 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -291,6 +291,97 @@
}
/**
+ * 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 @@
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 @@
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 @@
}
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 @@
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 @@
+ "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 @@
}
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 @@
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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
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 @@
}
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 @@
* @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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
*/
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 0000000..27869bb
--- /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 f6ba103..ae61a2d 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 @@
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 @@
/** @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 @@
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 @@
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 @@
if (text.length == 0 || count == 0) {
return 0;
}
- if (!mHasCompatScaling) {
- nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
- return count;
- }
+ 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;
+ 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);
}
- return count;
}
/**
@@ -2849,19 +2932,25 @@
if (text.length() == 0 || start == end) {
return 0;
}
- if (!mHasCompatScaling) {
- nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
- return end - start;
- }
+ 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;
+ 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);
}
- return end - start;
}
/**
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 2d33e8d..6da0719 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -269,6 +269,10 @@
* 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 00d049c..6ebfc63 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -41,6 +41,14 @@
#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 833069f..5613369 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -72,10 +72,13 @@
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 @@
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 ef4dce5..708f96e 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 @@
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 @@
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 aac928f..c32ea01 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -47,8 +47,8 @@
, mFilterBitmap(paint.mFilterBitmap)
, mStrikeThru(paint.mStrikeThru)
, mUnderline(paint.mUnderline)
- , mDevKern(paint.mDevKern) {}
-
+ , mDevKern(paint.mDevKern)
+ , mRunFlag(paint.mRunFlag) {}
Paint::~Paint() {}
@@ -68,21 +68,19 @@
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 @@
mStrikeThru = false;
mUnderline = false;
mDevKern = false;
+ mRunFlag = minikin::RunFlag::NONE;
}
void Paint::setLooper(sk_sp<BlurDrawLooper> looper) {
@@ -133,6 +132,8 @@
// 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 @@
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 @@
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 8ba7503..d572593 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -613,6 +613,12 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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);
}