OSDN Git Service

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