• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 package android.text;
17 
18 import android.graphics.Canvas;
19 import android.graphics.Paint;
20 import android.text.style.CharacterStyle;
21 import android.text.style.MetricAffectingSpan;
22 import android.text.style.ReplacementSpan;
23 
24 /**
25  * This class provides static methods for drawing and measuring styled text,
26  * like {@link android.text.Spanned} object with
27  * {@link android.text.style.ReplacementSpan}.
28  *
29  * @hide
30  */
31 public class Styled
32 {
33     /**
34      * Draws and/or measures a uniform run of text on a single line. No span of
35      * interest should start or end in the middle of this run (if not
36      * drawing, character spans that don't affect metrics can be ignored).
37      * Neither should the run direction change in the middle of the run.
38      *
39      * <p>The x position is the leading edge of the text. In a right-to-left
40      * paragraph, this will be to the right of the text to be drawn. Paint
41      * should not have an Align value other than LEFT or positioning will get
42      * confused.
43      *
44      * <p>On return, workPaint will reflect the original paint plus any
45      * modifications made by character styles on the run.
46      *
47      * <p>The returned width is signed and will be < 0 if the paragraph
48      * direction is right-to-left.
49      */
drawUniformRun(Canvas canvas, Spanned text, int start, int end, int dir, boolean runIsRtl, float x, int top, int y, int bottom, Paint.FontMetricsInt fmi, TextPaint paint, TextPaint workPaint, boolean needWidth)50     private static float drawUniformRun(Canvas canvas,
51                               Spanned text, int start, int end,
52                               int dir, boolean runIsRtl,
53                               float x, int top, int y, int bottom,
54                               Paint.FontMetricsInt fmi,
55                               TextPaint paint,
56                               TextPaint workPaint,
57                               boolean needWidth) {
58 
59         boolean haveWidth = false;
60         float ret = 0;
61         CharacterStyle[] spans = text.getSpans(start, end, CharacterStyle.class);
62 
63         ReplacementSpan replacement = null;
64 
65         // XXX: This shouldn't be modifying paint, only workPaint.
66         // However, the members belonging to TextPaint should have default
67         // values anyway.  Better to ensure this in the Layout constructor.
68         paint.bgColor = 0;
69         paint.baselineShift = 0;
70         workPaint.set(paint);
71 
72 		if (spans.length > 0) {
73 			for (int i = 0; i < spans.length; i++) {
74 				CharacterStyle span = spans[i];
75 
76 				if (span instanceof ReplacementSpan) {
77 					replacement = (ReplacementSpan)span;
78 				}
79 				else {
80 					span.updateDrawState(workPaint);
81 				}
82 			}
83 		}
84 
85         if (replacement == null) {
86             CharSequence tmp;
87             int tmpstart, tmpend;
88 
89             if (runIsRtl) {
90                 tmp = TextUtils.getReverse(text, start, end);
91                 tmpstart = 0;
92                 // XXX: assumes getReverse doesn't change the length of the text
93                 tmpend = end - start;
94             } else {
95                 tmp = text;
96                 tmpstart = start;
97                 tmpend = end;
98             }
99 
100             if (fmi != null) {
101                 workPaint.getFontMetricsInt(fmi);
102             }
103 
104             if (canvas != null) {
105                 if (workPaint.bgColor != 0) {
106                     int c = workPaint.getColor();
107                     Paint.Style s = workPaint.getStyle();
108                     workPaint.setColor(workPaint.bgColor);
109                     workPaint.setStyle(Paint.Style.FILL);
110 
111                     if (!haveWidth) {
112                         ret = workPaint.measureText(tmp, tmpstart, tmpend);
113                         haveWidth = true;
114                     }
115 
116                     if (dir == Layout.DIR_RIGHT_TO_LEFT)
117                         canvas.drawRect(x - ret, top, x, bottom, workPaint);
118                     else
119                         canvas.drawRect(x, top, x + ret, bottom, workPaint);
120 
121                     workPaint.setStyle(s);
122                     workPaint.setColor(c);
123                 }
124 
125                 if (dir == Layout.DIR_RIGHT_TO_LEFT) {
126                     if (!haveWidth) {
127                         ret = workPaint.measureText(tmp, tmpstart, tmpend);
128                         haveWidth = true;
129                     }
130 
131                     canvas.drawText(tmp, tmpstart, tmpend,
132                                     x - ret, y + workPaint.baselineShift, workPaint);
133                 } else {
134                     if (needWidth) {
135                         if (!haveWidth) {
136                             ret = workPaint.measureText(tmp, tmpstart, tmpend);
137                             haveWidth = true;
138                         }
139                     }
140 
141                     canvas.drawText(tmp, tmpstart, tmpend,
142                                     x, y + workPaint.baselineShift, workPaint);
143                 }
144             } else {
145                 if (needWidth && !haveWidth) {
146                     ret = workPaint.measureText(tmp, tmpstart, tmpend);
147                     haveWidth = true;
148                 }
149             }
150         } else {
151             ret = replacement.getSize(workPaint, text, start, end, fmi);
152 
153             if (canvas != null) {
154                 if (dir == Layout.DIR_RIGHT_TO_LEFT)
155                     replacement.draw(canvas, text, start, end,
156                                      x - ret, top, y, bottom, workPaint);
157                 else
158                     replacement.draw(canvas, text, start, end,
159                                      x, top, y, bottom, workPaint);
160             }
161         }
162 
163         if (dir == Layout.DIR_RIGHT_TO_LEFT)
164             return -ret;
165         else
166             return ret;
167     }
168 
169     /**
170      * Returns the advance widths for a uniform left-to-right run of text with
171      * no style changes in the middle of the run. If any style is replacement
172      * text, the first character will get the width of the replacement and the
173      * remaining characters will get a width of 0.
174      *
175      * @param paint the paint, will not be modified
176      * @param workPaint a paint to modify; on return will reflect the original
177      *        paint plus the effect of all spans on the run
178      * @param text the text
179      * @param start the start of the run
180      * @param end the limit of the run
181      * @param widths array to receive the advance widths of the characters. Must
182      *        be at least a large as (end - start).
183      * @param fmi FontMetrics information; can be null
184      * @return the actual number of widths returned
185      */
getTextWidths(TextPaint paint, TextPaint workPaint, Spanned text, int start, int end, float[] widths, Paint.FontMetricsInt fmi)186     public static int getTextWidths(TextPaint paint,
187                                     TextPaint workPaint,
188                                     Spanned text, int start, int end,
189                                     float[] widths, Paint.FontMetricsInt fmi) {
190         MetricAffectingSpan[] spans =
191             text.getSpans(start, end, MetricAffectingSpan.class);
192 
193 		ReplacementSpan replacement = null;
194         workPaint.set(paint);
195 
196 		for (int i = 0; i < spans.length; i++) {
197 			MetricAffectingSpan span = spans[i];
198 			if (span instanceof ReplacementSpan) {
199 				replacement = (ReplacementSpan)span;
200 			}
201 			else {
202 				span.updateMeasureState(workPaint);
203 			}
204 		}
205 
206         if (replacement == null) {
207             workPaint.getFontMetricsInt(fmi);
208             workPaint.getTextWidths(text, start, end, widths);
209         } else {
210             int wid = replacement.getSize(workPaint, text, start, end, fmi);
211 
212             if (end > start) {
213                 widths[0] = wid;
214                 for (int i = start + 1; i < end; i++)
215                     widths[i - start] = 0;
216             }
217         }
218         return end - start;
219     }
220 
221     /**
222      * Renders and/or measures a directional run of text on a single line.
223      * Unlike {@link #drawUniformRun}, this can render runs that cross style
224      * boundaries.  Returns the signed advance width, if requested.
225      *
226      * <p>The x position is the leading edge of the text. In a right-to-left
227      * paragraph, this will be to the right of the text to be drawn. Paint
228      * should not have an Align value other than LEFT or positioning will get
229      * confused.
230      *
231      * <p>This optimizes for unstyled text and so workPaint might not be
232      * modified by this call.
233      *
234      * <p>The returned advance width will be < 0 if the paragraph
235      * direction is right-to-left.
236      */
drawDirectionalRun(Canvas canvas, CharSequence text, int start, int end, int dir, boolean runIsRtl, float x, int top, int y, int bottom, Paint.FontMetricsInt fmi, TextPaint paint, TextPaint workPaint, boolean needWidth)237     private static float drawDirectionalRun(Canvas canvas,
238                                  CharSequence text, int start, int end,
239                                  int dir, boolean runIsRtl,
240                                  float x, int top, int y, int bottom,
241                                  Paint.FontMetricsInt fmi,
242                                  TextPaint paint,
243                                  TextPaint workPaint,
244                                  boolean needWidth) {
245 
246         // XXX: It looks like all calls to this API match dir and runIsRtl, so
247         // having both parameters is redundant and confusing.
248 
249         // fast path for unstyled text
250         if (!(text instanceof Spanned)) {
251             float ret = 0;
252 
253             if (runIsRtl) {
254                 CharSequence tmp = TextUtils.getReverse(text, start, end);
255                 // XXX: this assumes getReverse doesn't tweak the length of
256                 // the text
257                 int tmpend = end - start;
258 
259                 if (canvas != null || needWidth)
260                     ret = paint.measureText(tmp, 0, tmpend);
261 
262                 if (canvas != null)
263                     canvas.drawText(tmp, 0, tmpend,
264                                     x - ret, y, paint);
265             } else {
266                 if (needWidth)
267                     ret = paint.measureText(text, start, end);
268 
269                 if (canvas != null)
270                     canvas.drawText(text, start, end, x, y, paint);
271             }
272 
273             if (fmi != null) {
274                 paint.getFontMetricsInt(fmi);
275             }
276 
277             return ret * dir;   // Layout.DIR_RIGHT_TO_LEFT == -1
278         }
279 
280         float ox = x;
281         int minAscent = 0, maxDescent = 0, minTop = 0, maxBottom = 0;
282 
283         Spanned sp = (Spanned) text;
284         Class<?> division;
285 
286         if (canvas == null)
287             division = MetricAffectingSpan.class;
288         else
289             division = CharacterStyle.class;
290 
291         int next;
292         for (int i = start; i < end; i = next) {
293             next = sp.nextSpanTransition(i, end, division);
294 
295             // XXX: if dir and runIsRtl were not the same, this would draw
296             // spans in the wrong order, but no one appears to call it this
297             // way.
298             x += drawUniformRun(canvas, sp, i, next, dir, runIsRtl,
299                   x, top, y, bottom, fmi, paint, workPaint,
300                   needWidth || next != end);
301 
302             if (fmi != null) {
303                 if (fmi.ascent < minAscent)
304                     minAscent = fmi.ascent;
305                 if (fmi.descent > maxDescent)
306                     maxDescent = fmi.descent;
307 
308                 if (fmi.top < minTop)
309                     minTop = fmi.top;
310                 if (fmi.bottom > maxBottom)
311                     maxBottom = fmi.bottom;
312             }
313         }
314 
315         if (fmi != null) {
316             if (start == end) {
317                 paint.getFontMetricsInt(fmi);
318             } else {
319                 fmi.ascent = minAscent;
320                 fmi.descent = maxDescent;
321                 fmi.top = minTop;
322                 fmi.bottom = maxBottom;
323             }
324         }
325 
326         return x - ox;
327     }
328 
329     /**
330      * Draws a unidirectional run of text on a single line, and optionally
331      * returns the signed advance.  Unlike drawDirectionalRun, the paragraph
332      * direction and run direction can be different.
333      */
drawText(Canvas canvas, CharSequence text, int start, int end, int dir, boolean runIsRtl, float x, int top, int y, int bottom, TextPaint paint, TextPaint workPaint, boolean needWidth)334     /* package */ static float drawText(Canvas canvas,
335                                        CharSequence text, int start, int end,
336                                        int dir, boolean runIsRtl,
337                                        float x, int top, int y, int bottom,
338                                        TextPaint paint,
339                                        TextPaint workPaint,
340                                        boolean needWidth) {
341         // XXX this logic is (dir == DIR_LEFT_TO_RIGHT) == runIsRtl
342         if ((dir == Layout.DIR_RIGHT_TO_LEFT && !runIsRtl) ||
343             (runIsRtl && dir == Layout.DIR_LEFT_TO_RIGHT)) {
344             // TODO: this needs the real direction
345             float ch = drawDirectionalRun(null, text, start, end,
346                     Layout.DIR_LEFT_TO_RIGHT, false, 0, 0, 0, 0, null, paint,
347                     workPaint, true);
348 
349             ch *= dir;  // DIR_RIGHT_TO_LEFT == -1
350             drawDirectionalRun(canvas, text, start, end, -dir,
351                     runIsRtl, x + ch, top, y, bottom, null, paint,
352                     workPaint, true);
353 
354             return ch;
355         }
356 
357         return drawDirectionalRun(canvas, text, start, end, dir, runIsRtl,
358                        x, top, y, bottom, null, paint, workPaint,
359                        needWidth);
360     }
361 
362     /**
363      * Draws a run of text on a single line, with its
364      * origin at (x,y), in the specified Paint. The origin is interpreted based
365      * on the Align setting in the Paint.
366      *
367      * This method considers style information in the text (e.g. even when text
368      * is an instance of {@link android.text.Spanned}, this method correctly
369      * draws the text). See also
370      * {@link android.graphics.Canvas#drawText(CharSequence, int, int, float,
371      * float, Paint)} and
372      * {@link android.graphics.Canvas#drawRect(float, float, float, float,
373      * Paint)}.
374      *
375      * @param canvas The target canvas
376      * @param text The text to be drawn
377      * @param start The index of the first character in text to draw
378      * @param end (end - 1) is the index of the last character in text to draw
379      * @param direction The direction of the text. This must be
380      *        {@link android.text.Layout#DIR_LEFT_TO_RIGHT} or
381      *        {@link android.text.Layout#DIR_RIGHT_TO_LEFT}.
382      * @param x The x-coordinate of origin for where to draw the text
383      * @param top The top side of the rectangle to be drawn
384      * @param y The y-coordinate of origin for where to draw the text
385      * @param bottom The bottom side of the rectangle to be drawn
386      * @param paint The main {@link TextPaint} object.
387      * @param workPaint The {@link TextPaint} object used for temporal
388      *        workspace.
389      * @param needWidth If true, this method returns the width of drawn text
390      * @return Width of the drawn text if needWidth is true
391      */
drawText(Canvas canvas, CharSequence text, int start, int end, int direction, float x, int top, int y, int bottom, TextPaint paint, TextPaint workPaint, boolean needWidth)392     public static float drawText(Canvas canvas,
393                                  CharSequence text, int start, int end,
394                                  int direction,
395                                  float x, int top, int y, int bottom,
396                                  TextPaint paint,
397                                  TextPaint workPaint,
398                                  boolean needWidth) {
399         // For safety.
400         direction = direction >= 0 ? Layout.DIR_LEFT_TO_RIGHT
401                 : Layout.DIR_RIGHT_TO_LEFT;
402 
403         // Hide runIsRtl parameter since it is meaningless for external
404         // developers.
405         // XXX: the runIsRtl probably ought to be the same as direction, then
406         // this could draw rtl text.
407         return drawText(canvas, text, start, end, direction, false,
408                         x, top, y, bottom, paint, workPaint, needWidth);
409     }
410 
411     /**
412      * Returns the width of a run of left-to-right text on a single line,
413      * considering style information in the text (e.g. even when text is an
414      * instance of {@link android.text.Spanned}, this method correctly measures
415      * the width of the text).
416      *
417      * @param paint the main {@link TextPaint} object; will not be modified
418      * @param workPaint the {@link TextPaint} object available for modification;
419      *        will not necessarily be used
420      * @param text the text to measure
421      * @param start the index of the first character to start measuring
422      * @param end 1 beyond the index of the last character to measure
423      * @param fmi FontMetrics information; can be null
424      * @return The width of the text
425      */
measureText(TextPaint paint, TextPaint workPaint, CharSequence text, int start, int end, Paint.FontMetricsInt fmi)426     public static float measureText(TextPaint paint,
427                                     TextPaint workPaint,
428                                     CharSequence text, int start, int end,
429                                     Paint.FontMetricsInt fmi) {
430         return drawDirectionalRun(null, text, start, end,
431                        Layout.DIR_LEFT_TO_RIGHT, false,
432                        0, 0, 0, 0, fmi, paint, workPaint, true);
433     }
434 }
435