| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.graphics; |
| |
| import android.text.SpannableString; |
| import android.text.SpannableStringBuilder; |
| import android.text.SpannedString; |
| import android.text.TextUtils; |
| |
| import java.awt.BasicStroke; |
| import java.awt.Font; |
| import java.awt.Toolkit; |
| import java.awt.font.FontRenderContext; |
| import java.awt.geom.AffineTransform; |
| import java.awt.geom.Rectangle2D; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * A paint implementation overridden by the LayoutLib bridge. |
| */ |
| public class Paint extends _Original_Paint { |
| |
| private int mColor = 0xFFFFFFFF; |
| private float mStrokeWidth = 1.f; |
| private float mTextSize = 20; |
| private float mScaleX = 1; |
| private float mSkewX = 0; |
| private Align mAlign = Align.LEFT; |
| private Style mStyle = Style.FILL; |
| private float mStrokeMiter = 4.0f; |
| private Cap mCap = Cap.BUTT; |
| private Join mJoin = Join.MITER; |
| private int mFlags = 0; |
| |
| /** |
| * Class associating a {@link Font} and it's {@link java.awt.FontMetrics}. |
| */ |
| public static final class FontInfo { |
| Font mFont; |
| java.awt.FontMetrics mMetrics; |
| } |
| |
| private List<FontInfo> mFonts; |
| private final FontRenderContext mFontContext = new FontRenderContext( |
| new AffineTransform(), true, true); |
| |
| public static final int ANTI_ALIAS_FLAG = _Original_Paint.ANTI_ALIAS_FLAG; |
| public static final int FILTER_BITMAP_FLAG = _Original_Paint.FILTER_BITMAP_FLAG; |
| public static final int DITHER_FLAG = _Original_Paint.DITHER_FLAG; |
| public static final int UNDERLINE_TEXT_FLAG = _Original_Paint.UNDERLINE_TEXT_FLAG; |
| public static final int STRIKE_THRU_TEXT_FLAG = _Original_Paint.STRIKE_THRU_TEXT_FLAG; |
| public static final int FAKE_BOLD_TEXT_FLAG = _Original_Paint.FAKE_BOLD_TEXT_FLAG; |
| public static final int LINEAR_TEXT_FLAG = _Original_Paint.LINEAR_TEXT_FLAG; |
| public static final int SUBPIXEL_TEXT_FLAG = _Original_Paint.SUBPIXEL_TEXT_FLAG; |
| public static final int DEV_KERN_TEXT_FLAG = _Original_Paint.DEV_KERN_TEXT_FLAG; |
| |
| public static class FontMetrics extends _Original_Paint.FontMetrics { |
| } |
| |
| public static class FontMetricsInt extends _Original_Paint.FontMetricsInt { |
| } |
| |
| /** |
| * The Style specifies if the primitive being drawn is filled, |
| * stroked, or both (in the same color). The default is FILL. |
| */ |
| public enum Style { |
| /** |
| * Geometry and text drawn with this style will be filled, ignoring all |
| * stroke-related settings in the paint. |
| */ |
| FILL (0), |
| /** |
| * Geometry and text drawn with this style will be stroked, respecting |
| * the stroke-related fields on the paint. |
| */ |
| STROKE (1), |
| /** |
| * Geometry and text drawn with this style will be both filled and |
| * stroked at the same time, respecting the stroke-related fields on |
| * the paint. |
| */ |
| FILL_AND_STROKE (2); |
| |
| Style(int nativeInt) { |
| this.nativeInt = nativeInt; |
| } |
| final int nativeInt; |
| } |
| |
| /** |
| * The Cap specifies the treatment for the beginning and ending of |
| * stroked lines and paths. The default is BUTT. |
| */ |
| public enum Cap { |
| /** |
| * The stroke ends with the path, and does not project beyond it. |
| */ |
| BUTT (0), |
| /** |
| * The stroke projects out as a square, with the center at the end |
| * of the path. |
| */ |
| ROUND (1), |
| /** |
| * The stroke projects out as a semicircle, with the center at the |
| * end of the path. |
| */ |
| SQUARE (2); |
| |
| private Cap(int nativeInt) { |
| this.nativeInt = nativeInt; |
| } |
| final int nativeInt; |
| |
| /** custom for layoutlib */ |
| public int getJavaCap() { |
| switch (this) { |
| case BUTT: |
| return BasicStroke.CAP_BUTT; |
| case ROUND: |
| return BasicStroke.CAP_ROUND; |
| default: |
| case SQUARE: |
| return BasicStroke.CAP_SQUARE; |
| } |
| } |
| } |
| |
| /** |
| * The Join specifies the treatment where lines and curve segments |
| * join on a stroked path. The default is MITER. |
| */ |
| public enum Join { |
| /** |
| * The outer edges of a join meet at a sharp angle |
| */ |
| MITER (0), |
| /** |
| * The outer edges of a join meet in a circular arc. |
| */ |
| ROUND (1), |
| /** |
| * The outer edges of a join meet with a straight line |
| */ |
| BEVEL (2); |
| |
| private Join(int nativeInt) { |
| this.nativeInt = nativeInt; |
| } |
| final int nativeInt; |
| |
| /** custom for layoutlib */ |
| public int getJavaJoin() { |
| switch (this) { |
| default: |
| case MITER: |
| return BasicStroke.JOIN_MITER; |
| case ROUND: |
| return BasicStroke.JOIN_ROUND; |
| case BEVEL: |
| return BasicStroke.JOIN_BEVEL; |
| } |
| } |
| } |
| |
| /** |
| * Align specifies how drawText aligns its text relative to the |
| * [x,y] coordinates. The default is LEFT. |
| */ |
| public enum Align { |
| /** |
| * The text is drawn to the right of the x,y origin |
| */ |
| LEFT (0), |
| /** |
| * The text is drawn centered horizontally on the x,y origin |
| */ |
| CENTER (1), |
| /** |
| * The text is drawn to the left of the x,y origin |
| */ |
| RIGHT (2); |
| |
| private Align(int nativeInt) { |
| this.nativeInt = nativeInt; |
| } |
| final int nativeInt; |
| } |
| |
| public Paint() { |
| this(0); |
| } |
| |
| /* |
| * Do not remove or com.android.layoutlib.bridge.TestClassReplacement fails. |
| */ |
| @Override |
| public void finalize() { } |
| |
| public Paint(int flags) { |
| setFlags(flags | DEFAULT_PAINT_FLAGS); |
| initFont(); |
| } |
| |
| public Paint(Paint paint) { |
| set(paint); |
| initFont(); |
| } |
| |
| @Override |
| public void reset() { |
| super.reset(); |
| } |
| |
| /** |
| * Returns the list of {@link Font} objects. The first item is the main font, the rest |
| * are fall backs for characters not present in the main font. |
| */ |
| public List<FontInfo> getFonts() { |
| return mFonts; |
| } |
| |
| private void initFont() { |
| mTypeface = Typeface.DEFAULT; |
| updateFontObject(); |
| } |
| |
| /** |
| * Update the {@link Font} object from the typeface, text size and scaling |
| */ |
| @SuppressWarnings("deprecation") |
| private void updateFontObject() { |
| if (mTypeface != null) { |
| // Get the fonts from the TypeFace object. |
| List<Font> fonts = mTypeface.getFonts(); |
| |
| // create new font objects as well as FontMetrics, based on the current text size |
| // and skew info. |
| ArrayList<FontInfo> infoList = new ArrayList<FontInfo>(fonts.size()); |
| for (Font font : fonts) { |
| FontInfo info = new FontInfo(); |
| info.mFont = font.deriveFont(mTextSize); |
| if (mScaleX != 1.0 || mSkewX != 0) { |
| // TODO: support skew |
| info.mFont = info.mFont.deriveFont(new AffineTransform( |
| mScaleX, mSkewX, 0, 0, 1, 0)); |
| } |
| info.mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(info.mFont); |
| |
| infoList.add(info); |
| } |
| |
| mFonts = Collections.unmodifiableList(infoList); |
| } |
| } |
| |
| //---------------------------------------- |
| |
| public void set(Paint src) { |
| if (this != src) { |
| mColor = src.mColor; |
| mTextSize = src.mTextSize; |
| mScaleX = src.mScaleX; |
| mSkewX = src.mSkewX; |
| mAlign = src.mAlign; |
| mStyle = src.mStyle; |
| mFlags = src.mFlags; |
| |
| updateFontObject(); |
| |
| super.set(src); |
| } |
| } |
| |
| @Override |
| public void setCompatibilityScaling(float factor) { |
| super.setCompatibilityScaling(factor); |
| } |
| |
| @Override |
| public int getFlags() { |
| return mFlags; |
| } |
| |
| @Override |
| public void setFlags(int flags) { |
| mFlags = flags; |
| } |
| |
| @Override |
| public boolean isAntiAlias() { |
| return super.isAntiAlias(); |
| } |
| |
| @Override |
| public boolean isDither() { |
| return super.isDither(); |
| } |
| |
| @Override |
| public boolean isLinearText() { |
| return super.isLinearText(); |
| } |
| |
| @Override |
| public boolean isStrikeThruText() { |
| return super.isStrikeThruText(); |
| } |
| |
| @Override |
| public boolean isUnderlineText() { |
| return super.isUnderlineText(); |
| } |
| |
| @Override |
| public boolean isFakeBoldText() { |
| return super.isFakeBoldText(); |
| } |
| |
| @Override |
| public boolean isSubpixelText() { |
| return super.isSubpixelText(); |
| } |
| |
| @Override |
| public boolean isFilterBitmap() { |
| return super.isFilterBitmap(); |
| } |
| |
| /** |
| * Return the font's recommended interline spacing, given the Paint's |
| * settings for typeface, textSize, etc. If metrics is not null, return the |
| * fontmetric values in it. |
| * |
| * @param metrics If this object is not null, its fields are filled with |
| * the appropriate values given the paint's text attributes. |
| * @return the font's recommended interline spacing. |
| */ |
| public float getFontMetrics(FontMetrics metrics) { |
| if (mFonts.size() > 0) { |
| java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics; |
| if (metrics != null) { |
| // Android expects negative ascent so we invert the value from Java. |
| metrics.top = - javaMetrics.getMaxAscent(); |
| metrics.ascent = - javaMetrics.getAscent(); |
| metrics.descent = javaMetrics.getDescent(); |
| metrics.bottom = javaMetrics.getMaxDescent(); |
| metrics.leading = javaMetrics.getLeading(); |
| } |
| |
| return javaMetrics.getHeight(); |
| } |
| |
| return 0; |
| } |
| |
| public int getFontMetricsInt(FontMetricsInt metrics) { |
| if (mFonts.size() > 0) { |
| java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics; |
| if (metrics != null) { |
| // Android expects negative ascent so we invert the value from Java. |
| metrics.top = - javaMetrics.getMaxAscent(); |
| metrics.ascent = - javaMetrics.getAscent(); |
| metrics.descent = javaMetrics.getDescent(); |
| metrics.bottom = javaMetrics.getMaxDescent(); |
| metrics.leading = javaMetrics.getLeading(); |
| } |
| |
| return javaMetrics.getHeight(); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Reimplemented to return Paint.FontMetrics instead of _Original_Paint.FontMetrics |
| */ |
| public FontMetrics getFontMetrics() { |
| FontMetrics fm = new FontMetrics(); |
| getFontMetrics(fm); |
| return fm; |
| } |
| |
| /** |
| * Reimplemented to return Paint.FontMetricsInt instead of _Original_Paint.FontMetricsInt |
| */ |
| public FontMetricsInt getFontMetricsInt() { |
| FontMetricsInt fm = new FontMetricsInt(); |
| getFontMetricsInt(fm); |
| return fm; |
| } |
| |
| |
| |
| @Override |
| public float getFontMetrics(_Original_Paint.FontMetrics metrics) { |
| throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); |
| } |
| |
| @Override |
| public int getFontMetricsInt(_Original_Paint.FontMetricsInt metrics) { |
| throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); |
| } |
| |
| @Override |
| public Typeface setTypeface(Typeface typeface) { |
| if (typeface != null) { |
| mTypeface = typeface; |
| } else { |
| mTypeface = Typeface.DEFAULT; |
| } |
| |
| updateFontObject(); |
| |
| return typeface; |
| } |
| |
| @Override |
| public Typeface getTypeface() { |
| return super.getTypeface(); |
| } |
| |
| @Override |
| public int getColor() { |
| return mColor; |
| } |
| |
| @Override |
| public void setColor(int color) { |
| mColor = color; |
| } |
| |
| @Override |
| public void setARGB(int a, int r, int g, int b) { |
| super.setARGB(a, r, g, b); |
| } |
| |
| @Override |
| public void setAlpha(int alpha) { |
| mColor = (alpha << 24) | (mColor & 0x00FFFFFF); |
| } |
| |
| @Override |
| public int getAlpha() { |
| return mColor >>> 24; |
| } |
| |
| /** |
| * Set or clear the shader object. |
| * <p /> |
| * Pass null to clear any previous shader. |
| * As a convenience, the parameter passed is also returned. |
| * |
| * @param shader May be null. the new shader to be installed in the paint |
| * @return shader |
| */ |
| @Override |
| public Shader setShader(Shader shader) { |
| return mShader = shader; |
| } |
| |
| @Override |
| public Shader getShader() { |
| return super.getShader(); |
| } |
| |
| /** |
| * Set or clear the paint's colorfilter, returning the parameter. |
| * |
| * @param filter May be null. The new filter to be installed in the paint |
| * @return filter |
| */ |
| @Override |
| public ColorFilter setColorFilter(ColorFilter filter) { |
| mColorFilter = filter; |
| return filter; |
| } |
| |
| @Override |
| public ColorFilter getColorFilter() { |
| return super.getColorFilter(); |
| } |
| |
| /** |
| * Set or clear the xfermode object. |
| * <p /> |
| * Pass null to clear any previous xfermode. |
| * As a convenience, the parameter passed is also returned. |
| * |
| * @param xfermode May be null. The xfermode to be installed in the paint |
| * @return xfermode |
| */ |
| @Override |
| public Xfermode setXfermode(Xfermode xfermode) { |
| return mXfermode = xfermode; |
| } |
| |
| @Override |
| public Xfermode getXfermode() { |
| return super.getXfermode(); |
| } |
| |
| @Override |
| public Rasterizer setRasterizer(Rasterizer rasterizer) { |
| mRasterizer = rasterizer; |
| return rasterizer; |
| } |
| |
| @Override |
| public Rasterizer getRasterizer() { |
| return super.getRasterizer(); |
| } |
| |
| @Override |
| public void setShadowLayer(float radius, float dx, float dy, int color) { |
| // TODO Auto-generated method stub |
| } |
| |
| @Override |
| public void clearShadowLayer() { |
| super.clearShadowLayer(); |
| } |
| |
| public void setTextAlign(Align align) { |
| mAlign = align; |
| } |
| |
| @Override |
| public void setTextAlign(android.graphics._Original_Paint.Align align) { |
| throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); |
| } |
| |
| public Align getTextAlign() { |
| return mAlign; |
| } |
| |
| public void setStyle(Style style) { |
| mStyle = style; |
| } |
| |
| @Override |
| public void setStyle(android.graphics._Original_Paint.Style style) { |
| throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); |
| } |
| |
| public Style getStyle() { |
| return mStyle; |
| } |
| |
| @Override |
| public void setDither(boolean dither) { |
| mFlags |= dither ? DITHER_FLAG : ~DITHER_FLAG; |
| } |
| |
| @Override |
| public void setAntiAlias(boolean aa) { |
| mFlags |= aa ? ANTI_ALIAS_FLAG : ~ANTI_ALIAS_FLAG; |
| } |
| |
| @Override |
| public void setFakeBoldText(boolean flag) { |
| mFlags |= flag ? FAKE_BOLD_TEXT_FLAG : ~FAKE_BOLD_TEXT_FLAG; |
| } |
| |
| @Override |
| public void setLinearText(boolean flag) { |
| mFlags |= flag ? LINEAR_TEXT_FLAG : ~LINEAR_TEXT_FLAG; |
| } |
| |
| @Override |
| public void setSubpixelText(boolean flag) { |
| mFlags |= flag ? SUBPIXEL_TEXT_FLAG : ~SUBPIXEL_TEXT_FLAG; |
| } |
| |
| @Override |
| public void setUnderlineText(boolean flag) { |
| mFlags |= flag ? UNDERLINE_TEXT_FLAG : ~UNDERLINE_TEXT_FLAG; |
| } |
| |
| @Override |
| public void setStrikeThruText(boolean flag) { |
| mFlags |= flag ? STRIKE_THRU_TEXT_FLAG : ~STRIKE_THRU_TEXT_FLAG; |
| } |
| |
| @Override |
| public void setFilterBitmap(boolean flag) { |
| mFlags |= flag ? FILTER_BITMAP_FLAG : ~FILTER_BITMAP_FLAG; |
| } |
| |
| @Override |
| public float getStrokeWidth() { |
| return mStrokeWidth; |
| } |
| |
| @Override |
| public void setStrokeWidth(float width) { |
| mStrokeWidth = width; |
| } |
| |
| @Override |
| public float getStrokeMiter() { |
| return mStrokeMiter; |
| } |
| |
| @Override |
| public void setStrokeMiter(float miter) { |
| mStrokeMiter = miter; |
| } |
| |
| @Override |
| public void setStrokeCap(android.graphics._Original_Paint.Cap cap) { |
| throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); |
| } |
| |
| public void setStrokeCap(Cap cap) { |
| mCap = cap; |
| } |
| |
| public Cap getStrokeCap() { |
| return mCap; |
| } |
| |
| @Override |
| public void setStrokeJoin(android.graphics._Original_Paint.Join join) { |
| throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); |
| } |
| |
| public void setStrokeJoin(Join join) { |
| mJoin = join; |
| } |
| |
| public Join getStrokeJoin() { |
| return mJoin; |
| } |
| |
| @Override |
| public boolean getFillPath(Path src, Path dst) { |
| return super.getFillPath(src, dst); |
| } |
| |
| @Override |
| public PathEffect setPathEffect(PathEffect effect) { |
| mPathEffect = effect; |
| return effect; |
| } |
| |
| @Override |
| public PathEffect getPathEffect() { |
| return super.getPathEffect(); |
| } |
| |
| @Override |
| public MaskFilter setMaskFilter(MaskFilter maskfilter) { |
| mMaskFilter = maskfilter; |
| return maskfilter; |
| } |
| |
| @Override |
| public MaskFilter getMaskFilter() { |
| return super.getMaskFilter(); |
| } |
| |
| /** |
| * Return the paint's text size. |
| * |
| * @return the paint's text size. |
| */ |
| @Override |
| public float getTextSize() { |
| return mTextSize; |
| } |
| |
| /** |
| * Set the paint's text size. This value must be > 0 |
| * |
| * @param textSize set the paint's text size. |
| */ |
| @Override |
| public void setTextSize(float textSize) { |
| mTextSize = textSize; |
| |
| updateFontObject(); |
| } |
| |
| /** |
| * Return the paint's horizontal scale factor for text. The default value |
| * is 1.0. |
| * |
| * @return the paint's scale factor in X for drawing/measuring text |
| */ |
| @Override |
| public float getTextScaleX() { |
| return mScaleX; |
| } |
| |
| /** |
| * Set the paint's horizontal scale factor for text. The default value |
| * is 1.0. Values > 1.0 will stretch the text wider. Values < 1.0 will |
| * stretch the text narrower. |
| * |
| * @param scaleX set the paint's scale in X for drawing/measuring text. |
| */ |
| @Override |
| public void setTextScaleX(float scaleX) { |
| mScaleX = scaleX; |
| |
| updateFontObject(); |
| } |
| |
| /** |
| * Return the paint's horizontal skew factor for text. The default value |
| * is 0. |
| * |
| * @return the paint's skew factor in X for drawing text. |
| */ |
| @Override |
| public float getTextSkewX() { |
| return mSkewX; |
| } |
| |
| /** |
| * Set the paint's horizontal skew factor for text. The default value |
| * is 0. For approximating oblique text, use values around -0.25. |
| * |
| * @param skewX set the paint's skew factor in X for drawing text. |
| */ |
| @Override |
| public void setTextSkewX(float skewX) { |
| mSkewX = skewX; |
| |
| updateFontObject(); |
| } |
| |
| @Override |
| public float getFontSpacing() { |
| return super.getFontSpacing(); |
| } |
| |
| /** |
| * Return the distance above (negative) the baseline (ascent) based on the |
| * current typeface and text size. |
| * |
| * @return the distance above (negative) the baseline (ascent) based on the |
| * current typeface and text size. |
| */ |
| @Override |
| public float ascent() { |
| if (mFonts.size() > 0) { |
| java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics; |
| // Android expects negative ascent so we invert the value from Java. |
| return - javaMetrics.getAscent(); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Return the distance below (positive) the baseline (descent) based on the |
| * current typeface and text size. |
| * |
| * @return the distance below (positive) the baseline (descent) based on |
| * the current typeface and text size. |
| */ |
| @Override |
| public float descent() { |
| if (mFonts.size() > 0) { |
| java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics; |
| return javaMetrics.getDescent(); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Return the width of the text. |
| * |
| * @param text The text to measure |
| * @param index The index of the first character to start measuring |
| * @param count THe number of characters to measure, beginning with start |
| * @return The width of the text |
| */ |
| @Override |
| public float measureText(char[] text, int index, int count) { |
| // WARNING: the logic in this method is similar to Canvas.drawText. |
| // Any change to this method should be reflected in Canvas.drawText |
| if (mFonts.size() > 0) { |
| FontInfo mainFont = mFonts.get(0); |
| int i = index; |
| int lastIndex = index + count; |
| float total = 0f; |
| while (i < lastIndex) { |
| // always start with the main font. |
| int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex); |
| if (upTo == -1) { |
| // shortcut to exit |
| return total + mainFont.mMetrics.charsWidth(text, i, lastIndex - i); |
| } else if (upTo > 0) { |
| total += mainFont.mMetrics.charsWidth(text, i, upTo - i); |
| i = upTo; |
| // don't call continue at this point. Since it is certain the main font |
| // cannot display the font a index upTo (now ==i), we move on to the |
| // fallback fonts directly. |
| } |
| |
| // no char supported, attempt to read the next char(s) with the |
| // fallback font. In this case we only test the first character |
| // and then go back to test with the main font. |
| // Special test for 2-char characters. |
| boolean foundFont = false; |
| for (int f = 1 ; f < mFonts.size() ; f++) { |
| FontInfo fontInfo = mFonts.get(f); |
| |
| // need to check that the font can display the character. We test |
| // differently if the char is a high surrogate. |
| int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1; |
| upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount); |
| if (upTo == -1) { |
| total += fontInfo.mMetrics.charsWidth(text, i, charCount); |
| i += charCount; |
| foundFont = true; |
| break; |
| |
| } |
| } |
| |
| // in case no font can display the char, measure it with the main font. |
| if (foundFont == false) { |
| int size = Character.isHighSurrogate(text[i]) ? 2 : 1; |
| total += mainFont.mMetrics.charsWidth(text, i, size); |
| i += size; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Return the width of the text. |
| * |
| * @param text The text to measure |
| * @param start The index of the first character to start measuring |
| * @param end 1 beyond the index of the last character to measure |
| * @return The width of the text |
| */ |
| @Override |
| public float measureText(String text, int start, int end) { |
| return measureText(text.toCharArray(), start, end - start); |
| } |
| |
| /** |
| * Return the width of the text. |
| * |
| * @param text The text to measure |
| * @return The width of the text |
| */ |
| @Override |
| public float measureText(String text) { |
| return measureText(text.toCharArray(), 0, text.length()); |
| } |
| |
| /* |
| * re-implement to call SpannableStringBuilder.measureText with a Paint object |
| * instead of an _Original_Paint |
| */ |
| @Override |
| public float measureText(CharSequence text, int start, int end) { |
| if (text instanceof String) { |
| return measureText((String)text, start, end); |
| } |
| if (text instanceof SpannedString || |
| text instanceof SpannableString) { |
| return measureText(text.toString(), start, end); |
| } |
| if (text instanceof SpannableStringBuilder) { |
| return ((SpannableStringBuilder)text).measureText(start, end, this); |
| } |
| |
| char[] buf = TemporaryBuffer.obtain(end - start); |
| TextUtils.getChars(text, start, end, buf, 0); |
| float result = measureText(buf, 0, end - start); |
| TemporaryBuffer.recycle(buf); |
| return result; |
| } |
| |
| /** |
| * Measure the text, stopping early if the measured width exceeds maxWidth. |
| * Return the number of chars that were measured, and if measuredWidth is |
| * not null, return in it the actual width measured. |
| * |
| * @param text The text to measure |
| * @param index The offset into text to begin measuring at |
| * @param count The number of maximum number of entries to measure. If count |
| * is negative, then the characters before index are measured |
| * in reverse order. This allows for measuring the end of |
| * string. |
| * @param maxWidth The maximum width to accumulate. |
| * @param measuredWidth Optional. If not null, returns the actual width |
| * measured. |
| * @return The number of chars that were measured. Will always be <= |
| * abs(count). |
| */ |
| @Override |
| public int breakText(char[] text, int index, int count, |
| float maxWidth, float[] measuredWidth) { |
| int inc = count > 0 ? 1 : -1; |
| |
| int measureIndex = 0; |
| float measureAcc = 0; |
| for (int i = index ; i != index + count ; i += inc, measureIndex++) { |
| int start, end; |
| if (i < index) { |
| start = i; |
| end = index; |
| } else { |
| start = index; |
| end = i; |
| } |
| |
| // measure from start to end |
| float res = measureText(text, start, end - start + 1); |
| |
| if (measuredWidth != null) { |
| measuredWidth[measureIndex] = res; |
| } |
| |
| measureAcc += res; |
| if (res > maxWidth) { |
| // we should not return this char index, but since it's 0-based and we need |
| // to return a count, we simply return measureIndex; |
| return measureIndex; |
| } |
| |
| } |
| |
| return measureIndex; |
| } |
| |
| /** |
| * Measure the text, stopping early if the measured width exceeds maxWidth. |
| * Return the number of chars that were measured, and if measuredWidth is |
| * not null, return in it the actual width measured. |
| * |
| * @param text The text to measure |
| * @param measureForwards If true, measure forwards, starting at index. |
| * Otherwise, measure backwards, starting with the |
| * last character in the string. |
| * @param maxWidth The maximum width to accumulate. |
| * @param measuredWidth Optional. If not null, returns the actual width |
| * measured. |
| * @return The number of chars that were measured. Will always be <= |
| * abs(count). |
| */ |
| @Override |
| public int breakText(String text, boolean measureForwards, |
| float maxWidth, float[] measuredWidth) { |
| return breakText(text, |
| 0 /* start */, text.length() /* end */, |
| measureForwards, maxWidth, measuredWidth); |
| } |
| |
| /** |
| * Measure the text, stopping early if the measured width exceeds maxWidth. |
| * Return the number of chars that were measured, and if measuredWidth is |
| * not null, return in it the actual width measured. |
| * |
| * @param text The text to measure |
| * @param start The offset into text to begin measuring at |
| * @param end The end of the text slice to measure. |
| * @param measureForwards If true, measure forwards, starting at start. |
| * Otherwise, measure backwards, starting with end. |
| * @param maxWidth The maximum width to accumulate. |
| * @param measuredWidth Optional. If not null, returns the actual width |
| * measured. |
| * @return The number of chars that were measured. Will always be <= |
| * abs(end - start). |
| */ |
| @Override |
| public int breakText(CharSequence text, int start, int end, boolean measureForwards, |
| float maxWidth, float[] measuredWidth) { |
| char[] buf = new char[end - start]; |
| int result; |
| |
| TextUtils.getChars(text, start, end, buf, 0); |
| |
| if (measureForwards) { |
| result = breakText(buf, 0, end - start, maxWidth, measuredWidth); |
| } else { |
| result = breakText(buf, 0, -(end - start), maxWidth, measuredWidth); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Return the advance widths for the characters in the string. |
| * |
| * @param text The text to measure |
| * @param index The index of the first char to to measure |
| * @param count The number of chars starting with index to measure |
| * @param widths array to receive the advance widths of the characters. |
| * Must be at least a large as count. |
| * @return the actual number of widths returned. |
| */ |
| @Override |
| public int getTextWidths(char[] text, int index, int count, |
| float[] widths) { |
| if (mFonts.size() > 0) { |
| if ((index | count) < 0 || index + count > text.length |
| || count > widths.length) { |
| throw new ArrayIndexOutOfBoundsException(); |
| } |
| |
| // FIXME: handle multi-char characters. |
| // Need to figure out if the lengths of the width array takes into account |
| // multi-char characters. |
| for (int i = 0; i < count; i++) { |
| char c = text[i + index]; |
| boolean found = false; |
| for (FontInfo info : mFonts) { |
| if (info.mFont.canDisplay(c)) { |
| widths[i] = info.mMetrics.charWidth(c); |
| found = true; |
| break; |
| } |
| } |
| |
| if (found == false) { |
| // we stop there. |
| return i; |
| } |
| } |
| |
| return count; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Return the advance widths for the characters in the string. |
| * |
| * @param text The text to measure |
| * @param start The index of the first char to to measure |
| * @param end The end of the text slice to measure |
| * @param widths array to receive the advance widths of the characters. |
| * Must be at least a large as the text. |
| * @return the number of unichars in the specified text. |
| */ |
| @Override |
| public int getTextWidths(String text, int start, int end, float[] widths) { |
| if ((start | end | (end - start) | (text.length() - end)) < 0) { |
| throw new IndexOutOfBoundsException(); |
| } |
| if (end - start > widths.length) { |
| throw new ArrayIndexOutOfBoundsException(); |
| } |
| |
| return getTextWidths(text.toCharArray(), start, end - start, widths); |
| } |
| |
| /* |
| * re-implement to call SpannableStringBuilder.getTextWidths with a Paint object |
| * instead of an _Original_Paint |
| */ |
| @Override |
| public int getTextWidths(CharSequence text, int start, int end, float[] widths) { |
| if (text instanceof String) { |
| return getTextWidths((String)text, start, end, widths); |
| } |
| if (text instanceof SpannedString || text instanceof SpannableString) { |
| return getTextWidths(text.toString(), start, end, widths); |
| } |
| if (text instanceof SpannableStringBuilder) { |
| return ((SpannableStringBuilder)text).getTextWidths(start, end, widths, this); |
| } |
| |
| char[] buf = TemporaryBuffer.obtain(end - start); |
| TextUtils.getChars(text, start, end, buf, 0); |
| int result = getTextWidths(buf, 0, end - start, widths); |
| TemporaryBuffer.recycle(buf); |
| return result; |
| } |
| |
| @Override |
| public int getTextWidths(String text, float[] widths) { |
| return super.getTextWidths(text, widths); |
| } |
| |
| /** |
| * Return the path (outline) for the specified text. |
| * Note: just like Canvas.drawText, this will respect the Align setting in |
| * the paint. |
| * |
| * @param text The text to retrieve the path from |
| * @param index The index of the first character in text |
| * @param count The number of characterss starting with index |
| * @param x The x coordinate of the text's origin |
| * @param y The y coordinate of the text's origin |
| * @param path The path to receive the data describing the text. Must |
| * be allocated by the caller. |
| */ |
| @Override |
| public void getTextPath(char[] text, int index, int count, |
| float x, float y, Path path) { |
| |
| // TODO this is the ORIGINAL implementation. REPLACE AS NEEDED OR REMOVE |
| |
| if ((index | count) < 0 || index + count > text.length) { |
| throw new ArrayIndexOutOfBoundsException(); |
| } |
| |
| // TODO native_getTextPath(mNativePaint, text, index, count, x, y, path.ni()); |
| |
| throw new UnsupportedOperationException("IMPLEMENT AS NEEDED"); |
| } |
| |
| /** |
| * Return the path (outline) for the specified text. |
| * Note: just like Canvas.drawText, this will respect the Align setting |
| * in the paint. |
| * |
| * @param text The text to retrieve the path from |
| * @param start The first character in the text |
| * @param end 1 past the last charcter in the text |
| * @param x The x coordinate of the text's origin |
| * @param y The y coordinate of the text's origin |
| * @param path The path to receive the data describing the text. Must |
| * be allocated by the caller. |
| */ |
| @Override |
| public void getTextPath(String text, int start, int end, |
| float x, float y, Path path) { |
| if ((start | end | (end - start) | (text.length() - end)) < 0) { |
| throw new IndexOutOfBoundsException(); |
| } |
| |
| getTextPath(text.toCharArray(), start, end - start, x, y, path); |
| } |
| |
| /** |
| * Return in bounds (allocated by the caller) the smallest rectangle that |
| * encloses all of the characters, with an implied origin at (0,0). |
| * |
| * @param text String to measure and return its bounds |
| * @param start Index of the first char in the string to measure |
| * @param end 1 past the last char in the string measure |
| * @param bounds Returns the unioned bounds of all the text. Must be |
| * allocated by the caller. |
| */ |
| @Override |
| public void getTextBounds(String text, int start, int end, Rect bounds) { |
| if ((start | end | (end - start) | (text.length() - end)) < 0) { |
| throw new IndexOutOfBoundsException(); |
| } |
| if (bounds == null) { |
| throw new NullPointerException("need bounds Rect"); |
| } |
| |
| getTextBounds(text.toCharArray(), start, end - start, bounds); |
| } |
| |
| /** |
| * Return in bounds (allocated by the caller) the smallest rectangle that |
| * encloses all of the characters, with an implied origin at (0,0). |
| * |
| * @param text Array of chars to measure and return their unioned bounds |
| * @param index Index of the first char in the array to measure |
| * @param count The number of chars, beginning at index, to measure |
| * @param bounds Returns the unioned bounds of all the text. Must be |
| * allocated by the caller. |
| */ |
| @Override |
| public void getTextBounds(char[] text, int index, int count, Rect bounds) { |
| // FIXME |
| if (mFonts.size() > 0) { |
| if ((index | count) < 0 || index + count > text.length) { |
| throw new ArrayIndexOutOfBoundsException(); |
| } |
| if (bounds == null) { |
| throw new NullPointerException("need bounds Rect"); |
| } |
| |
| FontInfo mainInfo = mFonts.get(0); |
| |
| Rectangle2D rect = mainInfo.mFont.getStringBounds(text, index, index + count, mFontContext); |
| bounds.set(0, 0, (int)rect.getWidth(), (int)rect.getHeight()); |
| } |
| } |
| |
| public static void finalizer(int foo) { |
| // pass |
| } |
| } |