Files
Tachidesk/AndroidCompat/src/main/java/android/graphics/Paint.java
Constantin Piber b2cfb5a1e9 AndroidCompat: Use NotoSans as default font (#1572)
* Initial Noto fonts

* Use Noto for other default fonts

* Typeface: Prefer main font

Eagerly switch back to main font as soon as it can display again;
otherwise we might never switch back (or later than necessary); we
should always prefer the main font

* fix: Font metrics with fallback font on TextLine
2025-08-19 15:00:59 -04:00

1552 lines
50 KiB
Java

/*
* 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 Typeface mTypeface = Typeface.DEFAULT;
private static final Object sCacheLock = new Object();
private static final HashMap<String, Integer> 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;
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;
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<TextAttribute, Object> fontAttributes = new HashMap<TextAttribute, Object>();
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<TextAttribute, Object> 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() {
throw new RuntimeException("Stub!");
}
public void setStrokeCap(Cap cap) {
throw new RuntimeException("Stub!");
}
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<TextAttribute, Object> fontAttributes = new HashMap<TextAttribute, Object>();
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!");
}
}