summaryrefslogtreecommitdiffstats
path: root/tools/layoutlib/bridge/src/android/graphics
diff options
context:
space:
mode:
authorDeepanshu Gupta <deepanshu@google.com>2013-09-05 11:58:59 -0700
committerAndroid Git Automerger <android-git-automerger@android.com>2013-09-05 11:58:59 -0700
commitb50de4951aaea6d6f01432dc17b7c5de778a2e71 (patch)
tree708d2a4e9b47d90d4a64f73bad568ebf78e7fdd3 /tools/layoutlib/bridge/src/android/graphics
parent97c88368e0036665507c8cd84c71d26953784c85 (diff)
parentdd4efc22c736973ecfa1b7b4af9e71f971dd48b3 (diff)
downloadframeworks_base-b50de4951aaea6d6f01432dc17b7c5de778a2e71.zip
frameworks_base-b50de4951aaea6d6f01432dc17b7c5de778a2e71.tar.gz
frameworks_base-b50de4951aaea6d6f01432dc17b7c5de778a2e71.tar.bz2
am dd4efc22: am 3a762d8b: Merge "Fix text rendering" into jb-mr2-dev
* commit 'dd4efc22c736973ecfa1b7b4af9e71f971dd48b3': Fix text rendering
Diffstat (limited to 'tools/layoutlib/bridge/src/android/graphics')
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java215
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java89
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java136
3 files changed, 253 insertions, 187 deletions
diff --git a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java
new file mode 100644
index 0000000..62d0a0d
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2013 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 java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.font.FontRenderContext;
+import java.awt.font.GlyphVector;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.ibm.icu.lang.UScript;
+import com.ibm.icu.lang.UScriptRun;
+
+import android.graphics.Paint_Delegate.FontInfo;
+
+/**
+ * Render the text by breaking it into various scripts and using the right font for each script.
+ * Can be used to measure the text without actually drawing it.
+ */
+@SuppressWarnings("deprecation")
+public class BidiRenderer {
+
+ /* package */ static class ScriptRun {
+ int start;
+ int limit;
+ boolean isRtl;
+ int scriptCode;
+ FontInfo font;
+
+ public ScriptRun(int start, int limit, boolean isRtl) {
+ this.start = start;
+ this.limit = limit;
+ this.isRtl = isRtl;
+ this.scriptCode = UScript.INVALID_CODE;
+ }
+ }
+
+ /* package */ Graphics2D graphics;
+ /* package */ Paint_Delegate paint;
+ /* package */ char[] text;
+
+ /**
+ * @param graphics May be null.
+ * @param paint The Paint to use to get the fonts. Should not be null.
+ * @param text Unidirectional text. Should not be null.
+ */
+ /* package */ BidiRenderer(Graphics2D graphics, Paint_Delegate paint, char[] text) {
+ assert (paint != null);
+ this.graphics = graphics;
+ this.paint = paint;
+ this.text = text;
+ }
+
+ /**
+ * Render unidirectional text.
+ *
+ * This method can also be used to measure the width of the text without actually drawing it.
+ *
+ * @param start index of the first character
+ * @param limit index of the first character that should not be rendered.
+ * @param isRtl is the text right-to-left
+ * @param advances If not null, then advances for each character to be rendered are returned
+ * here.
+ * @param advancesIndex index into advances from where the advances need to be filled.
+ * @param draw If true and {@link graphics} is not null, draw the rendered text on the graphics
+ * at the given co-ordinates
+ * @param x The x-coordinate of the left edge of where the text should be drawn on the given
+ * graphics.
+ * @param y The y-coordinate at which to draw the text on the given graphics.
+ * @return The x-coordinate of the right edge of the drawn text. In other words,
+ * x + the width of the text.
+ */
+ /* package */ float renderText(int start, int limit, boolean isRtl, float advances[],
+ int advancesIndex, boolean draw, float x, float y) {
+ // We break the text into scripts and then select font based on it and then render each of
+ // the script runs.
+ for (ScriptRun run : getScriptRuns(text, start, limit, isRtl, paint.getFonts())) {
+ int flag = Font.LAYOUT_NO_LIMIT_CONTEXT | Font.LAYOUT_NO_START_CONTEXT;
+ flag |= isRtl ? Font.LAYOUT_RIGHT_TO_LEFT : Font.LAYOUT_LEFT_TO_RIGHT;
+ x = renderScript(run.start, run.limit, run.font, flag, advances, advancesIndex, draw,
+ x, y);
+ advancesIndex += run.limit - run.start;
+ }
+ return x;
+ }
+
+ /**
+ * Render a script run. Use the preferred font to render as much as possible. This also
+ * implements a fallback mechanism to render characters that cannot be drawn using the
+ * preferred font.
+ *
+ * @return x + width of the text drawn.
+ */
+ private float renderScript(int start, int limit, FontInfo preferredFont, int flag,
+ float advances[], int advancesIndex, boolean draw, float x, float y) {
+ List<FontInfo> fonts = paint.getFonts();
+ if (fonts == null || preferredFont == null) {
+ return x;
+ }
+
+ while (start < limit) {
+ boolean foundFont = false;
+ int canDisplayUpTo = preferredFont.mFont.canDisplayUpTo(text, start, limit);
+ if (canDisplayUpTo == -1) {
+ return render(start, limit, preferredFont, flag, advances, advancesIndex, draw,
+ x, y);
+ } else if (canDisplayUpTo > start) { // can draw something
+ x = render(start, canDisplayUpTo, preferredFont, flag, advances, advancesIndex,
+ draw, x, y);
+ advancesIndex += canDisplayUpTo - start;
+ start = canDisplayUpTo;
+ }
+
+ int charCount = Character.isHighSurrogate(text[start]) ? 2 : 1;
+ for (FontInfo font : fonts) {
+ canDisplayUpTo = font.mFont.canDisplayUpTo(text, start, start + charCount);
+ if (canDisplayUpTo == -1) {
+ x = render(start, start+charCount, font, flag, advances, advancesIndex, draw,
+ x, y);
+ start += charCount;
+ advancesIndex += charCount;
+ foundFont = true;
+ break;
+ }
+ }
+ if (!foundFont) {
+ // No font can display this char. Use the preferred font. The char will most
+ // probably appear as a box or a blank space. We could, probably, use some
+ // heuristics and break the character into the base character and diacritics and
+ // then draw it, but it's probably not worth the effort.
+ x = render(start, start + charCount, preferredFont, flag, advances, advancesIndex,
+ draw, x, y);
+ start += charCount;
+ advancesIndex += charCount;
+ }
+ }
+ return x;
+ }
+
+ /**
+ * Render the text with the given font.
+ */
+ private float render(int start, int limit, FontInfo font, int flag, float advances[],
+ int advancesIndex, boolean draw, float x, float y) {
+
+ float totalAdvance = 0;
+ // Since the metrics don't have anti-aliasing set, we create a new FontRenderContext with
+ // the anti-aliasing set.
+ FontRenderContext f = font.mMetrics.getFontRenderContext();
+ FontRenderContext frc = new FontRenderContext(f.getTransform(), paint.isAntiAliased(),
+ f.usesFractionalMetrics());
+ GlyphVector gv = font.mFont.layoutGlyphVector(frc, text, start, limit, flag);
+ int ng = gv.getNumGlyphs();
+ int[] ci = gv.getGlyphCharIndices(0, ng, null);
+ for (int i = 0; i < ng; i++) {
+ float adv = gv.getGlyphMetrics(i).getAdvanceX();
+ if (advances != null) {
+ int adv_idx = advancesIndex + ci[i];
+ advances[adv_idx] += adv;
+ }
+ totalAdvance += adv;
+ }
+ if (draw && graphics != null) {
+ graphics.drawGlyphVector(gv, x, y);
+ }
+ return x + totalAdvance;
+ }
+
+ // --- Static helper methods ---
+
+ /* package */ static List<ScriptRun> getScriptRuns(char[] text, int start, int limit,
+ boolean isRtl, List<FontInfo> fonts) {
+ LinkedList<ScriptRun> scriptRuns = new LinkedList<ScriptRun>();
+
+ int count = limit - start;
+ UScriptRun uScriptRun = new UScriptRun(text, start, count);
+ while (uScriptRun.next()) {
+ int scriptStart = uScriptRun.getScriptStart();
+ int scriptLimit = uScriptRun.getScriptLimit();
+ ScriptRun run = new ScriptRun(scriptStart, scriptLimit, isRtl);
+ run.scriptCode = uScriptRun.getScriptCode();
+ setScriptFont(text, run, fonts);
+ scriptRuns.add(run);
+ }
+
+ return scriptRuns;
+ }
+
+ // TODO: Replace this method with one which returns the font based on the scriptCode.
+ private static void setScriptFont(char[] text, ScriptRun run,
+ List<FontInfo> fonts) {
+ for (FontInfo fontInfo : fonts) {
+ if (fontInfo.mFont.canDisplayUpTo(text, run.start, run.limit) == -1) {
+ run.font = fontInfo;
+ return;
+ }
+ }
+ run.font = fonts.get(0);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index 4171bb5..da18864 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -23,7 +23,6 @@ import com.android.layoutlib.bridge.impl.GcSnapshot;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import android.graphics.Bitmap.Config;
-import android.graphics.Paint_Delegate.FontInfo;
import android.text.TextUtils;
import java.awt.Color;
@@ -35,7 +34,6 @@ import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.image.BufferedImage;
-import java.util.List;
/**
@@ -978,7 +976,8 @@ public final class Canvas_Delegate {
@LayoutlibDelegate
/*package*/ static void native_drawText(int nativeCanvas,
final char[] text, final int index, final int count,
- final float startX, final float startY, int flags, int paint) {
+ final float startX, final float startY, final int flags, int paint) {
+
draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
new GcSnapshot.Drawable() {
@Override
@@ -988,10 +987,10 @@ public final class Canvas_Delegate {
// Paint.TextAlign indicates how the text is positioned relative to X.
// LEFT is the default and there's nothing to do.
float x = startX;
- float y = startY;
+ int limit = index + count;
+ boolean isRtl = flags == Canvas.DIRECTION_RTL;
if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) {
- // TODO: check the value of bidiFlags.
- float m = paintDelegate.measureText(text, index, count, 0);
+ float m = paintDelegate.measureText(text, index, count, isRtl);
if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) {
x -= m / 2;
} else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) {
@@ -999,87 +998,15 @@ public final class Canvas_Delegate {
}
}
- List<FontInfo> fonts = paintDelegate.getFonts();
-
- if (fonts.size() > 0) {
- FontInfo mainFont = fonts.get(0);
- int i = index;
- int lastIndex = index + count;
- while (i < lastIndex) {
- // always start with the main font.
- int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex);
- if (upTo == -1) {
- // draw all the rest and exit.
- graphics.setFont(mainFont.mFont);
- graphics.drawChars(text, i, lastIndex - i, (int)x, (int)y);
- return;
- } else if (upTo > 0) {
- // draw what's possible
- graphics.setFont(mainFont.mFont);
- graphics.drawChars(text, i, upTo - i, (int)x, (int)y);
-
- // compute the width that was drawn to increase x
- x += mainFont.mMetrics.charsWidth(text, i, upTo - i);
-
- // move index to the first non displayed char.
- 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 < fonts.size() ; f++) {
- FontInfo fontInfo = fonts.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) {
- // draw that char
- graphics.setFont(fontInfo.mFont);
- graphics.drawChars(text, i, charCount, (int)x, (int)y);
-
- // update x
- x += fontInfo.mMetrics.charsWidth(text, i, charCount);
-
- // update the index in the text, and move on
- i += charCount;
- foundFont = true;
- break;
-
- }
- }
-
- // in case no font can display the char, display it with the main font.
- // (it'll put a square probably)
- if (foundFont == false) {
- int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1;
-
- graphics.setFont(mainFont.mFont);
- graphics.drawChars(text, i, charCount, (int)x, (int)y);
-
- // measure it to advance x
- x += mainFont.mMetrics.charsWidth(text, i, charCount);
-
- // and move to the next chars.
- i += charCount;
- }
- }
- }
+ new BidiRenderer(graphics, paintDelegate, text).renderText(
+ index, limit, isRtl, null, 0, true, x, startY);
}
});
}
@LayoutlibDelegate
/*package*/ static void native_drawText(int nativeCanvas, String text,
- int start, int end, float x, float y, int flags, int paint) {
+ int start, int end, float x, float y, final int flags, int paint) {
int count = end - start;
char[] buffer = TemporaryBuffer.obtain(count);
TextUtils.getChars(text, start, end, buffer, 0);
diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
index c9c9800..41953ed 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
@@ -32,7 +32,6 @@ import java.awt.Stroke;
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;
@@ -576,7 +575,7 @@ public class Paint_Delegate {
return 0;
}
- return delegate.measureText(text, index, count, bidiFlags);
+ return delegate.measureText(text, index, count, isRtl(bidiFlags));
}
@LayoutlibDelegate
@@ -615,7 +614,7 @@ public class Paint_Delegate {
}
// measure from start to end
- float res = delegate.measureText(text, start, end - start + 1, bidiFlags);
+ float res = delegate.measureText(text, start, end - start + 1, isRtl(bidiFlags));
if (measuredWidth != null) {
measuredWidth[measureIndex] = res;
@@ -980,51 +979,27 @@ public class Paint_Delegate {
/*package*/ static float native_getTextRunAdvances(int native_object,
char[] text, int index, int count, int contextIndex, int contextCount,
int flags, float[] advances, int advancesIndex) {
+
+ if (advances != null)
+ for (int i = advancesIndex; i< advancesIndex+count; i++)
+ advances[i]=0;
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
- if (delegate == null) {
+ if (delegate == null || delegate.mFonts == null || delegate.mFonts.size() == 0) {
return 0.f;
}
+ boolean isRtl = isRtl(flags);
- if (delegate.mFonts.size() > 0) {
- // FIXME: handle multi-char characters (see measureText)
- float totalAdvance = 0;
- for (int i = 0; i < count; i++) {
- char c = text[i + index];
- boolean found = false;
- for (FontInfo info : delegate.mFonts) {
- if (info.mFont.canDisplay(c)) {
- float adv = info.mMetrics.charWidth(c);
- totalAdvance += adv;
- if (advances != null) {
- advances[i] = adv;
- }
-
- found = true;
- break;
- }
- }
-
- if (found == false) {
- // no advance for this char.
- if (advances != null) {
- advances[i] = 0.f;
- }
- }
- }
-
- return totalAdvance;
- }
-
- return 0;
-
+ int limit = index + count;
+ return new BidiRenderer(null, delegate, text).renderText(
+ index, limit, isRtl, advances, advancesIndex, false, 0, 0);
}
@LayoutlibDelegate
/*package*/ static float native_getTextRunAdvances(int native_object,
String text, int start, int end, int contextStart, int contextEnd,
int flags, float[] advances, int advancesIndex) {
- // FIXME: support contextStart, contextEnd and direction flag
+ // FIXME: support contextStart and contextEnd
int count = end - start;
char[] buffer = TemporaryBuffer.obtain(count);
TextUtils.getChars(text, start, end, buffer, 0);
@@ -1080,19 +1055,12 @@ public class Paint_Delegate {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
+ if (delegate == null || delegate.mFonts == null || delegate.mFonts.size() == 0) {
return;
}
-
- // FIXME should test if the main font can display all those characters.
- // See MeasureText
- if (delegate.mFonts.size() > 0) {
- FontInfo mainInfo = delegate.mFonts.get(0);
-
- Rectangle2D rect = mainInfo.mFont.getStringBounds(text, index, index + count,
- delegate.mFontContext);
- bounds.set(0, 0, (int) rect.getWidth(), (int) rect.getHeight());
- }
+ int w = (int) delegate.measureText(text, index, count, isRtl(bidiFlags));
+ int h= delegate.getFonts().get(0).mMetrics.getHeight();
+ bounds.set(0, 0, w, h);
}
@LayoutlibDelegate
@@ -1176,6 +1144,7 @@ public class Paint_Delegate {
info.mFont = info.mFont.deriveFont(new AffineTransform(
mTextScaleX, mTextSkewX, 0, 1, 0, 0));
}
+ // The metrics here don't have anti-aliasing set.
info.mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(info.mFont);
infoList.add(info);
@@ -1185,64 +1154,9 @@ public class Paint_Delegate {
}
}
- /*package*/ float measureText(char[] text, int index, int count, int bidiFlags) {
- // TODO: find out what bidiFlags actually does.
-
- // WARNING: the logic in this method is similar to Canvas_Delegate.native_drawText
- // Any change to this method should be reflected there as well
-
- 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 total;
- }
-
- return 0;
+ /*package*/ float measureText(char[] text, int index, int count, boolean isRtl) {
+ return new BidiRenderer(null, this, text).renderText(
+ index, index + count, isRtl, null, 0, false, 0, 0);
}
private float getFontMetrics(FontMetrics metrics) {
@@ -1281,4 +1195,14 @@ public class Paint_Delegate {
}
}
+ private static boolean isRtl(int flag) {
+ switch(flag) {
+ case Paint.BIDI_RTL:
+ case Paint.BIDI_FORCE_RTL:
+ case Paint.BIDI_DEFAULT_RTL:
+ return true;
+ default:
+ return false;
+ }
+ }
}