Styled.java revision 9066cfe9886ac131c34d59ed0e2d287b0e3c0087
1/* 2 * Copyright (C) 2006 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 17package android.text; 18 19import android.graphics.Canvas; 20import android.graphics.Paint; 21import android.text.style.CharacterStyle; 22import android.text.style.MetricAffectingSpan; 23import android.text.style.ReplacementSpan; 24 25/** 26 * This class provides static methods for drawing and measuring styled texts, like 27 * {@link android.text.Spanned} object with {@link android.text.style.ReplacementSpan}. 28 * @hide 29 */ 30public class Styled 31{ 32 private static float each(Canvas canvas, 33 Spanned text, int start, int end, 34 int dir, boolean reverse, 35 float x, int top, int y, int bottom, 36 Paint.FontMetricsInt fmi, 37 TextPaint paint, 38 TextPaint workPaint, 39 boolean needwid) { 40 41 boolean havewid = false; 42 float ret = 0; 43 CharacterStyle[] spans = text.getSpans(start, end, CharacterStyle.class); 44 45 ReplacementSpan replacement = null; 46 47 paint.bgColor = 0; 48 paint.baselineShift = 0; 49 workPaint.set(paint); 50 51 if (spans.length > 0) { 52 for (int i = 0; i < spans.length; i++) { 53 CharacterStyle span = spans[i]; 54 55 if (span instanceof ReplacementSpan) { 56 replacement = (ReplacementSpan)span; 57 } 58 else { 59 span.updateDrawState(workPaint); 60 } 61 } 62 } 63 64 if (replacement == null) { 65 CharSequence tmp; 66 int tmpstart, tmpend; 67 68 if (reverse) { 69 tmp = TextUtils.getReverse(text, start, end); 70 tmpstart = 0; 71 tmpend = end - start; 72 } else { 73 tmp = text; 74 tmpstart = start; 75 tmpend = end; 76 } 77 78 if (fmi != null) { 79 workPaint.getFontMetricsInt(fmi); 80 } 81 82 if (canvas != null) { 83 if (workPaint.bgColor != 0) { 84 int c = workPaint.getColor(); 85 Paint.Style s = workPaint.getStyle(); 86 workPaint.setColor(workPaint.bgColor); 87 workPaint.setStyle(Paint.Style.FILL); 88 89 if (!havewid) { 90 ret = workPaint.measureText(tmp, tmpstart, tmpend); 91 havewid = true; 92 } 93 94 if (dir == Layout.DIR_RIGHT_TO_LEFT) 95 canvas.drawRect(x - ret, top, x, bottom, workPaint); 96 else 97 canvas.drawRect(x, top, x + ret, bottom, workPaint); 98 99 workPaint.setStyle(s); 100 workPaint.setColor(c); 101 } 102 103 if (dir == Layout.DIR_RIGHT_TO_LEFT) { 104 if (!havewid) { 105 ret = workPaint.measureText(tmp, tmpstart, tmpend); 106 havewid = true; 107 } 108 109 canvas.drawText(tmp, tmpstart, tmpend, 110 x - ret, y + workPaint.baselineShift, workPaint); 111 } else { 112 if (needwid) { 113 if (!havewid) { 114 ret = workPaint.measureText(tmp, tmpstart, tmpend); 115 havewid = true; 116 } 117 } 118 119 canvas.drawText(tmp, tmpstart, tmpend, 120 x, y + workPaint.baselineShift, workPaint); 121 } 122 } else { 123 if (needwid && !havewid) { 124 ret = workPaint.measureText(tmp, tmpstart, tmpend); 125 havewid = true; 126 } 127 } 128 } else { 129 ret = replacement.getSize(workPaint, text, start, end, fmi); 130 131 if (canvas != null) { 132 if (dir == Layout.DIR_RIGHT_TO_LEFT) 133 replacement.draw(canvas, text, start, end, 134 x - ret, top, y, bottom, workPaint); 135 else 136 replacement.draw(canvas, text, start, end, 137 x, top, y, bottom, workPaint); 138 } 139 } 140 141 if (dir == Layout.DIR_RIGHT_TO_LEFT) 142 return -ret; 143 else 144 return ret; 145 } 146 147 /** 148 * Return the advance widths for the characters in the string. 149 * See also {@link android.graphics.Paint#getTextWidths(CharSequence, int, int, float[])}. 150 * 151 * @param paint The main {@link TextPaint} object. 152 * @param workPaint The {@link TextPaint} object used for temporal workspace. 153 * @param text The text to measure 154 * @param start The index of the first char to to measure 155 * @param end The end of the text slice to measure 156 * @param widths Array to receive the advance widths of the characters. 157 * Must be at least a large as (end - start). 158 * @param fmi FontMetrics information. Can be null. 159 * @return The actual number of widths returned. 160 */ 161 public static int getTextWidths(TextPaint paint, 162 TextPaint workPaint, 163 Spanned text, int start, int end, 164 float[] widths, Paint.FontMetricsInt fmi) { 165 // Keep workPaint as is so that developers reuse the workspace. 166 MetricAffectingSpan[] spans = text.getSpans(start, end, MetricAffectingSpan.class); 167 168 ReplacementSpan replacement = null; 169 workPaint.set(paint); 170 171 for (int i = 0; i < spans.length; i++) { 172 MetricAffectingSpan span = spans[i]; 173 if (span instanceof ReplacementSpan) { 174 replacement = (ReplacementSpan)span; 175 } 176 else { 177 span.updateMeasureState(workPaint); 178 } 179 } 180 181 if (replacement == null) { 182 workPaint.getFontMetricsInt(fmi); 183 workPaint.getTextWidths(text, start, end, widths); 184 } else { 185 int wid = replacement.getSize(workPaint, text, start, end, fmi); 186 187 if (end > start) { 188 widths[0] = wid; 189 190 for (int i = start + 1; i < end; i++) 191 widths[i - start] = 0; 192 } 193 } 194 return end - start; 195 } 196 197 private static float foreach(Canvas canvas, 198 CharSequence text, int start, int end, 199 int dir, boolean reverse, 200 float x, int top, int y, int bottom, 201 Paint.FontMetricsInt fmi, 202 TextPaint paint, 203 TextPaint workPaint, 204 boolean needWidth) { 205 if (! (text instanceof Spanned)) { 206 float ret = 0; 207 208 if (reverse) { 209 CharSequence tmp = TextUtils.getReverse(text, start, end); 210 int tmpend = end - start; 211 212 if (canvas != null || needWidth) 213 ret = paint.measureText(tmp, 0, tmpend); 214 215 if (canvas != null) 216 canvas.drawText(tmp, 0, tmpend, 217 x - ret, y, paint); 218 } else { 219 if (needWidth) 220 ret = paint.measureText(text, start, end); 221 222 if (canvas != null) 223 canvas.drawText(text, start, end, x, y, paint); 224 } 225 226 if (fmi != null) { 227 paint.getFontMetricsInt(fmi); 228 } 229 230 return ret * dir; //Layout.DIR_RIGHT_TO_LEFT == -1 231 } 232 233 float ox = x; 234 int asc = 0, desc = 0; 235 int ftop = 0, fbot = 0; 236 237 Spanned sp = (Spanned) text; 238 Class division; 239 240 if (canvas == null) 241 division = MetricAffectingSpan.class; 242 else 243 division = CharacterStyle.class; 244 245 int next; 246 for (int i = start; i < end; i = next) { 247 next = sp.nextSpanTransition(i, end, division); 248 249 x += each(canvas, sp, i, next, dir, reverse, 250 x, top, y, bottom, fmi, paint, workPaint, 251 needWidth || next != end); 252 253 if (fmi != null) { 254 if (fmi.ascent < asc) 255 asc = fmi.ascent; 256 if (fmi.descent > desc) 257 desc = fmi.descent; 258 259 if (fmi.top < ftop) 260 ftop = fmi.top; 261 if (fmi.bottom > fbot) 262 fbot = fmi.bottom; 263 } 264 } 265 266 if (fmi != null) { 267 if (start == end) { 268 paint.getFontMetricsInt(fmi); 269 } else { 270 fmi.ascent = asc; 271 fmi.descent = desc; 272 fmi.top = ftop; 273 fmi.bottom = fbot; 274 } 275 } 276 277 return x - ox; 278 } 279 280 281 /* package */ static float drawText(Canvas canvas, 282 CharSequence text, int start, int end, 283 int direction, boolean reverse, 284 float x, int top, int y, int bottom, 285 TextPaint paint, 286 TextPaint workPaint, 287 boolean needWidth) { 288 if ((direction == Layout.DIR_RIGHT_TO_LEFT && !reverse) || 289 (reverse && direction == Layout.DIR_LEFT_TO_RIGHT)) { 290 float ch = foreach(null, text, start, end, Layout.DIR_LEFT_TO_RIGHT, 291 false, 0, 0, 0, 0, null, paint, workPaint, 292 true); 293 294 ch *= direction; // DIR_RIGHT_TO_LEFT == -1 295 foreach(canvas, text, start, end, -direction, 296 reverse, x + ch, top, y, bottom, null, paint, 297 workPaint, true); 298 299 return ch; 300 } 301 302 return foreach(canvas, text, start, end, direction, reverse, 303 x, top, y, bottom, null, paint, workPaint, 304 needWidth); 305 } 306 307 /** 308 * Draw the specified range of text, specified by start/end, with its origin at (x,y), 309 * in the specified Paint. The origin is interpreted based on the Align setting in the 310 * Paint. 311 * 312 * This method considers style information in the text 313 * (e.g. Even when text is an instance of {@link android.text.Spanned}, this method 314 * correctly draws the text). 315 * See also 316 * {@link android.graphics.Canvas#drawText(CharSequence, int, int, float, float, Paint)} 317 * and 318 * {@link android.graphics.Canvas#drawRect(float, float, float, float, Paint)}. 319 * 320 * @param canvas The target canvas. 321 * @param text The text to be drawn 322 * @param start The index of the first character in text to draw 323 * @param end (end - 1) is the index of the last character in text to draw 324 * @param direction The direction of the text. This must be 325 * {@link android.text.Layout#DIR_LEFT_TO_RIGHT} or 326 * {@link android.text.Layout#DIR_RIGHT_TO_LEFT}. 327 * @param x The x-coordinate of origin for where to draw the text 328 * @param top The top side of the rectangle to be drawn 329 * @param y The y-coordinate of origin for where to draw the text 330 * @param bottom The bottom side of the rectangle to be drawn 331 * @param paint The main {@link TextPaint} object. 332 * @param workPaint The {@link TextPaint} object used for temporal workspace. 333 * @param needWidth If true, this method returns the width of drawn text. 334 * @return Width of the drawn text if needWidth is true. 335 */ 336 public static float drawText(Canvas canvas, 337 CharSequence text, int start, int end, 338 int direction, 339 float x, int top, int y, int bottom, 340 TextPaint paint, 341 TextPaint workPaint, 342 boolean needWidth) { 343 // For safety. 344 direction = direction >= 0 ? Layout.DIR_LEFT_TO_RIGHT : Layout.DIR_RIGHT_TO_LEFT; 345 /* 346 * Hided "reverse" parameter since it is meaningless for external developers. 347 * Kept workPaint as is so that developers reuse the workspace. 348 */ 349 return drawText(canvas, text, start, end, direction, false, 350 x, top, y, bottom, paint, workPaint, needWidth); 351 } 352 353 /** 354 * Return the width of the text, considering style information in the text 355 * (e.g. Even when text is an instance of {@link android.text.Spanned}, this method 356 * correctly mesures the width of the text). 357 * 358 * @param paint The main {@link TextPaint} object. 359 * @param workPaint The {@link TextPaint} object used for temporal workspace. 360 * @param text The text to measure 361 * @param start The index of the first character to start measuring 362 * @param end 1 beyond the index of the last character to measure 363 * @param fmi FontMetrics information. Can be null 364 * @return The width of the text 365 */ 366 public static float measureText(TextPaint paint, 367 TextPaint workPaint, 368 CharSequence text, int start, int end, 369 Paint.FontMetricsInt fmi) { 370 // Keep workPaint as is so that developers reuse the workspace. 371 return foreach(null, text, start, end, 372 Layout.DIR_LEFT_TO_RIGHT, false, 373 0, 0, 0, 0, fmi, paint, workPaint, true); 374 } 375} 376