Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b5046342 authored by Xavier Ducrohet's avatar Xavier Ducrohet
Browse files

Support for fallback fonts in layoutlib. (do not merge)

BUG 2041229

This is integrated from Eclair.
parent c06c1d2c
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -13,6 +13,10 @@
     See the License for the specific language governing permissions and
     limitations under the License.
-->
<!--
	This is only used by the layoutlib to display
	layouts in ADT.
-->
<fonts>
    <font ttf="DroidSans">
        <name>sans-serif</name>
@@ -39,5 +43,6 @@
        <name>courier new</name>
        <name>monaco</name>
    </font>
    <font ttf="DroidSansFallback" />
    <fallback ttf="DroidSansFallback" />
    <fallback ttf="DroidSansJapanese" />
</fonts>
 No newline at end of file
+84 −6
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.Xfermode;
import android.graphics.Paint.Align;
import android.graphics.Paint.FontInfo;
import android.graphics.Paint.Style;
import android.graphics.Region.Op;

@@ -37,6 +38,7 @@ import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.List;
import java.util.Stack;

import javax.microedition.khronos.opengles.GL;
@@ -620,19 +622,21 @@ public class Canvas extends _Original_Canvas {
     */
    @Override
    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();

        g = (Graphics2D)g.create();
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        g.setFont(paint.getFont());

        // set the color. because this only handles RGB we have to handle the alpha separately
        // set the color. because this only handles RGB, the alpha channel is handled
        // as a composite.
        g.setColor(new Color(paint.getColor()));
        int alpha = paint.getAlpha();
        float falpha = alpha / 255.f;
        g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha));


        // Paint.TextAlign indicates how the text is positioned relative to X.
        // LEFT is the default and there's nothing to do.
        if (paint.getTextAlign() != Align.LEFT) {
@@ -644,10 +648,84 @@ 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);

                        // 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)
     * @see android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
+138 −47
Original line number Diff line number Diff line
@@ -26,6 +26,9 @@ 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;

/**
 * A paint implementation overridden by the LayoutLib bridge.
@@ -40,10 +43,17 @@ public class Paint extends _Original_Paint {
    private Style mStyle = Style.FILL;
    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(
            new AffineTransform(), true, true);
    private java.awt.FontMetrics mMetrics;

    @SuppressWarnings("hiding")
    public static final int ANTI_ALIAS_FLAG       = _Original_Paint.ANTI_ALIAS_FLAG;
@@ -192,10 +202,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() {
        return mFont;
    public List<FontInfo> getFonts() {
        return mFonts;
    }
    
    private void initFont() {
@@ -206,17 +217,29 @@ public class Paint extends _Original_Paint {
    /**
     * Update the {@link Font} object from the typeface, text size and scaling
     */
    @SuppressWarnings("deprecation")
    private void updateFontObject() {
        if (mTypeface != null) {
            // get the typeface font object, and get our font object from it, based on the current size
            mFont = mTypeface.getFont().deriveFont(mTextSize);
            // Get the fonts from the TypeFace object.
            List<Font> fonts = mTypeface.getFonts();

            // create new font objects as well as FontMetrics, based on the current text size
            // and skew info.
            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
                mFont = mFont.deriveFont(new AffineTransform(
                    info.mFont = info.mFont.deriveFont(new AffineTransform(
                            mScaleX, mSkewX, 0, 0, 1, 0));
                }
                info.mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(info.mFont);

            mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(mFont);
                infoList.add(info);
            }

            mFonts = Collections.unmodifiableList(infoList);
        }
    }
    
@@ -256,34 +279,36 @@ public class Paint extends _Original_Paint {
     * @return the font's recommended interline spacing.
     */
    public float getFontMetrics(FontMetrics metrics) {
        if (mMetrics != null) {
        if (mFonts.size() > 0) {
            java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics;
            if (metrics != null) {
                // ascent stuff should be negatif, but awt returns them as positive.
                metrics.top = - mMetrics.getMaxAscent();
                metrics.ascent = - mMetrics.getAscent();
                metrics.descent = mMetrics.getDescent();
                metrics.bottom = mMetrics.getMaxDescent();
                metrics.leading = mMetrics.getLeading();
                // Android expects negative ascent so we invert the value from Java.
                metrics.top = - javaMetrics.getMaxAscent();
                metrics.ascent = - javaMetrics.getAscent();
                metrics.descent = javaMetrics.getDescent();
                metrics.bottom = javaMetrics.getMaxDescent();
                metrics.leading = javaMetrics.getLeading();
            }

            return mMetrics.getHeight();
            return javaMetrics.getHeight();
        }
        
        return 0;
    }

    public int getFontMetricsInt(FontMetricsInt metrics) {
        if (mMetrics != null) {
        if (mFonts.size() > 0) {
            java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics;
            if (metrics != null) {
                // ascent stuff should be negatif, but awt returns them as positive.
                metrics.top = - mMetrics.getMaxAscent();
                metrics.ascent = - mMetrics.getAscent();
                metrics.descent = mMetrics.getDescent();
                metrics.bottom = mMetrics.getMaxDescent();
                metrics.leading = mMetrics.getLeading();
                // Android expects negative ascent so we invert the value from Java.
                metrics.top = - javaMetrics.getMaxAscent();
                metrics.ascent = - javaMetrics.getAscent();
                metrics.descent = javaMetrics.getDescent();
                metrics.bottom = javaMetrics.getMaxDescent();
                metrics.leading = javaMetrics.getLeading();
            }

            return mMetrics.getHeight();
            return javaMetrics.getHeight();
        }
        
        return 0;
@@ -521,9 +546,10 @@ public class Paint extends _Original_Paint {
     */
    @Override
    public float ascent() {
        if (mMetrics != null) {
            // ascent stuff should be negatif, but awt returns them as positive.
            return - mMetrics.getAscent();
        if (mFonts.size() > 0) {
            java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics;
            // Android expects negative ascent so we invert the value from Java.
            return - javaMetrics.getAscent();
        }
        
        return 0;
@@ -538,8 +564,9 @@ public class Paint extends _Original_Paint {
     */
    @Override
    public float descent() {
        if (mMetrics != null) {
            return mMetrics.getDescent();
        if (mFonts.size() > 0) {
            java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics;
            return javaMetrics.getDescent();
        }
        
        return 0;
@@ -555,10 +582,55 @@ public class Paint extends _Original_Paint {
     */
    @Override
    public float measureText(char[] text, int index, int count) {
        if (mFont != null && text != null && text.length > 0) {
            Rectangle2D bounds = mFont.getStringBounds(text, index, index + count, mFontContext);
            
            return (float)bounds.getWidth();
        // WARNING: the logic in this method is similar to Canvas.drawText.
        // 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.
                }

                // 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;
@@ -702,14 +774,30 @@ public class Paint extends _Original_Paint {
    @Override
    public int getTextWidths(char[] text, int index, int count,
                             float[] widths) {
        if (mMetrics != null) {
        if (mFonts.size() > 0) {
            if ((index | count) < 0 || index + count > text.length
                    || count > widths.length) {
                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++) {
                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;
@@ -849,7 +937,8 @@ public class Paint extends _Original_Paint {
     */
    @Override
    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) {
                throw new ArrayIndexOutOfBoundsException();
            }
@@ -857,7 +946,9 @@ public class Paint extends _Original_Paint {
                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());
        }
    }
+29 −19
Original line number Diff line number Diff line
@@ -21,6 +21,9 @@ import com.android.layoutlib.bridge.FontLoader;
import android.content.res.AssetManager;

import java.awt.Font;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Re-implementation of Typeface over java.awt
@@ -48,7 +51,7 @@ public class Typeface {
    private static FontLoader mFontLoader;

    private final int mStyle;
    private final Font mFont;
    private final List<Font> mFonts;
    private final String mFamily;

    // Style
@@ -58,10 +61,11 @@ public class Typeface {
    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() {
        return mFont;
    public List<Font> getFonts() {
        return mFonts;
    }

    /** Returns the typeface's intrinsic style attributes */
@@ -94,7 +98,10 @@ public class Typeface {
        styleBuffer[0] = style;
        Font font = mFontLoader.getFont(familyName, styleBuffer);
        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;
@@ -115,7 +122,10 @@ public class Typeface {
        styleBuffer[0] = style;
        Font font = mFontLoader.getFont(family.mFamily, styleBuffer);
        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;
@@ -142,9 +152,9 @@ public class Typeface {
    }

    // 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;
        mFont = f;
        mFonts = Collections.unmodifiableList(fonts);
        mStyle = style;
    }

+88 −54
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -51,11 +52,10 @@ public final class FontLoader {
    private static final String NODE_FONTS = "fonts";
    private static final String NODE_FONT = "font";
    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 FONT_EXT = ".ttf";

    private static final String[] FONT_STYLE_DEFAULT = { "", "-Regular" };
@@ -75,6 +75,8 @@ public final class FontLoader {
    private final Map<String, Map<Integer, Font>> mTtfToFontMap =
        new HashMap<String, Map<Integer, Font>>();

    private List<Font> mFallBackFonts = null;

    public static FontLoader create(String fontOsLocation) {
        try {
            SAXParserFactory parserFactory = SAXParserFactory.newInstance();
@@ -101,12 +103,35 @@ public final class FontLoader {
        return null;
    }

    private FontLoader(List<FontInfo> fontList) {
    private FontLoader(List<FontInfo> fontList, List<String> fallBackList) {
        for (FontInfo info : fontList) {
            for (String family : info.families) {
                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) {
@@ -209,10 +234,10 @@ public final class FontLoader {
    private final static class FontDefinitionParser extends DefaultHandler {
        private final String mOsFontsLocation;

        private int mDepth = 0;
        private FontInfo mFontInfo = null;
        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) {
            super();
@@ -220,7 +245,7 @@ public final class FontLoader {
        }

        FontLoader getFontLoader() {
            return new FontLoader(mFontList);
            return new FontLoader(mFontList, mFallBackList);
        }

        /* (non-Javadoc)
@@ -229,10 +254,11 @@ public final class FontLoader {
        @Override
        public void startElement(String uri, String localName, String name, Attributes attributes)
                throws SAXException {
            if (localName.equals(NODE_LEVEL[mDepth])) {
                mDepth++;
                
                if (mDepth == 2) { // font level.
            if (NODE_FONTS.equals(localName)) {
                mFontList = new ArrayList<FontInfo>();
                mFallBackList = new ArrayList<String>();
            } else if (NODE_FONT.equals(localName)) {
                if (mFontList != null) {
                    String ttf = attributes.getValue(ATTR_TTF);
                    if (ttf != null) {
                        mFontInfo = new FontInfo();
@@ -240,39 +266,47 @@ public final class FontLoader {
                        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);
        }

        /* (non-Javadoc)
         * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int)
         */
        @SuppressWarnings("unused")
        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            if (mFontInfo != null) {
            mBuilder.append(ch, start, length);
        }
        }

        /* (non-Javadoc)
         * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
         */
        @SuppressWarnings("unused")
        @Override
        public void endElement(String uri, String localName, String name) throws SAXException {
            if (localName.equals(NODE_LEVEL[mDepth-1])) {
                mDepth--;
                if (mDepth == 2) { // end of a <name> node
            if (NODE_FONTS.equals(localName)) {
                // top level, do nothing
            } else if (NODE_FONT.equals(localName)) {
                mFontInfo = null;
            } else if (NODE_NAME.equals(localName)) {
                // handle a new name for an existing Font Info
                if (mFontInfo != null) {
                    String family = trimXmlWhitespaces(mBuilder.toString());
                    mFontInfo.families.add(family);
                        mBuilder.setLength(0);
                    }
                } else if (mDepth == 1) { // end of a <font> node
                    mFontInfo = null;
                }
            } else if (NODE_FALLBACK.equals(localName)) {
                // nothing to do here.
            }
        }