435 lines
17 KiB
Java
435 lines
17 KiB
Java
|
/*
|
||
|
* Copyright (C) 2006 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.text;
|
||
|
|
||
|
import android.graphics.Canvas;
|
||
|
import android.graphics.Paint;
|
||
|
import android.text.style.CharacterStyle;
|
||
|
import android.text.style.MetricAffectingSpan;
|
||
|
import android.text.style.ReplacementSpan;
|
||
|
|
||
|
/**
|
||
|
* This class provides static methods for drawing and measuring styled text,
|
||
|
* like {@link android.text.Spanned} object with
|
||
|
* {@link android.text.style.ReplacementSpan}.
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
public class Styled
|
||
|
{
|
||
|
/**
|
||
|
* Draws and/or measures a uniform run of text on a single line. No span of
|
||
|
* interest should start or end in the middle of this run (if not
|
||
|
* drawing, character spans that don't affect metrics can be ignored).
|
||
|
* Neither should the run direction change in the middle of the run.
|
||
|
*
|
||
|
* <p>The x position is the leading edge of the text. In a right-to-left
|
||
|
* paragraph, this will be to the right of the text to be drawn. Paint
|
||
|
* should not have an Align value other than LEFT or positioning will get
|
||
|
* confused.
|
||
|
*
|
||
|
* <p>On return, workPaint will reflect the original paint plus any
|
||
|
* modifications made by character styles on the run.
|
||
|
*
|
||
|
* <p>The returned width is signed and will be < 0 if the paragraph
|
||
|
* direction is right-to-left.
|
||
|
*/
|
||
|
private static float 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) {
|
||
|
|
||
|
boolean haveWidth = false;
|
||
|
float ret = 0;
|
||
|
CharacterStyle[] spans = text.getSpans(start, end, CharacterStyle.class);
|
||
|
|
||
|
ReplacementSpan replacement = null;
|
||
|
|
||
|
// XXX: This shouldn't be modifying paint, only workPaint.
|
||
|
// However, the members belonging to TextPaint should have default
|
||
|
// values anyway. Better to ensure this in the Layout constructor.
|
||
|
paint.bgColor = 0;
|
||
|
paint.baselineShift = 0;
|
||
|
workPaint.set(paint);
|
||
|
|
||
|
if (spans.length > 0) {
|
||
|
for (int i = 0; i < spans.length; i++) {
|
||
|
CharacterStyle span = spans[i];
|
||
|
|
||
|
if (span instanceof ReplacementSpan) {
|
||
|
replacement = (ReplacementSpan)span;
|
||
|
}
|
||
|
else {
|
||
|
span.updateDrawState(workPaint);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (replacement == null) {
|
||
|
CharSequence tmp;
|
||
|
int tmpstart, tmpend;
|
||
|
|
||
|
if (runIsRtl) {
|
||
|
tmp = TextUtils.getReverse(text, start, end);
|
||
|
tmpstart = 0;
|
||
|
// XXX: assumes getReverse doesn't change the length of the text
|
||
|
tmpend = end - start;
|
||
|
} else {
|
||
|
tmp = text;
|
||
|
tmpstart = start;
|
||
|
tmpend = end;
|
||
|
}
|
||
|
|
||
|
if (fmi != null) {
|
||
|
workPaint.getFontMetricsInt(fmi);
|
||
|
}
|
||
|
|
||
|
if (canvas != null) {
|
||
|
if (workPaint.bgColor != 0) {
|
||
|
int c = workPaint.getColor();
|
||
|
Paint.Style s = workPaint.getStyle();
|
||
|
workPaint.setColor(workPaint.bgColor);
|
||
|
workPaint.setStyle(Paint.Style.FILL);
|
||
|
|
||
|
if (!haveWidth) {
|
||
|
ret = workPaint.measureText(tmp, tmpstart, tmpend);
|
||
|
haveWidth = true;
|
||
|
}
|
||
|
|
||
|
if (dir == Layout.DIR_RIGHT_TO_LEFT)
|
||
|
canvas.drawRect(x - ret, top, x, bottom, workPaint);
|
||
|
else
|
||
|
canvas.drawRect(x, top, x + ret, bottom, workPaint);
|
||
|
|
||
|
workPaint.setStyle(s);
|
||
|
workPaint.setColor(c);
|
||
|
}
|
||
|
|
||
|
if (dir == Layout.DIR_RIGHT_TO_LEFT) {
|
||
|
if (!haveWidth) {
|
||
|
ret = workPaint.measureText(tmp, tmpstart, tmpend);
|
||
|
haveWidth = true;
|
||
|
}
|
||
|
|
||
|
canvas.drawText(tmp, tmpstart, tmpend,
|
||
|
x - ret, y + workPaint.baselineShift, workPaint);
|
||
|
} else {
|
||
|
if (needWidth) {
|
||
|
if (!haveWidth) {
|
||
|
ret = workPaint.measureText(tmp, tmpstart, tmpend);
|
||
|
haveWidth = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
canvas.drawText(tmp, tmpstart, tmpend,
|
||
|
x, y + workPaint.baselineShift, workPaint);
|
||
|
}
|
||
|
} else {
|
||
|
if (needWidth && !haveWidth) {
|
||
|
ret = workPaint.measureText(tmp, tmpstart, tmpend);
|
||
|
haveWidth = true;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
ret = replacement.getSize(workPaint, text, start, end, fmi);
|
||
|
|
||
|
if (canvas != null) {
|
||
|
if (dir == Layout.DIR_RIGHT_TO_LEFT)
|
||
|
replacement.draw(canvas, text, start, end,
|
||
|
x - ret, top, y, bottom, workPaint);
|
||
|
else
|
||
|
replacement.draw(canvas, text, start, end,
|
||
|
x, top, y, bottom, workPaint);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (dir == Layout.DIR_RIGHT_TO_LEFT)
|
||
|
return -ret;
|
||
|
else
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the advance widths for a uniform left-to-right run of text with
|
||
|
* no style changes in the middle of the run. If any style is replacement
|
||
|
* text, the first character will get the width of the replacement and the
|
||
|
* remaining characters will get a width of 0.
|
||
|
*
|
||
|
* @param paint the paint, will not be modified
|
||
|
* @param workPaint a paint to modify; on return will reflect the original
|
||
|
* paint plus the effect of all spans on the run
|
||
|
* @param text the text
|
||
|
* @param start the start of the run
|
||
|
* @param end the limit of the run
|
||
|
* @param widths array to receive the advance widths of the characters. Must
|
||
|
* be at least a large as (end - start).
|
||
|
* @param fmi FontMetrics information; can be null
|
||
|
* @return the actual number of widths returned
|
||
|
*/
|
||
|
public static int getTextWidths(TextPaint paint,
|
||
|
TextPaint workPaint,
|
||
|
Spanned text, int start, int end,
|
||
|
float[] widths, Paint.FontMetricsInt fmi) {
|
||
|
MetricAffectingSpan[] spans =
|
||
|
text.getSpans(start, end, MetricAffectingSpan.class);
|
||
|
|
||
|
ReplacementSpan replacement = null;
|
||
|
workPaint.set(paint);
|
||
|
|
||
|
for (int i = 0; i < spans.length; i++) {
|
||
|
MetricAffectingSpan span = spans[i];
|
||
|
if (span instanceof ReplacementSpan) {
|
||
|
replacement = (ReplacementSpan)span;
|
||
|
}
|
||
|
else {
|
||
|
span.updateMeasureState(workPaint);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (replacement == null) {
|
||
|
workPaint.getFontMetricsInt(fmi);
|
||
|
workPaint.getTextWidths(text, start, end, widths);
|
||
|
} else {
|
||
|
int wid = replacement.getSize(workPaint, text, start, end, fmi);
|
||
|
|
||
|
if (end > start) {
|
||
|
widths[0] = wid;
|
||
|
for (int i = start + 1; i < end; i++)
|
||
|
widths[i - start] = 0;
|
||
|
}
|
||
|
}
|
||
|
return end - start;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Renders and/or measures a directional run of text on a single line.
|
||
|
* Unlike {@link #drawUniformRun}, this can render runs that cross style
|
||
|
* boundaries. Returns the signed advance width, if requested.
|
||
|
*
|
||
|
* <p>The x position is the leading edge of the text. In a right-to-left
|
||
|
* paragraph, this will be to the right of the text to be drawn. Paint
|
||
|
* should not have an Align value other than LEFT or positioning will get
|
||
|
* confused.
|
||
|
*
|
||
|
* <p>This optimizes for unstyled text and so workPaint might not be
|
||
|
* modified by this call.
|
||
|
*
|
||
|
* <p>The returned advance width will be < 0 if the paragraph
|
||
|
* direction is right-to-left.
|
||
|
*/
|
||
|
private static float 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) {
|
||
|
|
||
|
// XXX: It looks like all calls to this API match dir and runIsRtl, so
|
||
|
// having both parameters is redundant and confusing.
|
||
|
|
||
|
// fast path for unstyled text
|
||
|
if (!(text instanceof Spanned)) {
|
||
|
float ret = 0;
|
||
|
|
||
|
if (runIsRtl) {
|
||
|
CharSequence tmp = TextUtils.getReverse(text, start, end);
|
||
|
// XXX: this assumes getReverse doesn't tweak the length of
|
||
|
// the text
|
||
|
int tmpend = end - start;
|
||
|
|
||
|
if (canvas != null || needWidth)
|
||
|
ret = paint.measureText(tmp, 0, tmpend);
|
||
|
|
||
|
if (canvas != null)
|
||
|
canvas.drawText(tmp, 0, tmpend,
|
||
|
x - ret, y, paint);
|
||
|
} else {
|
||
|
if (needWidth)
|
||
|
ret = paint.measureText(text, start, end);
|
||
|
|
||
|
if (canvas != null)
|
||
|
canvas.drawText(text, start, end, x, y, paint);
|
||
|
}
|
||
|
|
||
|
if (fmi != null) {
|
||
|
paint.getFontMetricsInt(fmi);
|
||
|
}
|
||
|
|
||
|
return ret * dir; // Layout.DIR_RIGHT_TO_LEFT == -1
|
||
|
}
|
||
|
|
||
|
float ox = x;
|
||
|
int minAscent = 0, maxDescent = 0, minTop = 0, maxBottom = 0;
|
||
|
|
||
|
Spanned sp = (Spanned) text;
|
||
|
Class<?> division;
|
||
|
|
||
|
if (canvas == null)
|
||
|
division = MetricAffectingSpan.class;
|
||
|
else
|
||
|
division = CharacterStyle.class;
|
||
|
|
||
|
int next;
|
||
|
for (int i = start; i < end; i = next) {
|
||
|
next = sp.nextSpanTransition(i, end, division);
|
||
|
|
||
|
// XXX: if dir and runIsRtl were not the same, this would draw
|
||
|
// spans in the wrong order, but no one appears to call it this
|
||
|
// way.
|
||
|
x += drawUniformRun(canvas, sp, i, next, dir, runIsRtl,
|
||
|
x, top, y, bottom, fmi, paint, workPaint,
|
||
|
needWidth || next != end);
|
||
|
|
||
|
if (fmi != null) {
|
||
|
if (fmi.ascent < minAscent)
|
||
|
minAscent = fmi.ascent;
|
||
|
if (fmi.descent > maxDescent)
|
||
|
maxDescent = fmi.descent;
|
||
|
|
||
|
if (fmi.top < minTop)
|
||
|
minTop = fmi.top;
|
||
|
if (fmi.bottom > maxBottom)
|
||
|
maxBottom = fmi.bottom;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (fmi != null) {
|
||
|
if (start == end) {
|
||
|
paint.getFontMetricsInt(fmi);
|
||
|
} else {
|
||
|
fmi.ascent = minAscent;
|
||
|
fmi.descent = maxDescent;
|
||
|
fmi.top = minTop;
|
||
|
fmi.bottom = maxBottom;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return x - ox;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Draws a unidirectional run of text on a single line, and optionally
|
||
|
* returns the signed advance. Unlike drawDirectionalRun, the paragraph
|
||
|
* direction and run direction can be different.
|
||
|
*/
|
||
|
/* package */ static float 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) {
|
||
|
// XXX this logic is (dir == DIR_LEFT_TO_RIGHT) == runIsRtl
|
||
|
if ((dir == Layout.DIR_RIGHT_TO_LEFT && !runIsRtl) ||
|
||
|
(runIsRtl && dir == Layout.DIR_LEFT_TO_RIGHT)) {
|
||
|
// TODO: this needs the real direction
|
||
|
float ch = drawDirectionalRun(null, text, start, end,
|
||
|
Layout.DIR_LEFT_TO_RIGHT, false, 0, 0, 0, 0, null, paint,
|
||
|
workPaint, true);
|
||
|
|
||
|
ch *= dir; // DIR_RIGHT_TO_LEFT == -1
|
||
|
drawDirectionalRun(canvas, text, start, end, -dir,
|
||
|
runIsRtl, x + ch, top, y, bottom, null, paint,
|
||
|
workPaint, true);
|
||
|
|
||
|
return ch;
|
||
|
}
|
||
|
|
||
|
return drawDirectionalRun(canvas, text, start, end, dir, runIsRtl,
|
||
|
x, top, y, bottom, null, paint, workPaint,
|
||
|
needWidth);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Draws a run of text on a single line, with its
|
||
|
* origin at (x,y), in the specified Paint. The origin is interpreted based
|
||
|
* on the Align setting in the Paint.
|
||
|
*
|
||
|
* This method considers style information in the text (e.g. even when text
|
||
|
* is an instance of {@link android.text.Spanned}, this method correctly
|
||
|
* draws the text). See also
|
||
|
* {@link android.graphics.Canvas#drawText(CharSequence, int, int, float,
|
||
|
* float, Paint)} and
|
||
|
* {@link android.graphics.Canvas#drawRect(float, float, float, float,
|
||
|
* Paint)}.
|
||
|
*
|
||
|
* @param canvas The target canvas
|
||
|
* @param text The text to be drawn
|
||
|
* @param start The index of the first character in text to draw
|
||
|
* @param end (end - 1) is the index of the last character in text to draw
|
||
|
* @param direction The direction of the text. This must be
|
||
|
* {@link android.text.Layout#DIR_LEFT_TO_RIGHT} or
|
||
|
* {@link android.text.Layout#DIR_RIGHT_TO_LEFT}.
|
||
|
* @param x The x-coordinate of origin for where to draw the text
|
||
|
* @param top The top side of the rectangle to be drawn
|
||
|
* @param y The y-coordinate of origin for where to draw the text
|
||
|
* @param bottom The bottom side of the rectangle to be drawn
|
||
|
* @param paint The main {@link TextPaint} object.
|
||
|
* @param workPaint The {@link TextPaint} object used for temporal
|
||
|
* workspace.
|
||
|
* @param needWidth If true, this method returns the width of drawn text
|
||
|
* @return Width of the drawn text if needWidth is true
|
||
|
*/
|
||
|
public static float 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) {
|
||
|
// For safety.
|
||
|
direction = direction >= 0 ? Layout.DIR_LEFT_TO_RIGHT
|
||
|
: Layout.DIR_RIGHT_TO_LEFT;
|
||
|
|
||
|
// Hide runIsRtl parameter since it is meaningless for external
|
||
|
// developers.
|
||
|
// XXX: the runIsRtl probably ought to be the same as direction, then
|
||
|
// this could draw rtl text.
|
||
|
return drawText(canvas, text, start, end, direction, false,
|
||
|
x, top, y, bottom, paint, workPaint, needWidth);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the width of a run of left-to-right text on a single line,
|
||
|
* considering style information in the text (e.g. even when text is an
|
||
|
* instance of {@link android.text.Spanned}, this method correctly measures
|
||
|
* the width of the text).
|
||
|
*
|
||
|
* @param paint the main {@link TextPaint} object; will not be modified
|
||
|
* @param workPaint the {@link TextPaint} object available for modification;
|
||
|
* will not necessarily be used
|
||
|
* @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
|
||
|
* @param fmi FontMetrics information; can be null
|
||
|
* @return The width of the text
|
||
|
*/
|
||
|
public static float measureText(TextPaint paint,
|
||
|
TextPaint workPaint,
|
||
|
CharSequence text, int start, int end,
|
||
|
Paint.FontMetricsInt fmi) {
|
||
|
return drawDirectionalRun(null, text, start, end,
|
||
|
Layout.DIR_LEFT_TO_RIGHT, false,
|
||
|
0, 0, 0, 0, fmi, paint, workPaint, true);
|
||
|
}
|
||
|
}
|