/* * Copyright (C) 2006 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; import android.annotation.ColorInt; import android.annotation.ColorLong; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; import android.os.Build; import android.os.LocaleList; import android.text.SpannableString; import android.text.SpannedString; import android.text.TextUtils; import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.awt.Font; import java.awt.font.TextAttribute; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Objects; public class Paint { private static final String TAG = "Paint"; @ColorLong private long mColor; private ColorFilter mColorFilter; private MaskFilter mMaskFilter; private PathEffect mPathEffect; private Shader mShader; private Xfermode mXfermode; private boolean mHasCompatScaling; private float mCompatScaling; private float mInvCompatScaling; private LocaleList mLocales; private String mFontFeatureSettings; private String mFontVariationSettings; private float mShadowLayerRadius; private float mShadowLayerDx; private float mShadowLayerDy; @ColorLong private long mShadowLayerColor; private int mFlags; private Style mStyle = Style.FILL; private float mStrokeWidth = 1.0f; private Cap mStrokeCap = Cap.BUTT; private Typeface mTypeface = Typeface.DEFAULT; private static final Object sCacheLock = new Object(); private static final HashMap sMinikinLocaleListIdCache = new HashMap<>(); public int mBidiFlags = BIDI_DEFAULT_LTR; static final Style[] sStyleArray = { Style.FILL, Style.STROKE, Style.FILL_AND_STROKE }; static final Cap[] sCapArray = { Cap.BUTT, Cap.ROUND, Cap.SQUARE }; static final Join[] sJoinArray = { Join.MITER, Join.ROUND, Join.BEVEL }; static final Align[] sAlignArray = { Align.LEFT, Align.CENTER, Align.RIGHT }; /** @hide */ @Retention(RetentionPolicy.SOURCE) public @interface PaintFlag{} public static final int ANTI_ALIAS_FLAG = 0x01; public static final int FILTER_BITMAP_FLAG = 0x02; public static final int DITHER_FLAG = 0x04; public static final int UNDERLINE_TEXT_FLAG = 0x08; public static final int STRIKE_THRU_TEXT_FLAG = 0x10; public static final int FAKE_BOLD_TEXT_FLAG = 0x20; public static final int LINEAR_TEXT_FLAG = 0x40; public static final int SUBPIXEL_TEXT_FLAG = 0x80; /** Legacy Paint flag, no longer used. */ public static final int DEV_KERN_TEXT_FLAG = 0x100; /** @hide bit mask for the flag enabling subpixel glyph rendering for text */ public static final int LCD_RENDER_TEXT_FLAG = 0x200; public static final int EMBEDDED_BITMAP_TEXT_FLAG = 0x400; /** @hide bit mask for the flag forcing freetype's autohinter on for text */ public static final int AUTO_HINTING_TEXT_FLAG = 0x800; public static final int VERTICAL_TEXT_FLAG = 0x1000; public static final int TEXT_RUN_FLAG_LEFT_EDGE = 0x2000; 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; public static final int HINTING_OFF = 0x0; public static final int HINTING_ON = 0x1; public static final int BIDI_LTR = 0x0; public static final int BIDI_RTL = 0x1; public static final int BIDI_DEFAULT_LTR = 0x2; public static final int BIDI_DEFAULT_RTL = 0x3; public static final int BIDI_FORCE_LTR = 0x4; public static final int BIDI_FORCE_RTL = 0x5; private static final int BIDI_MAX_FLAG_VALUE = BIDI_FORCE_RTL; private static final int BIDI_FLAG_MASK = 0x7; public static final int DIRECTION_LTR = 0; public static final int DIRECTION_RTL = 1; /** @hide */ @Retention(RetentionPolicy.SOURCE) public @interface CursorOption {} public static final int CURSOR_AFTER = 0; public static final int CURSOR_AT_OR_AFTER = 1; public static final int CURSOR_BEFORE = 2; public static final int CURSOR_AT_OR_BEFORE = 3; public static final int CURSOR_AT = 4; private static final int CURSOR_OPT_MAX_VALUE = CURSOR_AT; /** @hide */ @Retention(RetentionPolicy.SOURCE) public @interface StartHyphenEdit {} public static final int START_HYPHEN_EDIT_NO_EDIT = 0x00; public static final int START_HYPHEN_EDIT_INSERT_HYPHEN = 0x01; public static final int START_HYPHEN_EDIT_INSERT_ZWJ = 0x02; /** @hide */ @Retention(RetentionPolicy.SOURCE) public @interface EndHyphenEdit {} public static final int END_HYPHEN_EDIT_NO_EDIT = 0x00; public static final int END_HYPHEN_EDIT_REPLACE_WITH_HYPHEN = 0x01; public static final int END_HYPHEN_EDIT_INSERT_HYPHEN = 0x02; public static final int END_HYPHEN_EDIT_INSERT_ARMENIAN_HYPHEN = 0x03; public static final int END_HYPHEN_EDIT_INSERT_MAQAF = 0x04; public static final int END_HYPHEN_EDIT_INSERT_UCAS_HYPHEN = 0x05; public static final int END_HYPHEN_EDIT_INSERT_ZWJ_AND_HYPHEN = 0x06; public enum Style { FILL (0), STROKE (1), FILL_AND_STROKE (2); Style(int nativeInt) { this.nativeInt = nativeInt; } final int nativeInt; } public enum Cap { BUTT (0), ROUND (1), SQUARE (2); private Cap(int nativeInt) { this.nativeInt = nativeInt; } final int nativeInt; } public enum Join { MITER (0), ROUND (1), BEVEL (2); private Join(int nativeInt) { this.nativeInt = nativeInt; } final int nativeInt; } public enum Align { LEFT (0), CENTER (1), RIGHT (2); private Align(int nativeInt) { this.nativeInt = nativeInt; } final int nativeInt; } public Paint() { this(ANTI_ALIAS_FLAG); } public Paint(int flags) { setFlags(flags | HIDDEN_DEFAULT_PAINT_FLAGS); // TODO: Turning off hinting has undesirable side effects, we need to // revisit hinting once we add support for subpixel positioning // setHinting(DisplayMetrics.DENSITY_DEVICE >= DisplayMetrics.DENSITY_TV // ? HINTING_OFF : HINTING_ON); mCompatScaling = mInvCompatScaling = 1; mColor = Color.pack(Color.BLACK); } public Paint(Paint paint) { setClassVariablesFrom(paint); } /** Restores the paint to its default settings. */ public void reset() { setFlags(HIDDEN_DEFAULT_PAINT_FLAGS | ANTI_ALIAS_FLAG); // TODO: Turning off hinting has undesirable side effects, we need to // revisit hinting once we add support for subpixel positioning // setHinting(DisplayMetrics.DENSITY_DEVICE >= DisplayMetrics.DENSITY_TV // ? HINTING_OFF : HINTING_ON); mColor = Color.pack(Color.BLACK); mColorFilter = null; mMaskFilter = null; mPathEffect = null; mShader = null; mXfermode = null; mHasCompatScaling = false; mCompatScaling = 1; mInvCompatScaling = 1; mBidiFlags = BIDI_DEFAULT_LTR; mFontFeatureSettings = null; mFontVariationSettings = null; mShadowLayerRadius = 0.0f; mShadowLayerDx = 0.0f; mShadowLayerDy = 0.0f; mShadowLayerColor = Color.pack(0); mStyle = Style.FILL; mStrokeWidth = 1.0f; mStrokeCap = Cap.BUTT; mTypeface = Typeface.DEFAULT; setFlags(ANTI_ALIAS_FLAG); } public void set(Paint src) { if (this != src) { // copy over the native settings setClassVariablesFrom(src); } } private void setClassVariablesFrom(Paint paint) { mColor = paint.mColor; mColorFilter = paint.mColorFilter; mMaskFilter = paint.mMaskFilter; mPathEffect = paint.mPathEffect; mShader = paint.mShader; mXfermode = paint.mXfermode; mHasCompatScaling = paint.mHasCompatScaling; mCompatScaling = paint.mCompatScaling; mInvCompatScaling = paint.mInvCompatScaling; mBidiFlags = paint.mBidiFlags; mLocales = paint.mLocales; mFontFeatureSettings = paint.mFontFeatureSettings; mFontVariationSettings = paint.mFontVariationSettings; mShadowLayerRadius = paint.mShadowLayerRadius; mShadowLayerDx = paint.mShadowLayerDx; mShadowLayerDy = paint.mShadowLayerDy; mShadowLayerColor = paint.mShadowLayerColor; mFlags = paint.mFlags; mStyle = paint.mStyle; mStrokeWidth = paint.mStrokeWidth; mStrokeCap = paint.mStrokeCap; mTypeface = paint.mTypeface; } /** @hide */ public void setCompatibilityScaling(float factor) { if (factor == 1.0) { mHasCompatScaling = false; mCompatScaling = mInvCompatScaling = 1.0f; } else { mHasCompatScaling = true; mCompatScaling = factor; mInvCompatScaling = 1.0f/factor; } } public int getBidiFlags() { return mBidiFlags; } public void setBidiFlags(int flags) { // only flag value is the 3-bit BIDI control setting flags &= BIDI_FLAG_MASK; if (flags > BIDI_MAX_FLAG_VALUE) { throw new IllegalArgumentException("unknown bidi flag: " + flags); } mBidiFlags = flags; } public @PaintFlag int getFlags() { return mFlags; } public void setFlags(@PaintFlag int flags) { mFlags = flags; Map fontAttributes = new HashMap(); if ((flags & UNDERLINE_TEXT_FLAG) != 0) { fontAttributes.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); } else { fontAttributes.put(TextAttribute.UNDERLINE, -1); } if ((flags & STRIKE_THRU_TEXT_FLAG) != 0) { fontAttributes.put(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON); } else { fontAttributes.put(TextAttribute.STRIKETHROUGH, false); } if ((flags & FAKE_BOLD_TEXT_FLAG) != 0) { fontAttributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD); } else { fontAttributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_REGULAR); } Map atts = mTypeface.getAttributes(); Object weight = atts.getOrDefault(TextAttribute.WEIGHT, null); if (weight instanceof Float) { fontAttributes.put(TextAttribute.WEIGHT, weight); } mTypeface = mTypeface.deriveFont(fontAttributes); } public int getHinting() { throw new RuntimeException("Stub!"); } public void setHinting(int mode) { throw new RuntimeException("Stub!"); } public final boolean isAntiAlias() { return (getFlags() & ANTI_ALIAS_FLAG) != 0; } public void setAntiAlias(boolean aa) { setFlags(getFlags() | ANTI_ALIAS_FLAG); } public final boolean isDither() { return (getFlags() & DITHER_FLAG) != 0; } public void setDither(boolean dither) { throw new RuntimeException("Stub!"); } public final boolean isLinearText() { return (getFlags() & LINEAR_TEXT_FLAG) != 0; } public void setLinearText(boolean linearText) { throw new RuntimeException("Stub!"); } public final boolean isSubpixelText() { return (getFlags() & SUBPIXEL_TEXT_FLAG) != 0; } public void setSubpixelText(boolean subpixelText) { throw new RuntimeException("Stub!"); } public final boolean isUnderlineText() { return (getFlags() & UNDERLINE_TEXT_FLAG) != 0; } public float getUnderlinePosition() { throw new RuntimeException("Stub!"); } public float getUnderlineThickness() { throw new RuntimeException("Stub!"); } public void setUnderlineText(boolean underlineText) { throw new RuntimeException("Stub!"); } public final boolean isStrikeThruText() { return (getFlags() & STRIKE_THRU_TEXT_FLAG) != 0; } public float getStrikeThruPosition() { throw new RuntimeException("Stub!"); } public float getStrikeThruThickness() { throw new RuntimeException("Stub!"); } public void setStrikeThruText(boolean strikeThruText) { throw new RuntimeException("Stub!"); } public final boolean isFakeBoldText() { return (getFlags() & FAKE_BOLD_TEXT_FLAG) != 0; } public void setFakeBoldText(boolean fakeBoldText) { throw new RuntimeException("Stub!"); } public final boolean isFilterBitmap() { return (getFlags() & FILTER_BITMAP_FLAG) != 0; } public void setFilterBitmap(boolean filter) { throw new RuntimeException("Stub!"); } public Style getStyle() { return mStyle; } public void setStyle(Style style) { mStyle = style; } @ColorInt public int getColor() { return Color.toArgb(mColor); } @ColorLong public long getColorLong() { return mColor; } public void setColor(@ColorInt int color) { mColor = Color.pack(color); } public void setColor(@ColorLong long color) { mColor = color; } public int getAlpha() { return Math.round(Color.alpha(mColor) * 255.0f); } public void setAlpha(int a) { // FIXME: No need to unpack this. Instead, just update the alpha bits. // b/122959599 ColorSpace cs = Color.colorSpace(mColor); float r = Color.red(mColor); float g = Color.green(mColor); float b = Color.blue(mColor); mColor = Color.pack(r, g, b, a * (1.0f / 255), cs); } public void setARGB(int a, int r, int g, int b) { setColor((a << 24) | (r << 16) | (g << 8) | b); } public float getStrokeWidth() { return mStrokeWidth; } public void setStrokeWidth(float width) { mStrokeWidth = width; } public float getStrokeMiter() { throw new RuntimeException("Stub!"); } public void setStrokeMiter(float miter) { throw new RuntimeException("Stub!"); } public Cap getStrokeCap() { return mStrokeCap; } public void setStrokeCap(Cap cap) { mStrokeCap = cap; } public Join getStrokeJoin() { throw new RuntimeException("Stub!"); } public void setStrokeJoin(Join join) { throw new RuntimeException("Stub!"); } public boolean getFillPath(Path src, Path dst) { throw new RuntimeException("Stub!"); } public Shader getShader() { return mShader; } public Shader setShader(Shader shader) { mShader = shader; return shader; } public ColorFilter getColorFilter() { return mColorFilter; } public ColorFilter setColorFilter(ColorFilter filter) { mColorFilter = filter; return filter; } public Xfermode getXfermode() { return mXfermode; } @Nullable public BlendMode getBlendMode() { throw new RuntimeException("Stub!"); } public Xfermode setXfermode(Xfermode xfermode) { return installXfermode(xfermode); } @Nullable private Xfermode installXfermode(Xfermode xfermode) { mXfermode = xfermode; return xfermode; } public void setBlendMode(@Nullable BlendMode blendmode) { throw new RuntimeException("Stub!"); } public PathEffect getPathEffect() { return mPathEffect; } public PathEffect setPathEffect(PathEffect effect) { mPathEffect = effect; return effect; } public MaskFilter getMaskFilter() { return mMaskFilter; } public MaskFilter setMaskFilter(MaskFilter maskfilter) { mMaskFilter = maskfilter; return maskfilter; } public Typeface getTypeface() { return mTypeface; } public Typeface setTypeface(Typeface typeface) { Map fontAttributes = new HashMap(); fontAttributes.put(TextAttribute.WEIGHT, typeface.getJavaWeight()); mTypeface = typeface .deriveFont(mTypeface.getFont().getStyle(), mTypeface.getFont().getSize()) .deriveFont(fontAttributes); setFlags(mFlags); return typeface; } public void setShadowLayer(float radius, float dx, float dy, @ColorInt int shadowColor) { setShadowLayer(radius, dx, dy, Color.pack(shadowColor)); } public void setShadowLayer(float radius, float dx, float dy, @ColorLong long shadowColor) { mShadowLayerRadius = radius; mShadowLayerDx = dx; mShadowLayerDy = dy; mShadowLayerColor = shadowColor; } public void clearShadowLayer() { setShadowLayer(0, 0, 0, 0); } public boolean hasShadowLayer() { throw new RuntimeException("Stub!"); } public float getShadowLayerRadius() { return mShadowLayerRadius; } public float getShadowLayerDx() { return mShadowLayerDx; } public float getShadowLayerDy() { return mShadowLayerDy; } public @ColorInt int getShadowLayerColor() { return Color.toArgb(mShadowLayerColor); } public @ColorLong long getShadowLayerColorLong() { return mShadowLayerColor; } public Align getTextAlign() { throw new RuntimeException("Stub!"); } public void setTextAlign(Align align) { throw new RuntimeException("Stub!"); } @NonNull public Locale getTextLocale() { return mLocales.get(0); } @NonNull @Size(min=1) public LocaleList getTextLocales() { return mLocales; } public void setTextLocale(@NonNull Locale locale) { throw new RuntimeException("Stub!"); } public void setTextLocales(@NonNull @Size(min=1) LocaleList locales) { throw new RuntimeException("Stub!"); } @Deprecated public boolean isElegantTextHeight() { throw new RuntimeException("Stub!"); } @Deprecated public void setElegantTextHeight(boolean elegant) { throw new RuntimeException("Stub!"); } public float getTextSize() { return mTypeface.getFont().getSize2D(); } public void setTextSize(float textSize) { mTypeface = mTypeface.deriveFont(textSize); } public float getTextScaleX() { throw new RuntimeException("Stub!"); } public void setTextScaleX(float scaleX) { throw new RuntimeException("Stub!"); } public float getTextSkewX() { throw new RuntimeException("Stub!"); } public void setTextSkewX(float skewX) { throw new RuntimeException("Stub!"); } public float getLetterSpacing() { throw new RuntimeException("Stub!"); } public void setLetterSpacing(float letterSpacing) { throw new RuntimeException("Stub!"); } public float getWordSpacing() { throw new RuntimeException("Stub!"); } public void setWordSpacing(float wordSpacing) { throw new RuntimeException("Stub!"); } public String getFontFeatureSettings() { return mFontFeatureSettings; } public void setFontFeatureSettings(String settings) { if (settings != null && settings.equals("")) { settings = null; } if ((settings == null && mFontFeatureSettings == null) || (settings != null && settings.equals(mFontFeatureSettings))) { return; } mFontFeatureSettings = settings; } public String getFontVariationSettings() { return mFontVariationSettings; } public boolean setFontVariationSettings(String fontVariationSettings) { return setFontVariationSettings(fontVariationSettings, 0 /* wght adjust */); } public boolean setFontVariationSettings(String fontVariationSettings, int wghtAdjust) { throw new RuntimeException("Stub!"); } public @StartHyphenEdit int getStartHyphenEdit() { throw new RuntimeException("Stub!"); } public @EndHyphenEdit int getEndHyphenEdit() { throw new RuntimeException("Stub!"); } public void setStartHyphenEdit(@StartHyphenEdit int startHyphen) { throw new RuntimeException("Stub!"); } public void setEndHyphenEdit(@EndHyphenEdit int endHyphen) { throw new RuntimeException("Stub!"); } public float ascent() { throw new RuntimeException("Stub!"); } public float descent() { throw new RuntimeException("Stub!"); } public static class FontMetrics { public float top; public float ascent; public float descent; public float bottom; public float leading; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || !(o instanceof FontMetrics)) return false; FontMetrics that = (FontMetrics) o; return that.top == top && that.ascent == ascent && that.descent == descent && that.bottom == bottom && that.leading == leading; } @Override public int hashCode() { return Objects.hash(top, ascent, descent, bottom, leading); } @Override public String toString() { return "FontMetrics{" + "top=" + top + ", ascent=" + ascent + ", descent=" + descent + ", bottom=" + bottom + ", leading=" + leading + '}'; } } /** @hide */ public static final class RunInfo { private int mClusterCount = 0; public int getClusterCount() { return mClusterCount; } public void setClusterCount(int clusterCount) { mClusterCount = clusterCount; } } public float getFontMetrics(FontMetrics metrics) { java.awt.Canvas c = new java.awt.Canvas(); java.awt.FontMetrics m = c.getFontMetrics(mTypeface.getFont()); metrics.top = m.getMaxDescent(); metrics.ascent = m.getAscent(); metrics.descent = m.getDescent(); metrics.bottom = m.getMaxAscent(); metrics.leading = m.getLeading(); return m.getLeading(); } public FontMetrics getFontMetrics() { FontMetrics fm = new FontMetrics(); getFontMetrics(fm); return fm; } public void getFontMetricsForLocale(@NonNull FontMetrics metrics) { throw new RuntimeException("Stub!"); } public void getFontMetricsInt( @NonNull CharSequence text, @IntRange(from = 0) int start, @IntRange(from = 0) int count, @IntRange(from = 0) int contextStart, @IntRange(from = 0) int contextCount, boolean isRtl, @NonNull FontMetricsInt outMetrics) { if (text == null) { throw new IllegalArgumentException("text must not be null"); } if (start < 0 || start >= text.length()) { throw new IllegalArgumentException("start argument is out of bounds."); } if (count < 0 || start + count > text.length()) { throw new IllegalArgumentException("count argument is out of bounds."); } if (contextStart < 0 || contextStart >= text.length()) { throw new IllegalArgumentException("ctxStart argument is out of bounds."); } if (contextCount < 0 || contextStart + contextCount > text.length()) { throw new IllegalArgumentException("ctxCount argument is out of bounds."); } if (outMetrics == null) { throw new IllegalArgumentException("outMetrics must not be null."); } if (count == 0) { getFontMetricsInt(outMetrics); return; } throw new RuntimeException("Stub!"); } public void getFontMetricsInt(@NonNull char[] text, @IntRange(from = 0) int start, @IntRange(from = 0) int count, @IntRange(from = 0) int contextStart, @IntRange(from = 0) int contextCount, boolean isRtl, @NonNull FontMetricsInt outMetrics) { if (text == null) { throw new IllegalArgumentException("text must not be null"); } if (start < 0 || start >= text.length) { throw new IllegalArgumentException("start argument is out of bounds."); } if (count < 0 || start + count > text.length) { throw new IllegalArgumentException("count argument is out of bounds."); } if (contextStart < 0 || contextStart >= text.length) { throw new IllegalArgumentException("ctxStart argument is out of bounds."); } if (contextCount < 0 || contextStart + contextCount > text.length) { throw new IllegalArgumentException("ctxCount argument is out of bounds."); } if (outMetrics == null) { throw new IllegalArgumentException("outMetrics must not be null."); } if (count == 0) { getFontMetricsInt(outMetrics); return; } throw new RuntimeException("Stub!"); } public static class FontMetricsInt { public int top; public int ascent; public int descent; public int bottom; public int leading; public void set(@NonNull FontMetricsInt fontMetricsInt) { top = fontMetricsInt.top; ascent = fontMetricsInt.ascent; descent = fontMetricsInt.descent; bottom = fontMetricsInt.bottom; leading = fontMetricsInt.leading; } public void set(@NonNull FontMetrics fontMetrics) { top = (int) Math.floor(fontMetrics.top); ascent = Math.round(fontMetrics.ascent); descent = Math.round(fontMetrics.descent); bottom = (int) Math.ceil(fontMetrics.bottom); leading = Math.round(fontMetrics.leading); } @Override public String toString() { return "FontMetricsInt: top=" + top + " ascent=" + ascent + " descent=" + descent + " bottom=" + bottom + " leading=" + leading; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof FontMetricsInt)) return false; FontMetricsInt that = (FontMetricsInt) o; return top == that.top && ascent == that.ascent && descent == that.descent && bottom == that.bottom && leading == that.leading; } @Override public int hashCode() { return Objects.hash(top, ascent, descent, bottom, leading); } } public int getFontMetricsInt(FontMetricsInt fmi) { throw new RuntimeException("Stub!"); } public FontMetricsInt getFontMetricsInt() { FontMetricsInt fm = new FontMetricsInt(); getFontMetricsInt(fm); return fm; } public void getFontMetricsIntForLocale(@NonNull FontMetricsInt metrics) { throw new RuntimeException("Stub!"); } public float getFontSpacing() { return getFontMetrics(null); } public float measureText(char[] text, int index, int count) { if (text == null) { throw new IllegalArgumentException("text cannot be null"); } if ((index | count) < 0 || index + count > text.length) { throw new ArrayIndexOutOfBoundsException(); } if (text.length == 0 || count == 0) { return 0f; } int oldFlag = getFlags(); setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE)); try { if (!mHasCompatScaling) { throw new RuntimeException("Stub!"); } final float oldSize = getTextSize(); setTextSize(oldSize * mCompatScaling); throw new RuntimeException("Stub!"); // setTextSize(oldSize); // return (float) Math.ceil(w * mInvCompatScaling); } finally { setFlags(oldFlag); } } public float measureText(String text, int start, int end) { if (text == null) { throw new IllegalArgumentException("text cannot be null"); } if ((start | end | (end - start) | (text.length() - end)) < 0) { throw new IndexOutOfBoundsException(); } if (text.length() == 0 || start == end) { return 0f; } int oldFlag = getFlags(); setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE)); try { if (!mHasCompatScaling) { throw new RuntimeException("Stub!"); } final float oldSize = getTextSize(); setTextSize(oldSize * mCompatScaling); throw new RuntimeException("Stub!"); // setTextSize(oldSize); // return (float) Math.ceil(w * mInvCompatScaling); } finally { setFlags(oldFlag); } } public float measureText(String text) { if (text == null) { throw new IllegalArgumentException("text cannot be null"); } return measureText(text, 0, text.length()); } public float measureText(CharSequence text, int start, int end) { if (text == null) { throw new IllegalArgumentException("text cannot be null"); } if ((start | end | (end - start) | (text.length() - end)) < 0) { throw new IndexOutOfBoundsException(); } if (text.length() == 0 || start == end) { return 0f; } if (text instanceof String) { return measureText((String)text, start, end); } if (text instanceof SpannedString || text instanceof SpannableString) { return measureText(text.toString(), start, end); } char[] buf = TemporaryBuffer.obtain(end - start); TextUtils.getChars(text, start, end, buf, 0); float result = measureText(buf, 0, end - start); TemporaryBuffer.recycle(buf); return result; } public int breakText(char[] text, int index, int count, float maxWidth, float[] measuredWidth) { if (text == null) { throw new IllegalArgumentException("text cannot be null"); } if (index < 0 || text.length - index < Math.abs(count)) { throw new ArrayIndexOutOfBoundsException(); } if (text.length == 0 || count == 0) { return 0; } if (!mHasCompatScaling) { throw new RuntimeException("Stub!"); } final float oldSize = getTextSize(); setTextSize(oldSize * mCompatScaling); throw new RuntimeException("Stub!"); // setTextSize(oldSize); // if (measuredWidth != null) measuredWidth[0] *= mInvCompatScaling; // return res; } public int breakText(CharSequence text, int start, int end, boolean measureForwards, float maxWidth, float[] measuredWidth) { if (text == null) { throw new IllegalArgumentException("text cannot be null"); } if ((start | end | (end - start) | (text.length() - end)) < 0) { throw new IndexOutOfBoundsException(); } if (text.length() == 0 || start == end) { return 0; } if (start == 0 && text instanceof String && end == text.length()) { return breakText((String) text, measureForwards, maxWidth, measuredWidth); } char[] buf = TemporaryBuffer.obtain(end - start); int result; TextUtils.getChars(text, start, end, buf, 0); if (measureForwards) { result = breakText(buf, 0, end - start, maxWidth, measuredWidth); } else { result = breakText(buf, 0, -(end - start), maxWidth, measuredWidth); } TemporaryBuffer.recycle(buf); return result; } public int breakText(String text, boolean measureForwards, float maxWidth, float[] measuredWidth) { if (text == null) { throw new IllegalArgumentException("text cannot be null"); } if (text.length() == 0) { return 0; } if (!mHasCompatScaling) { throw new RuntimeException("Stub!"); } final float oldSize = getTextSize(); setTextSize(oldSize*mCompatScaling); throw new RuntimeException("Stub!"); // setTextSize(oldSize); // if (measuredWidth != null) measuredWidth[0] *= mInvCompatScaling; // return res; } public int getTextWidths(char[] text, int index, int count, float[] widths) { if (text == null) { throw new IllegalArgumentException("text cannot be null"); } if ((index | count) < 0 || index + count > text.length || count > widths.length) { throw new ArrayIndexOutOfBoundsException(); } if (text.length == 0 || count == 0) { return 0; } int oldFlag = getFlags(); setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE)); try { if (!mHasCompatScaling) { throw new RuntimeException("Stub!"); // return count; } final float oldSize = getTextSize(); setTextSize(oldSize * mCompatScaling); throw new RuntimeException("Stub!"); // setTextSize(oldSize); // for (int i = 0; i < count; i++) { // widths[i] *= mInvCompatScaling; // } // return count; } finally { setFlags(oldFlag); } } public int getTextWidths(CharSequence text, int start, int end, float[] widths) { if (text == null) { throw new IllegalArgumentException("text cannot be null"); } if ((start | end | (end - start) | (text.length() - end)) < 0) { throw new IndexOutOfBoundsException(); } if (end - start > widths.length) { throw new ArrayIndexOutOfBoundsException(); } if (text.length() == 0 || start == end) { return 0; } if (text instanceof String) { return getTextWidths((String) text, start, end, widths); } if (text instanceof SpannedString || text instanceof SpannableString) { return getTextWidths(text.toString(), start, end, widths); } char[] buf = TemporaryBuffer.obtain(end - start); TextUtils.getChars(text, start, end, buf, 0); int result = getTextWidths(buf, 0, end - start, widths); TemporaryBuffer.recycle(buf); return result; } public int getTextWidths(String text, int start, int end, float[] widths) { if (text == null) { throw new IllegalArgumentException("text cannot be null"); } if ((start | end | (end - start) | (text.length() - end)) < 0) { throw new IndexOutOfBoundsException(); } if (end - start > widths.length) { throw new ArrayIndexOutOfBoundsException(); } if (text.length() == 0 || start == end) { return 0; } int oldFlag = getFlags(); setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE)); try { if (!mHasCompatScaling) { throw new RuntimeException("Stub!"); // return end - start; } final float oldSize = getTextSize(); setTextSize(oldSize * mCompatScaling); throw new RuntimeException("Stub!"); // setTextSize(oldSize); // for (int i = 0; i < end - start; i++) { // widths[i] *= mInvCompatScaling; // } // return end - start; } finally { setFlags(oldFlag); } } public int getTextWidths(String text, float[] widths) { return getTextWidths(text, 0, text.length(), widths); } public float getTextRunAdvances(@NonNull char[] chars, @IntRange(from = 0) int index, @IntRange(from = 0) int count, @IntRange(from = 0) int contextIndex, @IntRange(from = 0) int contextCount, boolean isRtl, @Nullable float[] advances, @IntRange(from = 0) int advancesIndex) { if (chars == null) { throw new IllegalArgumentException("text cannot be null"); } if ((index | count | contextIndex | contextCount | advancesIndex | (index - contextIndex) | (contextCount - count) | ((contextIndex + contextCount) - (index + count)) | (chars.length - (contextIndex + contextCount)) | (advances == null ? 0 : (advances.length - (advancesIndex + count)))) < 0) { throw new IndexOutOfBoundsException(); } if (chars.length == 0 || count == 0){ return 0f; } if (!mHasCompatScaling) { throw new RuntimeException("Stub!"); } final float oldSize = getTextSize(); setTextSize(oldSize * mCompatScaling); float res = 0.0f; throw new RuntimeException("Stub!"); // setTextSize(oldSize); // if (advances != null) { // for (int i = advancesIndex, e = i + count; i < e; i++) { // advances[i] *= mInvCompatScaling; // } // } // return res * mInvCompatScaling; // assume errors are not significant } public int getTextRunCursor(@NonNull char[] text, @IntRange(from = 0) int contextStart, @IntRange(from = 0) int contextLength, boolean isRtl, @IntRange(from = 0) int offset, @CursorOption int cursorOpt) { int contextEnd = contextStart + contextLength; if (((contextStart | contextEnd | offset | (contextEnd - contextStart) | (offset - contextStart) | (contextEnd - offset) | (text.length - contextEnd) | cursorOpt) < 0) || cursorOpt > CURSOR_OPT_MAX_VALUE) { throw new IndexOutOfBoundsException(); } throw new RuntimeException("Stub!"); } public int getTextRunCursor(@NonNull CharSequence text, @IntRange(from = 0) int contextStart, @IntRange(from = 0) int contextEnd, boolean isRtl, @IntRange(from = 0) int offset, @CursorOption int cursorOpt) { if (text instanceof String || text instanceof SpannedString || text instanceof SpannableString) { return getTextRunCursor(text.toString(), contextStart, contextEnd, isRtl, offset, cursorOpt); } int contextLen = contextEnd - contextStart; char[] buf = TemporaryBuffer.obtain(contextLen); TextUtils.getChars(text, contextStart, contextEnd, buf, 0); int relPos = getTextRunCursor(buf, 0, contextLen, isRtl, offset - contextStart, cursorOpt); TemporaryBuffer.recycle(buf); return (relPos == -1) ? -1 : relPos + contextStart; } public int getTextRunCursor(@NonNull String text, @IntRange(from = 0) int contextStart, @IntRange(from = 0) int contextEnd, boolean isRtl, @IntRange(from = 0) int offset, @CursorOption int cursorOpt) { if (((contextStart | contextEnd | offset | (contextEnd - contextStart) | (offset - contextStart) | (contextEnd - offset) | (text.length() - contextEnd) | cursorOpt) < 0) || cursorOpt > CURSOR_OPT_MAX_VALUE) { throw new IndexOutOfBoundsException(); } throw new RuntimeException("Stub!"); } public void getTextPath(char[] text, int index, int count, float x, float y, Path path) { if ((index | count) < 0 || index + count > text.length) { throw new ArrayIndexOutOfBoundsException(); } throw new RuntimeException("Stub!"); } public void getTextPath(String text, int start, int end, float x, float y, Path path) { if ((start | end | (end - start) | (text.length() - end)) < 0) { throw new IndexOutOfBoundsException(); } throw new RuntimeException("Stub!"); } public void getTextBounds(String text, int start, int end, Rect bounds) { if ((start | end | (end - start) | (text.length() - end)) < 0) { throw new IndexOutOfBoundsException(); } if (bounds == null) { throw new NullPointerException("need bounds Rect"); } throw new RuntimeException("Stub!"); } public void getTextBounds(@NonNull CharSequence text, int start, int end, @NonNull Rect bounds) { if ((start | end | (end - start) | (text.length() - end)) < 0) { throw new IndexOutOfBoundsException(); } if (bounds == null) { throw new NullPointerException("need bounds Rect"); } char[] buf = TemporaryBuffer.obtain(end - start); TextUtils.getChars(text, start, end, buf, 0); getTextBounds(buf, 0, end - start, bounds); TemporaryBuffer.recycle(buf); } public void getTextBounds(char[] text, int index, int count, Rect bounds) { if ((index | count) < 0 || index + count > text.length) { throw new ArrayIndexOutOfBoundsException(); } if (bounds == null) { throw new NullPointerException("need bounds Rect"); } throw new RuntimeException("Stub!"); } public boolean hasGlyph(String string) { throw new RuntimeException("Stub!"); } public float getRunAdvance(char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset) { if (text == null) { throw new IllegalArgumentException("text cannot be null"); } if ((contextStart | start | offset | end | contextEnd | start - contextStart | offset - start | end - offset | contextEnd - end | text.length - contextEnd) < 0) { throw new IndexOutOfBoundsException(); } if (end == start) { return 0.0f; } throw new RuntimeException("Stub!"); } public float getRunAdvance(CharSequence text, int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset) { if (text == null) { throw new IllegalArgumentException("text cannot be null"); } if ((contextStart | start | offset | end | contextEnd | start - contextStart | offset - start | end - offset | contextEnd - end | text.length() - contextEnd) < 0) { throw new IndexOutOfBoundsException(); } if (end == start) { return 0.0f; } // TODO performance: specialized alternatives to avoid buffer copy, if win is significant char[] buf = TemporaryBuffer.obtain(contextEnd - contextStart); TextUtils.getChars(text, contextStart, contextEnd, buf, 0); float result = getRunAdvance(buf, start - contextStart, end - contextStart, 0, contextEnd - contextStart, isRtl, offset - contextStart); TemporaryBuffer.recycle(buf); return result; } public float getRunCharacterAdvance(@NonNull char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset, @Nullable float[] advances, int advancesIndex) { return getRunCharacterAdvance(text, start, end, contextStart, contextEnd, isRtl, offset, advances, advancesIndex, null, null); } public float getRunCharacterAdvance(@NonNull char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset, @Nullable float[] advances, int advancesIndex, @Nullable RectF drawBounds, @Nullable RunInfo runInfo) { if (text == null) { throw new IllegalArgumentException("text cannot be null"); } if (contextStart < 0 || contextEnd > text.length) { throw new IndexOutOfBoundsException("Invalid Context Range: " + contextStart + ", " + contextEnd + " must be in 0, " + text.length); } if (start < contextStart || contextEnd < end) { throw new IndexOutOfBoundsException("Invalid start/end range: " + start + ", " + end + " must be in " + contextStart + ", " + contextEnd); } if (offset < start || end < offset) { throw new IndexOutOfBoundsException("Invalid offset position: " + offset + " must be in " + start + ", " + end); } if (advances != null && advances.length < advancesIndex - start + end) { throw new IndexOutOfBoundsException("Given array doesn't have enough space to receive " + "the result, advances.length: " + advances.length + " advanceIndex: " + advancesIndex + " needed space: " + (offset - start)); } if (end == start) { if (runInfo != null) { runInfo.setClusterCount(0); } return 0.0f; } throw new RuntimeException("Stub!"); } public float getRunCharacterAdvance(@NonNull CharSequence text, int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset, @Nullable float[] advances, int advancesIndex) { return getRunCharacterAdvance(text, start, end, contextStart, contextEnd, isRtl, offset, advances, advancesIndex, null, null); } public float getRunCharacterAdvance(@NonNull CharSequence text, int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset, @Nullable float[] advances, int advancesIndex, @Nullable RectF drawBounds, @Nullable RunInfo runInfo) { if (text == null) { throw new IllegalArgumentException("text cannot be null"); } if (contextStart < 0 || contextEnd > text.length()) { throw new IndexOutOfBoundsException("Invalid Context Range: " + contextStart + ", " + contextEnd + " must be in 0, " + text.length()); } if (start < contextStart || contextEnd < end) { throw new IndexOutOfBoundsException("Invalid start/end range: " + start + ", " + end + " must be in " + contextStart + ", " + contextEnd); } if (offset < start || end < offset) { throw new IndexOutOfBoundsException("Invalid offset position: " + offset + " must be in " + start + ", " + end); } if (advances != null && advances.length < advancesIndex - start + end) { throw new IndexOutOfBoundsException("Given array doesn't have enough space to receive " + "the result, advances.length: " + advances.length + " advanceIndex: " + advancesIndex + " needed space: " + (offset - start)); } if (end == start) { return 0.0f; } char[] buf = TemporaryBuffer.obtain(contextEnd - contextStart); TextUtils.getChars(text, contextStart, contextEnd, buf, 0); final float result = getRunCharacterAdvance(buf, start - contextStart, end - contextStart, 0, contextEnd - contextStart, isRtl, offset - contextStart, advances, advancesIndex, drawBounds, runInfo); TemporaryBuffer.recycle(buf); return result; } public int getOffsetForAdvance(char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl, float advance) { throw new RuntimeException("Stub!"); } public int getOffsetForAdvance(CharSequence text, int start, int end, int contextStart, int contextEnd, boolean isRtl, float advance) { if (text == null) { throw new IllegalArgumentException("text cannot be null"); } if ((contextStart | start | end | contextEnd | start - contextStart | end - start | contextEnd - end | text.length() - contextEnd) < 0) { throw new IndexOutOfBoundsException(); } // TODO performance: specialized alternatives to avoid buffer copy, if win is significant char[] buf = TemporaryBuffer.obtain(contextEnd - contextStart); TextUtils.getChars(text, contextStart, contextEnd, buf, 0); int result = getOffsetForAdvance(buf, start - contextStart, end - contextStart, 0, contextEnd - contextStart, isRtl, advance) + contextStart; TemporaryBuffer.recycle(buf); return result; } public boolean equalsForTextMeasurement(@NonNull Paint other) { throw new RuntimeException("Stub!"); } }