package android.graphics;
import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import android.annotation.ColorInt;
import android.annotation.ColorLong;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Px;
import android.annotation.Size;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.fonts.FontVariationAxis;
import android.os.Build;
import android.os.LocaleList;
import android.text.GraphicsOperations;
import android.text.SpannableString;
import android.text.SpannedString;
import android.text.TextUtils;
import com.android.internal.annotations.GuardedBy;
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
import libcore.util.NativeAllocationRegistry;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Objects;

public class Paint {

    private long mNativePaint;
    private long mNativeShader;
    private long mNativeColorFilter;

    private static class NoImagePreloadHolder {
        public static final NativeAllocationRegistry sRegistry =
                NativeAllocationRegistry.createMalloced(
                Paint.class.getClassLoader(), nGetNativeFinalizer());
    }

    private ColorFilter     mColorFilter;
    private MaskFilter      mMaskFilter;
    private PathEffect      mPathEffect;
    private Shader          mShader;

    private Typeface        mTypeface;
    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;

    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
    };

            ANTI_ALIAS_FLAG,
            FILTER_BITMAP_FLAG,
            DITHER_FLAG,
            UNDERLINE_TEXT_FLAG,
            STRIKE_THRU_TEXT_FLAG,
            FAKE_BOLD_TEXT_FLAG,
            LINEAR_TEXT_FLAG,
            SUBPIXEL_TEXT_FLAG,
            EMBEDDED_BITMAP_TEXT_FLAG
    })
    public 

    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;

    public static final int DEV_KERN_TEXT_FLAG  = 0x100;

    public static final int LCD_RENDER_TEXT_FLAG = 0x200;

    public static final int EMBEDDED_BITMAP_TEXT_FLAG = 0x400;

    public static final int AUTO_HINTING_TEXT_FLAG = 0x800;

    public static final int VERTICAL_TEXT_FLAG = 0x1000;

    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;

        CURSOR_AFTER, CURSOR_AT_OR_AFTER, CURSOR_BEFORE, CURSOR_AT_OR_BEFORE
    })

    public 

    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;

        START_HYPHEN_EDIT_NO_EDIT,
        START_HYPHEN_EDIT_INSERT_HYPHEN,
        START_HYPHEN_EDIT_INSERT_ZWJ
    })

    public 

    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;

        END_HYPHEN_EDIT_NO_EDIT,
        END_HYPHEN_EDIT_REPLACE_WITH_HYPHEN,
        END_HYPHEN_EDIT_INSERT_HYPHEN,
        END_HYPHEN_EDIT_INSERT_ARMENIAN_HYPHEN,
        END_HYPHEN_EDIT_INSERT_MAQAF,
        END_HYPHEN_EDIT_INSERT_UCAS_HYPHEN,
        END_HYPHEN_EDIT_INSERT_ZWJ_AND_HYPHEN
    })

    public 

    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) {
        mNativePaint = nInit();
        NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativePaint);
        setFlags(flags | HIDDEN_DEFAULT_PAINT_FLAGS);

        mCompatScaling = mInvCompatScaling = 1;
        setTextLocales(LocaleList.getAdjustedDefault());
        mColor = Color.pack(Color.BLACK);
        resetElegantTextHeight();
    }

    public Paint(Paint paint) {
        mNativePaint = nInitWithPaint(paint.getNativeInstance());
        NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativePaint);
        setClassVariablesFrom(paint);
    }

    public void reset() {
        nReset(mNativePaint);
        setFlags(HIDDEN_DEFAULT_PAINT_FLAGS | ANTI_ALIAS_FLAG);

        mColor = Color.pack(Color.BLACK);
        mColorFilter = null;
        mMaskFilter = null;
        mPathEffect = null;
        mShader = null;
        mNativeShader = 0;
        mTypeface = null;
        mXfermode = null;
        mHasCompatScaling = false;
        mCompatScaling = 1;
        mInvCompatScaling = 1;
        mBidiFlags = BIDI_DEFAULT_LTR;
        setTextLocales(LocaleList.getAdjustedDefault());
        resetElegantTextHeight();
        mFontFeatureSettings = null;
        mFontVariationSettings = null;
        mShadowLayerRadius = 0.0f;
        mShadowLayerDx = 0.0f;
        mShadowLayerDy = 0.0f;
        mShadowLayerColor = Color.pack(0);
    }

    public void set(Paint src) {
        if (this != src) {

            nSet(mNativePaint, src.mNativePaint);
            setClassVariablesFrom(src);
        }
    }

    private void setClassVariablesFrom(Paint paint) {
        mColor = paint.mColor;
        mColorFilter = paint.mColorFilter;
        mMaskFilter = paint.mMaskFilter;
        mPathEffect = paint.mPathEffect;
        mShader = paint.mShader;
        mNativeShader = paint.mNativeShader;
        mTypeface = paint.mTypeface;
        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;
    }

    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 synchronized long getNativeInstance() {
        boolean filter = isFilterBitmap();
        long newNativeShader = mShader == null ? 0 : mShader.getNativeInstance(filter);
        if (newNativeShader != mNativeShader) {
            mNativeShader = newNativeShader;
            nSetShader(mNativePaint, mNativeShader);
        }
        long newNativeColorFilter = mColorFilter == null ? 0 : mColorFilter.getNativeInstance();
        if (newNativeColorFilter != mNativeColorFilter) {
            mNativeColorFilter = newNativeColorFilter;
            nSetColorFilter(mNativePaint, mNativeColorFilter);
        }
        return mNativePaint;
    }

    public int getBidiFlags() {
        return mBidiFlags;
    }

    public void setBidiFlags(int flags) {

        flags &= BIDI_FLAG_MASK;
        if (flags > BIDI_MAX_FLAG_VALUE) {
            throw new IllegalArgumentException("unknown bidi flag: " + flags);
        }
        mBidiFlags = flags;
    }

    public 
        return nGetFlags(mNativePaint);
    }

    public void setFlags(
        nSetFlags(mNativePaint, flags);
    }

    public int getHinting() {
        return nGetHinting(mNativePaint);
    }

    public void setHinting(int mode) {
        nSetHinting(mNativePaint, mode);
    }

    public final boolean isAntiAlias() {
        return (getFlags() & ANTI_ALIAS_FLAG) != 0;
    }

    public void setAntiAlias(boolean aa) {
        nSetAntiAlias(mNativePaint, aa);
    }

    public final boolean isDither() {
        return (getFlags() & DITHER_FLAG) != 0;
    }

    public void setDither(boolean dither) {
        nSetDither(mNativePaint, dither);
    }

    public final boolean isLinearText() {
        return (getFlags() & LINEAR_TEXT_FLAG) != 0;
    }

    public void setLinearText(boolean linearText) {
        nSetLinearText(mNativePaint, linearText);
    }

    public final boolean isSubpixelText() {
        return (getFlags() & SUBPIXEL_TEXT_FLAG) != 0;
    }

    public void setSubpixelText(boolean subpixelText) {
        nSetSubpixelText(mNativePaint, subpixelText);
    }

    public final boolean isUnderlineText() {
        return (getFlags() & UNDERLINE_TEXT_FLAG) != 0;
    }

    public 
        return nGetUnderlinePosition(mNativePaint);
    }

    public 
        return nGetUnderlineThickness(mNativePaint);
    }

    public void setUnderlineText(boolean underlineText) {
        nSetUnderlineText(mNativePaint, underlineText);
    }

    public final boolean isStrikeThruText() {
        return (getFlags() & STRIKE_THRU_TEXT_FLAG) != 0;
    }

    public 
        return nGetStrikeThruPosition(mNativePaint);
    }

    public 
        return nGetStrikeThruThickness(mNativePaint);
    }

    public void setStrikeThruText(boolean strikeThruText) {
        nSetStrikeThruText(mNativePaint, strikeThruText);
    }

    public final boolean isFakeBoldText() {
        return (getFlags() & FAKE_BOLD_TEXT_FLAG) != 0;
    }

    public void setFakeBoldText(boolean fakeBoldText) {
        nSetFakeBoldText(mNativePaint, fakeBoldText);
    }

    public final boolean isFilterBitmap() {
        return (getFlags() & FILTER_BITMAP_FLAG) != 0;
    }

    public void setFilterBitmap(boolean filter) {
        nSetFilterBitmap(mNativePaint, filter);
    }

    public Style getStyle() {
        return sStyleArray[nGetStyle(mNativePaint)];
    }

    public void setStyle(Style style) {
        nSetStyle(mNativePaint, style.nativeInt);
    }

    public int getColor() {
        return Color.toArgb(mColor);
    }

    public long getColorLong() {
        return mColor;
    }

    public void setColor(
        nSetColor(mNativePaint, color);
        mColor = Color.pack(color);
    }

    public void setColor(
        ColorSpace cs = Color.colorSpace(color);
        nSetColor(mNativePaint, cs.getNativeInstance(), color);
        mColor = color;
    }

    public int getAlpha() {
        return Math.round(Color.alpha(mColor) * 255.0f);
    }

    public void setAlpha(int a) {

        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);
        nSetAlpha(mNativePaint, a);
    }

    public void setARGB(int a, int r, int g, int b) {
        setColor((a << 24) | (r << 16) | (g << 8) | b);
    }

    public float getStrokeWidth() {
        return nGetStrokeWidth(mNativePaint);
    }

    public void setStrokeWidth(float width) {
        nSetStrokeWidth(mNativePaint, width);
    }

    public float getStrokeMiter() {
        return nGetStrokeMiter(mNativePaint);
    }

    public void setStrokeMiter(float miter) {
        nSetStrokeMiter(mNativePaint, miter);
    }

    public Cap getStrokeCap() {
        return sCapArray[nGetStrokeCap(mNativePaint)];
    }

    public void setStrokeCap(Cap cap) {
        nSetStrokeCap(mNativePaint, cap.nativeInt);
    }

    public Join getStrokeJoin() {
        return sJoinArray[nGetStrokeJoin(mNativePaint)];
    }

    public void setStrokeJoin(Join join) {
        nSetStrokeJoin(mNativePaint, join.nativeInt);
    }

    public boolean getFillPath(Path src, Path dst) {
        return nGetFillPath(mNativePaint, src.readOnlyNI(), dst.mutateNI());
    }

    public Shader getShader() {
        return mShader;
    }

    public Shader setShader(Shader shader) {

        if (mShader != shader) {
            mNativeShader = -1;

            nSetShader(mNativePaint, 0);
        }

        mShader = shader;
        return shader;
    }

    public ColorFilter getColorFilter() {
        return mColorFilter;
    }

    public ColorFilter setColorFilter(ColorFilter filter) {

        if (mColorFilter != filter) {
            mNativeColorFilter = -1;
        }

        mColorFilter = filter;
        return filter;
    }

    public Xfermode getXfermode() {
        return mXfermode;
    }

    public BlendMode getBlendMode() {
        if (mXfermode == null) {
            return null;
        } else {
            return BlendMode.fromValue(mXfermode.porterDuffMode);
        }
    }

    public Xfermode setXfermode(Xfermode xfermode) {
        return installXfermode(xfermode);
    }

    private Xfermode installXfermode(Xfermode xfermode) {
        int newMode = xfermode != null ? xfermode.porterDuffMode : Xfermode.DEFAULT;
        int curMode = mXfermode != null ? mXfermode.porterDuffMode : Xfermode.DEFAULT;
        if (newMode != curMode) {
            nSetXfermode(mNativePaint, newMode);
        }
        mXfermode = xfermode;
        return xfermode;
    }

    public void setBlendMode(
        installXfermode(blendmode != null ? blendmode.getXfermode() : null);
    }

    public PathEffect getPathEffect() {
        return mPathEffect;
    }

    public PathEffect setPathEffect(PathEffect effect) {
        long effectNative = 0;
        if (effect != null) {
            effectNative = effect.native_instance;
        }
        nSetPathEffect(mNativePaint, effectNative);
        mPathEffect = effect;
        return effect;
    }

    public MaskFilter getMaskFilter() {
        return mMaskFilter;
    }

    public MaskFilter setMaskFilter(MaskFilter maskfilter) {
        long maskfilterNative = 0;
        if (maskfilter != null) {
            maskfilterNative = maskfilter.native_instance;
        }
        nSetMaskFilter(mNativePaint, maskfilterNative);
        mMaskFilter = maskfilter;
        return maskfilter;
    }

    public Typeface getTypeface() {
        return mTypeface;
    }

    public Typeface setTypeface(Typeface typeface) {
        final long typefaceNative = typeface == null ? 0 : typeface.native_instance;
        nSetTypeface(mNativePaint, typefaceNative);
        mTypeface = typeface;
        return typeface;
    }

    public Rasterizer getRasterizer() {
        return null;
    }

    public Rasterizer setRasterizer(Rasterizer rasterizer) {
        return rasterizer;
    }

    public void setShadowLayer(float radius, float dx, float dy, 
        setShadowLayer(radius, dx, dy, Color.pack(shadowColor));
    }

    public void setShadowLayer(float radius, float dx, float dy, 
        ColorSpace cs = Color.colorSpace(shadowColor);
        nSetShadowLayer(mNativePaint, radius, dx, dy, cs.getNativeInstance(), shadowColor);
        mShadowLayerRadius = radius;
        mShadowLayerDx = dx;
        mShadowLayerDy = dy;
        mShadowLayerColor = shadowColor;
    }

    public void clearShadowLayer() {
        setShadowLayer(0, 0, 0, 0);
    }

    public boolean hasShadowLayer() {
        return nHasShadowLayer(mNativePaint);
    }

    public float getShadowLayerRadius() {
        return mShadowLayerRadius;
    }

    public float getShadowLayerDx() {
        return mShadowLayerDx;
    }

    public float getShadowLayerDy() {
        return mShadowLayerDy;
    }

    public 
        return Color.toArgb(mShadowLayerColor);
    }

    public 
        return mShadowLayerColor;
    }

    public Align getTextAlign() {
        return sAlignArray[nGetTextAlign(mNativePaint)];
    }

    public void setTextAlign(Align align) {
        nSetTextAlign(mNativePaint, align.nativeInt);
    }

    public Locale getTextLocale() {
        return mLocales.get(0);
    }

    public LocaleList getTextLocales() {
        return mLocales;
    }

    public void setTextLocale(
        if (locale == null) {
            throw new IllegalArgumentException("locale cannot be null");
        }
        if (mLocales != null && mLocales.size() == 1 && locale.equals(mLocales.get(0))) {
            return;
        }
        mLocales = new LocaleList(locale);
        syncTextLocalesWithMinikin();
    }

    public void setTextLocales(
        if (locales == null || locales.isEmpty()) {
            throw new IllegalArgumentException("locales cannot be null or empty");
        }
        if (locales.equals(mLocales)) return;
        mLocales = locales;
        syncTextLocalesWithMinikin();
    }
    private void syncTextLocalesWithMinikin() {
        final String languageTags = mLocales.toLanguageTags();
        final Integer minikinLocaleListId;
        synchronized (sCacheLock) {
            minikinLocaleListId = sMinikinLocaleListIdCache.get(languageTags);
            if (minikinLocaleListId == null) {
                final int newID = nSetTextLocales(mNativePaint, languageTags);
                sMinikinLocaleListIdCache.put(languageTags, newID);
                return;
            }
        }
        nSetTextLocalesByMinikinLocaleListId(mNativePaint, minikinLocaleListId.intValue());
    }

    public boolean isElegantTextHeight() {
        int rawValue = nGetElegantTextHeight(mNativePaint);
        switch (rawValue) {
            case ELEGANT_TEXT_HEIGHT_DISABLED:
                return false;
            case ELEGANT_TEXT_HEIGHT_ENABLED:
                return true;
            case ELEGANT_TEXT_HEIGHT_UNSET:
            default:
                return com.android.text.flags.Flags.deprecateUiFonts();
        }
    }

    private static final int ELEGANT_TEXT_HEIGHT_UNSET = -1;
    private static final int ELEGANT_TEXT_HEIGHT_ENABLED = 0;
    private static final int ELEGANT_TEXT_HEIGHT_DISABLED = 1;

    public void setElegantTextHeight(boolean elegant) {
        nSetElegantTextHeight(mNativePaint,
                elegant ? ELEGANT_TEXT_HEIGHT_ENABLED : ELEGANT_TEXT_HEIGHT_DISABLED);
    }

    public static final long DEPRECATE_UI_FONT = 279646685L;
    private void resetElegantTextHeight() {
        if (CompatChanges.isChangeEnabled(DEPRECATE_UI_FONT)) {
            nSetElegantTextHeight(mNativePaint, ELEGANT_TEXT_HEIGHT_UNSET);
        } else {
            nSetElegantTextHeight(mNativePaint, ELEGANT_TEXT_HEIGHT_DISABLED);
        }
    }

    public float getTextSize() {
        return nGetTextSize(mNativePaint);
    }

    public void setTextSize(float textSize) {
        nSetTextSize(mNativePaint, textSize);
    }

    public float getTextScaleX() {
        return nGetTextScaleX(mNativePaint);
    }

    public void setTextScaleX(float scaleX) {
        nSetTextScaleX(mNativePaint, scaleX);
    }

    public float getTextSkewX() {
        return nGetTextSkewX(mNativePaint);
    }

    public void setTextSkewX(float skewX) {
        nSetTextSkewX(mNativePaint, skewX);
    }

    public float getLetterSpacing() {
        return nGetLetterSpacing(mNativePaint);
    }

    public void setLetterSpacing(float letterSpacing) {
        nSetLetterSpacing(mNativePaint, letterSpacing);
    }

    public 
        return nGetWordSpacing(mNativePaint);
    }

    public void setWordSpacing(
        nSetWordSpacing(mNativePaint, wordSpacing);
    }

    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;
        nSetFontFeatureSettings(mNativePaint, settings);
    }

    public String getFontVariationSettings() {
        return mFontVariationSettings;
    }

    public boolean setFontVariationSettings(String fontVariationSettings) {
        final String settings = TextUtils.nullIfEmpty(fontVariationSettings);
        if (settings == mFontVariationSettings
                || (settings != null && settings.equals(mFontVariationSettings))) {
            return true;
        }
        if (settings == null || settings.length() == 0) {
            mFontVariationSettings = null;
            setTypeface(Typeface.createFromTypefaceWithVariation(mTypeface,
                      Collections.emptyList()));
            return true;
        }

        Typeface targetTypeface = mTypeface == null ? Typeface.DEFAULT : mTypeface;
        FontVariationAxis[] axes = FontVariationAxis.fromFontVariationSettings(settings);
        final ArrayList<FontVariationAxis> filteredAxes = new ArrayList<FontVariationAxis>();
        for (final FontVariationAxis axis : axes) {
            if (targetTypeface.isSupportedAxes(axis.getOpenTypeTagValue())) {
                filteredAxes.add(axis);
            }
        }
        if (filteredAxes.isEmpty()) {
            return false;
        }
        mFontVariationSettings = settings;
        setTypeface(Typeface.createFromTypefaceWithVariation(targetTypeface, filteredAxes));
        return true;
    }

    public 
        return nGetStartHyphenEdit(mNativePaint);
    }

    public 
        return nGetEndHyphenEdit(mNativePaint);
    }

    public void setStartHyphenEdit(
        nSetStartHyphenEdit(mNativePaint, startHyphen);
    }

    public void setEndHyphenEdit(
        nSetEndHyphenEdit(mNativePaint, endHyphen);
    }

    public float ascent() {
        return nAscent(mNativePaint);
    }

    public float descent() {
        return nDescent(mNativePaint);
    }

    public static class FontMetrics {

        public float   top;

        public float   ascent;

        public float   descent;

        public float   bottom;

        public float   leading;

        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;
        }

        public int hashCode() {
            return Objects.hash(top, ascent, descent, bottom, leading);
        }

        public String toString() {
            return "FontMetrics{"
                    + "top=" + top
                    + ", ascent=" + ascent
                    + ", descent=" + descent
                    + ", bottom=" + bottom
                    + ", leading=" + leading
                    + '}';
        }
    }

    public float getFontMetrics(FontMetrics metrics) {
        return nGetFontMetrics(mNativePaint, metrics, false);
    }

    public FontMetrics getFontMetrics() {
        FontMetrics fm = new FontMetrics();
        getFontMetrics(fm);
        return fm;
    }

    public void getFontMetricsForLocale(
        nGetFontMetrics(mNativePaint, metrics, true);
    }

    public void getFontMetricsInt(

            boolean isRtl,

        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;
        }
        if (text instanceof String) {
            nGetFontMetricsIntForText(mNativePaint, (String) text, start, count, contextStart,
                    contextCount, isRtl, outMetrics);
        } else {
            char[] buf = TemporaryBuffer.obtain(contextCount);
            try {
                TextUtils.getChars(text, contextStart, contextStart + contextCount, buf, 0);
                nGetFontMetricsIntForText(mNativePaint, buf, start - contextStart, count, 0,
                        contextCount, isRtl, outMetrics);
            } finally {
                TemporaryBuffer.recycle(buf);
            }
        }
    }

    public void getFontMetricsInt(

            boolean isRtl,

        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;
        }
        nGetFontMetricsIntForText(mNativePaint, text, start, count, contextStart, contextCount,
                isRtl, outMetrics);
    }

    public static class FontMetricsInt {

        public int   top;

        public int   ascent;

        public int   descent;

        public int   bottom;

        public int   leading;

        public void set(
            top = fontMetricsInt.top;
            ascent = fontMetricsInt.ascent;
            descent = fontMetricsInt.descent;
            bottom = fontMetricsInt.bottom;
            leading = fontMetricsInt.leading;
        }

        public void set(

            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);
        }

            return "FontMetricsInt: top=" + top + " ascent=" + ascent +
                    " descent=" + descent + " bottom=" + bottom +
                    " leading=" + leading;
        }

        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;
        }

        public int hashCode() {
            return Objects.hash(top, ascent, descent, bottom, leading);
        }
    }

    public int getFontMetricsInt(FontMetricsInt fmi) {
        return nGetFontMetricsInt(mNativePaint, fmi, false);
    }
    public FontMetricsInt getFontMetricsInt() {
        FontMetricsInt fm = new FontMetricsInt();
        getFontMetricsInt(fm);
        return fm;
    }

    public void getFontMetricsIntForLocale(
        nGetFontMetricsInt(mNativePaint, metrics, true);
    }

    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;
        }
        if (!mHasCompatScaling) {
            return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
                    index, count, index, count, mBidiFlags, null, 0));
        }
        final float oldSize = getTextSize();
        setTextSize(oldSize * mCompatScaling);
        final float w = nGetTextAdvances(mNativePaint, text, index, count, index, count,
                mBidiFlags, null, 0);
        setTextSize(oldSize);
        return (float) Math.ceil(w*mInvCompatScaling);
    }

    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;
        }
        if (!mHasCompatScaling) {
            return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
                    start, end, start, end, mBidiFlags, null, 0));
        }
        final float oldSize = getTextSize();
        setTextSize(oldSize * mCompatScaling);
        final float w = nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags,
                null, 0);
        setTextSize(oldSize);
        return (float) Math.ceil(w * mInvCompatScaling);
    }

    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);
        }
        if (text instanceof GraphicsOperations) {
            return ((GraphicsOperations)text).measureText(start, end, this);
        }
        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) {
            return nBreakText(mNativePaint, text, index, count, maxWidth, mBidiFlags,
                    measuredWidth);
        }
        final float oldSize = getTextSize();
        setTextSize(oldSize * mCompatScaling);
        final int res = nBreakText(mNativePaint, text, index, count, maxWidth * mCompatScaling,
                mBidiFlags, measuredWidth);
        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) {
            return nBreakText(mNativePaint, text, measureForwards,
                    maxWidth, mBidiFlags, measuredWidth);
        }
        final float oldSize = getTextSize();
        setTextSize(oldSize*mCompatScaling);
        final int res = nBreakText(mNativePaint, text, measureForwards, maxWidth*mCompatScaling,
                mBidiFlags, measuredWidth);
        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;
        }
        if (!mHasCompatScaling) {
            nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
            return count;
        }
        final float oldSize = getTextSize();
        setTextSize(oldSize * mCompatScaling);
        nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
        setTextSize(oldSize);
        for (int i = 0; i < count; i++) {
            widths[i] *= mInvCompatScaling;
        }
        return count;
    }

    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);
        }
        if (text instanceof GraphicsOperations) {
            return ((GraphicsOperations) text).getTextWidths(start, end,
                                                                 widths, this);
        }
        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;
        }
        if (!mHasCompatScaling) {
            nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
            return end - start;
        }
        final float oldSize = getTextSize();
        setTextSize(oldSize * mCompatScaling);
        nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
        setTextSize(oldSize);
        for (int i = 0; i < end - start; i++) {
            widths[i] *= mInvCompatScaling;
        }
        return end - start;
    }

    public int getTextWidths(String text, float[] widths) {
        return getTextWidths(text, 0, text.length(), widths);
    }

    public float getTextRunAdvances(

        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) {
            return nGetTextAdvances(mNativePaint, chars, index, count, contextIndex, contextCount,
                    isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances,
                    advancesIndex);
        }
        final float oldSize = getTextSize();
        setTextSize(oldSize * mCompatScaling);
        final float res = nGetTextAdvances(mNativePaint, chars, index, count, contextIndex,
                contextCount, isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances, advancesIndex);
        setTextSize(oldSize);
        if (advances != null) {
            for (int i = advancesIndex, e = i + count; i < e; i++) {
                advances[i] *= mInvCompatScaling;
            }
        }
        return res * mInvCompatScaling; 
    }

    public int getTextRunCursor(

        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();
        }
        return nGetTextRunCursor(mNativePaint, text, contextStart, contextLength,
                isRtl ? DIRECTION_RTL : DIRECTION_LTR, offset, cursorOpt);
    }

    public int getTextRunCursor(

        if (text instanceof String || text instanceof SpannedString ||
                text instanceof SpannableString) {
            return getTextRunCursor(text.toString(), contextStart, contextEnd,
                    isRtl, offset, cursorOpt);
        }
        if (text instanceof GraphicsOperations) {
            return ((GraphicsOperations) text).getTextRunCursor(
                    contextStart, contextEnd, isRtl, offset, cursorOpt, this);
        }
        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(

        if (((contextStart | contextEnd | offset | (contextEnd - contextStart)
                | (offset - contextStart) | (contextEnd - offset)
                | (text.length() - contextEnd) | cursorOpt) < 0)
                || cursorOpt > CURSOR_OPT_MAX_VALUE) {
            throw new IndexOutOfBoundsException();
        }
        return nGetTextRunCursor(mNativePaint, text, contextStart, contextEnd,
                isRtl ? DIRECTION_RTL : DIRECTION_LTR, offset, cursorOpt);
    }

    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();
        }
        nGetTextPath(mNativePaint, mBidiFlags, text, index, count, x, y, path.mutateNI());
    }

    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();
        }
        nGetTextPath(mNativePaint, mBidiFlags, text, start, end, x, y, path.mutateNI());
    }

    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");
        }
        nGetStringBounds(mNativePaint, text, start, end, mBidiFlags, bounds);
    }

    public void getTextBounds(

        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");
        }
        nGetCharArrayBounds(mNativePaint, text, index, count, mBidiFlags,
            bounds);
    }

    public boolean hasGlyph(String string) {
        return nHasGlyph(mNativePaint, mBidiFlags, string);
    }

    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;
        }

        return nGetRunAdvance(mNativePaint, text, start, end, contextStart, contextEnd, isRtl,
                offset);
    }

    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;
        }

        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(
            int contextEnd, boolean isRtl, int offset,

        return getRunCharacterAdvance(text, start, end, contextStart, contextEnd, isRtl, offset,
                advances, advancesIndex, null);
    }

    public float getRunCharacterAdvance(
            int contextEnd, boolean isRtl, int offset,

        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;
        }
        return nGetRunCharacterAdvance(mNativePaint, text, start, end, contextStart, contextEnd,
                isRtl, offset, advances, advancesIndex, drawBounds);
    }

    public float getRunCharacterAdvance(
            int contextStart, int contextEnd, boolean isRtl, int offset,

        return getRunCharacterAdvance(text, start, end, contextStart, contextEnd, isRtl, offset,
                advances, advancesIndex, null);
    }

    public float getRunCharacterAdvance(
            int contextStart, int contextEnd, boolean isRtl, int offset,

        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);
        TemporaryBuffer.recycle(buf);
        return result;
    }

    public int getOffsetForAdvance(char[] 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();
        }

        return nGetOffsetForAdvance(mNativePaint, text, start, end, contextStart, contextEnd,
                isRtl, advance);
    }

    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();
        }

        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(
        return nEqualsForTextMeasurement(mNativePaint, other.mNativePaint);
    }

    private static native long nGetNativeFinalizer();
    private static native long nInit();
    private static native long nInitWithPaint(long paint);
    private static native int nBreakText(long nObject, char[] text, int index, int count,
            float maxWidth, int bidiFlags, float[] measuredWidth);
    private static native int nBreakText(long nObject, String text, boolean measureForwards,
            float maxWidth, int bidiFlags, float[] measuredWidth);
    private static native float nGetTextAdvances(long paintPtr, char[] text, int index, int count,
            int contextIndex, int contextCount, int bidiFlags, float[] advances, int advancesIndex);
    private static native float nGetTextAdvances(long paintPtr, String text, int start, int end,
            int contextStart, int contextEnd, int bidiFlags, float[] advances, int advancesIndex);
    private native int nGetTextRunCursor(long paintPtr, char[] text, int contextStart,
            int contextLength, int dir, int offset, int cursorOpt);
    private native int nGetTextRunCursor(long paintPtr, String text, int contextStart,
            int contextEnd, int dir, int offset, int cursorOpt);
    private static native void nGetTextPath(long paintPtr, int bidiFlags, char[] text, int index,
            int count, float x, float y, long path);
    private static native void nGetTextPath(long paintPtr, int bidiFlags, String text, int start,
            int end, float x, float y, long path);
    private static native void nGetStringBounds(long nativePaint, String text, int start, int end,
            int bidiFlags, Rect bounds);
    private static native void nGetCharArrayBounds(long nativePaint, char[] text, int index,
            int count, int bidiFlags, Rect bounds);
    private static native boolean nHasGlyph(long paintPtr, int bidiFlags, String string);
    private static native float nGetRunAdvance(long paintPtr, char[] text, int start, int end,
            int contextStart, int contextEnd, boolean isRtl, int offset);
    private static native float nGetRunCharacterAdvance(long paintPtr, char[] text, int start,
            int end, int contextStart, int contextEnd, boolean isRtl, int offset, float[] advances,
            int advancesIndex, RectF drawingBounds);
    private static native int nGetOffsetForAdvance(long paintPtr, char[] text, int start, int end,
            int contextStart, int contextEnd, boolean isRtl, float advance);
    private static native void nGetFontMetricsIntForText(long paintPtr, char[] text,
            int start, int count, int ctxStart, int ctxCount, boolean isRtl,
            FontMetricsInt outMetrics);
    private static native void nGetFontMetricsIntForText(long paintPtr, String text,
            int start, int count, int ctxStart, int ctxCount, boolean isRtl,
            FontMetricsInt outMetrics);

    private static native int nSetTextLocales(long paintPtr, String locales);

    private static native void nSetFontFeatureSettings(long paintPtr, String settings);

    private static native float nGetFontMetrics(long paintPtr, FontMetrics metrics,
            boolean useLocale);

    private static native int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi,
            boolean useLocale);

    private static native void nReset(long paintPtr);

    private static native void nSet(long paintPtrDest, long paintPtrSrc);

    private static native int nGetStyle(long paintPtr);

    private static native void nSetStyle(long paintPtr, int style);

    private static native int nGetStrokeCap(long paintPtr);

    private static native void nSetStrokeCap(long paintPtr, int cap);

    private static native int nGetStrokeJoin(long paintPtr);

    private static native void nSetStrokeJoin(long paintPtr, int join);

    private static native boolean nGetFillPath(long paintPtr, long src, long dst);

    private static native long nSetShader(long paintPtr, long shader);

    private static native long nSetColorFilter(long paintPtr, long filter);

    private static native void nSetXfermode(long paintPtr, int xfermode);

    private static native long nSetPathEffect(long paintPtr, long effect);

    private static native long nSetMaskFilter(long paintPtr, long maskfilter);

    private static native void nSetTypeface(long paintPtr, long typeface);

    private static native int nGetTextAlign(long paintPtr);

    private static native void nSetTextAlign(long paintPtr, int align);

    private static native void nSetTextLocalesByMinikinLocaleListId(long paintPtr,
            int mMinikinLocaleListId);

    private static native void nSetShadowLayer(long paintPtr,
            float radius, float dx, float dy, long colorSpaceHandle,

    private static native boolean nHasShadowLayer(long paintPtr);

    private static native float nGetLetterSpacing(long paintPtr);

    private static native void nSetLetterSpacing(long paintPtr, float letterSpacing);

    private static native float nGetWordSpacing(long paintPtr);

    private static native void nSetWordSpacing(long paintPtr, float wordSpacing);

    private static native int nGetStartHyphenEdit(long paintPtr);

    private static native int nGetEndHyphenEdit(long paintPtr);

    private static native void nSetStartHyphenEdit(long paintPtr, int hyphen);

    private static native void nSetEndHyphenEdit(long paintPtr, int hyphen);

    private static native void nSetStrokeMiter(long paintPtr, float miter);

    private static native float nGetStrokeMiter(long paintPtr);

    private static native void nSetStrokeWidth(long paintPtr, float width);

    private static native float nGetStrokeWidth(long paintPtr);

    private static native void nSetAlpha(long paintPtr, int a);

    private static native void nSetDither(long paintPtr, boolean dither);

    private static native int nGetFlags(long paintPtr);

    private static native void nSetFlags(long paintPtr, int flags);

    private static native int nGetHinting(long paintPtr);

    private static native void nSetHinting(long paintPtr, int mode);

    private static native void nSetAntiAlias(long paintPtr, boolean aa);

    private static native void nSetLinearText(long paintPtr, boolean linearText);

    private static native void nSetSubpixelText(long paintPtr, boolean subpixelText);

    private static native void nSetUnderlineText(long paintPtr, boolean underlineText);

    private static native void nSetFakeBoldText(long paintPtr, boolean fakeBoldText);

    private static native void nSetFilterBitmap(long paintPtr, boolean filter);

    private static native void nSetColor(long paintPtr, long colorSpaceHandle,

    private static native void nSetColor(long paintPtr, 

    private static native void nSetStrikeThruText(long paintPtr, boolean strikeThruText);

    private static native int nGetElegantTextHeight(long paintPtr);

    private static native void nSetElegantTextHeight(long paintPtr, int elegant);

    private static native float nGetTextSize(long paintPtr);

    private static native float nGetTextScaleX(long paintPtr);

    private static native void nSetTextScaleX(long paintPtr, float scaleX);

    private static native float nGetTextSkewX(long paintPtr);

    private static native void nSetTextSkewX(long paintPtr, float skewX);

    private static native float nAscent(long paintPtr);

    private static native float nDescent(long paintPtr);

    private static native float nGetUnderlinePosition(long paintPtr);

    private static native float nGetUnderlineThickness(long paintPtr);

    private static native float nGetStrikeThruPosition(long paintPtr);

    private static native float nGetStrikeThruThickness(long paintPtr);

    private static native void nSetTextSize(long paintPtr, float textSize);

    private static native boolean nEqualsForTextMeasurement(long leftPaintPtr, long rightPaintPtr);
} convert this to unity c# with all  of the features of this (make it draw directly to the camera)(GL_TRIANGLEFAN doesnt exist) i will also give the android bitmap library package android.graphics;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.DisplayMetrics;
import java.io.OutputStream;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
public final class Bitmap implements Parcelable {

    public static final int DENSITY_NONE = 0;

    public final int mNativeBitmap;

    public byte[] mBuffer;

    private final BitmapFinalizer mFinalizer;
    private final boolean mIsMutable;
    private byte[] mNinePatchChunk;   // may be null
    private int mWidth = -1;
    private int mHeight = -1;
    private boolean mRecycled;
    // Package-scoped for fast access.
     int mDensity = sDefaultDensity = getDefaultDensity();
    private static volatile Matrix sScaleMatrix;
    private static volatile int sDefaultDensity = -1;

    public static void setDefaultDensity(int density) {
        sDefaultDensity = density;
    }

     static int getDefaultDensity() {
        if (sDefaultDensity >= 0) {
            return sDefaultDensity;
        }
        sDefaultDensity = DisplayMetrics.DENSITY_DEVICE;
        return sDefaultDensity;
    }

     Bitmap(int nativeBitmap, byte[] buffer, boolean isMutable, byte[] ninePatchChunk,
            int density) {
        if (nativeBitmap == 0) {
            throw new RuntimeException("internal error: native bitmap is 0");
        }
        mBuffer = buffer;
        // we delete this in our finalizer
        mNativeBitmap = nativeBitmap;
        mFinalizer = new BitmapFinalizer(nativeBitmap);
        mIsMutable = isMutable;
        mNinePatchChunk = ninePatchChunk;
        if (density >= 0) {
            mDensity = density;
        }
    }

    public int getDensity() {
        return mDensity;
    }

    public void setDensity(int density) {
        mDensity = density;
    }

    public void setNinePatchChunk(byte[] chunk) {
        mNinePatchChunk = chunk;
    }

    public void recycle() {
        if (!mRecycled) {
            mBuffer = null;
            nativeRecycle(mNativeBitmap);
            mNinePatchChunk = null;
            mRecycled = true;
        }
    }

    public final boolean isRecycled() {
        return mRecycled;
    }

    public int getGenerationId() {
        return nativeGenerationId(mNativeBitmap);
    }

    private void checkRecycled(String errorMessage) {
        if (mRecycled) {
            throw new IllegalStateException(errorMessage);
        }
    }

    private static void checkXYSign(int x, int y) {
        if (x < 0) {
            throw new IllegalArgumentException("x must be >= 0");
        }
        if (y < 0) {
            throw new IllegalArgumentException("y must be >= 0");
        }
    }

    private static void checkWidthHeight(int width, int height) {
        if (width <= 0) {
            throw new IllegalArgumentException("width must be > 0");
        }
        if (height <= 0) {
            throw new IllegalArgumentException("height must be > 0");
        }
    }

    public enum Config {
        // these native values must match up with the enum in SkBitmap.h

        ALPHA_8     (2),

        RGB_565     (4),

        ARGB_4444   (5),

        ARGB_8888   (6);
        final int nativeInt;

        private static Config sConfigs[] = {
            null, null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888
        };

        Config(int ni) {
            this.nativeInt = ni;
        }
        static Config nativeToConfig(int ni) {
            return sConfigs[ni];
        }
    }

    public void copyPixelsToBuffer(Buffer dst) {
        int elements = dst.remaining();
        int shift;
        if (dst instanceof ByteBuffer) {
            shift = 0;
        } else if (dst instanceof ShortBuffer) {
            shift = 1;
        } else if (dst instanceof IntBuffer) {
            shift = 2;
        } else {
            throw new RuntimeException("unsupported Buffer subclass");
        }
        long bufferSize = (long)elements << shift;
        long pixelSize = getByteCount();
        if (bufferSize < pixelSize) {
            throw new RuntimeException("Buffer not large enough for pixels");
        }
        nativeCopyPixelsToBuffer(mNativeBitmap, dst);
        // now update the buffer's position
        int position = dst.position();
        position += pixelSize >> shift;
        dst.position(position);
    }

    public void copyPixelsFromBuffer(Buffer src) {
        checkRecycled("copyPixelsFromBuffer called on recycled bitmap");
        int elements = src.remaining();
        int shift;
        if (src instanceof ByteBuffer) {
            shift = 0;
        } else if (src instanceof ShortBuffer) {
            shift = 1;
        } else if (src instanceof IntBuffer) {
            shift = 2;
        } else {
            throw new RuntimeException("unsupported Buffer subclass");
        }
        long bufferBytes = (long)elements << shift;
        long bitmapBytes = getByteCount();
        if (bufferBytes < bitmapBytes) {
            throw new RuntimeException("Buffer not large enough for pixels");
        }
        nativeCopyPixelsFromBuffer(mNativeBitmap, src);
    }

    public Bitmap copy(Config config, boolean isMutable) {
        checkRecycled("Can't copy a recycled bitmap");
        Bitmap b = nativeCopy(mNativeBitmap, config.nativeInt, isMutable);
        if (b != null) {
            b.mDensity = mDensity;
        }
        return b;
    }

    public static Bitmap createScaledBitmap(Bitmap src, int dstWidth,
            int dstHeight, boolean filter) {
        Matrix m;
        synchronized (Bitmap.class) {
            // small pool of just 1 matrix
            m = sScaleMatrix;
            sScaleMatrix = null;
        }
        if (m == null) {
            m = new Matrix();
        }
        final int width = src.getWidth();
        final int height = src.getHeight();
        final float sx = dstWidth  / (float)width;
        final float sy = dstHeight / (float)height;
        m.setScale(sx, sy);
        Bitmap b = Bitmap.createBitmap(src, 0, 0, width, height, m, filter);
        synchronized (Bitmap.class) {
            // do we need to check for null? why not just assign everytime?
            if (sScaleMatrix == null) {
                sScaleMatrix = m;
            }
        }
        return b;
    }

    public static Bitmap createBitmap(Bitmap src) {
        return createBitmap(src, 0, 0, src.getWidth(), src.getHeight());
    }

    public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height) {
        return createBitmap(source, x, y, width, height, null, false);
    }

    public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height,
            Matrix m, boolean filter) {
        checkXYSign(x, y);
        checkWidthHeight(width, height);
        if (x + width > source.getWidth()) {
            throw new IllegalArgumentException("x + width must be <= bitmap.width()");
        }
        if (y + height > source.getHeight()) {
            throw new IllegalArgumentException("y + height must be <= bitmap.height()");
        }
        // check if we can just return our argument unchanged
        if (!source.isMutable() && x == 0 && y == 0 && width == source.getWidth() &&
                height == source.getHeight() && (m == null || m.isIdentity())) {
            return source;
        }
        int neww = width;
        int newh = height;
        Canvas canvas = new Canvas();
        Bitmap bitmap;
        Paint paint;
        Rect srcR = new Rect(x, y, x + width, y + height);
        RectF dstR = new RectF(0, 0, width, height);
        Config newConfig = Config.ARGB_8888;
        final Config config = source.getConfig();
        // GIF files generate null configs, assume ARGB_8888
        if (config != null) {
            switch (config) {
                case RGB_565:
                    newConfig = Config.RGB_565;
                    break;
                case ALPHA_8:
                    newConfig = Config.ALPHA_8;
                    break;
                //noinspection deprecation
                case ARGB_4444:
                case ARGB_8888:
                default:
                    newConfig = Config.ARGB_8888;
                    break;
            }
        }
        if (m == null || m.isIdentity()) {
            bitmap = createBitmap(neww, newh, newConfig, source.hasAlpha());
            paint = null;   // not needed
        } else {
            final boolean transformed = !m.rectStaysRect();
            RectF deviceR = new RectF();
            m.mapRect(deviceR, dstR);
            neww = Math.round(deviceR.width());
            newh = Math.round(deviceR.height());
            bitmap = createBitmap(neww, newh, transformed ? Config.ARGB_8888 : newConfig,
                    transformed || source.hasAlpha());
            canvas.translate(-deviceR.left, -deviceR.top);
            canvas.concat(m);
            paint = new Paint();
            paint.setFilterBitmap(filter);
            if (transformed) {
                paint.setAntiAlias(true);
            }
        }

        // The new bitmap was created from a known bitmap source so assume that
        // they use the same density
        bitmap.mDensity = source.mDensity;

        canvas.setBitmap(bitmap);
        canvas.drawBitmap(source, srcR, dstR, paint);
        canvas.setBitmap(null);
        return bitmap;
    }

    public static Bitmap createBitmap(int width, int height, Config config) {
        return createBitmap(width, height, config, true);
    }

    private static Bitmap createBitmap(int width, int height, Config config, boolean hasAlpha) {
        if (width <= 0 || height <= 0) {
            throw new IllegalArgumentException("width and height must be > 0");
        }
        Bitmap bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true);
        if (config == Config.ARGB_8888 && !hasAlpha) {
            bm.eraseColor(0xff000000);
            nativeSetHasAlpha(bm.mNativeBitmap, hasAlpha);
        } else {
            bm.eraseColor(0);
        }
        return bm;
    }

    public static Bitmap createBitmap(int colors[], int offset, int stride,
            int width, int height, Config config) {
        checkWidthHeight(width, height);
        if (Math.abs(stride) < width) {
            throw new IllegalArgumentException("abs(stride) must be >= width");
        }
        int lastScanline = offset + (height - 1) * stride;
        int length = colors.length;
        if (offset < 0 || (offset + width > length) || lastScanline < 0 ||
                (lastScanline + width > length)) {
            throw new ArrayIndexOutOfBoundsException();
        }
        if (width <= 0 || height <= 0) {
            throw new IllegalArgumentException("width and height must be > 0");
        }
        return nativeCreate(colors, offset, stride, width, height,
                            config.nativeInt, false);
    }

    public static Bitmap createBitmap(int colors[], int width, int height, Config config) {
        return createBitmap(colors, 0, width, width, height, config);
    }

    public byte[] getNinePatchChunk() {
        return mNinePatchChunk;
    }

    public enum CompressFormat {
        JPEG    (0),
        PNG     (1),
        WEBP    (2);
        CompressFormat(int nativeInt) {
            this.nativeInt = nativeInt;
        }
        final int nativeInt;
    }

    private final static int WORKING_COMPRESS_STORAGE = 4096;

    public boolean compress(CompressFormat format, int quality, OutputStream stream) {
        checkRecycled("Can't compress a recycled bitmap");
        // do explicit check before calling the native method
        if (stream == null) {
            throw new NullPointerException();
        }
        if (quality < 0 || quality > 100) {
            throw new IllegalArgumentException("quality must be 0..100");
        }
        return nativeCompress(mNativeBitmap, format.nativeInt, quality,
                              stream, new byte[WORKING_COMPRESS_STORAGE]);
    }

    public final boolean isMutable() {
        return mIsMutable;
    }

    public final int getWidth() {
        return mWidth == -1 ? mWidth = nativeWidth(mNativeBitmap) : mWidth;
    }

    public final int getHeight() {
        return mHeight == -1 ? mHeight = nativeHeight(mNativeBitmap) : mHeight;
    }

    public int getScaledWidth(Canvas canvas) {
        return scaleFromDensity(getWidth(), mDensity, canvas.mDensity);
    }

    public int getScaledHeight(Canvas canvas) {
        return scaleFromDensity(getHeight(), mDensity, canvas.mDensity);
    }

    public int getScaledWidth(DisplayMetrics metrics) {
        return scaleFromDensity(getWidth(), mDensity, metrics.densityDpi);
    }

    public int getScaledHeight(DisplayMetrics metrics) {
        return scaleFromDensity(getHeight(), mDensity, metrics.densityDpi);
    }

    public int getScaledWidth(int targetDensity) {
        return scaleFromDensity(getWidth(), mDensity, targetDensity);
    }

    public int getScaledHeight(int targetDensity) {
        return scaleFromDensity(getHeight(), mDensity, targetDensity);
    }

    static public int scaleFromDensity(int size, int sdensity, int tdensity) {
        if (sdensity == DENSITY_NONE || sdensity == tdensity) {
            return size;
        }

        // Scale by tdensity / sdensity, rounding up.
        return ((size * tdensity) + (sdensity >> 1)) / sdensity;
    }

    public final int getRowBytes() {
        return nativeRowBytes(mNativeBitmap);
    }

    public final int getByteCount() {
        // int result permits bitmaps up to 46,340 x 46,340
        return getRowBytes() * getHeight();
    }

    public final Config getConfig() {
        return Config.nativeToConfig(nativeConfig(mNativeBitmap));
    }

    public final boolean hasAlpha() {
        return nativeHasAlpha(mNativeBitmap);
    }

    public void setHasAlpha(boolean hasAlpha) {
        nativeSetHasAlpha(mNativeBitmap, hasAlpha);
    }

    public void eraseColor(int c) {
        checkRecycled("Can't erase a recycled bitmap");
        if (!isMutable()) {
            throw new IllegalStateException("cannot erase immutable bitmaps");
        }
        nativeErase(mNativeBitmap, c);
    }

    public int getPixel(int x, int y) {
        checkRecycled("Can't call getPixel() on a recycled bitmap");
        checkPixelAccess(x, y);
        return nativeGetPixel(mNativeBitmap, x, y);
    }

    public void getPixels(int[] pixels, int offset, int stride,
                          int x, int y, int width, int height) {
        checkRecycled("Can't call getPixels() on a recycled bitmap");
        if (width == 0 || height == 0) {
            return; // nothing to do
        }
        checkPixelsAccess(x, y, width, height, offset, stride, pixels);
        nativeGetPixels(mNativeBitmap, pixels, offset, stride,
                        x, y, width, height);
    }

    private void checkPixelAccess(int x, int y) {
        checkXYSign(x, y);
        if (x >= getWidth()) {
            throw new IllegalArgumentException("x must be < bitmap.width()");
        }
        if (y >= getHeight()) {
            throw new IllegalArgumentException("y must be < bitmap.height()");
        }
    }

    private void checkPixelsAccess(int x, int y, int width, int height,
                                   int offset, int stride, int pixels[]) {
        checkXYSign(x, y);
        if (width < 0) {
            throw new IllegalArgumentException("width must be >= 0");
        }
        if (height < 0) {
            throw new IllegalArgumentException("height must be >= 0");
        }
        if (x + width > getWidth()) {
            throw new IllegalArgumentException(
                    "x + width must be <= bitmap.width()");
        }
        if (y + height > getHeight()) {
            throw new IllegalArgumentException(
                    "y + height must be <= bitmap.height()");
        }
        if (Math.abs(stride) < width) {
            throw new IllegalArgumentException("abs(stride) must be >= width");
        }
        int lastScanline = offset + (height - 1) * stride;
        int length = pixels.length;
        if (offset < 0 || (offset + width > length)
                || lastScanline < 0
                || (lastScanline + width > length)) {
            throw new ArrayIndexOutOfBoundsException();
        }
    }

    public void setPixel(int x, int y, int color) {
        checkRecycled("Can't call setPixel() on a recycled bitmap");
        if (!isMutable()) {
            throw new IllegalStateException();
        }
        checkPixelAccess(x, y);
        nativeSetPixel(mNativeBitmap, x, y, color);
    }

    public void setPixels(int[] pixels, int offset, int stride,
                          int x, int y, int width, int height) {
        checkRecycled("Can't call setPixels() on a recycled bitmap");
        if (!isMutable()) {
            throw new IllegalStateException();
        }
        if (width == 0 || height == 0) {
            return; // nothing to do
        }
        checkPixelsAccess(x, y, width, height, offset, stride, pixels);
        nativeSetPixels(mNativeBitmap, pixels, offset, stride,
                        x, y, width, height);
    }
    public static final Parcelable.Creator<Bitmap> CREATOR
            = new Parcelable.Creator<Bitmap>() {

        public Bitmap createFromParcel(Parcel p) {
            Bitmap bm = nativeCreateFromParcel(p);
            if (bm == null) {
                throw new RuntimeException("Failed to unparcel Bitmap");
            }
            return bm;
        }
        public Bitmap[] newArray(int size) {
            return new Bitmap[size];
        }
    };

    public int describeContents() {
        return 0;
    }

    public void writeToParcel(Parcel p, int flags) {
        checkRecycled("Can't parcel a recycled bitmap");
        if (!nativeWriteToParcel(mNativeBitmap, mIsMutable, mDensity, p)) {
            throw new RuntimeException("native writeToParcel failed");
        }
    }

    public Bitmap extractAlpha() {
        return extractAlpha(null, null);
    }

    public Bitmap extractAlpha(Paint paint, int[] offsetXY) {
        checkRecycled("Can't extractAlpha on a recycled bitmap");
        int nativePaint = paint != null ? paint.mNativePaint : 0;
        Bitmap bm = nativeExtractAlpha(mNativeBitmap, nativePaint, offsetXY);
        if (bm == null) {
            throw new RuntimeException("Failed to extractAlpha on Bitmap");
        }
        bm.mDensity = mDensity;
        return bm;
    }

    public boolean sameAs(Bitmap other) {
        return this == other || (other != null && nativeSameAs(mNativeBitmap, other.mNativeBitmap));
    }

    public void prepareToDraw() {
        nativePrepareToDraw(mNativeBitmap);
    }
    private static class BitmapFinalizer {
        private final int mNativeBitmap;
        BitmapFinalizer(int nativeBitmap) {
            mNativeBitmap = nativeBitmap;
        }

        public void finalize() {
            try {
                super.finalize();
            } catch (Throwable t) {
                // Ignore
            } finally {
                nativeDestructor(mNativeBitmap);
            }
        }
    }
    //////////// native methods
    private static native Bitmap nativeCreate(int[] colors, int offset,
                                              int stride, int width, int height,
                                            int nativeConfig, boolean mutable);
    private static native Bitmap nativeCopy(int srcBitmap, int nativeConfig,
                                            boolean isMutable);
    private static native void nativeDestructor(int nativeBitmap);
    private static native void nativeRecycle(int nativeBitmap);
    private static native boolean nativeCompress(int nativeBitmap, int format,
                                            int quality, OutputStream stream,
                                            byte[] tempStorage);
    private static native void nativeErase(int nativeBitmap, int color);
    private static native int nativeWidth(int nativeBitmap);
    private static native int nativeHeight(int nativeBitmap);
    private static native int nativeRowBytes(int nativeBitmap);
    private static native int nativeConfig(int nativeBitmap);
    private static native boolean nativeHasAlpha(int nativeBitmap);
    private static native int nativeGetPixel(int nativeBitmap, int x, int y);
    private static native void nativeGetPixels(int nativeBitmap, int[] pixels,
                                               int offset, int stride, int x,
                                               int y, int width, int height);
    private static native void nativeSetPixel(int nativeBitmap, int x, int y,
                                              int color);
    private static native void nativeSetPixels(int nativeBitmap, int[] colors,
                                               int offset, int stride, int x,
                                               int y, int width, int height);
    private static native void nativeCopyPixelsToBuffer(int nativeBitmap,
                                                        Buffer dst);
    private static native void nativeCopyPixelsFromBuffer(int nb, Buffer src);
    private static native int nativeGenerationId(int nativeBitmap);
    private static native Bitmap nativeCreateFromParcel(Parcel p);
    // returns true on success
    private static native boolean nativeWriteToParcel(int nativeBitmap,
                                                      boolean isMutable,
                                                      int density,
                                                      Parcel p);
    // returns a new bitmap built from the native bitmap's alpha, and the paint
    private static native Bitmap nativeExtractAlpha(int nativeBitmap,
                                                    int nativePaint,
                                                    int[] offsetXY);
    private static native void nativePrepareToDraw(int nativeBitmap);
    private static native void nativeSetHasAlpha(int nBitmap, boolean hasAlpha);
    private static native boolean nativeSameAs(int nb0, int nb1);

     final int ni() {
        return mNativeBitmap;
    }
}package android.graphics;
import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import android.annotation.ColorInt;
import android.annotation.ColorLong;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Px;
import android.annotation.Size;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.fonts.FontVariationAxis;
import android.os.Build;
import android.os.LocaleList;
import android.text.GraphicsOperations;
import android.text.SpannableString;
import android.text.SpannedString;
import android.text.TextUtils;
import com.android.internal.annotations.GuardedBy;
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
import libcore.util.NativeAllocationRegistry;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Objects;

public class Paint {

    private long mNativePaint;
    private long mNativeShader;
    private long mNativeColorFilter;

    private static class NoImagePreloadHolder {
        public static final NativeAllocationRegistry sRegistry =
                NativeAllocationRegistry.createMalloced(
                Paint.class.getClassLoader(), nGetNativeFinalizer());
    }

    private ColorFilter     mColorFilter;
    private MaskFilter      mMaskFilter;
    private PathEffect      mPathEffect;
    private Shader          mShader;

    private Typeface        mTypeface;
    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;

    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
    };

            ANTI_ALIAS_FLAG,
            FILTER_BITMAP_FLAG,
            DITHER_FLAG,
            UNDERLINE_TEXT_FLAG,
            STRIKE_THRU_TEXT_FLAG,
            FAKE_BOLD_TEXT_FLAG,
            LINEAR_TEXT_FLAG,
            SUBPIXEL_TEXT_FLAG,
            EMBEDDED_BITMAP_TEXT_FLAG
    })
    public 

    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;

    public static final int DEV_KERN_TEXT_FLAG  = 0x100;

    public static final int LCD_RENDER_TEXT_FLAG = 0x200;

    public static final int EMBEDDED_BITMAP_TEXT_FLAG = 0x400;

    public static final int AUTO_HINTING_TEXT_FLAG = 0x800;

    public static final int VERTICAL_TEXT_FLAG = 0x1000;

    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;

        CURSOR_AFTER, CURSOR_AT_OR_AFTER, CURSOR_BEFORE, CURSOR_AT_OR_BEFORE
    })

    public 

    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;

        START_HYPHEN_EDIT_NO_EDIT,
        START_HYPHEN_EDIT_INSERT_HYPHEN,
        START_HYPHEN_EDIT_INSERT_ZWJ
    })

    public 

    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;

        END_HYPHEN_EDIT_NO_EDIT,
        END_HYPHEN_EDIT_REPLACE_WITH_HYPHEN,
        END_HYPHEN_EDIT_INSERT_HYPHEN,
        END_HYPHEN_EDIT_INSERT_ARMENIAN_HYPHEN,
        END_HYPHEN_EDIT_INSERT_MAQAF,
        END_HYPHEN_EDIT_INSERT_UCAS_HYPHEN,
        END_HYPHEN_EDIT_INSERT_ZWJ_AND_HYPHEN
    })

    public 

    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) {
        mNativePaint = nInit();
        NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativePaint);
        setFlags(flags | HIDDEN_DEFAULT_PAINT_FLAGS);

        mCompatScaling = mInvCompatScaling = 1;
        setTextLocales(LocaleList.getAdjustedDefault());
        mColor = Color.pack(Color.BLACK);
        resetElegantTextHeight();
    }

    public Paint(Paint paint) {
        mNativePaint = nInitWithPaint(paint.getNativeInstance());
        NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativePaint);
        setClassVariablesFrom(paint);
    }

    public void reset() {
        nReset(mNativePaint);
        setFlags(HIDDEN_DEFAULT_PAINT_FLAGS | ANTI_ALIAS_FLAG);

        mColor = Color.pack(Color.BLACK);
        mColorFilter = null;
        mMaskFilter = null;
        mPathEffect = null;
        mShader = null;
        mNativeShader = 0;
        mTypeface = null;
        mXfermode = null;
        mHasCompatScaling = false;
        mCompatScaling = 1;
        mInvCompatScaling = 1;
        mBidiFlags = BIDI_DEFAULT_LTR;
        setTextLocales(LocaleList.getAdjustedDefault());
        resetElegantTextHeight();
        mFontFeatureSettings = null;
        mFontVariationSettings = null;
        mShadowLayerRadius = 0.0f;
        mShadowLayerDx = 0.0f;
        mShadowLayerDy = 0.0f;
        mShadowLayerColor = Color.pack(0);
    }

    public void set(Paint src) {
        if (this != src) {

            nSet(mNativePaint, src.mNativePaint);
            setClassVariablesFrom(src);
        }
    }

    private void setClassVariablesFrom(Paint paint) {
        mColor = paint.mColor;
        mColorFilter = paint.mColorFilter;
        mMaskFilter = paint.mMaskFilter;
        mPathEffect = paint.mPathEffect;
        mShader = paint.mShader;
        mNativeShader = paint.mNativeShader;
        mTypeface = paint.mTypeface;
        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;
    }

    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 synchronized long getNativeInstance() {
        boolean filter = isFilterBitmap();
        long newNativeShader = mShader == null ? 0 : mShader.getNativeInstance(filter);
        if (newNativeShader != mNativeShader) {
            mNativeShader = newNativeShader;
            nSetShader(mNativePaint, mNativeShader);
        }
        long newNativeColorFilter = mColorFilter == null ? 0 : mColorFilter.getNativeInstance();
        if (newNativeColorFilter != mNativeColorFilter) {
            mNativeColorFilter = newNativeColorFilter;
            nSetColorFilter(mNativePaint, mNativeColorFilter);
        }
        return mNativePaint;
    }

    public int getBidiFlags() {
        return mBidiFlags;
    }

    public void setBidiFlags(int flags) {

        flags &= BIDI_FLAG_MASK;
        if (flags > BIDI_MAX_FLAG_VALUE) {
            throw new IllegalArgumentException("unknown bidi flag: " + flags);
        }
        mBidiFlags = flags;
    }

    public 
        return nGetFlags(mNativePaint);
    }

    public void setFlags(
        nSetFlags(mNativePaint, flags);
    }

    public int getHinting() {
        return nGetHinting(mNativePaint);
    }

    public void setHinting(int mode) {
        nSetHinting(mNativePaint, mode);
    }

    public final boolean isAntiAlias() {
        return (getFlags() & ANTI_ALIAS_FLAG) != 0;
    }

    public void setAntiAlias(boolean aa) {
        nSetAntiAlias(mNativePaint, aa);
    }

    public final boolean isDither() {
        return (getFlags() & DITHER_FLAG) != 0;
    }

    public void setDither(boolean dither) {
        nSetDither(mNativePaint, dither);
    }

    public final boolean isLinearText() {
        return (getFlags() & LINEAR_TEXT_FLAG) != 0;
    }

    public void setLinearText(boolean linearText) {
        nSetLinearText(mNativePaint, linearText);
    }

    public final boolean isSubpixelText() {
        return (getFlags() & SUBPIXEL_TEXT_FLAG) != 0;
    }

    public void setSubpixelText(boolean subpixelText) {
        nSetSubpixelText(mNativePaint, subpixelText);
    }

    public final boolean isUnderlineText() {
        return (getFlags() & UNDERLINE_TEXT_FLAG) != 0;
    }

    public 
        return nGetUnderlinePosition(mNativePaint);
    }

    public 
        return nGetUnderlineThickness(mNativePaint);
    }

    public void setUnderlineText(boolean underlineText) {
        nSetUnderlineText(mNativePaint, underlineText);
    }

    public final boolean isStrikeThruText() {
        return (getFlags() & STRIKE_THRU_TEXT_FLAG) != 0;
    }

    public 
        return nGetStrikeThruPosition(mNativePaint);
    }

    public 
        return nGetStrikeThruThickness(mNativePaint);
    }

    public void setStrikeThruText(boolean strikeThruText) {
        nSetStrikeThruText(mNativePaint, strikeThruText);
    }

    public final boolean isFakeBoldText() {
        return (getFlags() & FAKE_BOLD_TEXT_FLAG) != 0;
    }

    public void setFakeBoldText(boolean fakeBoldText) {
        nSetFakeBoldText(mNativePaint, fakeBoldText);
    }

    public final boolean isFilterBitmap() {
        return (getFlags() & FILTER_BITMAP_FLAG) != 0;
    }

    public void setFilterBitmap(boolean filter) {
        nSetFilterBitmap(mNativePaint, filter);
    }

    public Style getStyle() {
        return sStyleArray[nGetStyle(mNativePaint)];
    }

    public void setStyle(Style style) {
        nSetStyle(mNativePaint, style.nativeInt);
    }

    public int getColor() {
        return Color.toArgb(mColor);
    }

    public long getColorLong() {
        return mColor;
    }

    public void setColor(
        nSetColor(mNativePaint, color);
        mColor = Color.pack(color);
    }

    public void setColor(
        ColorSpace cs = Color.colorSpace(color);
        nSetColor(mNativePaint, cs.getNativeInstance(), color);
        mColor = color;
    }

    public int getAlpha() {
        return Math.round(Color.alpha(mColor) * 255.0f);
    }

    public void setAlpha(int a) {

        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);
        nSetAlpha(mNativePaint, a);
    }

    public void setARGB(int a, int r, int g, int b) {
        setColor((a << 24) | (r << 16) | (g << 8) | b);
    }

    public float getStrokeWidth() {
        return nGetStrokeWidth(mNativePaint);
    }

    public void setStrokeWidth(float width) {
        nSetStrokeWidth(mNativePaint, width);
    }

    public float getStrokeMiter() {
        return nGetStrokeMiter(mNativePaint);
    }

    public void setStrokeMiter(float miter) {
        nSetStrokeMiter(mNativePaint, miter);
    }

    public Cap getStrokeCap() {
        return sCapArray[nGetStrokeCap(mNativePaint)];
    }

    public void setStrokeCap(Cap cap) {
        nSetStrokeCap(mNativePaint, cap.nativeInt);
    }

    public Join getStrokeJoin() {
        return sJoinArray[nGetStrokeJoin(mNativePaint)];
    }

    public void setStrokeJoin(Join join) {
        nSetStrokeJoin(mNativePaint, join.nativeInt);
    }

    public boolean getFillPath(Path src, Path dst) {
        return nGetFillPath(mNativePaint, src.readOnlyNI(), dst.mutateNI());
    }

    public Shader getShader() {
        return mShader;
    }

    public Shader setShader(Shader shader) {

        if (mShader != shader) {
            mNativeShader = -1;

            nSetShader(mNativePaint, 0);
        }

        mShader = shader;
        return shader;
    }

    public ColorFilter getColorFilter() {
        return mColorFilter;
    }

    public ColorFilter setColorFilter(ColorFilter filter) {

        if (mColorFilter != filter) {
            mNativeColorFilter = -1;
        }

        mColorFilter = filter;
        return filter;
    }

    public Xfermode getXfermode() {
        return mXfermode;
    }

    public BlendMode getBlendMode() {
        if (mXfermode == null) {
            return null;
        } else {
            return BlendMode.fromValue(mXfermode.porterDuffMode);
        }
    }

    public Xfermode setXfermode(Xfermode xfermode) {
        return installXfermode(xfermode);
    }

    private Xfermode installXfermode(Xfermode xfermode) {
        int newMode = xfermode != null ? xfermode.porterDuffMode : Xfermode.DEFAULT;
        int curMode = mXfermode != null ? mXfermode.porterDuffMode : Xfermode.DEFAULT;
        if (newMode != curMode) {
            nSetXfermode(mNativePaint, newMode);
        }
        mXfermode = xfermode;
        return xfermode;
    }

    public void setBlendMode(
        installXfermode(blendmode != null ? blendmode.getXfermode() : null);
    }

    public PathEffect getPathEffect() {
        return mPathEffect;
    }

    public PathEffect setPathEffect(PathEffect effect) {
        long effectNative = 0;
        if (effect != null) {
            effectNative = effect.native_instance;
        }
        nSetPathEffect(mNativePaint, effectNative);
        mPathEffect = effect;
        return effect;
    }

    public MaskFilter getMaskFilter() {
        return mMaskFilter;
    }

    public MaskFilter setMaskFilter(MaskFilter maskfilter) {
        long maskfilterNative = 0;
        if (maskfilter != null) {
            maskfilterNative = maskfilter.native_instance;
        }
        nSetMaskFilter(mNativePaint, maskfilterNative);
        mMaskFilter = maskfilter;
        return maskfilter;
    }

    public Typeface getTypeface() {
        return mTypeface;
    }

    public Typeface setTypeface(Typeface typeface) {
        final long typefaceNative = typeface == null ? 0 : typeface.native_instance;
        nSetTypeface(mNativePaint, typefaceNative);
        mTypeface = typeface;
        return typeface;
    }

    public Rasterizer getRasterizer() {
        return null;
    }

    public Rasterizer setRasterizer(Rasterizer rasterizer) {
        return rasterizer;
    }

    public void setShadowLayer(float radius, float dx, float dy, 
        setShadowLayer(radius, dx, dy, Color.pack(shadowColor));
    }

    public void setShadowLayer(float radius, float dx, float dy, 
        ColorSpace cs = Color.colorSpace(shadowColor);
        nSetShadowLayer(mNativePaint, radius, dx, dy, cs.getNativeInstance(), shadowColor);
        mShadowLayerRadius = radius;
        mShadowLayerDx = dx;
        mShadowLayerDy = dy;
        mShadowLayerColor = shadowColor;
    }

    public void clearShadowLayer() {
        setShadowLayer(0, 0, 0, 0);
    }

    public boolean hasShadowLayer() {
        return nHasShadowLayer(mNativePaint);
    }

    public float getShadowLayerRadius() {
        return mShadowLayerRadius;
    }

    public float getShadowLayerDx() {
        return mShadowLayerDx;
    }

    public float getShadowLayerDy() {
        return mShadowLayerDy;
    }

    public 
        return Color.toArgb(mShadowLayerColor);
    }

    public 
        return mShadowLayerColor;
    }

    public Align getTextAlign() {
        return sAlignArray[nGetTextAlign(mNativePaint)];
    }

    public void setTextAlign(Align align) {
        nSetTextAlign(mNativePaint, align.nativeInt);
    }

    public Locale getTextLocale() {
        return mLocales.get(0);
    }

    public LocaleList getTextLocales() {
        return mLocales;
    }

    public void setTextLocale(
        if (locale == null) {
            throw new IllegalArgumentException("locale cannot be null");
        }
        if (mLocales != null && mLocales.size() == 1 && locale.equals(mLocales.get(0))) {
            return;
        }
        mLocales = new LocaleList(locale);
        syncTextLocalesWithMinikin();
    }

    public void setTextLocales(
        if (locales == null || locales.isEmpty()) {
            throw new IllegalArgumentException("locales cannot be null or empty");
        }
        if (locales.equals(mLocales)) return;
        mLocales = locales;
        syncTextLocalesWithMinikin();
    }
    private void syncTextLocalesWithMinikin() {
        final String languageTags = mLocales.toLanguageTags();
        final Integer minikinLocaleListId;
        synchronized (sCacheLock) {
            minikinLocaleListId = sMinikinLocaleListIdCache.get(languageTags);
            if (minikinLocaleListId == null) {
                final int newID = nSetTextLocales(mNativePaint, languageTags);
                sMinikinLocaleListIdCache.put(languageTags, newID);
                return;
            }
        }
        nSetTextLocalesByMinikinLocaleListId(mNativePaint, minikinLocaleListId.intValue());
    }

    public boolean isElegantTextHeight() {
        int rawValue = nGetElegantTextHeight(mNativePaint);
        switch (rawValue) {
            case ELEGANT_TEXT_HEIGHT_DISABLED:
                return false;
            case ELEGANT_TEXT_HEIGHT_ENABLED:
                return true;
            case ELEGANT_TEXT_HEIGHT_UNSET:
            default:
                return com.android.text.flags.Flags.deprecateUiFonts();
        }
    }

    private static final int ELEGANT_TEXT_HEIGHT_UNSET = -1;
    private static final int ELEGANT_TEXT_HEIGHT_ENABLED = 0;
    private static final int ELEGANT_TEXT_HEIGHT_DISABLED = 1;

    public void setElegantTextHeight(boolean elegant) {
        nSetElegantTextHeight(mNativePaint,
                elegant ? ELEGANT_TEXT_HEIGHT_ENABLED : ELEGANT_TEXT_HEIGHT_DISABLED);
    }

    public static final long DEPRECATE_UI_FONT = 279646685L;
    private void resetElegantTextHeight() {
        if (CompatChanges.isChangeEnabled(DEPRECATE_UI_FONT)) {
            nSetElegantTextHeight(mNativePaint, ELEGANT_TEXT_HEIGHT_UNSET);
        } else {
            nSetElegantTextHeight(mNativePaint, ELEGANT_TEXT_HEIGHT_DISABLED);
        }
    }

    public float getTextSize() {
        return nGetTextSize(mNativePaint);
    }

    public void setTextSize(float textSize) {
        nSetTextSize(mNativePaint, textSize);
    }

    public float getTextScaleX() {
        return nGetTextScaleX(mNativePaint);
    }

    public void setTextScaleX(float scaleX) {
        nSetTextScaleX(mNativePaint, scaleX);
    }

    public float getTextSkewX() {
        return nGetTextSkewX(mNativePaint);
    }

    public void setTextSkewX(float skewX) {
        nSetTextSkewX(mNativePaint, skewX);
    }

    public float getLetterSpacing() {
        return nGetLetterSpacing(mNativePaint);
    }

    public void setLetterSpacing(float letterSpacing) {
        nSetLetterSpacing(mNativePaint, letterSpacing);
    }

    public 
        return nGetWordSpacing(mNativePaint);
    }

    public void setWordSpacing(
        nSetWordSpacing(mNativePaint, wordSpacing);
    }

    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;
        nSetFontFeatureSettings(mNativePaint, settings);
    }

    public String getFontVariationSettings() {
        return mFontVariationSettings;
    }

    public boolean setFontVariationSettings(String fontVariationSettings) {
        final String settings = TextUtils.nullIfEmpty(fontVariationSettings);
        if (settings == mFontVariationSettings
                || (settings != null && settings.equals(mFontVariationSettings))) {
            return true;
        }
        if (settings == null || settings.length() == 0) {
            mFontVariationSettings = null;
            setTypeface(Typeface.createFromTypefaceWithVariation(mTypeface,
                      Collections.emptyList()));
            return true;
        }

        Typeface targetTypeface = mTypeface == null ? Typeface.DEFAULT : mTypeface;
        FontVariationAxis[] axes = FontVariationAxis.fromFontVariationSettings(settings);
        final ArrayList<FontVariationAxis> filteredAxes = new ArrayList<FontVariationAxis>();
        for (final FontVariationAxis axis : axes) {
            if (targetTypeface.isSupportedAxes(axis.getOpenTypeTagValue())) {
                filteredAxes.add(axis);
            }
        }
        if (filteredAxes.isEmpty()) {
            return false;
        }
        mFontVariationSettings = settings;
        setTypeface(Typeface.createFromTypefaceWithVariation(targetTypeface, filteredAxes));
        return true;
    }

    public 
        return nGetStartHyphenEdit(mNativePaint);
    }

    public 
        return nGetEndHyphenEdit(mNativePaint);
    }

    public void setStartHyphenEdit(
        nSetStartHyphenEdit(mNativePaint, startHyphen);
    }

    public void setEndHyphenEdit(
        nSetEndHyphenEdit(mNativePaint, endHyphen);
    }

    public float ascent() {
        return nAscent(mNativePaint);
    }

    public float descent() {
        return nDescent(mNativePaint);
    }

    public static class FontMetrics {

        public float   top;

        public float   ascent;

        public float   descent;

        public float   bottom;

        public float   leading;

        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;
        }

        public int hashCode() {
            return Objects.hash(top, ascent, descent, bottom, leading);
        }

        public String toString() {
            return "FontMetrics{"
                    + "top=" + top
                    + ", ascent=" + ascent
                    + ", descent=" + descent
                    + ", bottom=" + bottom
                    + ", leading=" + leading
                    + '}';
        }
    }

    public float getFontMetrics(FontMetrics metrics) {
        return nGetFontMetrics(mNativePaint, metrics, false);
    }

    public FontMetrics getFontMetrics() {
        FontMetrics fm = new FontMetrics();
        getFontMetrics(fm);
        return fm;
    }

    public void getFontMetricsForLocale(
        nGetFontMetrics(mNativePaint, metrics, true);
    }

    public void getFontMetricsInt(

            boolean isRtl,

        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;
        }
        if (text instanceof String) {
            nGetFontMetricsIntForText(mNativePaint, (String) text, start, count, contextStart,
                    contextCount, isRtl, outMetrics);
        } else {
            char[] buf = TemporaryBuffer.obtain(contextCount);
            try {
                TextUtils.getChars(text, contextStart, contextStart + contextCount, buf, 0);
                nGetFontMetricsIntForText(mNativePaint, buf, start - contextStart, count, 0,
                        contextCount, isRtl, outMetrics);
            } finally {
                TemporaryBuffer.recycle(buf);
            }
        }
    }

    public void getFontMetricsInt(

            boolean isRtl,

        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;
        }
        nGetFontMetricsIntForText(mNativePaint, text, start, count, contextStart, contextCount,
                isRtl, outMetrics);
    }

    public static class FontMetricsInt {

        public int   top;

        public int   ascent;

        public int   descent;

        public int   bottom;

        public int   leading;

        public void set(
            top = fontMetricsInt.top;
            ascent = fontMetricsInt.ascent;
            descent = fontMetricsInt.descent;
            bottom = fontMetricsInt.bottom;
            leading = fontMetricsInt.leading;
        }

        public void set(

            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);
        }

            return "FontMetricsInt: top=" + top + " ascent=" + ascent +
                    " descent=" + descent + " bottom=" + bottom +
                    " leading=" + leading;
        }

        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;
        }

        public int hashCode() {
            return Objects.hash(top, ascent, descent, bottom, leading);
        }
    }

    public int getFontMetricsInt(FontMetricsInt fmi) {
        return nGetFontMetricsInt(mNativePaint, fmi, false);
    }
    public FontMetricsInt getFontMetricsInt() {
        FontMetricsInt fm = new FontMetricsInt();
        getFontMetricsInt(fm);
        return fm;
    }

    public void getFontMetricsIntForLocale(
        nGetFontMetricsInt(mNativePaint, metrics, true);
    }

    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;
        }
        if (!mHasCompatScaling) {
            return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
                    index, count, index, count, mBidiFlags, null, 0));
        }
        final float oldSize = getTextSize();
        setTextSize(oldSize * mCompatScaling);
        final float w = nGetTextAdvances(mNativePaint, text, index, count, index, count,
                mBidiFlags, null, 0);
        setTextSize(oldSize);
        return (float) Math.ceil(w*mInvCompatScaling);
    }

    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;
        }
        if (!mHasCompatScaling) {
            return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
                    start, end, start, end, mBidiFlags, null, 0));
        }
        final float oldSize = getTextSize();
        setTextSize(oldSize * mCompatScaling);
        final float w = nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags,
                null, 0);
        setTextSize(oldSize);
        return (float) Math.ceil(w * mInvCompatScaling);
    }

    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);
        }
        if (text instanceof GraphicsOperations) {
            return ((GraphicsOperations)text).measureText(start, end, this);
        }
        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) {
            return nBreakText(mNativePaint, text, index, count, maxWidth, mBidiFlags,
                    measuredWidth);
        }
        final float oldSize = getTextSize();
        setTextSize(oldSize * mCompatScaling);
        final int res = nBreakText(mNativePaint, text, index, count, maxWidth * mCompatScaling,
                mBidiFlags, measuredWidth);
        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) {
            return nBreakText(mNativePaint, text, measureForwards,
                    maxWidth, mBidiFlags, measuredWidth);
        }
        final float oldSize = getTextSize();
        setTextSize(oldSize*mCompatScaling);
        final int res = nBreakText(mNativePaint, text, measureForwards, maxWidth*mCompatScaling,
                mBidiFlags, measuredWidth);
        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;
        }
        if (!mHasCompatScaling) {
            nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
            return count;
        }
        final float oldSize = getTextSize();
        setTextSize(oldSize * mCompatScaling);
        nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
        setTextSize(oldSize);
        for (int i = 0; i < count; i++) {
            widths[i] *= mInvCompatScaling;
        }
        return count;
    }

    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);
        }
        if (text instanceof GraphicsOperations) {
            return ((GraphicsOperations) text).getTextWidths(start, end,
                                                                 widths, this);
        }
        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;
        }
        if (!mHasCompatScaling) {
            nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
            return end - start;
        }
        final float oldSize = getTextSize();
        setTextSize(oldSize * mCompatScaling);
        nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
        setTextSize(oldSize);
        for (int i = 0; i < end - start; i++) {
            widths[i] *= mInvCompatScaling;
        }
        return end - start;
    }

    public int getTextWidths(String text, float[] widths) {
        return getTextWidths(text, 0, text.length(), widths);
    }

    public float getTextRunAdvances(

        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) {
            return nGetTextAdvances(mNativePaint, chars, index, count, contextIndex, contextCount,
                    isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances,
                    advancesIndex);
        }
        final float oldSize = getTextSize();
        setTextSize(oldSize * mCompatScaling);
        final float res = nGetTextAdvances(mNativePaint, chars, index, count, contextIndex,
                contextCount, isRtl ? BIDI_FORCE_RTL : BIDI_FORCE_LTR, advances, advancesIndex);
        setTextSize(oldSize);
        if (advances != null) {
            for (int i = advancesIndex, e = i + count; i < e; i++) {
                advances[i] *= mInvCompatScaling;
            }
        }
        return res * mInvCompatScaling; 
    }

    public int getTextRunCursor(

        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();
        }
        return nGetTextRunCursor(mNativePaint, text, contextStart, contextLength,
                isRtl ? DIRECTION_RTL : DIRECTION_LTR, offset, cursorOpt);
    }

    public int getTextRunCursor(

        if (text instanceof String || text instanceof SpannedString ||
                text instanceof SpannableString) {
            return getTextRunCursor(text.toString(), contextStart, contextEnd,
                    isRtl, offset, cursorOpt);
        }
        if (text instanceof GraphicsOperations) {
            return ((GraphicsOperations) text).getTextRunCursor(
                    contextStart, contextEnd, isRtl, offset, cursorOpt, this);
        }
        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(

        if (((contextStart | contextEnd | offset | (contextEnd - contextStart)
                | (offset - contextStart) | (contextEnd - offset)
                | (text.length() - contextEnd) | cursorOpt) < 0)
                || cursorOpt > CURSOR_OPT_MAX_VALUE) {
            throw new IndexOutOfBoundsException();
        }
        return nGetTextRunCursor(mNativePaint, text, contextStart, contextEnd,
                isRtl ? DIRECTION_RTL : DIRECTION_LTR, offset, cursorOpt);
    }

    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();
        }
        nGetTextPath(mNativePaint, mBidiFlags, text, index, count, x, y, path.mutateNI());
    }

    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();
        }
        nGetTextPath(mNativePaint, mBidiFlags, text, start, end, x, y, path.mutateNI());
    }

    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");
        }
        nGetStringBounds(mNativePaint, text, start, end, mBidiFlags, bounds);
    }

    public void getTextBounds(

        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");
        }
        nGetCharArrayBounds(mNativePaint, text, index, count, mBidiFlags,
            bounds);
    }

    public boolean hasGlyph(String string) {
        return nHasGlyph(mNativePaint, mBidiFlags, string);
    }

    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;
        }

        return nGetRunAdvance(mNativePaint, text, start, end, contextStart, contextEnd, isRtl,
                offset);
    }

    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;
        }

        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(
            int contextEnd, boolean isRtl, int offset,

        return getRunCharacterAdvance(text, start, end, contextStart, contextEnd, isRtl, offset,
                advances, advancesIndex, null);
    }

    public float getRunCharacterAdvance(
            int contextEnd, boolean isRtl, int offset,

        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;
        }
        return nGetRunCharacterAdvance(mNativePaint, text, start, end, contextStart, contextEnd,
                isRtl, offset, advances, advancesIndex, drawBounds);
    }

    public float getRunCharacterAdvance(
            int contextStart, int contextEnd, boolean isRtl, int offset,

        return getRunCharacterAdvance(text, start, end, contextStart, contextEnd, isRtl, offset,
                advances, advancesIndex, null);
    }

    public float getRunCharacterAdvance(
            int contextStart, int contextEnd, boolean isRtl, int offset,

        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);
        TemporaryBuffer.recycle(buf);
        return result;
    }

    public int getOffsetForAdvance(char[] 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();
        }

        return nGetOffsetForAdvance(mNativePaint, text, start, end, contextStart, contextEnd,
                isRtl, advance);
    }

    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();
        }

        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(
        return nEqualsForTextMeasurement(mNativePaint, other.mNativePaint);
    }

    private static native long nGetNativeFinalizer();
    private static native long nInit();
    private static native long nInitWithPaint(long paint);
    private static native int nBreakText(long nObject, char[] text, int index, int count,
            float maxWidth, int bidiFlags, float[] measuredWidth);
    private static native int nBreakText(long nObject, String text, boolean measureForwards,
            float maxWidth, int bidiFlags, float[] measuredWidth);
    private static native float nGetTextAdvances(long paintPtr, char[] text, int index, int count,
            int contextIndex, int contextCount, int bidiFlags, float[] advances, int advancesIndex);
    private static native float nGetTextAdvances(long paintPtr, String text, int start, int end,
            int contextStart, int contextEnd, int bidiFlags, float[] advances, int advancesIndex);
    private native int nGetTextRunCursor(long paintPtr, char[] text, int contextStart,
            int contextLength, int dir, int offset, int cursorOpt);
    private native int nGetTextRunCursor(long paintPtr, String text, int contextStart,
            int contextEnd, int dir, int offset, int cursorOpt);
    private static native void nGetTextPath(long paintPtr, int bidiFlags, char[] text, int index,
            int count, float x, float y, long path);
    private static native void nGetTextPath(long paintPtr, int bidiFlags, String text, int start,
            int end, float x, float y, long path);
    private static native void nGetStringBounds(long nativePaint, String text, int start, int end,
            int bidiFlags, Rect bounds);
    private static native void nGetCharArrayBounds(long nativePaint, char[] text, int index,
            int count, int bidiFlags, Rect bounds);
    private static native boolean nHasGlyph(long paintPtr, int bidiFlags, String string);
    private static native float nGetRunAdvance(long paintPtr, char[] text, int start, int end,
            int contextStart, int contextEnd, boolean isRtl, int offset);
    private static native float nGetRunCharacterAdvance(long paintPtr, char[] text, int start,
            int end, int contextStart, int contextEnd, boolean isRtl, int offset, float[] advances,
            int advancesIndex, RectF drawingBounds);
    private static native int nGetOffsetForAdvance(long paintPtr, char[] text, int start, int end,
            int contextStart, int contextEnd, boolean isRtl, float advance);
    private static native void nGetFontMetricsIntForText(long paintPtr, char[] text,
            int start, int count, int ctxStart, int ctxCount, boolean isRtl,
            FontMetricsInt outMetrics);
    private static native void nGetFontMetricsIntForText(long paintPtr, String text,
            int start, int count, int ctxStart, int ctxCount, boolean isRtl,
            FontMetricsInt outMetrics);

    private static native int nSetTextLocales(long paintPtr, String locales);

    private static native void nSetFontFeatureSettings(long paintPtr, String settings);

    private static native float nGetFontMetrics(long paintPtr, FontMetrics metrics,
            boolean useLocale);

    private static native int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi,
            boolean useLocale);

    private static native void nReset(long paintPtr);

    private static native void nSet(long paintPtrDest, long paintPtrSrc);

    private static native int nGetStyle(long paintPtr);

    private static native void nSetStyle(long paintPtr, int style);

    private static native int nGetStrokeCap(long paintPtr);

    private static native void nSetStrokeCap(long paintPtr, int cap);

    private static native int nGetStrokeJoin(long paintPtr);

    private static native void nSetStrokeJoin(long paintPtr, int join);

    private static native boolean nGetFillPath(long paintPtr, long src, long dst);

    private static native long nSetShader(long paintPtr, long shader);

    private static native long nSetColorFilter(long paintPtr, long filter);

    private static native void nSetXfermode(long paintPtr, int xfermode);

    private static native long nSetPathEffect(long paintPtr, long effect);

    private static native long nSetMaskFilter(long paintPtr, long maskfilter);

    private static native void nSetTypeface(long paintPtr, long typeface);

    private static native int nGetTextAlign(long paintPtr);

    private static native void nSetTextAlign(long paintPtr, int align);

    private static native void nSetTextLocalesByMinikinLocaleListId(long paintPtr,
            int mMinikinLocaleListId);

    private static native void nSetShadowLayer(long paintPtr,
            float radius, float dx, float dy, long colorSpaceHandle,

    private static native boolean nHasShadowLayer(long paintPtr);

    private static native float nGetLetterSpacing(long paintPtr);

    private static native void nSetLetterSpacing(long paintPtr, float letterSpacing);

    private static native float nGetWordSpacing(long paintPtr);

    private static native void nSetWordSpacing(long paintPtr, float wordSpacing);

    private static native int nGetStartHyphenEdit(long paintPtr);

    private static native int nGetEndHyphenEdit(long paintPtr);

    private static native void nSetStartHyphenEdit(long paintPtr, int hyphen);

    private static native void nSetEndHyphenEdit(long paintPtr, int hyphen);

    private static native void nSetStrokeMiter(long paintPtr, float miter);

    private static native float nGetStrokeMiter(long paintPtr);

    private static native void nSetStrokeWidth(long paintPtr, float width);

    private static native float nGetStrokeWidth(long paintPtr);

    private static native void nSetAlpha(long paintPtr, int a);

    private static native void nSetDither(long paintPtr, boolean dither);

    private static native int nGetFlags(long paintPtr);

    private static native void nSetFlags(long paintPtr, int flags);

    private static native int nGetHinting(long paintPtr);

    private static native void nSetHinting(long paintPtr, int mode);

    private static native void nSetAntiAlias(long paintPtr, boolean aa);

    private static native void nSetLinearText(long paintPtr, boolean linearText);

    private static native void nSetSubpixelText(long paintPtr, boolean subpixelText);

    private static native void nSetUnderlineText(long paintPtr, boolean underlineText);

    private static native void nSetFakeBoldText(long paintPtr, boolean fakeBoldText);

    private static native void nSetFilterBitmap(long paintPtr, boolean filter);

    private static native void nSetColor(long paintPtr, long colorSpaceHandle,

    private static native void nSetColor(long paintPtr, 

    private static native void nSetStrikeThruText(long paintPtr, boolean strikeThruText);

    private static native int nGetElegantTextHeight(long paintPtr);

    private static native void nSetElegantTextHeight(long paintPtr, int elegant);

    private static native float nGetTextSize(long paintPtr);

    private static native float nGetTextScaleX(long paintPtr);

    private static native void nSetTextScaleX(long paintPtr, float scaleX);

    private static native float nGetTextSkewX(long paintPtr);

    private static native void nSetTextSkewX(long paintPtr, float skewX);

    private static native float nAscent(long paintPtr);

    private static native float nDescent(long paintPtr);

    private static native float nGetUnderlinePosition(long paintPtr);

    private static native float nGetUnderlineThickness(long paintPtr);

    private static native float nGetStrikeThruPosition(long paintPtr);

    private static native float nGetStrikeThruThickness(long paintPtr);

    private static native void nSetTextSize(long paintPtr, float textSize);

    private static native boolean nEqualsForTextMeasurement(long leftPaintPtr, long rightPaintPtr);
}