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

Commit aa10b9fb authored by Xavier Ducrohet's avatar Xavier Ducrohet Committed by Android Git Automerger
Browse files

am e6ab011b: Merge change Ib12bcb7f into eclair

Merge commit 'e6ab011b' into eclair-mr2

* commit 'e6ab011b':
  Support for fallback fonts in layoutlib.
parents 452a5296 e6ab011b
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)
+132 −41
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.
@@ -44,10 +47,17 @@ public class Paint extends _Original_Paint {
    private Join mJoin = Join.MITER;
    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;
@@ -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() {
        return mFont;
    public List<FontInfo> getFonts() {
        return mFonts;
    }

    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
     */
    @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);
        }
    }

@@ -310,34 +333,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;
@@ -716,9 +741,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;
@@ -733,8 +759,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;
@@ -750,10 +777,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;
@@ -919,14 +991,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;
@@ -1070,7 +1158,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();
            }
@@ -1078,7 +1167,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.
            }
        }