From eb1fae940df0117deb0dc891f9f9bfeca35b1ac1 Mon Sep 17 00:00:00 2001 From: Deepanshu Gupta Date: Tue, 7 Jan 2014 11:58:44 -0800 Subject: [PATCH 1/2] Fix the measurement of text bounds. [DO NOT MERGE] Bug: 12366230 Change-Id: I203b678363dc7b688c744503ee10216baca658a9 (cherry-picked from 5ad7c183f39df43562c69aba21ea422ad69bdae0) --- .../src/android/graphics/BidiRenderer.java | 83 +++++++++++-------- .../src/android/graphics/Canvas_Delegate.java | 3 +- .../src/android/graphics/Paint_Delegate.java | 15 ++-- 3 files changed, 60 insertions(+), 41 deletions(-) diff --git a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java index 62d0a0d6ae9df..e1b3f92f6867d 100644 --- a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java +++ b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java @@ -20,6 +20,7 @@ import java.awt.Font; import java.awt.Graphics2D; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; +import java.awt.geom.Rectangle2D; import java.util.LinkedList; import java.util.List; @@ -27,6 +28,7 @@ import com.ibm.icu.lang.UScript; import com.ibm.icu.lang.UScriptRun; import android.graphics.Paint_Delegate.FontInfo; +import android.graphics.RectF;; /** * Render the text by breaking it into various scripts and using the right font for each script. @@ -50,9 +52,11 @@ public class BidiRenderer { } } - /* package */ Graphics2D graphics; - /* package */ Paint_Delegate paint; - /* package */ char[] text; + private Graphics2D graphics; + private Paint_Delegate paint; + private char[] text; + // Bounds of the text drawn so far. + private RectF bounds; /** * @param graphics May be null. @@ -82,56 +86,54 @@ public class BidiRenderer { * @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. + * @return A rectangle specifying the bounds of the text drawn. */ - /* package */ float renderText(int start, int limit, boolean isRtl, float advances[], + /* package */ RectF 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. + bounds = new RectF(x, y, x, y); 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); + renderScript(run.start, run.limit, run.font, flag, advances, advancesIndex, draw); advancesIndex += run.limit - run.start; } - return x; + return bounds; } /** - * 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. + * Render a script run to the right of the bounds passed. 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. */ - private float renderScript(int start, int limit, FontInfo preferredFont, int flag, - float advances[], int advancesIndex, boolean draw, float x, float y) { + private void renderScript(int start, int limit, FontInfo preferredFont, int flag, + float advances[], int advancesIndex, boolean draw) { List fonts = paint.getFonts(); if (fonts == null || preferredFont == null) { - return x; + return; } 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); + // We can draw all characters in the text. + render(start, limit, preferredFont, flag, advances, advancesIndex, draw); + return; } else if (canDisplayUpTo > start) { // can draw something - x = render(start, canDisplayUpTo, preferredFont, flag, advances, advancesIndex, - draw, x, y); + render(start, canDisplayUpTo, preferredFont, flag, advances, advancesIndex, draw); advancesIndex += canDisplayUpTo - start; start = canDisplayUpTo; } + // The current character cannot be drawn with the preferred font. Cycle through all the + // fonts to check which one can draw it. 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); + render(start, start+charCount, font, flag, advances, advancesIndex, draw); start += charCount; advancesIndex += charCount; foundFont = true; @@ -143,22 +145,20 @@ public class BidiRenderer { // 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); + render(start, start + charCount, preferredFont, flag, advances, advancesIndex, + draw); start += charCount; advancesIndex += charCount; } } - return x; } /** - * Render the text with the given font. + * Render the text with the given font to the right of the bounds passed. */ - private float render(int start, int limit, FontInfo font, int flag, float advances[], - int advancesIndex, boolean draw, float x, float y) { + private void render(int start, int limit, FontInfo font, int flag, float advances[], + int advancesIndex, boolean draw) { - 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(); @@ -173,12 +173,29 @@ public class BidiRenderer { int adv_idx = advancesIndex + ci[i]; advances[adv_idx] += adv; } - totalAdvance += adv; } if (draw && graphics != null) { - graphics.drawGlyphVector(gv, x, y); + graphics.drawGlyphVector(gv, bounds.right, bounds.bottom); } - return x + totalAdvance; + Rectangle2D awtBounds = gv.getVisualBounds(); + RectF visualBounds = awtRectToAndroidRect(awtBounds, bounds.right, bounds.bottom); + // If the width of the bounds is zero, no text has been drawn yet. Hence, use the + // coordinates from the bounds as an offset only. + if (Math.abs(bounds.right - bounds.left) == 0) { + bounds = visualBounds; + } else { + bounds.union(visualBounds); + } + } + + private RectF awtRectToAndroidRect(Rectangle2D awtRec, float offsetX, float offsetY) { + float left = (float) awtRec.getX(); + float top = (float) awtRec.getY(); + float right = (float) (left + awtRec.getWidth()); + float bottom = (float) (top + awtRec.getHeight()); + RectF androidRect = new RectF(left, top, right, bottom); + androidRect.offset(offsetX, offsetY); + return androidRect; } // --- Static helper methods --- diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java index da188644239dc..d1d0f09c3ed07 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java @@ -990,7 +990,8 @@ public final class Canvas_Delegate { int limit = index + count; boolean isRtl = flags == Canvas.DIRECTION_RTL; if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) { - float m = paintDelegate.measureText(text, index, count, isRtl); + RectF bounds = paintDelegate.measureText(text, index, count, isRtl); + float m = bounds.right - bounds.left; if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) { x -= m / 2; } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) { diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java index 41953ed280112..4ad1a175853bd 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java @@ -575,7 +575,8 @@ public class Paint_Delegate { return 0; } - return delegate.measureText(text, index, count, isRtl(bidiFlags)); + RectF bounds = delegate.measureText(text, index, count, isRtl(bidiFlags)); + return bounds.right - bounds.left; } @LayoutlibDelegate @@ -614,7 +615,8 @@ public class Paint_Delegate { } // measure from start to end - float res = delegate.measureText(text, start, end - start + 1, isRtl(bidiFlags)); + RectF bounds = delegate.measureText(text, start, end - start + 1, isRtl(bidiFlags)); + float res = bounds.right - bounds.left; if (measuredWidth != null) { measuredWidth[measureIndex] = res; @@ -991,8 +993,9 @@ public class Paint_Delegate { boolean isRtl = isRtl(flags); int limit = index + count; - return new BidiRenderer(null, delegate, text).renderText( + RectF bounds = new BidiRenderer(null, delegate, text).renderText( index, limit, isRtl, advances, advancesIndex, false, 0, 0); + return bounds.right - bounds.left; } @LayoutlibDelegate @@ -1058,9 +1061,7 @@ public class Paint_Delegate { if (delegate == null || delegate.mFonts == null || delegate.mFonts.size() == 0) { return; } - int w = (int) delegate.measureText(text, index, count, isRtl(bidiFlags)); - int h= delegate.getFonts().get(0).mMetrics.getHeight(); - bounds.set(0, 0, w, h); + delegate.measureText(text, index, count, isRtl(bidiFlags)).roundOut(bounds); } @LayoutlibDelegate @@ -1154,7 +1155,7 @@ public class Paint_Delegate { } } - /*package*/ float measureText(char[] text, int index, int count, boolean isRtl) { + /*package*/ RectF 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); } From 88301948c92fe1261cfa19383d4aaa5d5d0195ac Mon Sep 17 00:00:00 2001 From: Deepanshu Gupta Date: Tue, 7 Jan 2014 11:58:44 -0800 Subject: [PATCH 2/2] Improve text rendering and measurement. [DO NOT MERGE] 1. Fix a bug where baseline of the run was modified while rendering resulting in crooked text in some cases. 2. Use GlyphVector.getLogicalBounds() for text measurement which is more accurate than getVisualBounds(). 3. This change also optimizes text rendering by not computing the advances for individual glyphs when not needed. Change-Id: I66792c4d8f50eaf29afa70bccca1e6c812a3fa28 (cherry-picked from 45dbfcc781a3926d22571b6ccfa3f27ec896f119) --- .../src/android/graphics/BidiRenderer.java | 85 ++++++++++--------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java index e1b3f92f6867d..802cf1c70828b 100644 --- a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java +++ b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java @@ -28,7 +28,6 @@ import com.ibm.icu.lang.UScript; import com.ibm.icu.lang.UScriptRun; import android.graphics.Paint_Delegate.FontInfo; -import android.graphics.RectF;; /** * Render the text by breaking it into various scripts and using the right font for each script. @@ -52,11 +51,12 @@ public class BidiRenderer { } } - private Graphics2D graphics; - private Paint_Delegate paint; - private char[] text; + private Graphics2D mGraphics; + private Paint_Delegate mPaint; + private char[] mText; // Bounds of the text drawn so far. - private RectF bounds; + private RectF mBounds; + private float mBaseline; /** * @param graphics May be null. @@ -65,9 +65,9 @@ public class BidiRenderer { */ /* package */ BidiRenderer(Graphics2D graphics, Paint_Delegate paint, char[] text) { assert (paint != null); - this.graphics = graphics; - this.paint = paint; - this.text = text; + mGraphics = graphics; + mPaint = paint; + mText = text; } /** @@ -81,25 +81,26 @@ public class BidiRenderer { * @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 + * @param draw If true and {@code 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. + * @param y The y-coordinate at which to draw the text on the given mGraphics. * @return A rectangle specifying the bounds of the text drawn. */ - /* package */ RectF renderText(int start, int limit, boolean isRtl, float advances[], + /* package */ RectF 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. - bounds = new RectF(x, y, x, y); - for (ScriptRun run : getScriptRuns(text, start, limit, isRtl, paint.getFonts())) { + mBounds = new RectF(x, y, x, y); + mBaseline = y; + for (ScriptRun run : getScriptRuns(mText, start, limit, isRtl, mPaint.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; renderScript(run.start, run.limit, run.font, flag, advances, advancesIndex, draw); advancesIndex += run.limit - run.start; } - return bounds; + return mBounds; } /** @@ -108,20 +109,22 @@ public class BidiRenderer { * be drawn using the preferred font. */ private void renderScript(int start, int limit, FontInfo preferredFont, int flag, - float advances[], int advancesIndex, boolean draw) { - List fonts = paint.getFonts(); + float[] advances, int advancesIndex, boolean draw) { + List fonts = mPaint.getFonts(); if (fonts == null || preferredFont == null) { return; } while (start < limit) { boolean foundFont = false; - int canDisplayUpTo = preferredFont.mFont.canDisplayUpTo(text, start, limit); + int canDisplayUpTo = preferredFont.mFont.canDisplayUpTo(mText, start, limit); if (canDisplayUpTo == -1) { // We can draw all characters in the text. render(start, limit, preferredFont, flag, advances, advancesIndex, draw); return; - } else if (canDisplayUpTo > start) { // can draw something + } + if (canDisplayUpTo > start) { + // We can draw something. render(start, canDisplayUpTo, preferredFont, flag, advances, advancesIndex, draw); advancesIndex += canDisplayUpTo - start; start = canDisplayUpTo; @@ -129,9 +132,9 @@ public class BidiRenderer { // The current character cannot be drawn with the preferred font. Cycle through all the // fonts to check which one can draw it. - int charCount = Character.isHighSurrogate(text[start]) ? 2 : 1; + int charCount = Character.isHighSurrogate(mText[start]) ? 2 : 1; for (FontInfo font : fonts) { - canDisplayUpTo = font.mFont.canDisplayUpTo(text, start, start + charCount); + canDisplayUpTo = font.mFont.canDisplayUpTo(mText, start, start + charCount); if (canDisplayUpTo == -1) { render(start, start+charCount, font, flag, advances, advancesIndex, draw); start += charCount; @@ -154,41 +157,45 @@ public class BidiRenderer { } /** - * Render the text with the given font to the right of the bounds passed. + * Renders the text to the right of the bounds with the given font. + * @param font The font to render the text with. */ - private void render(int start, int limit, FontInfo font, int flag, float advances[], + private void render(int start, int limit, FontInfo font, int flag, float[] advances, int advancesIndex, boolean draw) { // 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(), + FontRenderContext frc = new FontRenderContext(f.getTransform(), mPaint.isAntiAliased(), f.usesFractionalMetrics()); - GlyphVector gv = font.mFont.layoutGlyphVector(frc, text, start, limit, flag); + GlyphVector gv = font.mFont.layoutGlyphVector(frc, mText, 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) { + if (advances != null) { + for (int i = 0; i < ng; i++) { int adv_idx = advancesIndex + ci[i]; - advances[adv_idx] += adv; + advances[adv_idx] += gv.getGlyphMetrics(i).getAdvanceX(); } } - if (draw && graphics != null) { - graphics.drawGlyphVector(gv, bounds.right, bounds.bottom); + if (draw && mGraphics != null) { + mGraphics.drawGlyphVector(gv, mBounds.right, mBaseline); } - Rectangle2D awtBounds = gv.getVisualBounds(); - RectF visualBounds = awtRectToAndroidRect(awtBounds, bounds.right, bounds.bottom); - // If the width of the bounds is zero, no text has been drawn yet. Hence, use the - // coordinates from the bounds as an offset only. - if (Math.abs(bounds.right - bounds.left) == 0) { - bounds = visualBounds; + + // Update the bounds. + Rectangle2D awtBounds = gv.getLogicalBounds(); + RectF bounds = awtRectToAndroidRect(awtBounds, mBounds.right, mBaseline); + // If the width of the bounds is zero, no text had been drawn earlier. Hence, use the + // coordinates from the bounds as an offset. + if (Math.abs(mBounds.right - mBounds.left) == 0) { + mBounds = bounds; } else { - bounds.union(visualBounds); + mBounds.union(bounds); } } - private RectF awtRectToAndroidRect(Rectangle2D awtRec, float offsetX, float offsetY) { + // --- Static helper methods --- + + private static RectF awtRectToAndroidRect(Rectangle2D awtRec, float offsetX, float offsetY) { float left = (float) awtRec.getX(); float top = (float) awtRec.getY(); float right = (float) (left + awtRec.getWidth()); @@ -198,8 +205,6 @@ public class BidiRenderer { return androidRect; } - // --- Static helper methods --- - /* package */ static List getScriptRuns(char[] text, int start, int limit, boolean isRtl, List fonts) { LinkedList scriptRuns = new LinkedList();