Loading api/current.txt +40 −0 Original line number Diff line number Diff line Loading @@ -16510,6 +16510,23 @@ package android.graphics.pdf { package android.graphics.text { public class GlyphStyle { ctor public GlyphStyle(@ColorInt int, @FloatRange(from=0) float, @FloatRange(from=0) float, @FloatRange(from=0) float, int); ctor public GlyphStyle(@NonNull android.graphics.Paint); method public void applyToPaint(@NonNull android.graphics.Paint); method @ColorInt public int getColor(); method public int getFlags(); method @FloatRange(from=0) public float getFontSize(); method @FloatRange(from=0) public float getScaleX(); method @FloatRange(from=0) public float getSkewX(); method public void setColor(@ColorInt int); method public void setFlags(int); method public void setFontSize(@FloatRange(from=0) float); method public void setFromPaint(@NonNull android.graphics.Paint); method public void setScaleX(@FloatRange(from=0) float); method public void setSkewX(@FloatRange(from=0) float); } public class LineBreaker { method @NonNull public android.graphics.text.LineBreaker.Result computeLineBreaks(@NonNull android.graphics.text.MeasuredText, @NonNull android.graphics.text.LineBreaker.ParagraphConstraints, @IntRange(from=0) int); field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2 Loading Loading @@ -16570,6 +16587,25 @@ package android.graphics.text { method @NonNull public android.graphics.text.MeasuredText.Builder setComputeLayout(boolean); } public final class PositionedGlyphs { method public float getAscent(); method public float getDescent(); method @NonNull public android.graphics.fonts.Font getFont(@IntRange(from=0) int); method @IntRange(from=0) public int getGlyphId(@IntRange(from=0) int); method public float getOriginX(); method public float getOriginY(); method public float getPositionX(@IntRange(from=0) int); method public float getPositionY(@IntRange(from=0) int); method @NonNull public android.graphics.text.GlyphStyle getStyle(); method public float getTotalAdvance(); method @IntRange(from=0) public int glyphCount(); } public class TextShaper { method @NonNull public static android.graphics.text.PositionedGlyphs shapeTextRun(@NonNull char[], int, int, int, int, float, float, boolean, @NonNull android.graphics.Paint); method @NonNull public static android.graphics.text.PositionedGlyphs shapeTextRun(@NonNull CharSequence, int, int, int, int, float, float, boolean, @NonNull android.graphics.Paint); } } package android.hardware { Loading Loading @@ -50000,6 +50036,10 @@ package android.text { method @NonNull public android.text.StaticLayout.Builder setUseLineSpacingFromFallbacks(boolean); } public class StyledTextShaper { method @NonNull public static java.util.List<android.graphics.text.PositionedGlyphs> shapeText(@NonNull CharSequence, int, int, @NonNull android.text.TextDirectionHeuristic, @NonNull android.text.TextPaint); } public interface TextDirectionHeuristic { method public boolean isRtl(char[], int, int); method public boolean isRtl(CharSequence, int, int); core/java/android/text/StyledTextShaper.java 0 → 100644 +67 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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.annotation.NonNull; import android.graphics.Paint; import android.graphics.text.PositionedGlyphs; import android.graphics.text.TextShaper; import java.util.List; /** * Provides text shaping for multi-styled text. * * @see TextShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint) * @see TextShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint) * @see StyledTextShaper#shapeText(CharSequence, int, int, TextDirectionHeuristic, TextPaint) */ public class StyledTextShaper { private StyledTextShaper() {} /** * Shape multi-styled text. * * @param text a styled text. * @param start a start index of shaping target in the text. * @param count a length of shaping target in the text. * @param dir a text direction. * @param paint a paint * @return a shape result. */ public static @NonNull List<PositionedGlyphs> shapeText( @NonNull CharSequence text, int start, int count, @NonNull TextDirectionHeuristic dir, @NonNull TextPaint paint) { MeasuredParagraph mp = MeasuredParagraph.buildForBidi( text, start, start + count, dir, null); TextLine tl = TextLine.obtain(); try { tl.set(paint, text, start, start + count, mp.getParagraphDir(), mp.getDirections(start, start + count), false /* tabstop is not supported */, null, -1, -1 // ellipsis is not supported. ); return tl.shape(); } finally { TextLine.recycle(tl); } } } core/java/android/text/TextLine.java +122 −18 Original line number Diff line number Diff line Loading @@ -23,6 +23,8 @@ import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.FontMetricsInt; import android.graphics.text.PositionedGlyphs; import android.graphics.text.TextShaper; import android.os.Build; import android.text.Layout.Directions; import android.text.Layout.TabStops; Loading @@ -35,6 +37,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import java.util.ArrayList; import java.util.List; /** * Represents a line of styled text, for measuring in visual order and Loading Loading @@ -306,6 +309,36 @@ public class TextLine { return measure(mLen, false, fmi); } /** * Shape the TextLine. */ List<PositionedGlyphs> shape() { List<PositionedGlyphs> glyphs = new ArrayList<>(); float horizontal = 0; float x = 0; 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); int segStart = runStart; for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) { if (j == runLimit || charAt(j) == TAB_CHAR) { horizontal += shapeRun(glyphs, segStart, j, runIsRtl, x + horizontal, runIndex != (runCount - 1) || j != mLen); if (j != runLimit) { // charAt(j) == TAB_CHAR horizontal = mDir * nextTab(horizontal * mDir); } segStart = j + 1; } } } return glyphs; } /** * Returns the signed graphical offset from the leading margin. * Loading Loading @@ -483,12 +516,12 @@ public class TextLine { if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) { float w = -measureRun(start, limit, limit, runIsRtl, null); handleRun(start, limit, limit, runIsRtl, c, x + w, top, handleRun(start, limit, limit, runIsRtl, c, null, x + w, top, y, bottom, null, false); return w; } return handleRun(start, limit, limit, runIsRtl, c, x, top, return handleRun(start, limit, limit, runIsRtl, c, null, x, top, y, bottom, null, needWidth); } Loading @@ -507,9 +540,34 @@ public class TextLine { */ private float measureRun(int start, int offset, int limit, boolean runIsRtl, FontMetricsInt fmi) { return handleRun(start, offset, limit, runIsRtl, null, 0, 0, 0, 0, fmi, true); return handleRun(start, offset, limit, runIsRtl, null, null, 0, 0, 0, 0, fmi, true); } /** * Shape a unidirectional (but possibly multi-styled) run of text. * * @param glyphs the output positioned glyphs list * @param start the line-relative start * @param limit the line-relative limit * @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. * @return the signed width of the run, based on the paragraph direction. * Only valid if needWidth is true. */ private float shapeRun(List<PositionedGlyphs> glyphs, int start, int limit, boolean runIsRtl, float x, boolean needWidth) { if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) { float w = -measureRun(start, limit, limit, runIsRtl, null); handleRun(start, limit, limit, runIsRtl, null, glyphs, x + w, 0, 0, 0, null, false); return w; } return handleRun(start, limit, limit, runIsRtl, null, glyphs, x, 0, 0, 0, null, needWidth); } /** * Walk the cursor through this line, skipping conjuncts and * zero-width characters. Loading Loading @@ -841,6 +899,7 @@ public class TextLine { * @param end the end of the text * @param runIsRtl true if the run is right-to-left * @param c the canvas, can be null if rendering is not needed * @param glyphs the output positioned glyph list, can be null if not necessary * @param x the edge of the run closest to the leading margin * @param top the top of the line * @param y the baseline Loading @@ -854,7 +913,7 @@ public class TextLine { */ private float handleText(TextPaint wp, int start, int end, int contextStart, int contextEnd, boolean runIsRtl, Canvas c, float x, int top, int y, int bottom, Canvas c, List<PositionedGlyphs> glyphs, float x, int top, int y, int bottom, FontMetricsInt fmi, boolean needWidth, int offset, @Nullable ArrayList<DecorationInfo> decorations) { Loading @@ -878,7 +937,6 @@ public class TextLine { totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset); } if (c != null) { final float leftX, rightX; if (runIsRtl) { leftX = x - totalWidth; Loading @@ -888,6 +946,11 @@ public class TextLine { rightX = x + totalWidth; } if (glyphs != null) { shapeTextRun(glyphs, wp, start, end, contextStart, contextEnd, runIsRtl, leftX); } if (c != null) { if (wp.bgColor != 0) { int previousColor = wp.getColor(); Paint.Style previousStyle = wp.getStyle(); Loading Loading @@ -1072,6 +1135,7 @@ public class TextLine { * @param limit the limit of the run * @param runIsRtl true if the run is right-to-left * @param c the canvas, can be null * @param glyphs the output positioned glyphs, can be null * @param x the end of the run closest to the leading margin * @param top the top of the line * @param y the baseline Loading @@ -1082,7 +1146,8 @@ public class TextLine { * valid if needWidth is true */ private float handleRun(int start, int measureLimit, int limit, boolean runIsRtl, Canvas c, float x, int top, int y, int limit, boolean runIsRtl, Canvas c, List<PositionedGlyphs> glyphs, float x, int top, int y, int bottom, FontMetricsInt fmi, boolean needWidth) { if (measureLimit < start || measureLimit > limit) { Loading Loading @@ -1115,7 +1180,7 @@ public class TextLine { wp.set(mPaint); wp.setStartHyphenEdit(adjustStartHyphenEdit(start, wp.getStartHyphenEdit())); wp.setEndHyphenEdit(adjustEndHyphenEdit(limit, wp.getEndHyphenEdit())); return handleText(wp, start, limit, start, limit, runIsRtl, c, x, top, return handleText(wp, start, limit, start, limit, runIsRtl, c, glyphs, x, top, y, bottom, fmi, needWidth, measureLimit, null); } Loading Loading @@ -1196,8 +1261,8 @@ public class TextLine { adjustStartHyphenEdit(activeStart, mPaint.getStartHyphenEdit())); activePaint.setEndHyphenEdit( adjustEndHyphenEdit(activeEnd, mPaint.getEndHyphenEdit())); x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x, top, y, bottom, fmi, needWidth || activeEnd < measureLimit, x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, glyphs, x, top, y, bottom, fmi, needWidth || activeEnd < measureLimit, Math.min(activeEnd, mlimit), mDecorations); activeStart = j; Loading @@ -1223,7 +1288,7 @@ public class TextLine { adjustStartHyphenEdit(activeStart, mPaint.getStartHyphenEdit())); activePaint.setEndHyphenEdit( adjustEndHyphenEdit(activeEnd, mPaint.getEndHyphenEdit())); x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x, x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, glyphs, x, top, y, bottom, fmi, needWidth || activeEnd < measureLimit, Math.min(activeEnd, mlimit), mDecorations); } Loading Loading @@ -1259,6 +1324,45 @@ public class TextLine { } } /** * Shape a text run with the set-up paint. * * @param glyphs the output positioned glyphs list * @param paint the paint used to render the text * @param start the start of the run * @param end the end of the run * @param contextStart the start of context for the run * @param contextEnd the end of the context for the run * @param runIsRtl true if the run is right-to-left * @param x the x position of the left edge of the run */ private void shapeTextRun(List<PositionedGlyphs> glyphs, TextPaint paint, int start, int end, int contextStart, int contextEnd, boolean runIsRtl, float x) { int count = end - start; int contextCount = contextEnd - contextStart; if (mCharsValid) { glyphs.add(TextShaper.shapeTextRun( mChars, start, count, contextStart, contextCount, x, 0f, runIsRtl, paint )); } else { glyphs.add(TextShaper.shapeTextRun( mText, mStart + start, count, mStart + contextStart, contextCount, x, 0f, runIsRtl, paint )); } } /** * Returns the next tab position. * Loading graphics/java/android/graphics/fonts/Font.java +74 −0 Original line number Diff line number Diff line Loading @@ -26,8 +26,11 @@ import android.graphics.Paint; import android.graphics.RectF; import android.os.LocaleList; import android.os.ParcelFileDescriptor; import android.util.Log; import android.util.LongSparseArray; import android.util.TypedValue; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import dalvik.annotation.optimization.CriticalNative; Loading @@ -40,6 +43,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.FileChannel; Loading @@ -56,6 +60,15 @@ public final class Font { private static final int STYLE_ITALIC = 1; private static final int STYLE_NORMAL = 0; private static final Object MAP_LOCK = new Object(); // We need to have mapping from native ptr to Font object for later accessing from TextShape // result since Typeface doesn't have reference to Font object and it is not always created from // Font object. Sometimes Typeface is created in native layer only and there might not be Font // object in Java layer. So, if not found in this cache, create new Font object for API user. @GuardedBy("MAP_LOCK") private static final LongSparseArray<WeakReference<Font>> FONT_PTR_MAP = new LongSparseArray<>(); /** * A builder class for creating new Font. */ Loading Loading @@ -501,6 +514,10 @@ public final class Font { mTtcIndex = ttcIndex; mAxes = axes; mLocaleList = localeList; synchronized (MAP_LOCK) { FONT_PTR_MAP.append(mNativePtr, new WeakReference<>(this)); } } /** Loading Loading @@ -637,6 +654,63 @@ public final class Font { + "}"; } /** * Lookup Font object from native pointer or create new one if not found. * @hide */ public static Font findOrCreateFontFromNativePtr(long ptr) { // First, lookup from known mapps. synchronized (MAP_LOCK) { WeakReference<Font> fontRef = FONT_PTR_MAP.get(ptr); if (fontRef != null) { Font font = fontRef.get(); if (font != null) { return font; } } // If not found, create Font object from native object for Java API users. ByteBuffer buffer = NativeFontBufferHelper.refByteBuffer(ptr); long packed = nGetFontInfo(ptr); int weight = (int) (packed & 0x0000_0000_0000_FFFFL); boolean italic = (packed & 0x0000_0000_0001_0000L) != 0; int ttcIndex = (int) ((packed & 0x0000_FFFF_0000_0000L) >> 32); int axisCount = (int) ((packed & 0xFFFF_0000_0000_0000L) >> 48); FontVariationAxis[] axes = new FontVariationAxis[axisCount]; char[] charBuffer = new char[4]; for (int i = 0; i < axisCount; ++i) { long packedAxis = nGetAxisInfo(ptr, i); float value = Float.intBitsToFloat((int) (packedAxis & 0x0000_0000_FFFF_FFFFL)); charBuffer[0] = (char) ((packedAxis & 0xFF00_0000_0000_0000L) >> 56); charBuffer[1] = (char) ((packedAxis & 0x00FF_0000_0000_0000L) >> 48); charBuffer[2] = (char) ((packedAxis & 0x0000_FF00_0000_0000L) >> 40); charBuffer[3] = (char) ((packedAxis & 0x0000_00FF_0000_0000L) >> 32); axes[i] = new FontVariationAxis(new String(charBuffer), value); } Font.Builder builder = new Font.Builder(buffer) .setWeight(weight) .setSlant(italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT) .setTtcIndex(ttcIndex) .setFontVariationSettings(axes); Font newFont = null; try { newFont = builder.build(); FONT_PTR_MAP.append(ptr, new WeakReference<>(newFont)); } catch (IOException e) { // This must not happen since the buffer was already created once. Log.e("Font", "Failed to create font object from existing buffer.", e); } return newFont; } } @CriticalNative private static native long nGetFontInfo(long ptr); @CriticalNative private static native long nGetAxisInfo(long ptr, int i); @FastNative private static native float nGetGlyphBounds(long font, int glyphId, long paint, RectF rect); Loading graphics/java/android/graphics/fonts/NativeFontBufferHelper.java 0 → 100644 +62 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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.graphics.fonts; import android.annotation.NonNull; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; import libcore.util.NativeAllocationRegistry; import java.nio.ByteBuffer; /** * This is a helper class for showing native allocated buffer in Java API. * * @hide */ public class NativeFontBufferHelper { private NativeFontBufferHelper() {} private static final NativeAllocationRegistry REGISTRY = NativeAllocationRegistry.createMalloced( ByteBuffer.class.getClassLoader(), nGetReleaseFunc()); /** * Wrap native buffer with ByteBuffer with adding reference to it. */ public static @NonNull ByteBuffer refByteBuffer(long fontPtr) { long refPtr = nRefFontBuffer(fontPtr); ByteBuffer buffer = nWrapByteBuffer(refPtr); // Releasing native object so that decreasing shared pointer ref count when the byte buffer // is GCed. REGISTRY.registerNativeAllocation(buffer, refPtr); return buffer; } @CriticalNative private static native long nRefFontBuffer(long fontPtr); @FastNative private static native ByteBuffer nWrapByteBuffer(long refPtr); @CriticalNative private static native long nGetReleaseFunc(); } Loading
api/current.txt +40 −0 Original line number Diff line number Diff line Loading @@ -16510,6 +16510,23 @@ package android.graphics.pdf { package android.graphics.text { public class GlyphStyle { ctor public GlyphStyle(@ColorInt int, @FloatRange(from=0) float, @FloatRange(from=0) float, @FloatRange(from=0) float, int); ctor public GlyphStyle(@NonNull android.graphics.Paint); method public void applyToPaint(@NonNull android.graphics.Paint); method @ColorInt public int getColor(); method public int getFlags(); method @FloatRange(from=0) public float getFontSize(); method @FloatRange(from=0) public float getScaleX(); method @FloatRange(from=0) public float getSkewX(); method public void setColor(@ColorInt int); method public void setFlags(int); method public void setFontSize(@FloatRange(from=0) float); method public void setFromPaint(@NonNull android.graphics.Paint); method public void setScaleX(@FloatRange(from=0) float); method public void setSkewX(@FloatRange(from=0) float); } public class LineBreaker { method @NonNull public android.graphics.text.LineBreaker.Result computeLineBreaks(@NonNull android.graphics.text.MeasuredText, @NonNull android.graphics.text.LineBreaker.ParagraphConstraints, @IntRange(from=0) int); field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2 Loading Loading @@ -16570,6 +16587,25 @@ package android.graphics.text { method @NonNull public android.graphics.text.MeasuredText.Builder setComputeLayout(boolean); } public final class PositionedGlyphs { method public float getAscent(); method public float getDescent(); method @NonNull public android.graphics.fonts.Font getFont(@IntRange(from=0) int); method @IntRange(from=0) public int getGlyphId(@IntRange(from=0) int); method public float getOriginX(); method public float getOriginY(); method public float getPositionX(@IntRange(from=0) int); method public float getPositionY(@IntRange(from=0) int); method @NonNull public android.graphics.text.GlyphStyle getStyle(); method public float getTotalAdvance(); method @IntRange(from=0) public int glyphCount(); } public class TextShaper { method @NonNull public static android.graphics.text.PositionedGlyphs shapeTextRun(@NonNull char[], int, int, int, int, float, float, boolean, @NonNull android.graphics.Paint); method @NonNull public static android.graphics.text.PositionedGlyphs shapeTextRun(@NonNull CharSequence, int, int, int, int, float, float, boolean, @NonNull android.graphics.Paint); } } package android.hardware { Loading Loading @@ -50000,6 +50036,10 @@ package android.text { method @NonNull public android.text.StaticLayout.Builder setUseLineSpacingFromFallbacks(boolean); } public class StyledTextShaper { method @NonNull public static java.util.List<android.graphics.text.PositionedGlyphs> shapeText(@NonNull CharSequence, int, int, @NonNull android.text.TextDirectionHeuristic, @NonNull android.text.TextPaint); } public interface TextDirectionHeuristic { method public boolean isRtl(char[], int, int); method public boolean isRtl(CharSequence, int, int);
core/java/android/text/StyledTextShaper.java 0 → 100644 +67 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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.annotation.NonNull; import android.graphics.Paint; import android.graphics.text.PositionedGlyphs; import android.graphics.text.TextShaper; import java.util.List; /** * Provides text shaping for multi-styled text. * * @see TextShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint) * @see TextShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint) * @see StyledTextShaper#shapeText(CharSequence, int, int, TextDirectionHeuristic, TextPaint) */ public class StyledTextShaper { private StyledTextShaper() {} /** * Shape multi-styled text. * * @param text a styled text. * @param start a start index of shaping target in the text. * @param count a length of shaping target in the text. * @param dir a text direction. * @param paint a paint * @return a shape result. */ public static @NonNull List<PositionedGlyphs> shapeText( @NonNull CharSequence text, int start, int count, @NonNull TextDirectionHeuristic dir, @NonNull TextPaint paint) { MeasuredParagraph mp = MeasuredParagraph.buildForBidi( text, start, start + count, dir, null); TextLine tl = TextLine.obtain(); try { tl.set(paint, text, start, start + count, mp.getParagraphDir(), mp.getDirections(start, start + count), false /* tabstop is not supported */, null, -1, -1 // ellipsis is not supported. ); return tl.shape(); } finally { TextLine.recycle(tl); } } }
core/java/android/text/TextLine.java +122 −18 Original line number Diff line number Diff line Loading @@ -23,6 +23,8 @@ import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.FontMetricsInt; import android.graphics.text.PositionedGlyphs; import android.graphics.text.TextShaper; import android.os.Build; import android.text.Layout.Directions; import android.text.Layout.TabStops; Loading @@ -35,6 +37,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import java.util.ArrayList; import java.util.List; /** * Represents a line of styled text, for measuring in visual order and Loading Loading @@ -306,6 +309,36 @@ public class TextLine { return measure(mLen, false, fmi); } /** * Shape the TextLine. */ List<PositionedGlyphs> shape() { List<PositionedGlyphs> glyphs = new ArrayList<>(); float horizontal = 0; float x = 0; 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); int segStart = runStart; for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) { if (j == runLimit || charAt(j) == TAB_CHAR) { horizontal += shapeRun(glyphs, segStart, j, runIsRtl, x + horizontal, runIndex != (runCount - 1) || j != mLen); if (j != runLimit) { // charAt(j) == TAB_CHAR horizontal = mDir * nextTab(horizontal * mDir); } segStart = j + 1; } } } return glyphs; } /** * Returns the signed graphical offset from the leading margin. * Loading Loading @@ -483,12 +516,12 @@ public class TextLine { if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) { float w = -measureRun(start, limit, limit, runIsRtl, null); handleRun(start, limit, limit, runIsRtl, c, x + w, top, handleRun(start, limit, limit, runIsRtl, c, null, x + w, top, y, bottom, null, false); return w; } return handleRun(start, limit, limit, runIsRtl, c, x, top, return handleRun(start, limit, limit, runIsRtl, c, null, x, top, y, bottom, null, needWidth); } Loading @@ -507,9 +540,34 @@ public class TextLine { */ private float measureRun(int start, int offset, int limit, boolean runIsRtl, FontMetricsInt fmi) { return handleRun(start, offset, limit, runIsRtl, null, 0, 0, 0, 0, fmi, true); return handleRun(start, offset, limit, runIsRtl, null, null, 0, 0, 0, 0, fmi, true); } /** * Shape a unidirectional (but possibly multi-styled) run of text. * * @param glyphs the output positioned glyphs list * @param start the line-relative start * @param limit the line-relative limit * @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. * @return the signed width of the run, based on the paragraph direction. * Only valid if needWidth is true. */ private float shapeRun(List<PositionedGlyphs> glyphs, int start, int limit, boolean runIsRtl, float x, boolean needWidth) { if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) { float w = -measureRun(start, limit, limit, runIsRtl, null); handleRun(start, limit, limit, runIsRtl, null, glyphs, x + w, 0, 0, 0, null, false); return w; } return handleRun(start, limit, limit, runIsRtl, null, glyphs, x, 0, 0, 0, null, needWidth); } /** * Walk the cursor through this line, skipping conjuncts and * zero-width characters. Loading Loading @@ -841,6 +899,7 @@ public class TextLine { * @param end the end of the text * @param runIsRtl true if the run is right-to-left * @param c the canvas, can be null if rendering is not needed * @param glyphs the output positioned glyph list, can be null if not necessary * @param x the edge of the run closest to the leading margin * @param top the top of the line * @param y the baseline Loading @@ -854,7 +913,7 @@ public class TextLine { */ private float handleText(TextPaint wp, int start, int end, int contextStart, int contextEnd, boolean runIsRtl, Canvas c, float x, int top, int y, int bottom, Canvas c, List<PositionedGlyphs> glyphs, float x, int top, int y, int bottom, FontMetricsInt fmi, boolean needWidth, int offset, @Nullable ArrayList<DecorationInfo> decorations) { Loading @@ -878,7 +937,6 @@ public class TextLine { totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset); } if (c != null) { final float leftX, rightX; if (runIsRtl) { leftX = x - totalWidth; Loading @@ -888,6 +946,11 @@ public class TextLine { rightX = x + totalWidth; } if (glyphs != null) { shapeTextRun(glyphs, wp, start, end, contextStart, contextEnd, runIsRtl, leftX); } if (c != null) { if (wp.bgColor != 0) { int previousColor = wp.getColor(); Paint.Style previousStyle = wp.getStyle(); Loading Loading @@ -1072,6 +1135,7 @@ public class TextLine { * @param limit the limit of the run * @param runIsRtl true if the run is right-to-left * @param c the canvas, can be null * @param glyphs the output positioned glyphs, can be null * @param x the end of the run closest to the leading margin * @param top the top of the line * @param y the baseline Loading @@ -1082,7 +1146,8 @@ public class TextLine { * valid if needWidth is true */ private float handleRun(int start, int measureLimit, int limit, boolean runIsRtl, Canvas c, float x, int top, int y, int limit, boolean runIsRtl, Canvas c, List<PositionedGlyphs> glyphs, float x, int top, int y, int bottom, FontMetricsInt fmi, boolean needWidth) { if (measureLimit < start || measureLimit > limit) { Loading Loading @@ -1115,7 +1180,7 @@ public class TextLine { wp.set(mPaint); wp.setStartHyphenEdit(adjustStartHyphenEdit(start, wp.getStartHyphenEdit())); wp.setEndHyphenEdit(adjustEndHyphenEdit(limit, wp.getEndHyphenEdit())); return handleText(wp, start, limit, start, limit, runIsRtl, c, x, top, return handleText(wp, start, limit, start, limit, runIsRtl, c, glyphs, x, top, y, bottom, fmi, needWidth, measureLimit, null); } Loading Loading @@ -1196,8 +1261,8 @@ public class TextLine { adjustStartHyphenEdit(activeStart, mPaint.getStartHyphenEdit())); activePaint.setEndHyphenEdit( adjustEndHyphenEdit(activeEnd, mPaint.getEndHyphenEdit())); x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x, top, y, bottom, fmi, needWidth || activeEnd < measureLimit, x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, glyphs, x, top, y, bottom, fmi, needWidth || activeEnd < measureLimit, Math.min(activeEnd, mlimit), mDecorations); activeStart = j; Loading @@ -1223,7 +1288,7 @@ public class TextLine { adjustStartHyphenEdit(activeStart, mPaint.getStartHyphenEdit())); activePaint.setEndHyphenEdit( adjustEndHyphenEdit(activeEnd, mPaint.getEndHyphenEdit())); x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x, x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, glyphs, x, top, y, bottom, fmi, needWidth || activeEnd < measureLimit, Math.min(activeEnd, mlimit), mDecorations); } Loading Loading @@ -1259,6 +1324,45 @@ public class TextLine { } } /** * Shape a text run with the set-up paint. * * @param glyphs the output positioned glyphs list * @param paint the paint used to render the text * @param start the start of the run * @param end the end of the run * @param contextStart the start of context for the run * @param contextEnd the end of the context for the run * @param runIsRtl true if the run is right-to-left * @param x the x position of the left edge of the run */ private void shapeTextRun(List<PositionedGlyphs> glyphs, TextPaint paint, int start, int end, int contextStart, int contextEnd, boolean runIsRtl, float x) { int count = end - start; int contextCount = contextEnd - contextStart; if (mCharsValid) { glyphs.add(TextShaper.shapeTextRun( mChars, start, count, contextStart, contextCount, x, 0f, runIsRtl, paint )); } else { glyphs.add(TextShaper.shapeTextRun( mText, mStart + start, count, mStart + contextStart, contextCount, x, 0f, runIsRtl, paint )); } } /** * Returns the next tab position. * Loading
graphics/java/android/graphics/fonts/Font.java +74 −0 Original line number Diff line number Diff line Loading @@ -26,8 +26,11 @@ import android.graphics.Paint; import android.graphics.RectF; import android.os.LocaleList; import android.os.ParcelFileDescriptor; import android.util.Log; import android.util.LongSparseArray; import android.util.TypedValue; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import dalvik.annotation.optimization.CriticalNative; Loading @@ -40,6 +43,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.FileChannel; Loading @@ -56,6 +60,15 @@ public final class Font { private static final int STYLE_ITALIC = 1; private static final int STYLE_NORMAL = 0; private static final Object MAP_LOCK = new Object(); // We need to have mapping from native ptr to Font object for later accessing from TextShape // result since Typeface doesn't have reference to Font object and it is not always created from // Font object. Sometimes Typeface is created in native layer only and there might not be Font // object in Java layer. So, if not found in this cache, create new Font object for API user. @GuardedBy("MAP_LOCK") private static final LongSparseArray<WeakReference<Font>> FONT_PTR_MAP = new LongSparseArray<>(); /** * A builder class for creating new Font. */ Loading Loading @@ -501,6 +514,10 @@ public final class Font { mTtcIndex = ttcIndex; mAxes = axes; mLocaleList = localeList; synchronized (MAP_LOCK) { FONT_PTR_MAP.append(mNativePtr, new WeakReference<>(this)); } } /** Loading Loading @@ -637,6 +654,63 @@ public final class Font { + "}"; } /** * Lookup Font object from native pointer or create new one if not found. * @hide */ public static Font findOrCreateFontFromNativePtr(long ptr) { // First, lookup from known mapps. synchronized (MAP_LOCK) { WeakReference<Font> fontRef = FONT_PTR_MAP.get(ptr); if (fontRef != null) { Font font = fontRef.get(); if (font != null) { return font; } } // If not found, create Font object from native object for Java API users. ByteBuffer buffer = NativeFontBufferHelper.refByteBuffer(ptr); long packed = nGetFontInfo(ptr); int weight = (int) (packed & 0x0000_0000_0000_FFFFL); boolean italic = (packed & 0x0000_0000_0001_0000L) != 0; int ttcIndex = (int) ((packed & 0x0000_FFFF_0000_0000L) >> 32); int axisCount = (int) ((packed & 0xFFFF_0000_0000_0000L) >> 48); FontVariationAxis[] axes = new FontVariationAxis[axisCount]; char[] charBuffer = new char[4]; for (int i = 0; i < axisCount; ++i) { long packedAxis = nGetAxisInfo(ptr, i); float value = Float.intBitsToFloat((int) (packedAxis & 0x0000_0000_FFFF_FFFFL)); charBuffer[0] = (char) ((packedAxis & 0xFF00_0000_0000_0000L) >> 56); charBuffer[1] = (char) ((packedAxis & 0x00FF_0000_0000_0000L) >> 48); charBuffer[2] = (char) ((packedAxis & 0x0000_FF00_0000_0000L) >> 40); charBuffer[3] = (char) ((packedAxis & 0x0000_00FF_0000_0000L) >> 32); axes[i] = new FontVariationAxis(new String(charBuffer), value); } Font.Builder builder = new Font.Builder(buffer) .setWeight(weight) .setSlant(italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT) .setTtcIndex(ttcIndex) .setFontVariationSettings(axes); Font newFont = null; try { newFont = builder.build(); FONT_PTR_MAP.append(ptr, new WeakReference<>(newFont)); } catch (IOException e) { // This must not happen since the buffer was already created once. Log.e("Font", "Failed to create font object from existing buffer.", e); } return newFont; } } @CriticalNative private static native long nGetFontInfo(long ptr); @CriticalNative private static native long nGetAxisInfo(long ptr, int i); @FastNative private static native float nGetGlyphBounds(long font, int glyphId, long paint, RectF rect); Loading
graphics/java/android/graphics/fonts/NativeFontBufferHelper.java 0 → 100644 +62 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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.graphics.fonts; import android.annotation.NonNull; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; import libcore.util.NativeAllocationRegistry; import java.nio.ByteBuffer; /** * This is a helper class for showing native allocated buffer in Java API. * * @hide */ public class NativeFontBufferHelper { private NativeFontBufferHelper() {} private static final NativeAllocationRegistry REGISTRY = NativeAllocationRegistry.createMalloced( ByteBuffer.class.getClassLoader(), nGetReleaseFunc()); /** * Wrap native buffer with ByteBuffer with adding reference to it. */ public static @NonNull ByteBuffer refByteBuffer(long fontPtr) { long refPtr = nRefFontBuffer(fontPtr); ByteBuffer buffer = nWrapByteBuffer(refPtr); // Releasing native object so that decreasing shared pointer ref count when the byte buffer // is GCed. REGISTRY.registerNativeAllocation(buffer, refPtr); return buffer; } @CriticalNative private static native long nRefFontBuffer(long fontPtr); @FastNative private static native ByteBuffer nWrapByteBuffer(long refPtr); @CriticalNative private static native long nGetReleaseFunc(); }