Loading core/api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -45091,6 +45091,7 @@ package android.text { ctor protected Layout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float); method public void draw(android.graphics.Canvas); method public void draw(android.graphics.Canvas, android.graphics.Path, android.graphics.Paint, int); method public void fillCharacterBounds(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull float[], @IntRange(from=0) int); method public final android.text.Layout.Alignment getAlignment(); method public abstract int getBottomPadding(); method public void getCursorPath(int, android.graphics.Path, CharSequence); core/java/android/text/Layout.java +96 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.text; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Canvas; import android.graphics.Paint; Loading Loading @@ -1311,6 +1312,101 @@ public abstract class Layout { return horizontal; } /** * Return the characters' bounds in the given range. The {@code bounds} array will be filled * starting from {@code boundsStart} (inclusive). The coordinates are in local text layout. * * @param start the start index to compute the character bounds, inclusive. * @param end the end index to compute the character bounds, exclusive. * @param bounds the array to fill in the character bounds. The array is divided into segments * of four where each index in that segment represents left, top, right and * bottom of the character. * @param boundsStart the inclusive start index in the array to start filling in the values * from. * * @throws IndexOutOfBoundsException if the range defined by {@code start} and {@code end} * exceeds the range of the text, or {@code bounds} doesn't have enough space to store the * result. * @throws IllegalArgumentException if {@code bounds} is null. */ public void fillCharacterBounds(@IntRange(from = 0) int start, @IntRange(from = 0) int end, @NonNull float[] bounds, @IntRange(from = 0) int boundsStart) { if (start < 0 || end < start || end > mText.length()) { throw new IndexOutOfBoundsException("given range: " + start + ", " + end + " is " + "out of the text range: 0, " + mText.length()); } if (bounds == null) { throw new IllegalArgumentException("bounds can't be null."); } final int neededLength = 4 * (end - start); if (neededLength > bounds.length - boundsStart) { throw new IndexOutOfBoundsException("bounds doesn't have enough space to store the " + "result, needed: " + neededLength + " had: " + (bounds.length - boundsStart)); } if (start == end) { return; } final int startLine = getLineForOffset(start); final int endLine = getLineForOffset(end - 1); float[] horizontalBounds = null; for (int line = startLine; line <= endLine; ++line) { final int lineStart = getLineStart(line); final int lineEnd = getLineEnd(line); final int lineLength = lineEnd - lineStart; final int dir = getParagraphDirection(line); final boolean hasTab = getLineContainsTab(line); final Directions directions = getLineDirections(line); TabStops tabStops = null; if (hasTab && mText instanceof Spanned) { // Just checking this line should be good enough, tabs should be // consistent across all lines in a paragraph. TabStopSpan[] tabs = getParagraphSpans((Spanned) mText, lineStart, lineEnd, TabStopSpan.class); if (tabs.length > 0) { tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse } } final TextLine tl = TextLine.obtain(); tl.set(mPaint, mText, lineStart, lineEnd, dir, directions, hasTab, tabStops, getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line), isFallbackLineSpacingEnabled()); if (horizontalBounds == null || horizontalBounds.length < 2 * lineLength) { horizontalBounds = new float[2 * lineLength]; } tl.measureAllBounds(horizontalBounds, null); TextLine.recycle(tl); final int lineLeft = getParagraphLeft(line); final int lineRight = getParagraphRight(line); final int lineStartPos = getLineStartPos(line, lineLeft, lineRight); final int lineTop = getLineTop(line); final int lineBottom = getLineBottom(line); final int startIndex = Math.max(start, lineStart); final int endIndex = Math.min(end, lineEnd); for (int index = startIndex; index < endIndex; ++index) { final int offset = index - lineStart; final float left = horizontalBounds[offset * 2] + lineStartPos; final float right = horizontalBounds[offset * 2 + 1] + lineStartPos; final int boundsIndex = boundsStart + 4 * (index - start); bounds[boundsIndex] = left; bounds[boundsIndex + 1] = lineTop; bounds[boundsIndex + 2] = right; bounds[boundsIndex + 3] = lineBottom; } } } /** * Get the leftmost position that should be exposed for horizontal * scrolling on the specified line. Loading core/java/android/widget/TextView.java +29 −55 Original line number Diff line number Diff line Loading @@ -12530,64 +12530,38 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void populateCharacterBounds(CursorAnchorInfo.Builder builder, int startIndex, int endIndex, float viewportToContentHorizontalOffset, float viewportToContentVerticalOffset) { final int minLine = mLayout.getLineForOffset(startIndex); final int maxLine = mLayout.getLineForOffset(endIndex - 1); final Rect rect = new Rect(); getLocalVisibleRect(rect); final RectF visibleRect = new RectF(rect); for (int line = minLine; line <= maxLine; ++line) { final int lineStart = mLayout.getLineStart(line); final int lineEnd = mLayout.getLineEnd(line); final int offsetStart = Math.max(lineStart, startIndex); final int offsetEnd = Math.min(lineEnd, endIndex); final boolean ltrLine = mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT; final float[] widths = new float[offsetEnd - offsetStart]; mLayout.getPaint().getTextWidths(mTransformed, offsetStart, offsetEnd, widths); final float top = mLayout.getLineTop(line); final float bottom = mLayout.getLineBottom(line); for (int offset = offsetStart; offset < offsetEnd; ++offset) { final float charWidth = widths[offset - offsetStart]; final boolean isRtl = mLayout.isRtlCharAt(offset); // TODO: This doesn't work perfectly for text with custom styles and // TAB chars. final float left; if (ltrLine) { if (isRtl) { left = mLayout.getSecondaryHorizontal(offset) - charWidth; } else { left = mLayout.getPrimaryHorizontal(offset); } } else { if (!isRtl) { left = mLayout.getSecondaryHorizontal(offset); } else { left = mLayout.getPrimaryHorizontal(offset) - charWidth; } } final float right = left + charWidth; // TODO: Check top-right and bottom-left as well. final float localLeft = left + viewportToContentHorizontalOffset; final float localRight = right + viewportToContentHorizontalOffset; final float localTop = top + viewportToContentVerticalOffset; final float localBottom = bottom + viewportToContentVerticalOffset; final boolean isTopLeftVisible = visibleRect.contains(localLeft, localTop); final boolean isBottomRightVisible = visibleRect.contains(localRight, localBottom); final float[] characterBounds = new float[4 * (endIndex - startIndex)]; mLayout.fillCharacterBounds(startIndex, endIndex, characterBounds, 0); final int limit = endIndex - startIndex; for (int offset = 0; offset < limit; ++offset) { final float left = characterBounds[offset * 4] + viewportToContentHorizontalOffset; final float top = characterBounds[offset * 4 + 1] + viewportToContentVerticalOffset; final float right = characterBounds[offset * 4 + 2] + viewportToContentHorizontalOffset; final float bottom = characterBounds[offset * 4 + 3] + viewportToContentVerticalOffset; final boolean hasVisibleRegion = visibleRect.intersects(left, top, right, bottom); final boolean hasInVisibleRegion = !visibleRect.contains(left, top, right, bottom); int characterBoundsFlags = 0; if (isTopLeftVisible || isBottomRightVisible) { if (hasVisibleRegion) { characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION; } if (!isTopLeftVisible || !isBottomRightVisible) { if (hasInVisibleRegion) { characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; } if (isRtl) { if (mLayout.isRtlCharAt(offset)) { characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL; } // Here offset is the index in Java chars. builder.addCharacterBounds(offset, localLeft, localTop, localRight, localBottom, characterBoundsFlags); } builder.addCharacterBounds(offset + startIndex, left, top, right, bottom, characterBoundsFlags); } } Loading core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttf +16 B (904 B) File changed.No diff preview for this file type. View original file View changed file core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttx +1 −0 Original line number Diff line number Diff line Loading @@ -144,6 +144,7 @@ <map code="0x0056" name="5em" /> <!-- V --> <map code="0x0058" name="10em" /> <!-- X --> <map code="0x005f" name="0em" /> <!-- _ --> <map code="0x000a" name="0em" /> <!-- NEW_LINE --> <map code="0x05D0" name="1em" /> <!-- HEBREW LETTER ALEF --> <map code="0x05D1" name="5em" /> <!-- HEBREW LETTER BET --> <map code="0xfffd" name="7em" /> <!-- REPLACEMENT CHAR --> Loading Loading
core/api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -45091,6 +45091,7 @@ package android.text { ctor protected Layout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float); method public void draw(android.graphics.Canvas); method public void draw(android.graphics.Canvas, android.graphics.Path, android.graphics.Paint, int); method public void fillCharacterBounds(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull float[], @IntRange(from=0) int); method public final android.text.Layout.Alignment getAlignment(); method public abstract int getBottomPadding(); method public void getCursorPath(int, android.graphics.Path, CharSequence);
core/java/android/text/Layout.java +96 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.text; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Canvas; import android.graphics.Paint; Loading Loading @@ -1311,6 +1312,101 @@ public abstract class Layout { return horizontal; } /** * Return the characters' bounds in the given range. The {@code bounds} array will be filled * starting from {@code boundsStart} (inclusive). The coordinates are in local text layout. * * @param start the start index to compute the character bounds, inclusive. * @param end the end index to compute the character bounds, exclusive. * @param bounds the array to fill in the character bounds. The array is divided into segments * of four where each index in that segment represents left, top, right and * bottom of the character. * @param boundsStart the inclusive start index in the array to start filling in the values * from. * * @throws IndexOutOfBoundsException if the range defined by {@code start} and {@code end} * exceeds the range of the text, or {@code bounds} doesn't have enough space to store the * result. * @throws IllegalArgumentException if {@code bounds} is null. */ public void fillCharacterBounds(@IntRange(from = 0) int start, @IntRange(from = 0) int end, @NonNull float[] bounds, @IntRange(from = 0) int boundsStart) { if (start < 0 || end < start || end > mText.length()) { throw new IndexOutOfBoundsException("given range: " + start + ", " + end + " is " + "out of the text range: 0, " + mText.length()); } if (bounds == null) { throw new IllegalArgumentException("bounds can't be null."); } final int neededLength = 4 * (end - start); if (neededLength > bounds.length - boundsStart) { throw new IndexOutOfBoundsException("bounds doesn't have enough space to store the " + "result, needed: " + neededLength + " had: " + (bounds.length - boundsStart)); } if (start == end) { return; } final int startLine = getLineForOffset(start); final int endLine = getLineForOffset(end - 1); float[] horizontalBounds = null; for (int line = startLine; line <= endLine; ++line) { final int lineStart = getLineStart(line); final int lineEnd = getLineEnd(line); final int lineLength = lineEnd - lineStart; final int dir = getParagraphDirection(line); final boolean hasTab = getLineContainsTab(line); final Directions directions = getLineDirections(line); TabStops tabStops = null; if (hasTab && mText instanceof Spanned) { // Just checking this line should be good enough, tabs should be // consistent across all lines in a paragraph. TabStopSpan[] tabs = getParagraphSpans((Spanned) mText, lineStart, lineEnd, TabStopSpan.class); if (tabs.length > 0) { tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse } } final TextLine tl = TextLine.obtain(); tl.set(mPaint, mText, lineStart, lineEnd, dir, directions, hasTab, tabStops, getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line), isFallbackLineSpacingEnabled()); if (horizontalBounds == null || horizontalBounds.length < 2 * lineLength) { horizontalBounds = new float[2 * lineLength]; } tl.measureAllBounds(horizontalBounds, null); TextLine.recycle(tl); final int lineLeft = getParagraphLeft(line); final int lineRight = getParagraphRight(line); final int lineStartPos = getLineStartPos(line, lineLeft, lineRight); final int lineTop = getLineTop(line); final int lineBottom = getLineBottom(line); final int startIndex = Math.max(start, lineStart); final int endIndex = Math.min(end, lineEnd); for (int index = startIndex; index < endIndex; ++index) { final int offset = index - lineStart; final float left = horizontalBounds[offset * 2] + lineStartPos; final float right = horizontalBounds[offset * 2 + 1] + lineStartPos; final int boundsIndex = boundsStart + 4 * (index - start); bounds[boundsIndex] = left; bounds[boundsIndex + 1] = lineTop; bounds[boundsIndex + 2] = right; bounds[boundsIndex + 3] = lineBottom; } } } /** * Get the leftmost position that should be exposed for horizontal * scrolling on the specified line. Loading
core/java/android/widget/TextView.java +29 −55 Original line number Diff line number Diff line Loading @@ -12530,64 +12530,38 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void populateCharacterBounds(CursorAnchorInfo.Builder builder, int startIndex, int endIndex, float viewportToContentHorizontalOffset, float viewportToContentVerticalOffset) { final int minLine = mLayout.getLineForOffset(startIndex); final int maxLine = mLayout.getLineForOffset(endIndex - 1); final Rect rect = new Rect(); getLocalVisibleRect(rect); final RectF visibleRect = new RectF(rect); for (int line = minLine; line <= maxLine; ++line) { final int lineStart = mLayout.getLineStart(line); final int lineEnd = mLayout.getLineEnd(line); final int offsetStart = Math.max(lineStart, startIndex); final int offsetEnd = Math.min(lineEnd, endIndex); final boolean ltrLine = mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT; final float[] widths = new float[offsetEnd - offsetStart]; mLayout.getPaint().getTextWidths(mTransformed, offsetStart, offsetEnd, widths); final float top = mLayout.getLineTop(line); final float bottom = mLayout.getLineBottom(line); for (int offset = offsetStart; offset < offsetEnd; ++offset) { final float charWidth = widths[offset - offsetStart]; final boolean isRtl = mLayout.isRtlCharAt(offset); // TODO: This doesn't work perfectly for text with custom styles and // TAB chars. final float left; if (ltrLine) { if (isRtl) { left = mLayout.getSecondaryHorizontal(offset) - charWidth; } else { left = mLayout.getPrimaryHorizontal(offset); } } else { if (!isRtl) { left = mLayout.getSecondaryHorizontal(offset); } else { left = mLayout.getPrimaryHorizontal(offset) - charWidth; } } final float right = left + charWidth; // TODO: Check top-right and bottom-left as well. final float localLeft = left + viewportToContentHorizontalOffset; final float localRight = right + viewportToContentHorizontalOffset; final float localTop = top + viewportToContentVerticalOffset; final float localBottom = bottom + viewportToContentVerticalOffset; final boolean isTopLeftVisible = visibleRect.contains(localLeft, localTop); final boolean isBottomRightVisible = visibleRect.contains(localRight, localBottom); final float[] characterBounds = new float[4 * (endIndex - startIndex)]; mLayout.fillCharacterBounds(startIndex, endIndex, characterBounds, 0); final int limit = endIndex - startIndex; for (int offset = 0; offset < limit; ++offset) { final float left = characterBounds[offset * 4] + viewportToContentHorizontalOffset; final float top = characterBounds[offset * 4 + 1] + viewportToContentVerticalOffset; final float right = characterBounds[offset * 4 + 2] + viewportToContentHorizontalOffset; final float bottom = characterBounds[offset * 4 + 3] + viewportToContentVerticalOffset; final boolean hasVisibleRegion = visibleRect.intersects(left, top, right, bottom); final boolean hasInVisibleRegion = !visibleRect.contains(left, top, right, bottom); int characterBoundsFlags = 0; if (isTopLeftVisible || isBottomRightVisible) { if (hasVisibleRegion) { characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION; } if (!isTopLeftVisible || !isBottomRightVisible) { if (hasInVisibleRegion) { characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; } if (isRtl) { if (mLayout.isRtlCharAt(offset)) { characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL; } // Here offset is the index in Java chars. builder.addCharacterBounds(offset, localLeft, localTop, localRight, localBottom, characterBoundsFlags); } builder.addCharacterBounds(offset + startIndex, left, top, right, bottom, characterBoundsFlags); } } Loading
core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttf +16 B (904 B) File changed.No diff preview for this file type. View original file View changed file
core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttx +1 −0 Original line number Diff line number Diff line Loading @@ -144,6 +144,7 @@ <map code="0x0056" name="5em" /> <!-- V --> <map code="0x0058" name="10em" /> <!-- X --> <map code="0x005f" name="0em" /> <!-- _ --> <map code="0x000a" name="0em" /> <!-- NEW_LINE --> <map code="0x05D0" name="1em" /> <!-- HEBREW LETTER ALEF --> <map code="0x05D1" name="5em" /> <!-- HEBREW LETTER BET --> <map code="0xfffd" name="7em" /> <!-- REPLACEMENT CHAR --> Loading