convert this to c# 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);
}