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