am aa10b9fb: am e6ab011b: Merge change Ib12bcb7f into eclair

Merge commit 'aa10b9fb52db88e3cc9045019f42fa83e9da9868' into eclair-mr2-plus-aosp

* commit 'aa10b9fb52db88e3cc9045019f42fa83e9da9868':
  Support for fallback fonts in layoutlib.
This commit is contained in:
Xavier Ducrohet
2009-11-09 17:00:33 -08:00
committed by Android Git Automerger
5 changed files with 337 additions and 119 deletions

View File

@@ -13,6 +13,10 @@
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<!--
This is only used by the layoutlib to display
layouts in ADT.
-->
<fonts> <fonts>
<font ttf="DroidSans"> <font ttf="DroidSans">
<name>sans-serif</name> <name>sans-serif</name>
@@ -39,5 +43,6 @@
<name>courier new</name> <name>courier new</name>
<name>monaco</name> <name>monaco</name>
</font> </font>
<font ttf="DroidSansFallback" /> <fallback ttf="DroidSansFallback" />
<fallback ttf="DroidSansJapanese" />
</fonts> </fonts>

View File

@@ -26,6 +26,7 @@ import android.graphics.RectF;
import android.graphics.Region; import android.graphics.Region;
import android.graphics.Xfermode; import android.graphics.Xfermode;
import android.graphics.Paint.Align; import android.graphics.Paint.Align;
import android.graphics.Paint.FontInfo;
import android.graphics.Paint.Style; import android.graphics.Paint.Style;
import android.graphics.Region.Op; import android.graphics.Region.Op;
@@ -37,6 +38,7 @@ import java.awt.Rectangle;
import java.awt.RenderingHints; import java.awt.RenderingHints;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.util.List;
import java.util.Stack; import java.util.Stack;
import javax.microedition.khronos.opengles.GL; import javax.microedition.khronos.opengles.GL;
@@ -620,19 +622,21 @@ public class Canvas extends _Original_Canvas {
*/ */
@Override @Override
public void drawText(char[] text, int index, int count, float x, float y, Paint paint) { public void drawText(char[] text, int index, int count, float x, float y, Paint paint) {
// WARNING: the logic in this method is similar to Paint.measureText.
// Any change to this method should be reflected in Paint.measureText
Graphics2D g = getGraphics2d(); Graphics2D g = getGraphics2d();
g = (Graphics2D)g.create(); g = (Graphics2D)g.create();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setFont(paint.getFont()); // set the color. because this only handles RGB, the alpha channel is handled
// as a composite.
// set the color. because this only handles RGB we have to handle the alpha separately
g.setColor(new Color(paint.getColor())); g.setColor(new Color(paint.getColor()));
int alpha = paint.getAlpha(); int alpha = paint.getAlpha();
float falpha = alpha / 255.f; float falpha = alpha / 255.f;
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha)); g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha));
// Paint.TextAlign indicates how the text is positioned relative to X. // Paint.TextAlign indicates how the text is positioned relative to X.
// LEFT is the default and there's nothing to do. // LEFT is the default and there's nothing to do.
if (paint.getTextAlign() != Align.LEFT) { if (paint.getTextAlign() != Align.LEFT) {
@@ -644,9 +648,83 @@ public class Canvas extends _Original_Canvas {
} }
} }
g.drawChars(text, index, count, (int)x, (int)y); List<FontInfo> fonts = paint.getFonts();
try {
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.
g.setFont(mainFont.mFont);
g.drawChars(text, i, lastIndex - i, (int)x, (int)y);
return;
} else if (upTo > 0) {
// draw what's possible
g.setFont(mainFont.mFont);
g.drawChars(text, i, upTo - i, (int)x, (int)y);
g.dispose(); // 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
g.setFont(fontInfo.mFont);
g.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;
g.setFont(mainFont.mFont);
g.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;
}
}
}
} finally {
g.dispose();
}
} }
/* (non-Javadoc) /* (non-Javadoc)

View File

@@ -26,6 +26,9 @@ import java.awt.Toolkit;
import java.awt.font.FontRenderContext; import java.awt.font.FontRenderContext;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** /**
* A paint implementation overridden by the LayoutLib bridge. * A paint implementation overridden by the LayoutLib bridge.
@@ -44,10 +47,17 @@ public class Paint extends _Original_Paint {
private Join mJoin = Join.MITER; private Join mJoin = Join.MITER;
private int mFlags = 0; private int mFlags = 0;
private Font mFont; /**
* Class associating a {@link Font} and it's {@link java.awt.FontMetrics}.
*/
public static final class FontInfo {
Font mFont;
java.awt.FontMetrics mMetrics;
}
private List<FontInfo> mFonts;
private final FontRenderContext mFontContext = new FontRenderContext( private final FontRenderContext mFontContext = new FontRenderContext(
new AffineTransform(), true, true); new AffineTransform(), true, true);
private java.awt.FontMetrics mMetrics;
@SuppressWarnings("hiding") @SuppressWarnings("hiding")
public static final int ANTI_ALIAS_FLAG = _Original_Paint.ANTI_ALIAS_FLAG; public static final int ANTI_ALIAS_FLAG = _Original_Paint.ANTI_ALIAS_FLAG;
@@ -201,10 +211,11 @@ public class Paint extends _Original_Paint {
} }
/** /**
* Returns the {@link Font} object. * Returns the list of {@link Font} objects. The first item is the main font, the rest
* are fall backs for characters not present in the main font.
*/ */
public Font getFont() { public List<FontInfo> getFonts() {
return mFont; return mFonts;
} }
private void initFont() { private void initFont() {
@@ -215,17 +226,29 @@ public class Paint extends _Original_Paint {
/** /**
* Update the {@link Font} object from the typeface, text size and scaling * Update the {@link Font} object from the typeface, text size and scaling
*/ */
@SuppressWarnings("deprecation")
private void updateFontObject() { private void updateFontObject() {
if (mTypeface != null) { if (mTypeface != null) {
// get the typeface font object, and get our font object from it, based on the current size // Get the fonts from the TypeFace object.
mFont = mTypeface.getFont().deriveFont(mTextSize); List<Font> fonts = mTypeface.getFonts();
if (mScaleX != 1.0 || mSkewX != 0) {
// TODO: support skew // create new font objects as well as FontMetrics, based on the current text size
mFont = mFont.deriveFont(new AffineTransform( // and skew info.
mScaleX, mSkewX, 0, 0, 1, 0)); ArrayList<FontInfo> infoList = new ArrayList<FontInfo>(fonts.size());
for (Font font : fonts) {
FontInfo info = new FontInfo();
info.mFont = font.deriveFont(mTextSize);
if (mScaleX != 1.0 || mSkewX != 0) {
// TODO: support skew
info.mFont = info.mFont.deriveFont(new AffineTransform(
mScaleX, mSkewX, 0, 0, 1, 0));
}
info.mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(info.mFont);
infoList.add(info);
} }
mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(mFont); mFonts = Collections.unmodifiableList(infoList);
} }
} }
@@ -310,34 +333,36 @@ public class Paint extends _Original_Paint {
* @return the font's recommended interline spacing. * @return the font's recommended interline spacing.
*/ */
public float getFontMetrics(FontMetrics metrics) { public float getFontMetrics(FontMetrics metrics) {
if (mMetrics != null) { if (mFonts.size() > 0) {
java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics;
if (metrics != null) { if (metrics != null) {
// ascent stuff should be negatif, but awt returns them as positive. // Android expects negative ascent so we invert the value from Java.
metrics.top = - mMetrics.getMaxAscent(); metrics.top = - javaMetrics.getMaxAscent();
metrics.ascent = - mMetrics.getAscent(); metrics.ascent = - javaMetrics.getAscent();
metrics.descent = mMetrics.getDescent(); metrics.descent = javaMetrics.getDescent();
metrics.bottom = mMetrics.getMaxDescent(); metrics.bottom = javaMetrics.getMaxDescent();
metrics.leading = mMetrics.getLeading(); metrics.leading = javaMetrics.getLeading();
} }
return mMetrics.getHeight(); return javaMetrics.getHeight();
} }
return 0; return 0;
} }
public int getFontMetricsInt(FontMetricsInt metrics) { public int getFontMetricsInt(FontMetricsInt metrics) {
if (mMetrics != null) { if (mFonts.size() > 0) {
java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics;
if (metrics != null) { if (metrics != null) {
// ascent stuff should be negatif, but awt returns them as positive. // Android expects negative ascent so we invert the value from Java.
metrics.top = - mMetrics.getMaxAscent(); metrics.top = - javaMetrics.getMaxAscent();
metrics.ascent = - mMetrics.getAscent(); metrics.ascent = - javaMetrics.getAscent();
metrics.descent = mMetrics.getDescent(); metrics.descent = javaMetrics.getDescent();
metrics.bottom = mMetrics.getMaxDescent(); metrics.bottom = javaMetrics.getMaxDescent();
metrics.leading = mMetrics.getLeading(); metrics.leading = javaMetrics.getLeading();
} }
return mMetrics.getHeight(); return javaMetrics.getHeight();
} }
return 0; return 0;
@@ -716,9 +741,10 @@ public class Paint extends _Original_Paint {
*/ */
@Override @Override
public float ascent() { public float ascent() {
if (mMetrics != null) { if (mFonts.size() > 0) {
// ascent stuff should be negatif, but awt returns them as positive. java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics;
return - mMetrics.getAscent(); // Android expects negative ascent so we invert the value from Java.
return - javaMetrics.getAscent();
} }
return 0; return 0;
@@ -733,8 +759,9 @@ public class Paint extends _Original_Paint {
*/ */
@Override @Override
public float descent() { public float descent() {
if (mMetrics != null) { if (mFonts.size() > 0) {
return mMetrics.getDescent(); java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics;
return javaMetrics.getDescent();
} }
return 0; return 0;
@@ -750,10 +777,55 @@ public class Paint extends _Original_Paint {
*/ */
@Override @Override
public float measureText(char[] text, int index, int count) { public float measureText(char[] text, int index, int count) {
if (mFont != null && text != null && text.length > 0) { // WARNING: the logic in this method is similar to Canvas.drawText.
Rectangle2D bounds = mFont.getStringBounds(text, index, index + count, mFontContext); // Any change to this method should be reflected in Canvas.drawText
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.
}
return (float)bounds.getWidth(); // 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 0; return 0;
@@ -919,14 +991,30 @@ public class Paint extends _Original_Paint {
@Override @Override
public int getTextWidths(char[] text, int index, int count, public int getTextWidths(char[] text, int index, int count,
float[] widths) { float[] widths) {
if (mMetrics != null) { if (mFonts.size() > 0) {
if ((index | count) < 0 || index + count > text.length if ((index | count) < 0 || index + count > text.length
|| count > widths.length) { || count > widths.length) {
throw new ArrayIndexOutOfBoundsException(); throw new ArrayIndexOutOfBoundsException();
} }
// FIXME: handle multi-char characters.
// Need to figure out if the lengths of the width array takes into account
// multi-char characters.
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
widths[i] = mMetrics.charWidth(text[i + index]); char c = text[i + index];
boolean found = false;
for (FontInfo info : mFonts) {
if (info.mFont.canDisplay(c)) {
widths[i] = info.mMetrics.charWidth(c);
found = true;
break;
}
}
if (found == false) {
// we stop there.
return i;
}
} }
return count; return count;
@@ -1070,7 +1158,8 @@ public class Paint extends _Original_Paint {
*/ */
@Override @Override
public void getTextBounds(char[] text, int index, int count, Rect bounds) { public void getTextBounds(char[] text, int index, int count, Rect bounds) {
if (mFont != null) { // FIXME
if (mFonts.size() > 0) {
if ((index | count) < 0 || index + count > text.length) { if ((index | count) < 0 || index + count > text.length) {
throw new ArrayIndexOutOfBoundsException(); throw new ArrayIndexOutOfBoundsException();
} }
@@ -1078,7 +1167,9 @@ public class Paint extends _Original_Paint {
throw new NullPointerException("need bounds Rect"); throw new NullPointerException("need bounds Rect");
} }
Rectangle2D rect = mFont.getStringBounds(text, index, index + count, mFontContext); FontInfo mainInfo = mFonts.get(0);
Rectangle2D rect = mainInfo.mFont.getStringBounds(text, index, index + count, mFontContext);
bounds.set(0, 0, (int)rect.getWidth(), (int)rect.getHeight()); bounds.set(0, 0, (int)rect.getWidth(), (int)rect.getHeight());
} }
} }

View File

@@ -21,9 +21,12 @@ import com.android.layoutlib.bridge.FontLoader;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import java.awt.Font; import java.awt.Font;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** /**
* Re-implementation of Typeface over java.awt * Re-implementation of Typeface over java.awt
*/ */
public class Typeface { public class Typeface {
private static final String DEFAULT_FAMILY = "sans-serif"; private static final String DEFAULT_FAMILY = "sans-serif";
@@ -46,11 +49,11 @@ public class Typeface {
private static Typeface[] sDefaults; private static Typeface[] sDefaults;
private static FontLoader mFontLoader; private static FontLoader mFontLoader;
private final int mStyle; private final int mStyle;
private final Font mFont; private final List<Font> mFonts;
private final String mFamily; private final String mFamily;
// Style // Style
public static final int NORMAL = _Original_Typeface.NORMAL; public static final int NORMAL = _Original_Typeface.NORMAL;
public static final int BOLD = _Original_Typeface.BOLD; public static final int BOLD = _Original_Typeface.BOLD;
@@ -58,12 +61,13 @@ public class Typeface {
public static final int BOLD_ITALIC = _Original_Typeface.BOLD_ITALIC; public static final int BOLD_ITALIC = _Original_Typeface.BOLD_ITALIC;
/** /**
* Returns the underlying {@link Font} object. * Returns the underlying {@link Font} objects. The first item in the list is the real
* font. Any other items are fallback fonts for characters not found in the first one.
*/ */
public Font getFont() { public List<Font> getFonts() {
return mFont; return mFonts;
} }
/** Returns the typeface's intrinsic style attributes */ /** Returns the typeface's intrinsic style attributes */
public int getStyle() { public int getStyle() {
return mStyle; return mStyle;
@@ -94,9 +98,12 @@ public class Typeface {
styleBuffer[0] = style; styleBuffer[0] = style;
Font font = mFontLoader.getFont(familyName, styleBuffer); Font font = mFontLoader.getFont(familyName, styleBuffer);
if (font != null) { if (font != null) {
return new Typeface(familyName, styleBuffer[0], font); ArrayList<Font> list = new ArrayList<Font>();
list.add(font);
list.addAll(mFontLoader.getFallBackFonts());
return new Typeface(familyName, styleBuffer[0], list);
} }
return null; return null;
} }
@@ -115,7 +122,10 @@ public class Typeface {
styleBuffer[0] = style; styleBuffer[0] = style;
Font font = mFontLoader.getFont(family.mFamily, styleBuffer); Font font = mFontLoader.getFont(family.mFamily, styleBuffer);
if (font != null) { if (font != null) {
return new Typeface(family.mFamily, styleBuffer[0], font); ArrayList<Font> list = new ArrayList<Font>();
list.add(font);
list.addAll(mFontLoader.getFallBackFonts());
return new Typeface(family.mFamily, styleBuffer[0], list);
} }
return null; return null;
@@ -129,7 +139,7 @@ public class Typeface {
public static Typeface defaultFromStyle(int style) { public static Typeface defaultFromStyle(int style) {
return sDefaults[style]; return sDefaults[style];
} }
/** /**
* Create a new typeface from the specified font data. * Create a new typeface from the specified font data.
* @param mgr The application's asset manager * @param mgr The application's asset manager
@@ -140,17 +150,17 @@ public class Typeface {
return null; return null;
//return new Typeface(nativeCreateFromAsset(mgr, path)); //return new Typeface(nativeCreateFromAsset(mgr, path));
} }
// don't allow clients to call this directly // don't allow clients to call this directly
private Typeface(String family, int style, Font f) { private Typeface(String family, int style, List<Font> fonts) {
mFamily = family; mFamily = family;
mFont = f; mFonts = Collections.unmodifiableList(fonts);
mStyle = style; mStyle = style;
} }
public static void init(FontLoader fontLoader) { public static void init(FontLoader fontLoader) {
mFontLoader = fontLoader; mFontLoader = fontLoader;
DEFAULT = create(DEFAULT_FAMILY, NORMAL); DEFAULT = create(DEFAULT_FAMILY, NORMAL);
DEFAULT_BOLD = create(DEFAULT_FAMILY, BOLD); DEFAULT_BOLD = create(DEFAULT_FAMILY, BOLD);
SANS_SERIF = create("sans-serif", NORMAL); SANS_SERIF = create("sans-serif", NORMAL);
@@ -162,14 +172,14 @@ public class Typeface {
create(DEFAULT_FAMILY, ITALIC), create(DEFAULT_FAMILY, ITALIC),
create(DEFAULT_FAMILY, BOLD_ITALIC), create(DEFAULT_FAMILY, BOLD_ITALIC),
}; };
/* /*
DEFAULT = create((String)null, 0); DEFAULT = create((String)null, 0);
DEFAULT_BOLD = create((String)null, Typeface.BOLD); DEFAULT_BOLD = create((String)null, Typeface.BOLD);
SANS_SERIF = create("sans-serif", 0); SANS_SERIF = create("sans-serif", 0);
SERIF = create("serif", 0); SERIF = create("serif", 0);
MONOSPACE = create("monospace", 0); MONOSPACE = create("monospace", 0);
sDefaults = new Typeface[] { sDefaults = new Typeface[] {
DEFAULT, DEFAULT,
DEFAULT_BOLD, DEFAULT_BOLD,

View File

@@ -29,6 +29,7 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@@ -47,14 +48,13 @@ import javax.xml.parsers.SAXParserFactory;
*/ */
public final class FontLoader { public final class FontLoader {
private static final String FONTS_DEFINITIONS = "fonts.xml"; private static final String FONTS_DEFINITIONS = "fonts.xml";
private static final String NODE_FONTS = "fonts"; private static final String NODE_FONTS = "fonts";
private static final String NODE_FONT = "font"; private static final String NODE_FONT = "font";
private static final String NODE_NAME = "name"; private static final String NODE_NAME = "name";
private static final String NODE_FALLBACK = "fallback";
private static final String ATTR_TTF = "ttf";
private static final String[] NODE_LEVEL = { NODE_FONTS, NODE_FONT, NODE_NAME }; private static final String ATTR_TTF = "ttf";
private static final String FONT_EXT = ".ttf"; private static final String FONT_EXT = ".ttf";
@@ -62,7 +62,7 @@ public final class FontLoader {
private static final String[] FONT_STYLE_BOLD = { "-Bold" }; private static final String[] FONT_STYLE_BOLD = { "-Bold" };
private static final String[] FONT_STYLE_ITALIC = { "-Italic" }; private static final String[] FONT_STYLE_ITALIC = { "-Italic" };
private static final String[] FONT_STYLE_BOLDITALIC = { "-BoldItalic" }; private static final String[] FONT_STYLE_BOLDITALIC = { "-BoldItalic" };
// list of font style, in the order matching the Typeface Font style // list of font style, in the order matching the Typeface Font style
private static final String[][] FONT_STYLES = { private static final String[][] FONT_STYLES = {
FONT_STYLE_DEFAULT, FONT_STYLE_DEFAULT,
@@ -70,23 +70,25 @@ public final class FontLoader {
FONT_STYLE_ITALIC, FONT_STYLE_ITALIC,
FONT_STYLE_BOLDITALIC FONT_STYLE_BOLDITALIC
}; };
private final Map<String, String> mFamilyToTtf = new HashMap<String, String>(); private final Map<String, String> mFamilyToTtf = new HashMap<String, String>();
private final Map<String, Map<Integer, Font>> mTtfToFontMap = private final Map<String, Map<Integer, Font>> mTtfToFontMap =
new HashMap<String, Map<Integer, Font>>(); new HashMap<String, Map<Integer, Font>>();
private List<Font> mFallBackFonts = null;
public static FontLoader create(String fontOsLocation) { public static FontLoader create(String fontOsLocation) {
try { try {
SAXParserFactory parserFactory = SAXParserFactory.newInstance(); SAXParserFactory parserFactory = SAXParserFactory.newInstance();
parserFactory.setNamespaceAware(true); parserFactory.setNamespaceAware(true);
SAXParser parser = parserFactory.newSAXParser(); SAXParser parser = parserFactory.newSAXParser();
File f = new File(fontOsLocation + File.separator + FONTS_DEFINITIONS); File f = new File(fontOsLocation + File.separator + FONTS_DEFINITIONS);
FontDefinitionParser definitionParser = new FontDefinitionParser( FontDefinitionParser definitionParser = new FontDefinitionParser(
fontOsLocation + File.separator); fontOsLocation + File.separator);
parser.parse(new FileInputStream(f), definitionParser); parser.parse(new FileInputStream(f), definitionParser);
return definitionParser.getFontLoader(); return definitionParser.getFontLoader();
} catch (ParserConfigurationException e) { } catch (ParserConfigurationException e) {
// return null below // return null below
@@ -101,12 +103,35 @@ public final class FontLoader {
return null; return null;
} }
private FontLoader(List<FontInfo> fontList) { private FontLoader(List<FontInfo> fontList, List<String> fallBackList) {
for (FontInfo info : fontList) { for (FontInfo info : fontList) {
for (String family : info.families) { for (String family : info.families) {
mFamilyToTtf.put(family, info.ttf); mFamilyToTtf.put(family, info.ttf);
} }
} }
ArrayList<Font> list = new ArrayList<Font>();
for (String path : fallBackList) {
File f = new File(path + FONT_EXT);
if (f.isFile()) {
try {
Font font = Font.createFont(Font.TRUETYPE_FONT, f);
if (font != null) {
list.add(font);
}
} catch (FontFormatException e) {
// skip this font name
} catch (IOException e) {
// skip this font name
}
}
}
mFallBackFonts = Collections.unmodifiableList(list);
}
public List<Font> getFallBackFonts() {
return mFallBackFonts;
} }
public synchronized Font getFont(String family, int[] style) { public synchronized Font getFont(String family, int[] style) {
@@ -116,25 +141,25 @@ public final class FontLoader {
// get the ttf name from the family // get the ttf name from the family
String ttf = mFamilyToTtf.get(family); String ttf = mFamilyToTtf.get(family);
if (ttf == null) { if (ttf == null) {
return null; return null;
} }
// get the font from the ttf // get the font from the ttf
Map<Integer, Font> styleMap = mTtfToFontMap.get(ttf); Map<Integer, Font> styleMap = mTtfToFontMap.get(ttf);
if (styleMap == null) { if (styleMap == null) {
styleMap = new HashMap<Integer, Font>(); styleMap = new HashMap<Integer, Font>();
mTtfToFontMap.put(ttf, styleMap); mTtfToFontMap.put(ttf, styleMap);
} }
Font f = styleMap.get(style); Font f = styleMap.get(style);
if (f != null) { if (f != null) {
return f; return f;
} }
// if it doesn't exist, we create it, and we can't, we try with a simpler style // if it doesn't exist, we create it, and we can't, we try with a simpler style
switch (style[0]) { switch (style[0]) {
case Typeface.NORMAL: case Typeface.NORMAL:
@@ -178,7 +203,7 @@ public final class FontLoader {
private Font getFont(String ttf, String[] fontFileSuffix) { private Font getFont(String ttf, String[] fontFileSuffix) {
for (String suffix : fontFileSuffix) { for (String suffix : fontFileSuffix) {
String name = ttf + suffix + FONT_EXT; String name = ttf + suffix + FONT_EXT;
File f = new File(name); File f = new File(name);
if (f.isFile()) { if (f.isFile()) {
try { try {
@@ -193,14 +218,14 @@ public final class FontLoader {
} }
} }
} }
return null; return null;
} }
private final static class FontInfo { private final static class FontInfo {
String ttf; String ttf;
final Set<String> families; final Set<String> families;
FontInfo() { FontInfo() {
families = new HashSet<String>(); families = new HashSet<String>();
} }
@@ -208,19 +233,19 @@ public final class FontLoader {
private final static class FontDefinitionParser extends DefaultHandler { private final static class FontDefinitionParser extends DefaultHandler {
private final String mOsFontsLocation; private final String mOsFontsLocation;
private int mDepth = 0;
private FontInfo mFontInfo = null; private FontInfo mFontInfo = null;
private final StringBuilder mBuilder = new StringBuilder(); private final StringBuilder mBuilder = new StringBuilder();
private final List<FontInfo> mFontList = new ArrayList<FontInfo>(); private List<FontInfo> mFontList;
private List<String> mFallBackList;
private FontDefinitionParser(String osFontsLocation) { private FontDefinitionParser(String osFontsLocation) {
super(); super();
mOsFontsLocation = osFontsLocation; mOsFontsLocation = osFontsLocation;
} }
FontLoader getFontLoader() { FontLoader getFontLoader() {
return new FontLoader(mFontList); return new FontLoader(mFontList, mFallBackList);
} }
/* (non-Javadoc) /* (non-Javadoc)
@@ -229,10 +254,11 @@ public final class FontLoader {
@Override @Override
public void startElement(String uri, String localName, String name, Attributes attributes) public void startElement(String uri, String localName, String name, Attributes attributes)
throws SAXException { throws SAXException {
if (localName.equals(NODE_LEVEL[mDepth])) { if (NODE_FONTS.equals(localName)) {
mDepth++; mFontList = new ArrayList<FontInfo>();
mFallBackList = new ArrayList<String>();
if (mDepth == 2) { // font level. } else if (NODE_FONT.equals(localName)) {
if (mFontList != null) {
String ttf = attributes.getValue(ATTR_TTF); String ttf = attributes.getValue(ATTR_TTF);
if (ttf != null) { if (ttf != null) {
mFontInfo = new FontInfo(); mFontInfo = new FontInfo();
@@ -240,42 +266,50 @@ public final class FontLoader {
mFontList.add(mFontInfo); mFontList.add(mFontInfo);
} }
} }
} else if (NODE_NAME.equals(localName)) {
// do nothing, we'll handle the name in the endElement
} else if (NODE_FALLBACK.equals(localName)) {
if (mFallBackList != null) {
String ttf = attributes.getValue(ATTR_TTF);
if (ttf != null) {
mFallBackList.add(mOsFontsLocation + ttf);
}
}
} }
mBuilder.setLength(0);
super.startElement(uri, localName, name, attributes); super.startElement(uri, localName, name, attributes);
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int) * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int)
*/ */
@SuppressWarnings("unused")
@Override @Override
public void characters(char[] ch, int start, int length) throws SAXException { public void characters(char[] ch, int start, int length) throws SAXException {
if (mFontInfo != null) { mBuilder.append(ch, start, length);
mBuilder.append(ch, start, length);
}
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String) * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
*/ */
@SuppressWarnings("unused")
@Override @Override
public void endElement(String uri, String localName, String name) throws SAXException { public void endElement(String uri, String localName, String name) throws SAXException {
if (localName.equals(NODE_LEVEL[mDepth-1])) { if (NODE_FONTS.equals(localName)) {
mDepth--; // top level, do nothing
if (mDepth == 2) { // end of a <name> node } else if (NODE_FONT.equals(localName)) {
if (mFontInfo != null) { mFontInfo = null;
String family = trimXmlWhitespaces(mBuilder.toString()); } else if (NODE_NAME.equals(localName)) {
mFontInfo.families.add(family); // handle a new name for an existing Font Info
mBuilder.setLength(0); if (mFontInfo != null) {
} String family = trimXmlWhitespaces(mBuilder.toString());
} else if (mDepth == 1) { // end of a <font> node mFontInfo.families.add(family);
mFontInfo = null;
} }
} else if (NODE_FALLBACK.equals(localName)) {
// nothing to do here.
} }
} }
private String trimXmlWhitespaces(String value) { private String trimXmlWhitespaces(String value) {
if (value == null) { if (value == null) {
return null; return null;
@@ -283,7 +317,7 @@ public final class FontLoader {
// look for carriage return and replace all whitespace around it by just 1 space. // look for carriage return and replace all whitespace around it by just 1 space.
int index; int index;
while ((index = value.indexOf('\n')) != -1) { while ((index = value.indexOf('\n')) != -1) {
// look for whitespace on each side // look for whitespace on each side
int left = index - 1; int left = index - 1;
@@ -294,7 +328,7 @@ public final class FontLoader {
break; break;
} }
} }
int right = index + 1; int right = index + 1;
int count = value.length(); int count = value.length();
while (right < count) { while (right < count) {
@@ -304,7 +338,7 @@ public final class FontLoader {
break; break;
} }
} }
// remove all between left and right (non inclusive) and replace by a single space. // remove all between left and right (non inclusive) and replace by a single space.
String leftString = null; String leftString = null;
if (left >= 0) { if (left >= 0) {
@@ -314,7 +348,7 @@ public final class FontLoader {
if (right < count) { if (right < count) {
rightString = value.substring(right); rightString = value.substring(right);
} }
if (leftString != null) { if (leftString != null) {
value = leftString; value = leftString;
if (rightString != null) { if (rightString != null) {
@@ -324,24 +358,24 @@ public final class FontLoader {
value = rightString != null ? rightString : ""; value = rightString != null ? rightString : "";
} }
} }
// now we un-escape the string // now we un-escape the string
int length = value.length(); int length = value.length();
char[] buffer = value.toCharArray(); char[] buffer = value.toCharArray();
for (int i = 0 ; i < length ; i++) { for (int i = 0 ; i < length ; i++) {
if (buffer[i] == '\\') { if (buffer[i] == '\\') {
if (buffer[i+1] == 'n') { if (buffer[i+1] == 'n') {
// replace the char with \n // replace the char with \n
buffer[i+1] = '\n'; buffer[i+1] = '\n';
} }
// offset the rest of the buffer since we go from 2 to 1 char // offset the rest of the buffer since we go from 2 to 1 char
System.arraycopy(buffer, i+1, buffer, i, length - i - 1); System.arraycopy(buffer, i+1, buffer, i, length - i - 1);
length--; length--;
} }
} }
return new String(buffer, 0, length); return new String(buffer, 0, length);
} }