• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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 
17 package android.graphics;
18 
19 import java.awt.Font;
20 import java.awt.Graphics2D;
21 import java.awt.font.FontRenderContext;
22 import java.awt.font.GlyphVector;
23 import java.awt.geom.Rectangle2D;
24 import java.util.LinkedList;
25 import java.util.List;
26 
27 import com.ibm.icu.lang.UScript;
28 import com.ibm.icu.lang.UScriptRun;
29 
30 import android.graphics.Paint_Delegate.FontInfo;
31 
32 /**
33  * Render the text by breaking it into various scripts and using the right font for each script.
34  * Can be used to measure the text without actually drawing it.
35  */
36 @SuppressWarnings("deprecation")
37 public class BidiRenderer {
38 
39     /* package */ static class ScriptRun {
40         int start;
41         int limit;
42         boolean isRtl;
43         int scriptCode;
44         FontInfo font;
45 
ScriptRun(int start, int limit, boolean isRtl)46         public ScriptRun(int start, int limit, boolean isRtl) {
47             this.start = start;
48             this.limit = limit;
49             this.isRtl = isRtl;
50             this.scriptCode = UScript.INVALID_CODE;
51         }
52     }
53 
54     private Graphics2D mGraphics;
55     private Paint_Delegate mPaint;
56     private char[] mText;
57     // Bounds of the text drawn so far.
58     private RectF mBounds;
59     private float mBaseline;
60 
61     /**
62      * @param graphics May be null.
63      * @param paint The Paint to use to get the fonts. Should not be null.
64      * @param text Unidirectional text. Should not be null.
65      */
BidiRenderer(Graphics2D graphics, Paint_Delegate paint, char[] text)66     /* package */ BidiRenderer(Graphics2D graphics, Paint_Delegate paint, char[] text) {
67         assert (paint != null);
68         mGraphics = graphics;
69         mPaint = paint;
70         mText = text;
71     }
72 
73     /**
74      * Render unidirectional text.
75      *
76      * This method can also be used to measure the width of the text without actually drawing it.
77      *
78      * @param start index of the first character
79      * @param limit index of the first character that should not be rendered.
80      * @param isRtl is the text right-to-left
81      * @param advances If not null, then advances for each character to be rendered are returned
82      *            here.
83      * @param advancesIndex index into advances from where the advances need to be filled.
84      * @param draw If true and {@code graphics} is not null, draw the rendered text on the graphics
85      *            at the given co-ordinates
86      * @param x The x-coordinate of the left edge of where the text should be drawn on the given
87      *            graphics.
88      * @param y The y-coordinate at which to draw the text on the given mGraphics.
89      * @return A rectangle specifying the bounds of the text drawn.
90      */
renderText(int start, int limit, boolean isRtl, float[] advances, int advancesIndex, boolean draw, float x, float y)91     /* package */ RectF renderText(int start, int limit, boolean isRtl, float[] advances,
92             int advancesIndex, boolean draw, float x, float y) {
93         // We break the text into scripts and then select font based on it and then render each of
94         // the script runs.
95         mBounds = new RectF(x, y, x, y);
96         mBaseline = y;
97         for (ScriptRun run : getScriptRuns(mText, start, limit, isRtl, mPaint.getFonts())) {
98             int flag = Font.LAYOUT_NO_LIMIT_CONTEXT | Font.LAYOUT_NO_START_CONTEXT;
99             flag |= isRtl ? Font.LAYOUT_RIGHT_TO_LEFT : Font.LAYOUT_LEFT_TO_RIGHT;
100             renderScript(run.start, run.limit, run.font, flag, advances, advancesIndex, draw);
101             advancesIndex += run.limit - run.start;
102         }
103         return mBounds;
104     }
105 
106     /**
107      * Render a script run to the right of the bounds passed. Use the preferred font to render as
108      * much as possible. This also implements a fallback mechanism to render characters that cannot
109      * be drawn using the preferred font.
110      */
renderScript(int start, int limit, FontInfo preferredFont, int flag, float[] advances, int advancesIndex, boolean draw)111     private void renderScript(int start, int limit, FontInfo preferredFont, int flag,
112             float[] advances, int advancesIndex, boolean draw) {
113         List<FontInfo> fonts = mPaint.getFonts();
114         if (fonts == null || preferredFont == null) {
115             return;
116         }
117 
118         while (start < limit) {
119             boolean foundFont = false;
120             int canDisplayUpTo = preferredFont.mFont.canDisplayUpTo(mText, start, limit);
121             if (canDisplayUpTo == -1) {
122                 // We can draw all characters in the text.
123                 render(start, limit, preferredFont, flag, advances, advancesIndex, draw);
124                 return;
125             }
126             if (canDisplayUpTo > start) {
127                 // We can draw something.
128                 render(start, canDisplayUpTo, preferredFont, flag, advances, advancesIndex, draw);
129                 advancesIndex += canDisplayUpTo - start;
130                 start = canDisplayUpTo;
131             }
132 
133             // The current character cannot be drawn with the preferred font. Cycle through all the
134             // fonts to check which one can draw it.
135             int charCount = Character.isHighSurrogate(mText[start]) ? 2 : 1;
136             for (FontInfo font : fonts) {
137                 canDisplayUpTo = font.mFont.canDisplayUpTo(mText, start, start + charCount);
138                 if (canDisplayUpTo == -1) {
139                     render(start, start+charCount, font, flag, advances, advancesIndex, draw);
140                     start += charCount;
141                     advancesIndex += charCount;
142                     foundFont = true;
143                     break;
144                 }
145             }
146             if (!foundFont) {
147                 // No font can display this char. Use the preferred font. The char will most
148                 // probably appear as a box or a blank space. We could, probably, use some
149                 // heuristics and break the character into the base character and diacritics and
150                 // then draw it, but it's probably not worth the effort.
151                 render(start, start + charCount, preferredFont, flag, advances, advancesIndex,
152                         draw);
153                 start += charCount;
154                 advancesIndex += charCount;
155             }
156         }
157     }
158 
159     /**
160      * Renders the text to the right of the bounds with the given font.
161      * @param font The font to render the text with.
162      */
render(int start, int limit, FontInfo font, int flag, float[] advances, int advancesIndex, boolean draw)163     private void render(int start, int limit, FontInfo font, int flag, float[] advances,
164             int advancesIndex, boolean draw) {
165 
166         // Since the metrics don't have anti-aliasing set, we create a new FontRenderContext with
167         // the anti-aliasing set.
168         FontRenderContext f = font.mMetrics.getFontRenderContext();
169         FontRenderContext frc = new FontRenderContext(f.getTransform(), mPaint.isAntiAliased(),
170                 f.usesFractionalMetrics());
171         GlyphVector gv = font.mFont.layoutGlyphVector(frc, mText, start, limit, flag);
172         int ng = gv.getNumGlyphs();
173         int[] ci = gv.getGlyphCharIndices(0, ng, null);
174         if (advances != null) {
175             for (int i = 0; i < ng; i++) {
176                 int adv_idx = advancesIndex + ci[i];
177                 advances[adv_idx] += gv.getGlyphMetrics(i).getAdvanceX();
178             }
179         }
180         if (draw && mGraphics != null) {
181             mGraphics.drawGlyphVector(gv, mBounds.right, mBaseline);
182         }
183 
184         // Update the bounds.
185         Rectangle2D awtBounds = gv.getLogicalBounds();
186         RectF bounds = awtRectToAndroidRect(awtBounds, mBounds.right, mBaseline);
187         // If the width of the bounds is zero, no text had been drawn earlier. Hence, use the
188         // coordinates from the bounds as an offset.
189         if (Math.abs(mBounds.right - mBounds.left) == 0) {
190             mBounds = bounds;
191         } else {
192             mBounds.union(bounds);
193         }
194     }
195 
196     // --- Static helper methods ---
197 
awtRectToAndroidRect(Rectangle2D awtRec, float offsetX, float offsetY)198     private static RectF awtRectToAndroidRect(Rectangle2D awtRec, float offsetX, float offsetY) {
199         float left = (float) awtRec.getX();
200         float top = (float) awtRec.getY();
201         float right = (float) (left + awtRec.getWidth());
202         float bottom = (float) (top + awtRec.getHeight());
203         RectF androidRect = new RectF(left, top, right, bottom);
204         androidRect.offset(offsetX, offsetY);
205         return androidRect;
206     }
207 
getScriptRuns(char[] text, int start, int limit, boolean isRtl, List<FontInfo> fonts)208     /* package */  static List<ScriptRun> getScriptRuns(char[] text, int start, int limit,
209             boolean isRtl, List<FontInfo> fonts) {
210         LinkedList<ScriptRun> scriptRuns = new LinkedList<ScriptRun>();
211 
212         int count = limit - start;
213         UScriptRun uScriptRun = new UScriptRun(text, start, count);
214         while (uScriptRun.next()) {
215             int scriptStart = uScriptRun.getScriptStart();
216             int scriptLimit = uScriptRun.getScriptLimit();
217             ScriptRun run = new ScriptRun(scriptStart, scriptLimit, isRtl);
218             run.scriptCode = uScriptRun.getScriptCode();
219             setScriptFont(text, run, fonts);
220             scriptRuns.add(run);
221         }
222 
223         return scriptRuns;
224     }
225 
226     // TODO: Replace this method with one which returns the font based on the scriptCode.
setScriptFont(char[] text, ScriptRun run, List<FontInfo> fonts)227     private static void setScriptFont(char[] text, ScriptRun run,
228             List<FontInfo> fonts) {
229         for (FontInfo fontInfo : fonts) {
230             if (fontInfo.mFont.canDisplayUpTo(text, run.start, run.limit) == -1) {
231                 run.font = fontInfo;
232                 return;
233             }
234         }
235         run.font = fonts.get(0);
236     }
237 }
238