Home | History | Annotate | Download | only in graphics
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.graphics;
     18 
     19 import android.text.SpannableString;
     20 import android.text.SpannableStringBuilder;
     21 import android.text.SpannedString;
     22 import android.text.TextUtils;
     23 
     24 import java.awt.BasicStroke;
     25 import java.awt.Font;
     26 import java.awt.Toolkit;
     27 import java.awt.font.FontRenderContext;
     28 import java.awt.geom.AffineTransform;
     29 import java.awt.geom.Rectangle2D;
     30 import java.util.ArrayList;
     31 import java.util.Collections;
     32 import java.util.List;
     33 
     34 /**
     35  * A paint implementation overridden by the LayoutLib bridge.
     36  */
     37 public class Paint extends _Original_Paint {
     38 
     39     private int mColor = 0xFFFFFFFF;
     40     private float mStrokeWidth = 1.f;
     41     private float mTextSize = 20;
     42     private float mScaleX = 1;
     43     private float mSkewX = 0;
     44     private Align mAlign = Align.LEFT;
     45     private Style mStyle = Style.FILL;
     46     private float mStrokeMiter = 4.0f;
     47     private Cap mCap = Cap.BUTT;
     48     private Join mJoin = Join.MITER;
     49     private int mFlags = 0;
     50 
     51     /**
     52      * Class associating a {@link Font} and it's {@link java.awt.FontMetrics}.
     53      */
     54     public static final class FontInfo {
     55         Font mFont;
     56         java.awt.FontMetrics mMetrics;
     57     }
     58 
     59     private List<FontInfo> mFonts;
     60     private final FontRenderContext mFontContext = new FontRenderContext(
     61             new AffineTransform(), true, true);
     62 
     63     public static final int ANTI_ALIAS_FLAG       = _Original_Paint.ANTI_ALIAS_FLAG;
     64     public static final int FILTER_BITMAP_FLAG    = _Original_Paint.FILTER_BITMAP_FLAG;
     65     public static final int DITHER_FLAG           = _Original_Paint.DITHER_FLAG;
     66     public static final int UNDERLINE_TEXT_FLAG   = _Original_Paint.UNDERLINE_TEXT_FLAG;
     67     public static final int STRIKE_THRU_TEXT_FLAG = _Original_Paint.STRIKE_THRU_TEXT_FLAG;
     68     public static final int FAKE_BOLD_TEXT_FLAG   = _Original_Paint.FAKE_BOLD_TEXT_FLAG;
     69     public static final int LINEAR_TEXT_FLAG      = _Original_Paint.LINEAR_TEXT_FLAG;
     70     public static final int SUBPIXEL_TEXT_FLAG    = _Original_Paint.SUBPIXEL_TEXT_FLAG;
     71     public static final int DEV_KERN_TEXT_FLAG    = _Original_Paint.DEV_KERN_TEXT_FLAG;
     72 
     73     public static class FontMetrics extends _Original_Paint.FontMetrics {
     74     }
     75 
     76     public static class FontMetricsInt extends _Original_Paint.FontMetricsInt {
     77     }
     78 
     79     /**
     80      * The Style specifies if the primitive being drawn is filled,
     81      * stroked, or both (in the same color). The default is FILL.
     82      */
     83     public enum Style {
     84         /**
     85          * Geometry and text drawn with this style will be filled, ignoring all
     86          * stroke-related settings in the paint.
     87          */
     88         FILL            (0),
     89         /**
     90          * Geometry and text drawn with this style will be stroked, respecting
     91          * the stroke-related fields on the paint.
     92          */
     93         STROKE          (1),
     94         /**
     95          * Geometry and text drawn with this style will be both filled and
     96          * stroked at the same time, respecting the stroke-related fields on
     97          * the paint.
     98          */
     99         FILL_AND_STROKE (2);
    100 
    101         Style(int nativeInt) {
    102             this.nativeInt = nativeInt;
    103         }
    104         final int nativeInt;
    105     }
    106 
    107     /**
    108      * The Cap specifies the treatment for the beginning and ending of
    109      * stroked lines and paths. The default is BUTT.
    110      */
    111     public enum Cap {
    112         /**
    113          * The stroke ends with the path, and does not project beyond it.
    114          */
    115         BUTT    (0),
    116         /**
    117          * The stroke projects out as a square, with the center at the end
    118          * of the path.
    119          */
    120         ROUND   (1),
    121         /**
    122          * The stroke projects out as a semicircle, with the center at the
    123          * end of the path.
    124          */
    125         SQUARE  (2);
    126 
    127         private Cap(int nativeInt) {
    128             this.nativeInt = nativeInt;
    129         }
    130         final int nativeInt;
    131 
    132         /** custom for layoutlib */
    133         public int getJavaCap() {
    134             switch (this) {
    135                 case BUTT:
    136                     return BasicStroke.CAP_BUTT;
    137                 case ROUND:
    138                     return BasicStroke.CAP_ROUND;
    139                 default:
    140                 case SQUARE:
    141                     return BasicStroke.CAP_SQUARE;
    142             }
    143         }
    144     }
    145 
    146     /**
    147      * The Join specifies the treatment where lines and curve segments
    148      * join on a stroked path. The default is MITER.
    149      */
    150     public enum Join {
    151         /**
    152          * The outer edges of a join meet at a sharp angle
    153          */
    154         MITER   (0),
    155         /**
    156          * The outer edges of a join meet in a circular arc.
    157          */
    158         ROUND   (1),
    159         /**
    160          * The outer edges of a join meet with a straight line
    161          */
    162         BEVEL   (2);
    163 
    164         private Join(int nativeInt) {
    165             this.nativeInt = nativeInt;
    166         }
    167         final int nativeInt;
    168 
    169         /** custom for layoutlib */
    170         public int getJavaJoin() {
    171             switch (this) {
    172                 default:
    173                 case MITER:
    174                     return BasicStroke.JOIN_MITER;
    175                 case ROUND:
    176                     return BasicStroke.JOIN_ROUND;
    177                 case BEVEL:
    178                     return BasicStroke.JOIN_BEVEL;
    179             }
    180         }
    181     }
    182 
    183     /**
    184      * Align specifies how drawText aligns its text relative to the
    185      * [x,y] coordinates. The default is LEFT.
    186      */
    187     public enum Align {
    188         /**
    189          * The text is drawn to the right of the x,y origin
    190          */
    191         LEFT    (0),
    192         /**
    193          * The text is drawn centered horizontally on the x,y origin
    194          */
    195         CENTER  (1),
    196         /**
    197          * The text is drawn to the left of the x,y origin
    198          */
    199         RIGHT   (2);
    200 
    201         private Align(int nativeInt) {
    202             this.nativeInt = nativeInt;
    203         }
    204         final int nativeInt;
    205     }
    206 
    207     public Paint() {
    208         this(0);
    209     }
    210 
    211     /*
    212      * Do not remove or com.android.layoutlib.bridge.TestClassReplacement fails.
    213      */
    214     @Override
    215     public void finalize() { }
    216 
    217     public Paint(int flags) {
    218         setFlags(flags | DEFAULT_PAINT_FLAGS);
    219         initFont();
    220     }
    221 
    222     public Paint(Paint paint) {
    223         set(paint);
    224         initFont();
    225     }
    226 
    227     @Override
    228     public void reset() {
    229         super.reset();
    230     }
    231 
    232     /**
    233      * Returns the list of {@link Font} objects. The first item is the main font, the rest
    234      * are fall backs for characters not present in the main font.
    235      */
    236     public List<FontInfo> getFonts() {
    237         return mFonts;
    238     }
    239 
    240     private void initFont() {
    241         mTypeface = Typeface.DEFAULT;
    242         updateFontObject();
    243     }
    244 
    245     /**
    246      * Update the {@link Font} object from the typeface, text size and scaling
    247      */
    248     @SuppressWarnings("deprecation")
    249     private void updateFontObject() {
    250         if (mTypeface != null) {
    251             // Get the fonts from the TypeFace object.
    252             List<Font> fonts = mTypeface.getFonts();
    253 
    254             // create new font objects as well as FontMetrics, based on the current text size
    255             // and skew info.
    256             ArrayList<FontInfo> infoList = new ArrayList<FontInfo>(fonts.size());
    257             for (Font font : fonts) {
    258                 FontInfo info = new FontInfo();
    259                 info.mFont = font.deriveFont(mTextSize);
    260                 if (mScaleX != 1.0 || mSkewX != 0) {
    261                     // TODO: support skew
    262                     info.mFont = info.mFont.deriveFont(new AffineTransform(
    263                             mScaleX, mSkewX, 0, 0, 1, 0));
    264                 }
    265                 info.mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(info.mFont);
    266 
    267                 infoList.add(info);
    268             }
    269 
    270             mFonts = Collections.unmodifiableList(infoList);
    271         }
    272     }
    273 
    274     //----------------------------------------
    275 
    276     public void set(Paint src) {
    277         if (this != src) {
    278             mColor = src.mColor;
    279             mTextSize = src.mTextSize;
    280             mScaleX = src.mScaleX;
    281             mSkewX = src.mSkewX;
    282             mAlign = src.mAlign;
    283             mStyle = src.mStyle;
    284             mFlags = src.mFlags;
    285 
    286             updateFontObject();
    287 
    288             super.set(src);
    289         }
    290     }
    291 
    292     @Override
    293     public void setCompatibilityScaling(float factor) {
    294         super.setCompatibilityScaling(factor);
    295     }
    296 
    297     @Override
    298     public int getFlags() {
    299         return mFlags;
    300     }
    301 
    302     @Override
    303     public void setFlags(int flags) {
    304         mFlags = flags;
    305     }
    306 
    307     @Override
    308     public boolean isAntiAlias() {
    309         return super.isAntiAlias();
    310     }
    311 
    312     @Override
    313     public boolean isDither() {
    314         return super.isDither();
    315     }
    316 
    317     @Override
    318     public boolean isLinearText() {
    319         return super.isLinearText();
    320     }
    321 
    322     @Override
    323     public boolean isStrikeThruText() {
    324         return super.isStrikeThruText();
    325     }
    326 
    327     @Override
    328     public boolean isUnderlineText() {
    329         return super.isUnderlineText();
    330     }
    331 
    332     @Override
    333     public boolean isFakeBoldText() {
    334         return super.isFakeBoldText();
    335     }
    336 
    337     @Override
    338     public boolean isSubpixelText() {
    339         return super.isSubpixelText();
    340     }
    341 
    342     @Override
    343     public boolean isFilterBitmap() {
    344         return super.isFilterBitmap();
    345     }
    346 
    347     /**
    348      * Return the font's recommended interline spacing, given the Paint's
    349      * settings for typeface, textSize, etc. If metrics is not null, return the
    350      * fontmetric values in it.
    351      *
    352      * @param metrics If this object is not null, its fields are filled with
    353      *                the appropriate values given the paint's text attributes.
    354      * @return the font's recommended interline spacing.
    355      */
    356     public float getFontMetrics(FontMetrics metrics) {
    357         if (mFonts.size() > 0) {
    358             java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics;
    359             if (metrics != null) {
    360                 // Android expects negative ascent so we invert the value from Java.
    361                 metrics.top = - javaMetrics.getMaxAscent();
    362                 metrics.ascent = - javaMetrics.getAscent();
    363                 metrics.descent = javaMetrics.getDescent();
    364                 metrics.bottom = javaMetrics.getMaxDescent();
    365                 metrics.leading = javaMetrics.getLeading();
    366             }
    367 
    368             return javaMetrics.getHeight();
    369         }
    370 
    371         return 0;
    372     }
    373 
    374     public int getFontMetricsInt(FontMetricsInt metrics) {
    375         if (mFonts.size() > 0) {
    376             java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics;
    377             if (metrics != null) {
    378                 // Android expects negative ascent so we invert the value from Java.
    379                 metrics.top = - javaMetrics.getMaxAscent();
    380                 metrics.ascent = - javaMetrics.getAscent();
    381                 metrics.descent = javaMetrics.getDescent();
    382                 metrics.bottom = javaMetrics.getMaxDescent();
    383                 metrics.leading = javaMetrics.getLeading();
    384             }
    385 
    386             return javaMetrics.getHeight();
    387         }
    388 
    389         return 0;
    390     }
    391 
    392     /**
    393      * Reimplemented to return Paint.FontMetrics instead of _Original_Paint.FontMetrics
    394      */
    395     public FontMetrics getFontMetrics() {
    396         FontMetrics fm = new FontMetrics();
    397         getFontMetrics(fm);
    398         return fm;
    399     }
    400 
    401     /**
    402      * Reimplemented to return Paint.FontMetricsInt instead of _Original_Paint.FontMetricsInt
    403      */
    404     public FontMetricsInt getFontMetricsInt() {
    405         FontMetricsInt fm = new FontMetricsInt();
    406         getFontMetricsInt(fm);
    407         return fm;
    408     }
    409 
    410 
    411 
    412     @Override
    413     public float getFontMetrics(_Original_Paint.FontMetrics metrics) {
    414         throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN");
    415     }
    416 
    417     @Override
    418     public int getFontMetricsInt(_Original_Paint.FontMetricsInt metrics) {
    419         throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN");
    420     }
    421 
    422     @Override
    423     public Typeface setTypeface(Typeface typeface) {
    424         if (typeface != null) {
    425             mTypeface = typeface;
    426         } else {
    427             mTypeface = Typeface.DEFAULT;
    428         }
    429 
    430         updateFontObject();
    431 
    432         return typeface;
    433     }
    434 
    435     @Override
    436     public Typeface getTypeface() {
    437         return super.getTypeface();
    438     }
    439 
    440     @Override
    441     public int getColor() {
    442         return mColor;
    443     }
    444 
    445     @Override
    446     public void setColor(int color) {
    447         mColor = color;
    448     }
    449 
    450     @Override
    451     public void setARGB(int a, int r, int g, int b) {
    452         super.setARGB(a, r, g, b);
    453     }
    454 
    455     @Override
    456     public void setAlpha(int alpha) {
    457         mColor = (alpha << 24) | (mColor & 0x00FFFFFF);
    458     }
    459 
    460     @Override
    461     public int getAlpha() {
    462         return mColor >>> 24;
    463     }
    464 
    465     /**
    466      * Set or clear the shader object.
    467      * <p />
    468      * Pass null to clear any previous shader.
    469      * As a convenience, the parameter passed is also returned.
    470      *
    471      * @param shader May be null. the new shader to be installed in the paint
    472      * @return       shader
    473      */
    474     @Override
    475     public Shader setShader(Shader shader) {
    476         return mShader = shader;
    477     }
    478 
    479     @Override
    480     public Shader getShader() {
    481         return super.getShader();
    482     }
    483 
    484     /**
    485      * Set or clear the paint's colorfilter, returning the parameter.
    486      *
    487      * @param filter May be null. The new filter to be installed in the paint
    488      * @return       filter
    489      */
    490     @Override
    491     public ColorFilter setColorFilter(ColorFilter filter) {
    492         mColorFilter = filter;
    493         return filter;
    494     }
    495 
    496     @Override
    497     public ColorFilter getColorFilter() {
    498         return super.getColorFilter();
    499     }
    500 
    501     /**
    502      * Set or clear the xfermode object.
    503      * <p />
    504      * Pass null to clear any previous xfermode.
    505      * As a convenience, the parameter passed is also returned.
    506      *
    507      * @param xfermode May be null. The xfermode to be installed in the paint
    508      * @return         xfermode
    509      */
    510     @Override
    511     public Xfermode setXfermode(Xfermode xfermode) {
    512         return mXfermode = xfermode;
    513     }
    514 
    515     @Override
    516     public Xfermode getXfermode() {
    517         return super.getXfermode();
    518     }
    519 
    520     @Override
    521     public Rasterizer setRasterizer(Rasterizer rasterizer) {
    522         mRasterizer = rasterizer;
    523         return rasterizer;
    524     }
    525 
    526     @Override
    527     public Rasterizer getRasterizer() {
    528         return super.getRasterizer();
    529     }
    530 
    531     @Override
    532     public void setShadowLayer(float radius, float dx, float dy, int color) {
    533         // TODO Auto-generated method stub
    534     }
    535 
    536     @Override
    537     public void clearShadowLayer() {
    538         super.clearShadowLayer();
    539     }
    540 
    541     public void setTextAlign(Align align) {
    542         mAlign = align;
    543     }
    544 
    545     @Override
    546     public void setTextAlign(android.graphics._Original_Paint.Align align) {
    547         throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN");
    548     }
    549 
    550     public Align getTextAlign() {
    551         return mAlign;
    552     }
    553 
    554     public void setStyle(Style style) {
    555         mStyle = style;
    556     }
    557 
    558     @Override
    559     public void setStyle(android.graphics._Original_Paint.Style style) {
    560         throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN");
    561     }
    562 
    563     public Style getStyle() {
    564         return mStyle;
    565     }
    566 
    567     @Override
    568     public void setDither(boolean dither) {
    569         mFlags |= dither ? DITHER_FLAG : ~DITHER_FLAG;
    570     }
    571 
    572     @Override
    573     public void setAntiAlias(boolean aa) {
    574         mFlags |= aa ? ANTI_ALIAS_FLAG : ~ANTI_ALIAS_FLAG;
    575     }
    576 
    577     @Override
    578     public void setFakeBoldText(boolean flag) {
    579         mFlags |= flag ? FAKE_BOLD_TEXT_FLAG : ~FAKE_BOLD_TEXT_FLAG;
    580     }
    581 
    582     @Override
    583     public void setLinearText(boolean flag) {
    584         mFlags |= flag ? LINEAR_TEXT_FLAG : ~LINEAR_TEXT_FLAG;
    585     }
    586 
    587     @Override
    588     public void setSubpixelText(boolean flag) {
    589         mFlags |= flag ? SUBPIXEL_TEXT_FLAG : ~SUBPIXEL_TEXT_FLAG;
    590     }
    591 
    592     @Override
    593     public void setUnderlineText(boolean flag) {
    594         mFlags |= flag ? UNDERLINE_TEXT_FLAG : ~UNDERLINE_TEXT_FLAG;
    595     }
    596 
    597     @Override
    598     public void setStrikeThruText(boolean flag) {
    599         mFlags |= flag ? STRIKE_THRU_TEXT_FLAG : ~STRIKE_THRU_TEXT_FLAG;
    600     }
    601 
    602     @Override
    603     public void setFilterBitmap(boolean flag) {
    604         mFlags |= flag ? FILTER_BITMAP_FLAG : ~FILTER_BITMAP_FLAG;
    605     }
    606 
    607     @Override
    608     public float getStrokeWidth() {
    609         return mStrokeWidth;
    610     }
    611 
    612     @Override
    613     public void setStrokeWidth(float width) {
    614         mStrokeWidth = width;
    615     }
    616 
    617     @Override
    618     public float getStrokeMiter() {
    619         return mStrokeMiter;
    620     }
    621 
    622     @Override
    623     public void setStrokeMiter(float miter) {
    624         mStrokeMiter = miter;
    625     }
    626 
    627     @Override
    628     public void setStrokeCap(android.graphics._Original_Paint.Cap cap) {
    629         throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN");
    630     }
    631 
    632     public void setStrokeCap(Cap cap) {
    633         mCap = cap;
    634     }
    635 
    636     public Cap getStrokeCap() {
    637         return mCap;
    638     }
    639 
    640     @Override
    641     public void setStrokeJoin(android.graphics._Original_Paint.Join join) {
    642         throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN");
    643     }
    644 
    645     public void setStrokeJoin(Join join) {
    646         mJoin = join;
    647     }
    648 
    649     public Join getStrokeJoin() {
    650         return mJoin;
    651     }
    652 
    653     @Override
    654     public boolean getFillPath(Path src, Path dst) {
    655         return super.getFillPath(src, dst);
    656     }
    657 
    658     @Override
    659     public PathEffect setPathEffect(PathEffect effect) {
    660         mPathEffect = effect;
    661         return effect;
    662     }
    663 
    664     @Override
    665     public PathEffect getPathEffect() {
    666         return super.getPathEffect();
    667     }
    668 
    669     @Override
    670     public MaskFilter setMaskFilter(MaskFilter maskfilter) {
    671         mMaskFilter = maskfilter;
    672         return maskfilter;
    673     }
    674 
    675     @Override
    676     public MaskFilter getMaskFilter() {
    677         return super.getMaskFilter();
    678     }
    679 
    680     /**
    681      * Return the paint's text size.
    682      *
    683      * @return the paint's text size.
    684      */
    685     @Override
    686     public float getTextSize() {
    687         return mTextSize;
    688     }
    689 
    690     /**
    691      * Set the paint's text size. This value must be > 0
    692      *
    693      * @param textSize set the paint's text size.
    694      */
    695     @Override
    696     public void setTextSize(float textSize) {
    697         mTextSize = textSize;
    698 
    699         updateFontObject();
    700     }
    701 
    702     /**
    703      * Return the paint's horizontal scale factor for text. The default value
    704      * is 1.0.
    705      *
    706      * @return the paint's scale factor in X for drawing/measuring text
    707      */
    708     @Override
    709     public float getTextScaleX() {
    710         return mScaleX;
    711     }
    712 
    713     /**
    714      * Set the paint's horizontal scale factor for text. The default value
    715      * is 1.0. Values > 1.0 will stretch the text wider. Values < 1.0 will
    716      * stretch the text narrower.
    717      *
    718      * @param scaleX set the paint's scale in X for drawing/measuring text.
    719      */
    720     @Override
    721     public void setTextScaleX(float scaleX) {
    722         mScaleX = scaleX;
    723 
    724         updateFontObject();
    725     }
    726 
    727     /**
    728      * Return the paint's horizontal skew factor for text. The default value
    729      * is 0.
    730      *
    731      * @return         the paint's skew factor in X for drawing text.
    732      */
    733     @Override
    734     public float getTextSkewX() {
    735         return mSkewX;
    736     }
    737 
    738     /**
    739      * Set the paint's horizontal skew factor for text. The default value
    740      * is 0. For approximating oblique text, use values around -0.25.
    741      *
    742      * @param skewX set the paint's skew factor in X for drawing text.
    743      */
    744     @Override
    745     public void setTextSkewX(float skewX) {
    746         mSkewX = skewX;
    747 
    748         updateFontObject();
    749     }
    750 
    751     @Override
    752     public float getFontSpacing() {
    753         return super.getFontSpacing();
    754     }
    755 
    756     /**
    757      * Return the distance above (negative) the baseline (ascent) based on the
    758      * current typeface and text size.
    759      *
    760      * @return the distance above (negative) the baseline (ascent) based on the
    761      *         current typeface and text size.
    762      */
    763     @Override
    764     public float ascent() {
    765         if (mFonts.size() > 0) {
    766             java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics;
    767             // Android expects negative ascent so we invert the value from Java.
    768             return - javaMetrics.getAscent();
    769         }
    770 
    771         return 0;
    772     }
    773 
    774     /**
    775      * Return the distance below (positive) the baseline (descent) based on the
    776      * current typeface and text size.
    777      *
    778      * @return the distance below (positive) the baseline (descent) based on
    779      *         the current typeface and text size.
    780      */
    781     @Override
    782     public float descent() {
    783         if (mFonts.size() > 0) {
    784             java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics;
    785             return javaMetrics.getDescent();
    786         }
    787 
    788         return 0;
    789     }
    790 
    791     /**
    792      * Return the width of the text.
    793      *
    794      * @param text  The text to measure
    795      * @param index The index of the first character to start measuring
    796      * @param count THe number of characters to measure, beginning with start
    797      * @return      The width of the text
    798      */
    799     @Override
    800     public float measureText(char[] text, int index, int count) {
    801         // WARNING: the logic in this method is similar to Canvas.drawText.
    802         // Any change to this method should be reflected in Canvas.drawText
    803         if (mFonts.size() > 0) {
    804             FontInfo mainFont = mFonts.get(0);
    805             int i = index;
    806             int lastIndex = index + count;
    807             float total = 0f;
    808             while (i < lastIndex) {
    809                 // always start with the main font.
    810                 int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex);
    811                 if (upTo == -1) {
    812                     // shortcut to exit
    813                     return total + mainFont.mMetrics.charsWidth(text, i, lastIndex - i);
    814                 } else if (upTo > 0) {
    815                     total += mainFont.mMetrics.charsWidth(text, i, upTo - i);
    816                     i = upTo;
    817                     // don't call continue at this point. Since it is certain the main font
    818                     // cannot display the font a index upTo (now ==i), we move on to the
    819                     // fallback fonts directly.
    820                 }
    821 
    822                 // no char supported, attempt to read the next char(s) with the
    823                 // fallback font. In this case we only test the first character
    824                 // and then go back to test with the main font.
    825                 // Special test for 2-char characters.
    826                 boolean foundFont = false;
    827                 for (int f = 1 ; f < mFonts.size() ; f++) {
    828                     FontInfo fontInfo = mFonts.get(f);
    829 
    830                     // need to check that the font can display the character. We test
    831                     // differently if the char is a high surrogate.
    832                     int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1;
    833                     upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount);
    834                     if (upTo == -1) {
    835                         total += fontInfo.mMetrics.charsWidth(text, i, charCount);
    836                         i += charCount;
    837                         foundFont = true;
    838                         break;
    839 
    840                     }
    841                 }
    842 
    843                 // in case no font can display the char, measure it with the main font.
    844                 if (foundFont == false) {
    845                     int size = Character.isHighSurrogate(text[i]) ? 2 : 1;
    846                     total += mainFont.mMetrics.charsWidth(text, i, size);
    847                     i += size;
    848                 }
    849             }
    850         }
    851 
    852         return 0;
    853     }
    854 
    855     /**
    856      * Return the width of the text.
    857      *
    858      * @param text  The text to measure
    859      * @param start The index of the first character to start measuring
    860      * @param end   1 beyond the index of the last character to measure
    861      * @return      The width of the text
    862      */
    863     @Override
    864     public float measureText(String text, int start, int end) {
    865         return measureText(text.toCharArray(), start, end - start);
    866     }
    867 
    868     /**
    869      * Return the width of the text.
    870      *
    871      * @param text  The text to measure
    872      * @return      The width of the text
    873      */
    874     @Override
    875     public float measureText(String text) {
    876         return measureText(text.toCharArray(), 0, text.length());
    877     }
    878 
    879     /*
    880      * re-implement to call SpannableStringBuilder.measureText with a Paint object
    881      * instead of an _Original_Paint
    882      */
    883     @Override
    884     public float measureText(CharSequence text, int start, int end) {
    885         if (text instanceof String) {
    886             return measureText((String)text, start, end);
    887         }
    888         if (text instanceof SpannedString ||
    889             text instanceof SpannableString) {
    890             return measureText(text.toString(), start, end);
    891         }
    892         if (text instanceof SpannableStringBuilder) {
    893             return ((SpannableStringBuilder)text).measureText(start, end, this);
    894         }
    895 
    896         char[] buf = TemporaryBuffer.obtain(end - start);
    897         TextUtils.getChars(text, start, end, buf, 0);
    898         float result = measureText(buf, 0, end - start);
    899         TemporaryBuffer.recycle(buf);
    900         return result;
    901     }
    902 
    903     /**
    904      * Measure the text, stopping early if the measured width exceeds maxWidth.
    905      * Return the number of chars that were measured, and if measuredWidth is
    906      * not null, return in it the actual width measured.
    907      *
    908      * @param text  The text to measure
    909      * @param index The offset into text to begin measuring at
    910      * @param count The number of maximum number of entries to measure. If count
    911      *              is negative, then the characters before index are measured
    912      *              in reverse order. This allows for measuring the end of
    913      *              string.
    914      * @param maxWidth The maximum width to accumulate.
    915      * @param measuredWidth Optional. If not null, returns the actual width
    916      *                     measured.
    917      * @return The number of chars that were measured. Will always be <=
    918      *         abs(count).
    919      */
    920     @Override
    921     public int breakText(char[] text, int index, int count,
    922                                 float maxWidth, float[] measuredWidth) {
    923         int inc = count > 0 ? 1 : -1;
    924 
    925         int measureIndex = 0;
    926         float measureAcc = 0;
    927         for (int i = index ; i != index + count ; i += inc, measureIndex++) {
    928             int start, end;
    929             if (i < index) {
    930                 start = i;
    931                 end = index;
    932             } else {
    933                 start = index;
    934                 end = i;
    935             }
    936 
    937             // measure from start to end
    938             float res = measureText(text, start, end - start + 1);
    939 
    940             if (measuredWidth != null) {
    941                 measuredWidth[measureIndex] = res;
    942             }
    943 
    944             measureAcc += res;
    945             if (res > maxWidth) {
    946                 // we should not return this char index, but since it's 0-based and we need
    947                 // to return a count, we simply return measureIndex;
    948                 return measureIndex;
    949             }
    950 
    951         }
    952 
    953         return measureIndex;
    954     }
    955 
    956     /**
    957      * Measure the text, stopping early if the measured width exceeds maxWidth.
    958      * Return the number of chars that were measured, and if measuredWidth is
    959      * not null, return in it the actual width measured.
    960      *
    961      * @param text  The text to measure
    962      * @param measureForwards If true, measure forwards, starting at index.
    963      *                        Otherwise, measure backwards, starting with the
    964      *                        last character in the string.
    965      * @param maxWidth The maximum width to accumulate.
    966      * @param measuredWidth Optional. If not null, returns the actual width
    967      *                     measured.
    968      * @return The number of chars that were measured. Will always be <=
    969      *         abs(count).
    970      */
    971     @Override
    972     public int breakText(String text, boolean measureForwards,
    973                                 float maxWidth, float[] measuredWidth) {
    974         return breakText(text,
    975                 0 /* start */, text.length() /* end */,
    976                 measureForwards, maxWidth, measuredWidth);
    977     }
    978 
    979     /**
    980      * Measure the text, stopping early if the measured width exceeds maxWidth.
    981      * Return the number of chars that were measured, and if measuredWidth is
    982      * not null, return in it the actual width measured.
    983      *
    984      * @param text  The text to measure
    985      * @param start The offset into text to begin measuring at
    986      * @param end   The end of the text slice to measure.
    987      * @param measureForwards If true, measure forwards, starting at start.
    988      *                        Otherwise, measure backwards, starting with end.
    989      * @param maxWidth The maximum width to accumulate.
    990      * @param measuredWidth Optional. If not null, returns the actual width
    991      *                     measured.
    992      * @return The number of chars that were measured. Will always be <=
    993      *         abs(end - start).
    994      */
    995     @Override
    996     public int breakText(CharSequence text, int start, int end, boolean measureForwards,
    997             float maxWidth, float[] measuredWidth) {
    998         char[] buf = new char[end - start];
    999         int result;
   1000 
   1001         TextUtils.getChars(text, start, end, buf, 0);
   1002 
   1003         if (measureForwards) {
   1004             result = breakText(buf, 0, end - start, maxWidth, measuredWidth);
   1005         } else {
   1006             result = breakText(buf, 0, -(end - start), maxWidth, measuredWidth);
   1007         }
   1008 
   1009         return result;
   1010     }
   1011 
   1012     /**
   1013      * Return the advance widths for the characters in the string.
   1014      *
   1015      * @param text     The text to measure
   1016      * @param index    The index of the first char to to measure
   1017      * @param count    The number of chars starting with index to measure
   1018      * @param widths   array to receive the advance widths of the characters.
   1019      *                 Must be at least a large as count.
   1020      * @return         the actual number of widths returned.
   1021      */
   1022     @Override
   1023     public int getTextWidths(char[] text, int index, int count,
   1024                              float[] widths) {
   1025         if (mFonts.size() > 0) {
   1026             if ((index | count) < 0 || index + count > text.length
   1027                     || count > widths.length) {
   1028                 throw new ArrayIndexOutOfBoundsException();
   1029             }
   1030 
   1031             // FIXME: handle multi-char characters.
   1032             // Need to figure out if the lengths of the width array takes into account
   1033             // multi-char characters.
   1034             for (int i = 0; i < count; i++) {
   1035                 char c = text[i + index];
   1036                 boolean found = false;
   1037                 for (FontInfo info : mFonts) {
   1038                     if (info.mFont.canDisplay(c)) {
   1039                         widths[i] = info.mMetrics.charWidth(c);
   1040                         found = true;
   1041                         break;
   1042                     }
   1043                 }
   1044 
   1045                 if (found == false) {
   1046                     // we stop there.
   1047                     return i;
   1048                 }
   1049             }
   1050 
   1051             return count;
   1052         }
   1053 
   1054         return 0;
   1055     }
   1056 
   1057     /**
   1058      * Return the advance widths for the characters in the string.
   1059      *
   1060      * @param text   The text to measure
   1061      * @param start  The index of the first char to to measure
   1062      * @param end    The end of the text slice to measure
   1063      * @param widths array to receive the advance widths of the characters.
   1064      *               Must be at least a large as the text.
   1065      * @return       the number of unichars in the specified text.
   1066      */
   1067     @Override
   1068     public int getTextWidths(String text, int start, int end, float[] widths) {
   1069         if ((start | end | (end - start) | (text.length() - end)) < 0) {
   1070             throw new IndexOutOfBoundsException();
   1071         }
   1072         if (end - start > widths.length) {
   1073             throw new ArrayIndexOutOfBoundsException();
   1074         }
   1075 
   1076         return getTextWidths(text.toCharArray(), start, end - start, widths);
   1077     }
   1078 
   1079     /*
   1080      * re-implement to call SpannableStringBuilder.getTextWidths with a Paint object
   1081      * instead of an _Original_Paint
   1082      */
   1083     @Override
   1084     public int getTextWidths(CharSequence text, int start, int end, float[] widths) {
   1085         if (text instanceof String) {
   1086             return getTextWidths((String)text, start, end, widths);
   1087         }
   1088         if (text instanceof SpannedString || text instanceof SpannableString) {
   1089             return getTextWidths(text.toString(), start, end, widths);
   1090         }
   1091         if (text instanceof SpannableStringBuilder) {
   1092             return ((SpannableStringBuilder)text).getTextWidths(start, end, widths, this);
   1093         }
   1094 
   1095         char[] buf = TemporaryBuffer.obtain(end - start);
   1096         TextUtils.getChars(text, start, end, buf, 0);
   1097         int result = getTextWidths(buf, 0, end - start, widths);
   1098         TemporaryBuffer.recycle(buf);
   1099         return result;
   1100     }
   1101 
   1102     @Override
   1103     public int getTextWidths(String text, float[] widths) {
   1104         return super.getTextWidths(text, widths);
   1105     }
   1106 
   1107     /**
   1108      * Return the path (outline) for the specified text.
   1109      * Note: just like Canvas.drawText, this will respect the Align setting in
   1110      * the paint.
   1111      *
   1112      * @param text     The text to retrieve the path from
   1113      * @param index    The index of the first character in text
   1114      * @param count    The number of characterss starting with index
   1115      * @param x        The x coordinate of the text's origin
   1116      * @param y        The y coordinate of the text's origin
   1117      * @param path     The path to receive the data describing the text. Must
   1118      *                 be allocated by the caller.
   1119      */
   1120     @Override
   1121     public void getTextPath(char[] text, int index, int count,
   1122                             float x, float y, Path path) {
   1123 
   1124         // TODO this is the ORIGINAL implementation. REPLACE AS NEEDED OR REMOVE
   1125 
   1126         if ((index | count) < 0 || index + count > text.length) {
   1127             throw new ArrayIndexOutOfBoundsException();
   1128         }
   1129 
   1130         // TODO native_getTextPath(mNativePaint, text, index, count, x, y, path.ni());
   1131 
   1132         throw new UnsupportedOperationException("IMPLEMENT AS NEEDED");
   1133     }
   1134 
   1135     /**
   1136      * Return the path (outline) for the specified text.
   1137      * Note: just like Canvas.drawText, this will respect the Align setting
   1138      * in the paint.
   1139      *
   1140      * @param text  The text to retrieve the path from
   1141      * @param start The first character in the text
   1142      * @param end   1 past the last charcter in the text
   1143      * @param x     The x coordinate of the text's origin
   1144      * @param y     The y coordinate of the text's origin
   1145      * @param path  The path to receive the data describing the text. Must
   1146      *              be allocated by the caller.
   1147      */
   1148     @Override
   1149     public void getTextPath(String text, int start, int end,
   1150                             float x, float y, Path path) {
   1151         if ((start | end | (end - start) | (text.length() - end)) < 0) {
   1152             throw new IndexOutOfBoundsException();
   1153         }
   1154 
   1155         getTextPath(text.toCharArray(), start, end - start, x, y, path);
   1156     }
   1157 
   1158     /**
   1159      * Return in bounds (allocated by the caller) the smallest rectangle that
   1160      * encloses all of the characters, with an implied origin at (0,0).
   1161      *
   1162      * @param text  String to measure and return its bounds
   1163      * @param start Index of the first char in the string to measure
   1164      * @param end   1 past the last char in the string measure
   1165      * @param bounds Returns the unioned bounds of all the text. Must be
   1166      *               allocated by the caller.
   1167      */
   1168     @Override
   1169     public void getTextBounds(String text, int start, int end, Rect bounds) {
   1170         if ((start | end | (end - start) | (text.length() - end)) < 0) {
   1171             throw new IndexOutOfBoundsException();
   1172         }
   1173         if (bounds == null) {
   1174             throw new NullPointerException("need bounds Rect");
   1175         }
   1176 
   1177         getTextBounds(text.toCharArray(), start, end - start, bounds);
   1178     }
   1179 
   1180     /**
   1181      * Return in bounds (allocated by the caller) the smallest rectangle that
   1182      * encloses all of the characters, with an implied origin at (0,0).
   1183      *
   1184      * @param text  Array of chars to measure and return their unioned bounds
   1185      * @param index Index of the first char in the array to measure
   1186      * @param count The number of chars, beginning at index, to measure
   1187      * @param bounds Returns the unioned bounds of all the text. Must be
   1188      *               allocated by the caller.
   1189      */
   1190     @Override
   1191     public void getTextBounds(char[] text, int index, int count, Rect bounds) {
   1192         // FIXME
   1193         if (mFonts.size() > 0) {
   1194             if ((index | count) < 0 || index + count > text.length) {
   1195                 throw new ArrayIndexOutOfBoundsException();
   1196             }
   1197             if (bounds == null) {
   1198                 throw new NullPointerException("need bounds Rect");
   1199             }
   1200 
   1201             FontInfo mainInfo = mFonts.get(0);
   1202 
   1203             Rectangle2D rect = mainInfo.mFont.getStringBounds(text, index, index + count, mFontContext);
   1204             bounds.set(0, 0, (int)rect.getWidth(), (int)rect.getHeight());
   1205         }
   1206     }
   1207 
   1208     public static void finalizer(int foo) {
   1209         // pass
   1210     }
   1211 }
   1212