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