This is an automated email from the ASF dual-hosted git repository. joshtynjala pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/royale-compiler.git
commit ed97a921d80e187c36b5318cebcc452ff1139b87 Author: Josh Tynjala <[email protected]> AuthorDate: Tue Jan 6 11:42:43 2026 -0800 Enable [Embed] for TTF/OTF fonts when targeting SWF Supports the embedded font format for flash.text.TextField. Does not support the CFF font format for Flash Text Engine, so specifying embedAsCff="true" will report a compile-time error. Compile-time errors will also be reported when trying to use systemFont or sourceList attributes that the Flex SDK supported. It may be possible to support them with some tweaks to the way that embedding assets in general works. It looks like a file system path is currently required, and is always derived from source at this time. Based on the JREFontManager from the Flex SDK, which uses java.awt.Font. Seems to work well with some TTF and OTF fonts that I tested. Does not support additional, or custom, font managers. --- .../royale/compiler/embedding/EmbedAttribute.java | 3 - .../royale/compiler/fonts/CachedFontFace.java | 172 +++++++ .../royale/compiler/fonts/CachedFontManager.java | 295 +++++++++++ .../royale/compiler/fonts/DefineFont3Face.java | 209 ++++++++ .../org/apache/royale/compiler/fonts/FSType.java | 78 +++ .../royale/compiler/fonts/FontDescription.java | 131 +++++ .../org/apache/royale/compiler/fonts/FontFace.java | 74 +++ .../apache/royale/compiler/fonts/FontManager.java | 435 ++++++++++++++++ .../org/apache/royale/compiler/fonts/FontSet.java | 56 ++ .../royale/compiler/fonts/JREFontManager.java | 536 ++++++++++++++++++++ .../apache/royale/compiler/fonts/LocalFont.java | 80 +++ .../compiler/internal/embedding/EmbedData.java | 81 ++- .../embedding/transcoders/FontTranscoder.java | 342 +++++++++++++ .../embedding/transcoders/TranscoderBase.java | 9 +- ...va => CffFontEmbeddingNotSupportedProblem.java} | 20 +- ...=> EmbedCouldNotDetermineFontAliasProblem.java} | 17 +- ...EmbedCouldNotDetermineFontLocationProblem.java} | 17 +- ...NotSupported.java => NoFontManagerProblem.java} | 21 +- ...=> SystemFontEmbeddingNotSupportedProblem.java} | 20 +- ...upported.java => UnableToBuildFontProblem.java} | 20 +- .../apache/royale/swf/builders/FontBuilder.java | 561 +++++++++++++++++++++ .../builders/PathIteratorWrapper.java} | 39 +- .../royale/swf/builders/ZoneRecordBuilder.java | 84 +++ .../java/org/apache/royale/swf/io/SWFWriter.java | 18 +- .../org/apache/royale/utils/AbstractCache.java | 134 +++++ .../java/org/apache/royale/utils/LRUCache.java | 365 ++++++++++++++ 26 files changed, 3700 insertions(+), 117 deletions(-) diff --git a/compiler-common/src/main/java/org/apache/royale/compiler/embedding/EmbedAttribute.java b/compiler-common/src/main/java/org/apache/royale/compiler/embedding/EmbedAttribute.java index dcbc35541..c7bc9ea82 100644 --- a/compiler-common/src/main/java/org/apache/royale/compiler/embedding/EmbedAttribute.java +++ b/compiler-common/src/main/java/org/apache/royale/compiler/embedding/EmbedAttribute.java @@ -40,9 +40,6 @@ public enum EmbedAttribute SMOOTHING("smoothing"), SYMBOL("symbol"), CHARSET("charset"), - // obsolete font related attributes, which are kept - // here so we don't report problems on them, and instead - // report one problem that font embeddeding is not supported. ADV_ANTI_ALIASING("advancedAntiAliasing"), EMBED_AS_CFF("embedAsCFF"), UNICODE_RANGE("unicodeRange"), diff --git a/compiler/src/main/java/org/apache/royale/compiler/fonts/CachedFontFace.java b/compiler/src/main/java/org/apache/royale/compiler/fonts/CachedFontFace.java new file mode 100644 index 000000000..7b610ce05 --- /dev/null +++ b/compiler/src/main/java/org/apache/royale/compiler/fonts/CachedFontFace.java @@ -0,0 +1,172 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.royale.compiler.fonts; + +import org.apache.royale.swf.types.GlyphEntry; +import org.apache.royale.utils.IntMapLRUCache; + +/** + * Provides a simple LRU cache layer to a FontFace. + */ +public abstract class CachedFontFace extends FontFace +{ + protected int style; + protected FSType fsType; + protected String copyright; + protected String trademark; + public boolean useTwips; + public static final short GLYPH_CACHE_PURGE = 10; + public final GlyphCache glyphCache; + + protected CachedFontFace(int maxCachedGlyphs) + { + glyphCache = new GlyphCache(this, maxCachedGlyphs / 5, maxCachedGlyphs, GLYPH_CACHE_PURGE); + } + + protected CachedFontFace(int maxCachedGlyphs, int style, FSType fsType, String copyright, String trademark, boolean useTwips) + { + this(maxCachedGlyphs); + this.style = style; + this.fsType = fsType; + this.copyright = copyright; + this.trademark = trademark; + this.useTwips = useTwips; + } + + public boolean isBold() + { + return isBold(style); + } + + public boolean isItalic() + { + return isItalic(style); + } + + public FSType getFSType() + { + return fsType; + } + + public void setFSType(FSType t) + { + this.fsType = t; + } + + public String getCopyright() + { + return copyright; + } + + public void setCopyright(String c) + { + this.copyright = c; + } + + public String getTrademark() + { + return trademark; + } + + public void setTrademark(String t) + { + this.trademark = t; + } + + /** + * Checks if a style is BOLD (1) or BOLD-ITALIC (3) + * + * @param style + * @return boolean if bold style + */ + public static boolean isBold(int style) + { + return style == BOLD || style == BOLD + ITALIC; + } + + /** + * Checks if a style is ITALIC (2) or BOLD-ITALIC (3) + * + * @param style + * @return boolean if italic style + */ + public static boolean isItalic(int style) + { + return style == ITALIC || style == ITALIC + BOLD; + } + + public static int guessStyleFromSubFamilyName(String subFamilyName) + { + int style = PLAIN; + + if (subFamilyName != null) + { + subFamilyName = subFamilyName.toLowerCase(); + + if (subFamilyName.indexOf("regular") != -1) + { + style = PLAIN; + } + + if (subFamilyName.indexOf("bold") != -1) + { + style += BOLD; + } + + if (subFamilyName.indexOf("italic") != -1 || + subFamilyName.indexOf("oblique") != -1) + { + style += ITALIC; + } + } + + return style; + } + + protected abstract GlyphEntry createGlyphEntry(char c); + + protected abstract GlyphEntry createGlyphEntry(char c, char referenceChar); + + + static class GlyphCache extends IntMapLRUCache + { + private CachedFontFace fontFace; + + GlyphCache(CachedFontFace face, int initialSize, int maxSize, int purgeSize) + { + super(initialSize, maxSize, purgeSize); + fontFace = face; + } + + public Object fetch(int key) + { + char c = (char)key; + + if (fontFace.canDisplay(c)) + { + return fontFace.createGlyphEntry(c, c); + } + else + { + return null; + } + } + } +} diff --git a/compiler/src/main/java/org/apache/royale/compiler/fonts/CachedFontManager.java b/compiler/src/main/java/org/apache/royale/compiler/fonts/CachedFontManager.java new file mode 100644 index 000000000..a89cff23e --- /dev/null +++ b/compiler/src/main/java/org/apache/royale/compiler/fonts/CachedFontManager.java @@ -0,0 +1,295 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.royale.compiler.fonts; + +import java.net.URL; +import java.util.Map; + +import org.apache.royale.swf.TagType; +import org.apache.royale.swf.builders.FontBuilder; +import org.apache.royale.swf.tags.DefineFont3Tag; +import org.apache.royale.swf.tags.DefineFontTag; +import org.apache.royale.swf.types.GlyphEntry; +import org.apache.royale.utils.LRUCache; + +/** + * Provides a simple LRU caching mechanism for Font Manager implementations. + * + * A manager's caches and cache's members should be thread safe. + */ +@SuppressWarnings("unchecked") +public abstract class CachedFontManager extends FontManager +{ + public static final short PURGE_SIZE = 2; + public static final float DEFAULT_FONT_SIZE = 240f; //12pt * 20 - authoring tool multiplies size by 20 to increase granularity in twips + public static final Float DEFAULT_FONT_SIZE_OBJECT = new Float(DEFAULT_FONT_SIZE); + public static final String DEFAULT_FONT_SIZE_STRING = "240"; + public static final String COMPATIBILITY_VERSION = "CompatibilityVersion"; + + public static final String MAX_CACHED_FONTS_KEY = "max-cached-fonts"; + public static final String MAX_GLYPHS_PER_FACE_KEY = "max-glyphs-per-face"; + + public short maxEntries = 20; + public short maxGlyphsPerFace = 1000; + public short maxFacesPerFont = 4; // Note that Flash only supports PLAIN, BOLD, ITALIC, and BOLD+ITALIC + + private FontCache fontCache; + private FontFileCache fontFileCache; + + protected CachedFontManager() + { + super(); + } + + public void initialize(Map map) + { + super.initialize(map); + + if (map != null) + { + String prop = (String)map.get(MAX_CACHED_FONTS_KEY); + if (prop != null) + { + try + { + maxEntries = Short.parseShort(prop); + } + catch (Throwable t) + { + } + } + + prop = (String)map.get(MAX_GLYPHS_PER_FACE_KEY); + if (prop != null) + { + try + { + maxGlyphsPerFace = Short.parseShort(prop); + } + catch (Throwable t) + { + } + } + } + + fontCache = new FontCache(this); + fontFileCache = new FontFileCache(this); + } + + public DefineFontTag createDefineFont(int tagCode, FontDescription font) + { + if (font.compactFontFormat) + { + throw new FontManager.EmbedAsCffNotSupportedException(); + } + FontFace fontFace; + TagType tagType = TagType.getTagType(tagCode); + boolean useTwips = TagType.DefineFont3.equals(tagType); + if (font.source instanceof URL) + { + URL location = (URL)font.source; + fontFace = getEntryFromLocation(location, font.style, useTwips); + if (font.alias == null) + font.alias = fontFace.getFamily(); + + if (fontFace == null) + FontManager.throwFontNotFound(font.alias, null, font.style, location.toString()); + } + else + { + String fontFamily = font.source.toString(); + fontFace = getEntryFromSystem(fontFamily, font.style, useTwips); + if (font.alias == null) + font.alias = fontFace.getFamily(); + + if (fontFace == null) + FontManager.throwFontNotFound(font.alias, fontFamily, font.style, null); + } + + FontBuilder builder = new FontBuilder(tagCode, fontFace, font.alias, font.advancedAntiAliasing); + + // Add characters for unicode-range + int[][] ranges = getUnicodeRanges(font.unicodeRanges); + if (ranges != null) + { + for (int i = 0; i < ranges.length; i++) + { + int[] range = ranges[i]; + if (range != null && range.length == 2) + { + int count = range[1] - range[0] + 1; // Inclusive range + builder.addCharset(range[0], count); + } + } + } + else + { + builder.addAllChars(); + } + + return (DefineFontTag)builder.build(); + } + + public void loadDefineFont(DefineFontTag tag, Object location) + { + if (tag instanceof DefineFont3Tag) + { + loadDefineFont3((DefineFont3Tag)tag, location); + } + } + + protected void loadDefineFont3(DefineFont3Tag tag, Object location) + { + String family = null; + String locationKey = null; + + if (location != null) + { + if (location instanceof URL) + locationKey = ((URL)location).toExternalForm(); + else + locationKey = location.toString(); + + family = (String)getFontFileCache().get(locationKey); + } + + if (family == null) + { + family = DefineFont3Face.getFamily(tag); + + if (locationKey != null) + getFontFileCache().put(locationKey, family); + } + + int style = DefineFont3Face.getStyle(tag); + + // Look for whether we've got a FontSet for this family... + FontSet fontSet = (FontSet)getFontCache().get(family); + if (fontSet == null) + { + fontSet = new FontSet(maxFacesPerFont); + getFontCache().put(family, fontSet); + } + + // Look to see whether we've got a FontFace for this style.... + FontFace face = fontSet.get(style); + if (face == null) + { + face = new DefineFont3Face(tag); + fontSet.put(style, face); + } + else + { + // We already have a FontFace for this style, if it's a + // CachedFontFace, try to update the existing cache with any + // extra glyphs present on this tag... + if (face instanceof CachedFontFace) + { + CachedFontFace cachedFontFace = (CachedFontFace)face; + int[] codepoints = tag.getCodeTable(); + if (codepoints != null) + { + for (char i = 0; i < codepoints.length; i++) + { + char c = (char) codepoints[i]; + GlyphEntry ge = cachedFontFace.getGlyphEntry(c); + if (ge == null) + { + ge = DefineFont3Face.createGlyphEntryFromDefineFont(c, i, tag); + cachedFontFace.glyphCache.put(c, ge); + } + } + } + } + } + } + + protected FontCache getFontCache() + { + if (fontCache == null) + initialize(null); + + return fontCache; + } + + protected FontFileCache getFontFileCache() + { + if (fontFileCache == null) + initialize(null); + + return fontFileCache; + } + + protected abstract String createFontFromLocation(Object location, int style, boolean useTwips); + protected abstract FontSet createSetForSystemFont(String family, int style, boolean useTwips); + + /** + * A cache that maps font family names to a <code>FontSet</code> - a set + * of derived styles from the base family font. + */ + static class FontCache extends LRUCache + { + private static final long serialVersionUID = -2402480346505475961L; + + FontCache(CachedFontManager manager) + { + super(manager.maxEntries / 2, manager.maxEntries, PURGE_SIZE); + } + + /** + * We don't know whether we're looking by location or os family name, so + * we fail fast and don't attempt to fetch. + * + * @param key the font family name + * @return null + */ + protected Object fetch(Object key) + { + return null; + } + } + + /** + * A cache mapping a font file location to a family name. If the location + * new, it will load the file to determine the family name and create an + * entry in the main fontCache. + */ + static class FontFileCache extends LRUCache + { + private static final long serialVersionUID = 5379979428987581921L; + + FontFileCache(CachedFontManager manager) + { + super(manager.maxEntries / 2, manager.maxEntries, PURGE_SIZE); + } + + /** + * We fail fast if we've not seen this location because + * we don't know whether the location will match + * requested style... + * + * @param key the location of the font file + */ + protected Object fetch(Object key) + { + return null; + } + } +} diff --git a/compiler/src/main/java/org/apache/royale/compiler/fonts/DefineFont3Face.java b/compiler/src/main/java/org/apache/royale/compiler/fonts/DefineFont3Face.java new file mode 100644 index 000000000..e4d7bd299 --- /dev/null +++ b/compiler/src/main/java/org/apache/royale/compiler/fonts/DefineFont3Face.java @@ -0,0 +1,209 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.royale.compiler.fonts; + +import org.apache.royale.swf.tags.DefineFont3Tag; +import org.apache.royale.swf.types.GlyphEntry; + +/** + * A wrapper to make a DefineFont3 SWF Tag behave like a + * CachedFontFace for use in the general Flex SDK FontManager + * subsystem. + */ +public class DefineFont3Face extends CachedFontFace +{ + private final DefineFont3Tag tag; + private final char[] indicies; + private char firstChar; + + /** + * Constructor. + * @param tag The DefineFont. Must not be null and tag.codeTable must not + * be null. + */ + public DefineFont3Face(DefineFont3Tag tag) + { + super(tag.getCodeTable().length + 1); + this.tag = tag; + style = getStyle(tag); + + // Transpose the Array of chars into an Array of indicies into the + // other tables in the DefineFont tag... + firstChar = (char) tag.getCodeTable()[0]; + int charCount = tag.getCodeTable().length; + char lastChar = (char) tag.getCodeTable()[charCount - 1]; + indicies = new char[lastChar + 1]; + for (char i = 0; i < charCount; i++) + { + char c = (char) tag.getCodeTable()[i]; + indicies[c] = i; + } + + if (tag.getLicense() != null) + { + copyright = tag.getLicense().getFontCopyright(); + } + + if (tag.getZones() != null && tag.getZones().getZoneTable() != null) + { + useTwips = true; + } + } + + //-------------------------------------------------------------------------- + // + // FontFace implementation + // + //-------------------------------------------------------------------------- + + public boolean canDisplay(char c) + { + if (c < indicies.length) + { + int index = indicies[c]; + if (c == firstChar || index > 0) + return true; + } + return false; + } + + public int getAdvance(char c) + { + int index = indicies[c]; + return tag.getFontAdvanceTable()[index]; + } + + public int getAscent() + { + return tag.getFontAscent(); + } + + public int getDescent() + { + return tag.getFontDescent(); + } + + public double getEmScale() + { + return 1.0; + } + + public String getFamily() + { + return getFamily(tag); + } + + public int getFirstChar() + { + return firstChar; + } + + public GlyphEntry getGlyphEntry(char c) + { + return (GlyphEntry)glyphCache.get(c); + } + + public int getLineGap() + { + return tag.getFontLeading(); + } + + public int getMissingGlyphCode() + { + return 0; + } + + public int getNumGlyphs() + { + return tag.getCodeTable().length; + } + + public double getPointSize() + { + return 1.0f; + } + + public String getPostscriptName() + { + return getFamily(tag); + } + + public static String getFamily(DefineFont3Tag tag) + { + String family = tag.getFontName(); + if (tag.getLicense() != null) + { + String fontName = tag.getLicense().getFontName(); + if (fontName != null && !"".equals(fontName)) + family = tag.getLicense().getFontName(); + } + return family; + } + + public static int getStyle(DefineFont3Tag tag) + { + int style = 0; + if (tag.isFontFlagsBold()) + style += FontFace.BOLD; + if (tag.isFontFlagsItalic()) + style += FontFace.ITALIC; + return style; + } + + public static GlyphEntry createGlyphEntryFromDefineFont(char c, char index, DefineFont3Tag tag) + { + GlyphEntry ge = new GlyphEntry(); + ge.character = c; + ge.setGlyphIndex(index); + + if (tag.getGlyphShapeTable() != null) + ge.shape = tag.getGlyphShapeTable()[index]; + + if (tag.getFontAdvanceTable() != null) + ge.setGlyphAdvance(tag.getFontAdvanceTable()[index]); + + if (tag.getFontBoundsTable() != null) + ge.bounds = tag.getFontBoundsTable()[index]; + + if (tag.getZones() != null && tag.getZones().getZoneTable() != null) + ge.zoneRecord = tag.getZones().getZoneTable()[index]; + + return ge; + } + + //-------------------------------------------------------------------------- + // + // CachedFontFace implementation + // + //-------------------------------------------------------------------------- + + protected GlyphEntry createGlyphEntry(char c) + { + char index = indicies[c]; + return createGlyphEntryFromDefineFont(c, index, tag); + } + + protected GlyphEntry createGlyphEntry(char c, char referenceChar) + { + // We don't use any glyph index offsets based on a reference char. + return createGlyphEntry(c); + } + +} diff --git a/compiler/src/main/java/org/apache/royale/compiler/fonts/FSType.java b/compiler/src/main/java/org/apache/royale/compiler/fonts/FSType.java new file mode 100644 index 000000000..025c59877 --- /dev/null +++ b/compiler/src/main/java/org/apache/royale/compiler/fonts/FSType.java @@ -0,0 +1,78 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.royale.compiler.fonts; + +/** + * Returns information on the fsType of the font. More information on + * this can be found here: + * <p> + * http://www.microsoft.com/typography/tt/ttf_spec/ttch02.doc + * <p> + * http://www.microsoft.com/typography/otspec/os2.htm + */ +public class FSType +{ + public int fsType; + public String description; + public boolean installable, editable, previewAndPrint, noEmbedding, usableByFlex; + + public FSType(int fsType, String description, boolean installable, boolean editable, boolean previewAndPrint, boolean noEmbedding) + { + this.fsType = fsType; + this.description = description; + this.installable = installable; + this.editable = editable; + this.previewAndPrint = previewAndPrint; + this.noEmbedding = noEmbedding; + this.usableByFlex = true; //installable || editable; + } + + public static FSType getFSType(int lt) + { + String description; + boolean installable = false, editable = false, previewAndPrint = false, noEmbedding = false; + boolean noEmbeddingBit = (lt & 0x0002) == 0x0002; + boolean previewAndPrintBit = (lt & 0x0004) == 0x0004; + boolean editableBit = (lt & 0x0008) == 0x0008; + // the most permissible bit wins + if (editableBit) + { + editable = true; + description = "Editable embedding"; + } + else if (previewAndPrintBit) + { + previewAndPrint = true; + description = "Preview and Print embedding"; + } + else if (noEmbeddingBit) + { + noEmbedding = true; + description = "No embedding allowed"; + } + else + { + installable = true; + description = "Installable embedding"; + } + + return new FSType(lt, description, installable, editable, previewAndPrint, noEmbedding); + } +} diff --git a/compiler/src/main/java/org/apache/royale/compiler/fonts/FontDescription.java b/compiler/src/main/java/org/apache/royale/compiler/fonts/FontDescription.java new file mode 100644 index 000000000..80efc18ee --- /dev/null +++ b/compiler/src/main/java/org/apache/royale/compiler/fonts/FontDescription.java @@ -0,0 +1,131 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.royale.compiler.fonts; + +/** + * A FontDescription provides FontManagers a context that describes + * how to locate a font for embedding, the font style characteristics + * and any options that may control how it is to be embedded. + */ +public class FontDescription +{ + /** + * The name to use to register the font with the SWF. + */ + public String alias; + + /** + * The source of the font information, typically a URL pointing to a font + * file. + * + * The source may alternatively be just a String representing the font + * family name of a font installed locally on the operating system. + */ + public Object source; + + /** + * The font style, represented as an int. + * + * Plain is 0, Bold is 1, Italic is 2, and Bold+Italic is 3. + */ + public int style; + + /** + * The Unicode characters to include in the DefineFont, or pass null to + * include all available characters. + */ + public String unicodeRanges; + + /** + * Controls whether advanced anti-aliasing information should be included + * (if it is available). + */ + public boolean advancedAntiAliasing; + + /** + * Controls whether the font should be embedded using compact font format + * (if supported). + */ + public boolean compactFontFormat; + + /** + * Tests whether another FontDescription describes the same font. + * + * Note that the alias is not considered in the comparison. + * + * @param value Another FontDescription instance to test for equality. + * @return + */ + public boolean equals(Object value) + { + if (this == value) + { + return true; + } + else if (value != null && value instanceof FontDescription) + { + FontDescription other = (FontDescription)value; + + if (style != other.style) + return false; + + if (compactFontFormat != other.compactFontFormat) + return false; + + if (advancedAntiAliasing != other.advancedAntiAliasing) + return false; + + if (unicodeRanges == null && other.unicodeRanges != null) + return false; + + if (source == null && other.source != null) + return false; + + if (unicodeRanges != null && !unicodeRanges.equals(other.unicodeRanges)) + return false; + + if (source != null && !source.equals(other.source)) + return false; + + return true; + } + + return false; + } + + /** + * Computes a hash code for this FontDescription instance. Note that the + * alias is not considered in calculating a hash code. + * + * @return a hash code based on all fields used to describe the font. + */ + public int hashCode() + { + int result = style; + + if (source != null) + result ^= source.hashCode(); + + if (unicodeRanges != null) + result ^= unicodeRanges.hashCode(); + + return result; + } +} diff --git a/compiler/src/main/java/org/apache/royale/compiler/fonts/FontFace.java b/compiler/src/main/java/org/apache/royale/compiler/fonts/FontFace.java new file mode 100644 index 000000000..0c90f33fd --- /dev/null +++ b/compiler/src/main/java/org/apache/royale/compiler/fonts/FontFace.java @@ -0,0 +1,74 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.royale.compiler.fonts; + +import java.util.HashMap; + +import org.apache.royale.swf.types.GlyphEntry; + +/** + * A face represents one style of a font from a single family. For + * now, the font size is not considered in the face as a FontBuilder + * does not depend on size to generate a DefineFont or DefineFont2 + * tag. + */ +public abstract class FontFace +{ + public static final int PLAIN = 0; + public static final int BOLD = 1; + public static final int ITALIC = 2; + + public static final int SWF_EM_SQUARE = 1024; + public static final int TTF_EM_SQUARE = 2048; + + public abstract GlyphEntry getGlyphEntry(char c); + public abstract int getMissingGlyphCode(); + public abstract double getPointSize(); + public abstract String getFamily(); + public abstract int getAscent(); + public abstract int getDescent(); + public abstract int getLineGap(); + public abstract int getFirstChar(); + public abstract int getNumGlyphs(); + public abstract boolean canDisplay(char c); + public abstract int getAdvance(char c); + public abstract boolean isBold(); + public abstract boolean isItalic(); + public abstract double getEmScale(); + public abstract String getCopyright(); + public abstract void setCopyright(String c); + public abstract String getTrademark(); + public abstract void setTrademark(String t); + public abstract FSType getFSType(); + public abstract void setFSType(FSType t); + public abstract String getPostscriptName(); + + public void setProperty(String name, Object value) + { + properties.put(name, value); + } + + public Object getProperty(String name) + { + return properties.get(name); + } + + protected HashMap<String, Object> properties = new HashMap<String, Object>(); +} \ No newline at end of file diff --git a/compiler/src/main/java/org/apache/royale/compiler/fonts/FontManager.java b/compiler/src/main/java/org/apache/royale/compiler/fonts/FontManager.java new file mode 100644 index 000000000..abd42ee9c --- /dev/null +++ b/compiler/src/main/java/org/apache/royale/compiler/fonts/FontManager.java @@ -0,0 +1,435 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.royale.compiler.fonts; + +import java.net.URL; +import java.util.List; +import java.util.Properties; +import java.util.Map; +import java.util.StringTokenizer; + +import org.apache.royale.swf.tags.DefineFontTag; +import org.apache.royale.utils.Trace; + +/** + * The FontManager provides a common interface to locating fonts from + * either locally (i.e. from the Operating System) or externally + * (i.e. from URL locations). + */ +@SuppressWarnings("unchecked") +public abstract class FontManager +{ + public static final String LOCAL_FONT_PATHS = "local-font-paths"; + + public static final int PLAIN = 0; + public static final int BOLD = 1; + public static final int ITALIC = 2; + + protected Properties languageRanges; + protected FontManager parent; + + protected int majorCompatibilityVersion = 4; + + /** + * Constructor + */ + protected FontManager() + { + } + + /** + * Initialization properties can be provided as name/value pairs. + * + * @param map + */ + public void initialize(Map map) + { + if (map != null) + { + String compatVersion = (String)map.get(CachedFontManager.COMPATIBILITY_VERSION); + if (compatVersion != null) + { + String[] parts = compatVersion.split("\\."); + if (parts.length > 0) + { + try + { + int major = Integer.parseInt(parts[0]); + majorCompatibilityVersion = major; + } + catch (Throwable t) + { + } + } + } + } + } + + /** + * Provides the ability to chain managers. + * + * @param parent + */ + public void setParent(FontManager parent) + { + this.parent = parent; + } + + public void setLanguageRange(Properties languageRanges) + { + this.languageRanges = languageRanges; + } + + /** + * If a given language token is registered, the corresponding unicode range + * (specified as a CSS-2 formatted string) is returned. + * + * @param lang + */ + public String getLanguageRange(String lang) + { + String range = null; + + if (languageRanges != null && lang != null) + range = languageRanges.getProperty(lang); + + return range; + } + + /** + * Create a SWF DefineFont tag from a font file location specified as a URL. + * + * @param tagCode Specifies the version of the DefineFont SWF tag to create. + * @return A DefineFont tag + */ + public DefineFontTag createDefineFont(int tagCode, FontDescription desc) + { + // No op + return null; + } + + /** + * Attempts to load a font from the cache by location or from disk if it is + * the first request at this address. The location is bound to a font family + * name and defineFont type after the initial loading, and the relationship + * exists for the lifetime of the cache. + * + * @param location + * @param style + * @return FontSet.FontFace + */ + public abstract FontFace getEntryFromLocation(URL location, int style, + boolean useTwips); + + /** + * Attempts to locate a font by family name, style, and defineFont type from + * the runtime's list of fonts, which are primarily operating system + * registered fonts. + * + * @param familyName + * @param style either FontFace.PLAIN, FontFace.BOLD, FontFace.ITALIC or + * FontFace.BOLD+FontFace.ITALIC + * @return FontFace + */ + public abstract FontFace getEntryFromSystem(String familyName, int style, + boolean useTwips); + + /** + * Allows a DefineFont SWF tag to be the basis of a FontFace. + * + * @param tag The DefineFont tag + * @param location The original location of the asset that created the + * DefineFont SWF tag. + */ + public void loadDefineFont(DefineFontTag tag, Object location) + { + // No-op + } + + /** + * Allows a DefineFont SWF tag to be the basis of a FontFace. + * + * @param tag The DefineFont tag. + */ + public void loadDefineFont(DefineFontTag tag) + { + loadDefineFont(tag, null); + } + + /** + * Parses a String representation of Unicode character ranges into an array + * of int arrays. e.g. U+0020-U+007F,U+20345. Note that int is used to + * support code points beyond the BMP. + * + * @param value String representation of unicode character ranges + * @return int[][] Array of an array of ints representing code points of + * the specified ranges. + * @see http://www.w3.org/TR/REC-CSS2/fonts.html#descdef-unicode-range + */ + public int[][] getUnicodeRanges(String value) + { + int[][] ranges = null; + + // Check if it's a registered language name + String langRange = getLanguageRange(value); + if (langRange != null) + value = langRange; + + if (value != null) + { + // Remove extraneous formatting first + value = value.replace(';', ' ').replace('\n', ' ').replace('\r', ' ').replace('\f', ' '); + + StringTokenizer st = new StringTokenizer(value, ","); + + int count = st.countTokens(); + ranges = new int[count][2]; + parseRanges(st, ranges); + } + + return ranges; + } + + public static boolean isItalic(int style) + { + return style == ITALIC || style == (BOLD + ITALIC); + } + + public static boolean isBold(int style) + { + return style == BOLD || style == (BOLD + ITALIC); + } + + /** + * Given a list of class names, this utility method attempts to construct a + * chain of FontManagers. The class must extend FontManager and have a + * public no-args constructor. Invalid classes are skipped. + * + * @param managerClasses + * @return the last FontManager in the chain + * @deprecated + */ + public static FontManager create(List managerClasses, Map map) + { + return FontManager.create(managerClasses, map, null); + } + + /** + * Given a list of class names, this utility method attempts to construct a + * chain of FontManagers. The class must extend FontManager and have a + * public no-args constructor. Invalid classes are skipped. + * + * @param managerClasses List of class names representing FontManager + * implementations. + * @param map A Map of settings to be passed to the FontManager instance + * during initialization. + * @param languageRanges List of unicode character ranges for a given + * language. + * @return the last FontManager in the chain + */ + public static FontManager create(List managerClasses, Map map, Properties languageRanges) + { + FontManager manager = null; + + if (managerClasses != null) + { + for (int i = 0; i < managerClasses.size(); i++) + { + try + { + Object className = managerClasses.get(i); + if (className != null) + { + Class clazz = Class.forName(className.toString()); + Object obj = clazz.newInstance(); + if (obj instanceof FontManager) + { + FontManager fm = (FontManager)obj; + fm.initialize(map); + + if (manager != null) + fm.setParent(manager); + + if (languageRanges != null) + fm.setLanguageRange(languageRanges); + + manager = fm; + } + } + } + catch (Throwable t) + { + if (Trace.font) + { + Trace.trace(t.getMessage()); + } + } + } + } + + return manager; + } + + + public static void throwFontNotFound(String alias, String fontFamily, int style, String location) + { + StringBuilder message = new StringBuilder("Font for alias '"); + message.append(alias).append("' "); + if (style == FontFace.BOLD) + { + message.append("with bold weight "); + } + else if (style == FontFace.ITALIC) + { + message.append("with italic style "); + } + else if (style == (FontFace.BOLD + FontFace.ITALIC)) + { + message.append("with bold weight and italic style "); + } + else + { + message.append("with plain weight and style "); + } + + if (location != null) + { + message.append("was not found at: ").append(location.toString()); + } + else + { + message.append("was not found by family name '").append(fontFamily).append("'"); + } + throw new FontNotFoundException(message.toString()); + } + + /** + * Values are expressed as hexadecimal numbers, prefixed with + * "U+". For single numbers, the character '?' is assumed to mean + * 'any value' which creates a range of character positions. Otherwise, the + * range can be specified explicitly using a hyphen, e.g. U+00A0-U+00FF + * + * @param st + * @param ranges + */ + private static void parseRanges(StringTokenizer st, int[][] ranges) + { + int i = 0; + while (st.hasMoreElements()) + { + String element = ((String)st.nextElement()).trim().toUpperCase(); + + if (element.startsWith("U+")) + { + String range = element.substring(2).trim(); + String low; + String high; + + if (range.indexOf('?') > 0) // Wild-Card Range, e.g. U+00?? + { + low = range.replace('?', '0'); + high = range.replace('?', 'F'); + } + else if (range.indexOf('-') > 0) // Basic Range, e.g. U+0020-007E + { + low = range.substring(0, range.indexOf('-')); + String temp = range.substring(range.indexOf('-') + 1).trim(); + + // Support Flex's legacy additional U+ prefix on the + // high range (but not part of the CSS-2 specification). + if (temp.startsWith("U+")) + { + high = temp.substring(2).trim(); + } + else + { + high = temp; + } + } + else if (range.length() <= 8) // Single Char, e.g. U+0041 + { + low = range; + high = range; + } + else + { + throw new InvalidUnicodeRangeException(range); + } + + try + { + ranges[i][0] = Integer.parseInt(low, 16); + ranges[i][1] = Integer.parseInt(high, 16); + } + catch (Exception ex) + { + throw new InvalidUnicodeRangeException(range); + } + + i++; + } + else if (element.length() == 0) + { + continue; + } + else + { + throw new InvalidUnicodeRangeException(element); + } + } + } + + public static final class FontNotFoundException extends RuntimeException + { + private static final long serialVersionUID = -2385779348825570473L; + + public FontNotFoundException(String message) + { + super(message); + } + } + + public static final class InvalidUnicodeRangeException extends + RuntimeException + { + private static final long serialVersionUID = 3173208110428813980L; + + public InvalidUnicodeRangeException(String range) + { + this.range = range; + } + + public String range; + } + + public static final class EmbedAsCffNotSupportedException extends RuntimeException + { + public EmbedAsCffNotSupportedException() + { + } + } + + public static final class SystemFontNotSupportedException extends RuntimeException + { + public SystemFontNotSupportedException() + { + } + } +} diff --git a/compiler/src/main/java/org/apache/royale/compiler/fonts/FontSet.java b/compiler/src/main/java/org/apache/royale/compiler/fonts/FontSet.java new file mode 100644 index 000000000..f3aafa7ea --- /dev/null +++ b/compiler/src/main/java/org/apache/royale/compiler/fonts/FontSet.java @@ -0,0 +1,56 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.royale.compiler.fonts; + +import org.apache.royale.utils.IntMapLRUCache; + +/** + * A <code>FontSet</code> is a collection of styles or "faces" for a + * given font. Each <code>FontFace</code> is associated with a + * <code>java.awt.Font</code> instance and contains a cache of + * converted glyphs, or character shape outlines. + */ +public class FontSet +{ + public FontSet(int maxFacesPerFont) + { + entries = new IntMapLRUCache(maxFacesPerFont, maxFacesPerFont) + { + + public Object fetch(int key) + { + throw new UnsupportedOperationException(); + } + }; + } + + public FontFace put(int style, FontFace entry) + { + return (FontFace)entries.put(style, entry); + } + + public FontFace get(int style) + { + return (FontFace)entries.get(style); + } + + private IntMapLRUCache entries; + +} diff --git a/compiler/src/main/java/org/apache/royale/compiler/fonts/JREFontManager.java b/compiler/src/main/java/org/apache/royale/compiler/fonts/JREFontManager.java new file mode 100644 index 000000000..62eb73645 --- /dev/null +++ b/compiler/src/main/java/org/apache/royale/compiler/fonts/JREFontManager.java @@ -0,0 +1,536 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.royale.compiler.fonts; + +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphMetrics; +import java.awt.font.GlyphVector; +import java.awt.font.TextAttribute; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.awt.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.BufferedInputStream; +import java.net.URL; +import java.util.Locale; +import java.util.Map; + +import org.apache.royale.swf.ISWFConstants; +import org.apache.royale.swf.builders.PathIteratorWrapper; +import org.apache.royale.swf.builders.ShapeBuilder; +import org.apache.royale.swf.types.GlyphEntry; +import org.apache.royale.swf.types.Shape; +import org.apache.royale.utils.Trace; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * The <code>JREFontManager</code> attempts to optimize the loading of + * JDK fonts through caching of <code>FontSet</code>s. A + * <code>FontSet</code> keeps track of the different styles (faces) + * for a given font family as <code>FontFace</code> instances. A + * manager can derive available styles from a single + * <code>FontFace</code> through its reference to a + * <code>java.awt.Font</code> object. + */ +@SuppressWarnings("unchecked") +public class JREFontManager extends CachedFontManager +{ + private boolean readLocalFonts; + private Map<String, LocalFont> localFonts; + public static String LOCAL_FONTS_SNAPSHOT = "flex.fonts-snapshot"; + private String localFontsFile; + + public void initialize(Map map) + { + super.initialize(map); + if (map != null) + { + localFontsFile = (String)map.get(LOCAL_FONTS_SNAPSHOT); + } + if (localFontsFile == null) + { + localFontsFile = "localFonts.ser"; + } + } + + protected String createFontFromLocation(Object location, int requestedStyle, boolean useTwips) + { + String family = null; + InputStream is = null; + + try + { + if (location != null && location instanceof URL) + { + URL url = (URL)location; + + if (url.getProtocol().toLowerCase().indexOf("file") > -1) + { + File f = new File(url.getFile()); + // Do NOT buffer this stream, Font.createFont() does it for us. + is = new FileInputStream(f); + } + else + { + is = url.openStream(); + } + + Font font = Font.createFont(Font.TRUETYPE_FONT, is); + + // Get the family and fontName info before we derive a new size as + // the name might be reset to dialog! + family = font.getFamily(); + String fontName = font.getFontName(Locale.ENGLISH); + + // Prior to Flex 4 we tried to validate that the requested style + // matched the style of the font. + if (majorCompatibilityVersion < 4) + { + // We need to examine the name for the real style as + // Font.createFont always sets style to PLAIN and the user + // may have given us the wrong style info + int guessedStyle = CachedFontFace.guessStyleFromSubFamilyName(fontName); + + // Only bother deriving a font and storing the work if this + // really was the style requested + if (requestedStyle != guessedStyle) + return null; + } + + // java.awt.Font doesn't provide these values + FSType type = null; + String copyright = null; + String trademark = null; + + // Set to a default size so metrics are established + // getAttributes() returns a Map which is based on a Hashtable of <Obj,Obj>, or <TA,Obj> in practice + // we need to suppress the cast because getAttributes defines the return type as <TA,?> + Map<TextAttribute, Object> attributes = (Map<TextAttribute, Object>) font.getAttributes(); + + attributes.put(TextAttribute.FAMILY, family); + attributes.put(TextAttribute.SIZE, DEFAULT_FONT_SIZE_OBJECT); + attributes.put(TextAttribute.POSTURE, CachedFontFace.isItalic(requestedStyle) ? TextAttribute.POSTURE_OBLIQUE : TextAttribute.POSTURE_REGULAR); + attributes.put(TextAttribute.WEIGHT, CachedFontFace.isBold(requestedStyle) ? TextAttribute.WEIGHT_BOLD : TextAttribute.WEIGHT_REGULAR); + + font = font.deriveFont(attributes); + + FontSet fontSet = (FontSet)getFontCache().get(family); + if (fontSet == null) + { + fontSet = new FontSet(maxFacesPerFont); + getFontCache().put(family, fontSet); + } + + fontSet.put(requestedStyle, new JREFontFace(font, requestedStyle, maxGlyphsPerFace, type, copyright, trademark, useTwips)); + } + } + catch (FileNotFoundException ex) + { + return null; + } + catch (FontFormatException ex) + { + return null; + } + catch (IOException ex) + { + return null; + } + finally + { + try + { + is.close(); + } + catch (Throwable t) + { + } + } + + return family; + } + + /** + * Attempts to load a font from the cache by location or from disk if it is the first + * request at this address. The location is bound to a font family name after the initial + * loading, and the relationship exists for the lifetime of the cache. + * + * @param location + * @param style + * @return FontSet.FontFace + */ + public FontFace getEntryFromLocation(URL location, int style, boolean useTwips) + { + FontFace entry = null; + Object fam = getFontFileCache().get(location); + + if (fam == null) + { + fam = createFontFromLocation(location, style, useTwips); + } + + if (fam != null) + { + String family = fam.toString(); + + FontSet fontSet = (FontSet)getFontCache().get(family); + + // The font file cache should still have this family + // from the location fetch above... + if (fontSet != null) + { + entry = fontSet.get(style); + } + } + + return entry; + } + + private static ArrayList<String> systemFontNames = null; + + protected FontSet createSetForSystemFont(String family, int style, boolean useTwips) + { + FontSet fontSet = null; + if (family != null) + { + if (systemFontNames == null) + initializeSystemFonts(); + + if (systemFontNames != null && !systemFontNames.contains(family.trim().toLowerCase())) + { + if (Trace.font) + Trace.trace("Font family '" + family + "' not known to JRE."); + + return null; + } + + //Load a font by family and style, set size to 240 for greater granularity + Font font = Font.decode(family + "-" + getStyleAsString(style) + "-" + DEFAULT_FONT_SIZE_STRING); + + fontSet = new FontSet(maxFacesPerFont); + fontSet.put(font.getStyle(), new JREFontFace(font, font.getStyle(), maxGlyphsPerFace, null, null, null, useTwips)); + } + return fontSet; + } + + /** + * Attempts to locate a font by family name and style from the JRE's list of + * fonts, which are primarily system registered fonts. + * + * @param familyName + * @param style - either Font.PLAIN, Font.BOLD, Font.ITALIC or Font.BOLD+Font.ITALIC + * @return FontSet.FontFace + */ + public FontFace getEntryFromSystem(String familyName, int style, boolean useTwips) + { + if (!readLocalFonts) + { + readLocalFonts(); + } + + FontFace entry = null; + + FontSet fontSet = (FontSet)getFontCache().get(familyName); + + // This is likely to be the first time looking for this family + if (fontSet == null) + { + fontSet = createSetForSystemFont(familyName, style, useTwips); + } + + // If the family was invalid on the OS there's nothing more we can do here + if (fontSet != null) + { + entry = fontSet.get(style); + } + + if (entry != null) + { + LocalFont font = localFonts.get(entry.getPostscriptName()); + if (font == null) + { + // silent failure + if (Trace.font) + { + Trace.trace("Information on font " + familyName + " could not be found. Run FontSnapshot to get a list of the current local fonts."); + } + } + else + { + entry.setCopyright(font.copyright); + entry.setTrademark(font.trademark); + entry.setFSType(FSType.getFSType(font.fsType)); + } + } + + return entry; + } + + private void readLocalFonts() + { + readLocalFonts = true; + + localFonts = new HashMap<String, LocalFont>(); +// initDefaultLocalFonts(); + try + { + InputStream buffStream = new BufferedInputStream(new FileInputStream(localFontsFile)); + ObjectInputStream in = new ObjectInputStream(buffStream); + + // there's no way around suppressing the cast warning from Object, the types get erased anyway + Map<String, LocalFont> customLocalFonts = (Map<String, LocalFont>)in.readObject(); + + localFonts.putAll(customLocalFonts); + } + catch(FileNotFoundException fnfe) + { + // ignore... a message will be printed out later if needed + if (Trace.font) + { + fnfe.printStackTrace(); + } + } + catch(Exception fnfe) + { + if (Trace.font) + { + Trace.trace("Could not read localFonts.ser: " + fnfe); + } + } + } + + private String getStyleAsString(int style) + { + String styleName; + + switch (style) + { + case 1: + styleName = "bold"; + break; + case 2: + styleName = "italic"; + break; + case 3: + styleName = "bolditalic"; + break; + default: + styleName = "plain"; + } + + return styleName; + } + + private static void initializeSystemFonts() + { + GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + String[] fnts = ge.getAvailableFontFamilyNames(); + + if (fnts != null) + { + systemFontNames = new ArrayList<String>(fnts.length); + for (int i = 0; i < fnts.length; i++) + { + systemFontNames.add(fnts[i].trim().toLowerCase()); + } + } + } + + public static class JREFontFace extends CachedFontFace + { + // FIXME: need to deal with useTwips differently for caching + + public JREFontFace(Font font, int style, int maxGlyph, FSType fsType, String copyright, String trademark, boolean useTwips) + { + super(maxGlyph, style, fsType, copyright, trademark, useTwips); + this.font = font; + init(); + } + + private void init() + { + //Convert from device to grid co-ordinates, fixed at 72dpi + emScale = SWF_EM_SQUARE / getPointSize(); //If you want to correct for resolution, multiply this value by 72/resolution + + scaleTransform = new AffineTransform(); + scaleTransform.setToScale(emScale, emScale); + + //We use a BufferedImage to get to the system FontMetrics... + //Feel free to suggest a better way of getting this object. + if (graphics == null) + { + BufferedImage bi = new BufferedImage(50, 50, BufferedImage.TYPE_INT_RGB); + graphics = bi.createGraphics(); + graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + } + + fontMetrics = graphics.getFontMetrics(font); + frc = new FontRenderContext(null, true, false); + + ascent = (int)Math.rint(fontMetrics.getAscent() * emScale * (useTwips ? ISWFConstants.TWIPS_PER_PIXEL : 1)); + descent = (int)Math.rint(fontMetrics.getDescent() * emScale * (useTwips ? ISWFConstants.TWIPS_PER_PIXEL : 1)); + + //Ignore JDK leading values, they never match Flash MX - using an estimation technique similar to Breeze instead. + //leading = (int)StrictMath.rint(fontMetrics.getLeading() * emScale); + //if (leading == 0) + lineGap = (int)Math.rint(Math.abs((getPointSize() - ascent - descent))); + } + + public boolean canDisplay(char c) + { + return font.canDisplay(c); + } + + public String getFamily() + { + return font.getName(); + } + + public int getFirstChar() + { + return 0; + } + + public GlyphEntry getGlyphEntry(char c) + { + return (GlyphEntry)glyphCache.get(c); + } + + protected GlyphEntry createGlyphEntry(char c) + { + return createGlyphEntry(c, c); + } + + public GlyphEntry createGlyphEntry(char c, char referenceChar) + { + Shape swfShape = null; + int advance = 0; + + GlyphVector gv = font.createGlyphVector(frc, new char[]{referenceChar}); + java.awt.Shape glyphOutline = gv.getGlyphOutline(0); + + GlyphMetrics metrics = gv.getGlyphMetrics(0); + advance = (int)Math.rint(metrics.getAdvance()); //Do not scale here, DefineText needs values unscaled + + java.awt.Shape scaledShape = scaleTransform.createTransformedShape(glyphOutline); + swfShape = createGlyphShape(scaledShape); + + GlyphEntry ge = new GlyphEntry(); + ge = new GlyphEntry(); //Note: we will set the index on building DefineFont2 tag + ge.setGlyphAdvance((int)(advance * emScale * (useTwips ? ISWFConstants.TWIPS_PER_PIXEL : 1))); + ge.shape = swfShape; + //Glyph bounds are not used by the Flash Player so no need to calculate + //Rectangle2D bs = scaledShape.getBounds2D(); + //bounds = new Rect((int)StrictMath.rint(bs.getMinX() * (useTwips ? SwfConstants.TWIPS_PER_PIXEL : 1)), + // (int)StrictMath.rint(bs.getMaxX() * (useTwips ? SwfConstants.TWIPS_PER_PIXEL : 1)), + // (int)StrictMath.rint(bs.getMinY() * (useTwips ? SwfConstants.TWIPS_PER_PIXEL : 1)), + // (int)StrictMath.rint(bs.getMaxY()) * (useTwips ? SwfConstants.TWIPS_PER_PIXEL : 1)); + //ge.bounds = bounds; + ge.character = c; + + return ge; + } + + private Shape createGlyphShape(java.awt.Shape outline) + { + ShapeBuilder shape = new ShapeBuilder(useTwips); + shape.setCurrentLineStyle(0); + shape.setCurrentFillStyle1(1); + shape.setUseFillStyle1(true); + shape.setFont12or3(true); + shape.processShape(new PathIteratorWrapper(outline.getPathIterator(null))); + + return shape.build(); + } + + public int getAdvance(char c) + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public Font getFont() + { + return font; + } + + public int getMissingGlyphCode() + { + return font.getMissingGlyphCode(); + } + + public double getPointSize() + { + return font.getSize2D(); + } + + public FontRenderContext getFontRenderContext() + { + return frc; + } + + public int getAscent() + { + return ascent; + } + + public int getDescent() + { + return descent; + } + + public int getLineGap() + { + return lineGap; + } + + public int getNumGlyphs() + { + return font.getNumGlyphs(); + } + + public double getEmScale() + { + return emScale; + } + + public String getPostscriptName() + { + return font.getPSName(); + } + + private Font font; + private static Graphics2D graphics; + private FontRenderContext frc; + private FontMetrics fontMetrics; + private int ascent; + private int descent; + private int lineGap; + private double emScale; + private AffineTransform scaleTransform; + } +} diff --git a/compiler/src/main/java/org/apache/royale/compiler/fonts/LocalFont.java b/compiler/src/main/java/org/apache/royale/compiler/fonts/LocalFont.java new file mode 100644 index 000000000..0272ce0b8 --- /dev/null +++ b/compiler/src/main/java/org/apache/royale/compiler/fonts/LocalFont.java @@ -0,0 +1,80 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.royale.compiler.fonts; + +import java.io.Serializable; + + +/** + * Represents a font on the local disk. + */ +public class LocalFont implements Serializable +{ + private static final long serialVersionUID = 2175878176353318644L; + + public String postscriptName; + public String path; + public int fsType; + public String copyright; + public String trademark; + + public LocalFont(String postscriptName, String path, int fsType, String copyright, String trademark) + { + this.postscriptName = postscriptName; + this.path = path; + this.fsType = fsType; + this.copyright = copyright; + this.trademark = trademark; + } + + // we purposefully leave path out of equals() + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (o == null || getClass() != o.getClass()) + { + return false; + } + + final LocalFont localFont = (LocalFont)o; + + if (fsType != localFont.fsType) + { + return false; + } + if (copyright != null ? !copyright.equals(localFont.copyright) : localFont.copyright != null) + { + return false; + } + if (postscriptName != null ? !postscriptName.equals(localFont.postscriptName) : localFont.postscriptName != null) + { + return false; + } + if (trademark != null ? !trademark.equals(localFont.trademark) : localFont.trademark != null) + { + return false; + } + + return true; + } +} diff --git a/compiler/src/main/java/org/apache/royale/compiler/internal/embedding/EmbedData.java b/compiler/src/main/java/org/apache/royale/compiler/internal/embedding/EmbedData.java index 9aa27e9c2..bc8eb097d 100644 --- a/compiler/src/main/java/org/apache/royale/compiler/internal/embedding/EmbedData.java +++ b/compiler/src/main/java/org/apache/royale/compiler/internal/embedding/EmbedData.java @@ -40,6 +40,7 @@ import org.apache.royale.compiler.embedding.IEmbedData; import org.apache.royale.compiler.embedding.transcoders.ITranscoder; import org.apache.royale.compiler.internal.definitions.ClassDefinition; import org.apache.royale.compiler.internal.embedding.transcoders.DataTranscoder; +import org.apache.royale.compiler.internal.embedding.transcoders.FontTranscoder; import org.apache.royale.compiler.internal.embedding.transcoders.ImageTranscoder; import org.apache.royale.compiler.internal.embedding.transcoders.JPEGTranscoder; import org.apache.royale.compiler.internal.embedding.transcoders.MovieTranscoder; @@ -60,7 +61,6 @@ import org.apache.royale.compiler.problems.EmbedSourceAttributeDoesNotExistProbl import org.apache.royale.compiler.problems.EmbedUnknownAttributeProblem; import org.apache.royale.compiler.problems.EmbedUnknownMimeTypeProblem; import org.apache.royale.compiler.problems.EmbedUnrecogniedFileTypeProblem; -import org.apache.royale.compiler.problems.FontEmbeddingNotSupported; import org.apache.royale.compiler.problems.ICompilerProblem; import org.apache.royale.compiler.projects.IASProject; import org.apache.royale.compiler.projects.ICompilerProject; @@ -350,15 +350,39 @@ public class EmbedData implements IEmbedData { attributes.put(EmbedAttribute.CHARSET, value); } - else if (EmbedAttribute.ADV_ANTI_ALIASING.equals(key) || - EmbedAttribute.EMBED_AS_CFF.equals(key) || - EmbedAttribute.UNICODE_RANGE.equals(key) || - EmbedAttribute.FONT_FAMILY.equals(key) || - EmbedAttribute.FONT_NAME.equals(key) || - EmbedAttribute.FONT_STYLE.equals(key) || - EmbedAttribute.FONT_WEIGHT.equals(key) || - EmbedAttribute.SYSTEM_FONT.equals(key) || - EmbedAttribute.SOURCE_LIST.equals(key)) + else if (EmbedAttribute.ADV_ANTI_ALIASING.equals(key)) + { + attributes.put(EmbedAttribute.ADV_ANTI_ALIASING, Boolean.parseBoolean(value)); + } + else if (EmbedAttribute.EMBED_AS_CFF.equals(key)) + { + attributes.put(EmbedAttribute.EMBED_AS_CFF, Boolean.parseBoolean(value)); + } + else if (EmbedAttribute.UNICODE_RANGE.equals(key)) + { + attributes.put(EmbedAttribute.UNICODE_RANGE, value); + } + else if (EmbedAttribute.FONT_FAMILY.equals(key)) + { + attributes.put(EmbedAttribute.FONT_FAMILY, value); + } + else if (EmbedAttribute.FONT_NAME.equals(key)) + { + attributes.put(EmbedAttribute.FONT_NAME, value); + } + else if (EmbedAttribute.FONT_STYLE.equals(key)) + { + attributes.put(EmbedAttribute.FONT_STYLE, value); + } + else if (EmbedAttribute.FONT_WEIGHT.equals(key)) + { + attributes.put(EmbedAttribute.FONT_WEIGHT, value); + } + else if (EmbedAttribute.SYSTEM_FONT.equals(key)) + { + attributes.put(EmbedAttribute.SYSTEM_FONT, value); + } + else if (EmbedAttribute.SOURCE_LIST.equals(key)) { // silently ignore these, as proper problem will be reported elsewhere } @@ -404,30 +428,40 @@ public class EmbedData implements IEmbedData */ public boolean createTranscoder(ICompilerProject project, ISourceLocation location, Collection<ICompilerProblem> problems) { - // there should always be a source, with the exception of skin embedding, so don't - // create a transcoder in this error state + // there should always be a source, with the exception of skin or font + // embedding, so don't create a transcoder in this error state String source = (String)getAttribute(EmbedAttribute.SOURCE); - if (source == null && getAttribute(EmbedAttribute.SKIN_CLASS) == null) + if (source == null + && getAttribute(EmbedAttribute.SKIN_CLASS) == null + && getAttribute(EmbedAttribute.SYSTEM_FONT) == null) { problems.add(new EmbedNoSourceAttributeProblem(location)); return false; } + String uniqueName = source; - List<File> sourcePaths = ((IASProject)project).getSourcePath(); - for (File sourcePath : sourcePaths) + if (source != null) { - String sourcePathString = sourcePath.getAbsolutePath(); - if (source.startsWith(sourcePathString)) - { - uniqueName = source.substring(sourcePathString.length()); - uniqueName = uniqueName.replace("\\", "/"); - break; - } + List<File> sourcePaths = ((IASProject)project).getSourcePath(); + for (File sourcePath : sourcePaths) + { + String sourcePathString = sourcePath.getAbsolutePath(); + if (source.startsWith(sourcePathString)) + { + uniqueName = source.substring(sourcePathString.length()); + uniqueName = uniqueName.replace("\\", "/"); + break; + } + } } // also check that we have a mimetype set, as don't know what transcoder // to create without it! EmbedMIMEType mimeType = (EmbedMIMEType)getAttribute(EmbedAttribute.MIME_TYPE); + if (mimeType == null && getAttribute(EmbedAttribute.SYSTEM_FONT) != null) + { + mimeType = EmbedMIMEType.FONT; + } if (mimeType == null) { problems.add(new EmbedUnrecogniedFileTypeProblem(location, source)); @@ -490,8 +524,7 @@ public class EmbedData implements IEmbedData case FONT: case DFONT: { - problems.add(new FontEmbeddingNotSupported(location)); - transcoder = null; + transcoder = new FontTranscoder(this, workspace); break; } case TEXT: diff --git a/compiler/src/main/java/org/apache/royale/compiler/internal/embedding/transcoders/FontTranscoder.java b/compiler/src/main/java/org/apache/royale/compiler/internal/embedding/transcoders/FontTranscoder.java new file mode 100644 index 000000000..e25fde83d --- /dev/null +++ b/compiler/src/main/java/org/apache/royale/compiler/internal/embedding/transcoders/FontTranscoder.java @@ -0,0 +1,342 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.royale.compiler.internal.embedding.transcoders; + +import java.io.File; +import java.io.Serializable; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.royale.compiler.common.ISourceLocation; +import org.apache.royale.compiler.embedding.EmbedAttribute; +import org.apache.royale.compiler.filespecs.IBinaryFileSpecification; +import org.apache.royale.compiler.fonts.FontDescription; +import org.apache.royale.compiler.fonts.FontFace; +import org.apache.royale.compiler.fonts.FontManager; +import org.apache.royale.compiler.fonts.JREFontManager; +import org.apache.royale.compiler.internal.embedding.EmbedData; +import org.apache.royale.compiler.internal.workspaces.Workspace; +import org.apache.royale.compiler.problems.CffFontEmbeddingNotSupportedProblem; +import org.apache.royale.compiler.problems.EmbedCouldNotDetermineFontAliasProblem; +import org.apache.royale.compiler.problems.EmbedCouldNotDetermineFontLocationProblem; +import org.apache.royale.compiler.problems.EmbedInvalidUnicodeRangeProblem; +import org.apache.royale.compiler.problems.FileNotFoundProblem; +import org.apache.royale.compiler.problems.ICompilerProblem; +import org.apache.royale.compiler.problems.NoFontManagerProblem; +import org.apache.royale.compiler.problems.SystemFontEmbeddingNotSupportedProblem; +import org.apache.royale.compiler.problems.UnableToBuildFontProblem; +import org.apache.royale.swf.TagType; +import org.apache.royale.swf.tags.DefineFontTag; +import org.apache.royale.swf.tags.ICharacterTag; +import org.apache.royale.swf.tags.ITag; +import org.apache.royale.utils.Trace; + +/** + * Handle the embedding of fonts + */ +public class FontTranscoder extends TranscoderBase +{ + private boolean advAntiAliasing; + private boolean embedAsCff; + private boolean flashType; + private String fontFamily; + private String fontName; + private String fontStyle; + private String fontWeight; + private String systemFont; + private String unicodeRange; + + private String alias; + private List<Serializable> locations; + + /** + * Constructor. + * + * @param data The embedding data. + * @param workspace The workspace. + */ + public FontTranscoder(EmbedData data, Workspace workspace) + { + super(data, workspace); + advAntiAliasing = true; + embedAsCff = false; + flashType = true; + } + + @Override + public boolean analyze(ISourceLocation location, Collection<ICompilerProblem> problems) + { + boolean result = super.analyze(location, problems); + baseClassQName = "flash.text.Font"; + return result; + } + + @Override + protected boolean setAttribute(EmbedAttribute attribute) + { + boolean isSupported = true; + switch (attribute) + { + case ADV_ANTI_ALIASING: + advAntiAliasing = (Boolean)data.getAttribute(EmbedAttribute.ADV_ANTI_ALIASING); + break; + case EMBED_AS_CFF: + embedAsCff = (Boolean)data.getAttribute(EmbedAttribute.EMBED_AS_CFF); + break; + case FLASH_TYPE: + flashType = (Boolean)data.getAttribute(EmbedAttribute.FLASH_TYPE); + break; + case FONT_NAME: + fontName = (String)data.getAttribute(EmbedAttribute.FONT_NAME); + break; + case FONT_FAMILY: + fontFamily = (String)data.getAttribute(EmbedAttribute.FONT_FAMILY); + break; + case FONT_STYLE: + fontStyle = (String)data.getAttribute(EmbedAttribute.FONT_STYLE); + break; + case FONT_WEIGHT: + fontWeight = (String)data.getAttribute(EmbedAttribute.FONT_WEIGHT); + break; + case SYSTEM_FONT: + systemFont = (String)data.getAttribute(EmbedAttribute.SYSTEM_FONT); + break; + case UNICODE_RANGE: + unicodeRange = (String)data.getAttribute(EmbedAttribute.UNICODE_RANGE); + break; + default: + isSupported = super.setAttribute(attribute); + } + + return isSupported; + } + + @Override + protected boolean checkAttributeValues(ISourceLocation location, Collection<ICompilerProblem> problems) + { + boolean result = super.checkAttributeValues(location, problems); + if (!result) + return false; + + alias = fontName; + if (alias == null) + { + alias = systemFont; + } + if (alias == null) + { + alias = fontFamily; + } + if (alias == null) + { + problems.add(new EmbedCouldNotDetermineFontAliasProblem(location)); + return false; + } + + if (source != null) + { + if (systemFont == null) + { + IBinaryFileSpecification fileSpec = workspace.getLatestBinaryFileSpecification(source); + if (fileSpec != null) + { + File file = new File(fileSpec.getPath()); + if (!file.exists()) + { + problems.add(new FileNotFoundProblem(location, file.getAbsolutePath())); + return false; + } + URI uri = file.toURI(); + try + { + URL url = uri.toURL(); + locations = new LinkedList<Serializable>(); + locations.add(url); + } + catch (MalformedURLException e) + { + problems.add(new UnableToBuildFontProblem(alias)); + return false; + } + } + } + } + else if (systemFont != null) + { + // the compilation unit needs a file path, which is normally + // derived from source. + problems.add(new SystemFontEmbeddingNotSupportedProblem(location, systemFont)); + return false; + } + + if (locations == null) + { + problems.add(new EmbedCouldNotDetermineFontLocationProblem(location)); + return false; + } + + return result; + } + + @Override + protected Map<String, ICharacterTag> doTranscode(Collection<ITag> tags, Collection<ICompilerProblem> problems) + { + FontDescription fontDesc = new FontDescription(); + fontDesc.alias = alias; + fontDesc.style = getFontStyle(fontWeight, fontStyle); + fontDesc.unicodeRanges = unicodeRange; + fontDesc.advancedAntiAliasing = data.getAttribute(EmbedAttribute.ADV_ANTI_ALIASING) != null ? advAntiAliasing : flashType; + fontDesc.compactFontFormat = embedAsCff; + + DefineFontTag assetTag = buildFont(fontDesc, locations, problems); + if (assetTag == null) + return null; + + Map<String, ICharacterTag> symbolTags = Collections.singletonMap(data.getQName(), (ICharacterTag)assetTag); + return symbolTags; + } + + private DefineFontTag buildFont(FontDescription fontDesc, List<Serializable> locations, Collection<ICompilerProblem> problems) + { + FontManager fontManager = new JREFontManager(); + + DefineFontTag defineFontTag = null; + for (Iterator<Serializable> it = locations.iterator(); it.hasNext();) + { + Object fontSource = it.next(); + try + { + // For now, keep the Flex 3 behavior of throwing errors for each + // location when no FontManager exists. + if (fontManager == null) + { + problems.add(new NoFontManagerProblem()); + return null; + } + + fontDesc.source = fontSource; + defineFontTag = fontManager.createDefineFont(TagType.DefineFont3.getValue(), fontDesc); + } + catch (FontManager.EmbedAsCffNotSupportedException e) + { + problems.add(new CffFontEmbeddingNotSupportedProblem(alias)); + return null; + } + catch (FontManager.SystemFontNotSupportedException e) + { + problems.add(new SystemFontEmbeddingNotSupportedProblem(systemFont)); + return null; + } + catch (FontManager.InvalidUnicodeRangeException e) + { + // For now, keep the Flex 3 error message for invalid unicode + // ranges... + problems.add(new EmbedInvalidUnicodeRangeProblem(e.range)); + return null; + } + catch (Exception e) + { + if (Trace.error) + { + e.printStackTrace(); + } + problems.add(new UnableToBuildFontProblem(fontDesc.alias)); + return null; + } + + } + + if (defineFontTag == null) + { + problems.add(new UnableToBuildFontProblem(fontDesc.alias)); + } + return defineFontTag; + } + + public static int getFontStyle(String weight, String style) + { + int s = FontFace.PLAIN; + + if (style == null) + style = "normal"; + + if (weight == null) + weight = "normal"; + + if (isBold( weight )) + s += FontFace.BOLD; + + if (isItalic( style )) + s += FontFace.ITALIC; + + return s; + } + + public static boolean isBold(String value) + { + boolean bold = false; + + if (value != null) + { + String b = value.trim().toLowerCase(); + if (b.startsWith("bold")) + { + bold = true; + } + else + { + try + { + int w = Integer.parseInt(b); + if (w >= 700) + bold = true; + } + catch (Throwable t) + { + } + } + } + + return bold; + } + + public static boolean isItalic(String value) + { + boolean italic = false; + + if (value != null) + { + String ital = value.trim().toLowerCase(); + if (ital.equals("italic") || ital.equals("oblique")) + italic = true; + } + + return italic; + } + +} + diff --git a/compiler/src/main/java/org/apache/royale/compiler/internal/embedding/transcoders/TranscoderBase.java b/compiler/src/main/java/org/apache/royale/compiler/internal/embedding/transcoders/TranscoderBase.java index 60fa3700f..e68a53cd3 100644 --- a/compiler/src/main/java/org/apache/royale/compiler/internal/embedding/transcoders/TranscoderBase.java +++ b/compiler/src/main/java/org/apache/royale/compiler/internal/embedding/transcoders/TranscoderBase.java @@ -326,7 +326,14 @@ public abstract class TranscoderBase implements ITranscoder return false; TranscoderBase t = (TranscoderBase)o; - if (!source.equals(t.source) || + if (source == null) + { + if (t.source != null) + { + return false; + } + } + else if (!source.equals(t.source) || !baseClassQName.equals(t.baseClassQName) || mimeType != t.mimeType) { diff --git a/compiler/src/main/java/org/apache/royale/compiler/problems/FontEmbeddingNotSupported.java b/compiler/src/main/java/org/apache/royale/compiler/problems/CffFontEmbeddingNotSupportedProblem.java similarity index 63% copy from compiler/src/main/java/org/apache/royale/compiler/problems/FontEmbeddingNotSupported.java copy to compiler/src/main/java/org/apache/royale/compiler/problems/CffFontEmbeddingNotSupportedProblem.java index 1af2ff38f..d3b4162f8 100644 --- a/compiler/src/main/java/org/apache/royale/compiler/problems/FontEmbeddingNotSupported.java +++ b/compiler/src/main/java/org/apache/royale/compiler/problems/CffFontEmbeddingNotSupportedProblem.java @@ -19,24 +19,20 @@ package org.apache.royale.compiler.problems; -import org.apache.royale.compiler.common.ISourceLocation; - /** - * Problem created when a user tried to embed a font. + * Problem created when a user tried to embed a CFF font. */ -public class FontEmbeddingNotSupported extends CompilerProblem +public class CffFontEmbeddingNotSupportedProblem extends CompilerProblem { public static final String DESCRIPTION = - "The direct embedding of fonts is not supported. Use the ${FONTSWF} utility and embed the resulting ${SWF}."; - - public static final int errorCode = 5034; + "Failed to embed font '${fontName}' because ${embedAsCff} is not supported."; - public FontEmbeddingNotSupported(ISourceLocation site) + public CffFontEmbeddingNotSupportedProblem(String fontName) { - super(site); + super(); + this.fontName = fontName; } - // Prevent these from being localized. - public final String FONTSWF = "fontswf"; - public final String SWF = "SWF"; + public final String fontName; + public final String embedAsCff = "embedAsCff"; } diff --git a/compiler/src/main/java/org/apache/royale/compiler/problems/FontEmbeddingNotSupported.java b/compiler/src/main/java/org/apache/royale/compiler/problems/EmbedCouldNotDetermineFontAliasProblem.java similarity index 67% copy from compiler/src/main/java/org/apache/royale/compiler/problems/FontEmbeddingNotSupported.java copy to compiler/src/main/java/org/apache/royale/compiler/problems/EmbedCouldNotDetermineFontAliasProblem.java index 1af2ff38f..b79a95858 100644 --- a/compiler/src/main/java/org/apache/royale/compiler/problems/FontEmbeddingNotSupported.java +++ b/compiler/src/main/java/org/apache/royale/compiler/problems/EmbedCouldNotDetermineFontAliasProblem.java @@ -22,21 +22,16 @@ package org.apache.royale.compiler.problems; import org.apache.royale.compiler.common.ISourceLocation; /** - * Problem created when a user tried to embed a font. + * This problem gets created when none of the fontName, fontFamily, and + * systemFont attributes are set when embedding a font. */ -public class FontEmbeddingNotSupported extends CompilerProblem +public final class EmbedCouldNotDetermineFontAliasProblem extends CompilerProblem { public static final String DESCRIPTION = - "The direct embedding of fonts is not supported. Use the ${FONTSWF} utility and embed the resulting ${SWF}."; + "Could not determine the embedded font alias from fontName, fontFamily, and systemFont"; - public static final int errorCode = 5034; - - public FontEmbeddingNotSupported(ISourceLocation site) + public EmbedCouldNotDetermineFontAliasProblem(ISourceLocation location) { - super(site); + super(location); } - - // Prevent these from being localized. - public final String FONTSWF = "fontswf"; - public final String SWF = "SWF"; } diff --git a/compiler/src/main/java/org/apache/royale/compiler/problems/FontEmbeddingNotSupported.java b/compiler/src/main/java/org/apache/royale/compiler/problems/EmbedCouldNotDetermineFontLocationProblem.java similarity index 67% copy from compiler/src/main/java/org/apache/royale/compiler/problems/FontEmbeddingNotSupported.java copy to compiler/src/main/java/org/apache/royale/compiler/problems/EmbedCouldNotDetermineFontLocationProblem.java index 1af2ff38f..d50da0c94 100644 --- a/compiler/src/main/java/org/apache/royale/compiler/problems/FontEmbeddingNotSupported.java +++ b/compiler/src/main/java/org/apache/royale/compiler/problems/EmbedCouldNotDetermineFontLocationProblem.java @@ -22,21 +22,16 @@ package org.apache.royale.compiler.problems; import org.apache.royale.compiler.common.ISourceLocation; /** - * Problem created when a user tried to embed a font. + * This problem gets created when source, systemFonts, and sourceList attributes + * conflict when embedding a font. */ -public class FontEmbeddingNotSupported extends CompilerProblem +public final class EmbedCouldNotDetermineFontLocationProblem extends CompilerProblem { public static final String DESCRIPTION = - "The direct embedding of fonts is not supported. Use the ${FONTSWF} utility and embed the resulting ${SWF}."; + "Could not determine the embedded font location"; - public static final int errorCode = 5034; - - public FontEmbeddingNotSupported(ISourceLocation site) + public EmbedCouldNotDetermineFontLocationProblem(ISourceLocation location) { - super(site); + super(location); } - - // Prevent these from being localized. - public final String FONTSWF = "fontswf"; - public final String SWF = "SWF"; } diff --git a/compiler/src/main/java/org/apache/royale/compiler/problems/FontEmbeddingNotSupported.java b/compiler/src/main/java/org/apache/royale/compiler/problems/NoFontManagerProblem.java similarity index 62% copy from compiler/src/main/java/org/apache/royale/compiler/problems/FontEmbeddingNotSupported.java copy to compiler/src/main/java/org/apache/royale/compiler/problems/NoFontManagerProblem.java index 1af2ff38f..af43db1f7 100644 --- a/compiler/src/main/java/org/apache/royale/compiler/problems/FontEmbeddingNotSupported.java +++ b/compiler/src/main/java/org/apache/royale/compiler/problems/NoFontManagerProblem.java @@ -19,24 +19,13 @@ package org.apache.royale.compiler.problems; -import org.apache.royale.compiler.common.ISourceLocation; - -/** - * Problem created when a user tried to embed a font. - */ -public class FontEmbeddingNotSupported extends CompilerProblem +public final class NoFontManagerProblem extends CompilerProblem { public static final String DESCRIPTION = - "The direct embedding of fonts is not supported. Use the ${FONTSWF} utility and embed the resulting ${SWF}."; - - public static final int errorCode = 5034; - - public FontEmbeddingNotSupported(ISourceLocation site) + "No FontManager provided. Cannot build font."; + + public NoFontManagerProblem() { - super(site); + super(); } - - // Prevent these from being localized. - public final String FONTSWF = "fontswf"; - public final String SWF = "SWF"; } diff --git a/compiler/src/main/java/org/apache/royale/compiler/problems/FontEmbeddingNotSupported.java b/compiler/src/main/java/org/apache/royale/compiler/problems/SystemFontEmbeddingNotSupportedProblem.java similarity index 65% copy from compiler/src/main/java/org/apache/royale/compiler/problems/FontEmbeddingNotSupported.java copy to compiler/src/main/java/org/apache/royale/compiler/problems/SystemFontEmbeddingNotSupportedProblem.java index 1af2ff38f..554a346a8 100644 --- a/compiler/src/main/java/org/apache/royale/compiler/problems/FontEmbeddingNotSupported.java +++ b/compiler/src/main/java/org/apache/royale/compiler/problems/SystemFontEmbeddingNotSupportedProblem.java @@ -22,21 +22,23 @@ package org.apache.royale.compiler.problems; import org.apache.royale.compiler.common.ISourceLocation; /** - * Problem created when a user tried to embed a font. + * Problem created when a user tried to embed a system font. */ -public class FontEmbeddingNotSupported extends CompilerProblem +public class SystemFontEmbeddingNotSupportedProblem extends CompilerProblem { public static final String DESCRIPTION = - "The direct embedding of fonts is not supported. Use the ${FONTSWF} utility and embed the resulting ${SWF}."; + "Failed to embed font '${fontName}'. Embedding of system fonts is not supported."; - public static final int errorCode = 5034; + public SystemFontEmbeddingNotSupportedProblem(ISourceLocation location, String fontName) + { + super(location); + this.fontName = fontName; + } - public FontEmbeddingNotSupported(ISourceLocation site) + public SystemFontEmbeddingNotSupportedProblem(String fontName) { - super(site); + this(null, fontName); } - // Prevent these from being localized. - public final String FONTSWF = "fontswf"; - public final String SWF = "SWF"; + public final String fontName; } diff --git a/compiler/src/main/java/org/apache/royale/compiler/problems/FontEmbeddingNotSupported.java b/compiler/src/main/java/org/apache/royale/compiler/problems/UnableToBuildFontProblem.java similarity index 62% copy from compiler/src/main/java/org/apache/royale/compiler/problems/FontEmbeddingNotSupported.java copy to compiler/src/main/java/org/apache/royale/compiler/problems/UnableToBuildFontProblem.java index 1af2ff38f..fc05bf204 100644 --- a/compiler/src/main/java/org/apache/royale/compiler/problems/FontEmbeddingNotSupported.java +++ b/compiler/src/main/java/org/apache/royale/compiler/problems/UnableToBuildFontProblem.java @@ -19,24 +19,16 @@ package org.apache.royale.compiler.problems; -import org.apache.royale.compiler.common.ISourceLocation; - -/** - * Problem created when a user tried to embed a font. - */ -public class FontEmbeddingNotSupported extends CompilerProblem +public final class UnableToBuildFontProblem extends CompilerProblem { public static final String DESCRIPTION = - "The direct embedding of fonts is not supported. Use the ${FONTSWF} utility and embed the resulting ${SWF}."; - - public static final int errorCode = 5034; + "Unable to build font ${fontName}"; - public FontEmbeddingNotSupported(ISourceLocation site) + public UnableToBuildFontProblem(String fontName) { - super(site); + super(); + this.fontName = fontName; } - // Prevent these from being localized. - public final String FONTSWF = "fontswf"; - public final String SWF = "SWF"; + public final String fontName; } diff --git a/compiler/src/main/java/org/apache/royale/swf/builders/FontBuilder.java b/compiler/src/main/java/org/apache/royale/swf/builders/FontBuilder.java new file mode 100644 index 000000000..5c388ec86 --- /dev/null +++ b/compiler/src/main/java/org/apache/royale/swf/builders/FontBuilder.java @@ -0,0 +1,561 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.royale.swf.builders; + +import java.net.URL; +import java.util.Iterator; +import java.util.Map; + +import org.apache.royale.compiler.fonts.FSType; +import org.apache.royale.compiler.fonts.FontFace; +import org.apache.royale.compiler.fonts.FontManager; +import org.apache.royale.swf.TagType; +import org.apache.royale.swf.tags.DefineFont2Tag; +import org.apache.royale.swf.tags.DefineFont3Tag; +import org.apache.royale.swf.tags.DefineFontAlignZonesTag; +import org.apache.royale.swf.tags.DefineFontNameTag; +import org.apache.royale.swf.tags.DefineFontTag; +import org.apache.royale.swf.types.GlyphEntry; +import org.apache.royale.swf.types.KerningRecord; +import org.apache.royale.swf.types.Rect; +import org.apache.royale.swf.types.Shape; +import org.apache.royale.swf.types.ZoneRecord; +import org.apache.royale.utils.IntMap; +import org.apache.royale.utils.Trace; + +/** + * A utility class to build a DefineFont2 or DefineFont3 tag. One + * must supply a font family name and style to establish a default + * font face and a <code>FontManager</code> to locate and cache fonts + * and glyphs. + */ +@SuppressWarnings("unchecked") +public final class FontBuilder implements ITagBuilder +{ + public DefineFont2Tag tag; + + private boolean flashType; + private IntMap glyphEntryMap; // Code-point ordered collection of glyphs + private FontFace defaultFace; + private double fontHeight; + private ZoneRecordBuilder zoneRecordBuilder; + + private static final Rect IDENTITY_RECT = new Rect(0, 0, 0, 0); + private static boolean useLicenseTag = true; + + private FontBuilder(int code, boolean hasLayout, boolean useFlashType) + { + TagType tagType = TagType.getTagType(code); + switch (tagType) + { + case DefineFont2: + tag = new DefineFont2Tag(); + break; + case DefineFont3: + tag = new DefineFont3Tag(); + break; + default: + throw new SWFFontNotSupportedException("Cannot build DefineFont for SWF tag code " + code); + } + + tag.setFontFlagsHasLayout(hasLayout); + glyphEntryMap = new IntMap(100); //Sorted by code point order + flashType = useFlashType; + } + + /** + * Build a DefineFont2 or DefineFont3 tag for a given FontFace. + * + * Note that with this constructor, hasLayout is assumed to be true + * but flashType is assumed to be false. + * + * @param code Determines the version of DefineFont SWF tag to use. + * @param fontFace The FontFace to build into a DefineFont tag. + * @param alias The name used to bind a DefineFont tag to other SWF tags + * (such as DefineEditText). + * @deprecated + */ + public FontBuilder(int code, FontFace fontFace, String alias) + { + this(code, fontFace, alias, false); + } + + /** + * Build a DefineFont2 or DefineFont3 tag for a given FontFace. + * + * Note that with this constructor, hasLayout is assumed to be true. + * + * @param code Determines the version of DefineFont SWF tag to use. + * @param fontFace The FontFace to build into a DefineFont tag. + * @param alias The name used to bind a DefineFont tag to other SWF tags + * (such as DefineEditText). + */ + public FontBuilder(int code, FontFace fontFace, String alias, boolean flashType) + { + this(code, true, flashType); + defaultFace = fontFace; + + if (Trace.font) + Trace.trace("Initializing font '" + fontFace.getFamily() + "' as '" + alias + "'"); + + init(alias); + } + + /** + * Build a DefineFont2 or DefineFont3 tag from a system font by family name. + * + * @param code Determines the version of DefineFont SWF tag to use. + * @param manager A FontManager resolves the fontFamily and style to + * a FontFace. + * @param alias The name used to bind a DefineFont tag to other SWF tags + * (such as DefineEditText). + * @param fontFamily The name of the font family. + * @param style An integer describing the style variant of the FontFace, + * either plain, bold, italic, or bolditalic. + * @param hasLayout Determines whether font layout metrics should be encoded. + * @param flashType Determines whether FlashType advanced anti-aliasing + * information should be included. + */ + public FontBuilder(int code, FontManager manager, String alias, + String fontFamily, int style, boolean hasLayout, boolean flashType) + { + this(code, hasLayout, flashType); + + if (manager == null) + throw new NoFontManagerException(); + + if (Trace.font) + Trace.trace("Locating font using FontManager '" + manager.getClass().getName() + "'"); + + TagType tagType = TagType.getTagType(code); + boolean useTwips = !TagType.DefineFont.equals(tagType) && !TagType.DefineFont2.equals(tagType); + FontFace fontFace = manager.getEntryFromSystem(fontFamily, style, useTwips); + + if (fontFace == null) + FontManager.throwFontNotFound(alias, fontFamily, style, null); + + if (Trace.font) + Trace.trace("Initializing font '" + fontFamily + "' as '" + alias + "'"); + + defaultFace = fontFace; + + init(alias); + } + + /** + * Load a font from a URL + * + * @param code + * @param alias The name used to bind a DefineFont tag to a DefineEditText tag. + * @param location remote url or a relative, local file path + * @param style + * @param hasLayout + */ + public FontBuilder(int code, FontManager manager, String alias, + URL location, int style, boolean hasLayout, boolean flashType) + { + this(code, hasLayout, flashType); + + if (manager == null) + throw new NoFontManagerException(); + + if (Trace.font) + Trace.trace("Locating font using FontManager '" + manager.getClass().getName() + "'"); + + TagType tagType = TagType.getTagType(code); + boolean useTwips = !TagType.DefineFont.equals(tagType) && !TagType.DefineFont2.equals(tagType); + FontFace fontFace = manager.getEntryFromLocation(location, style, useTwips); + + if (fontFace == null) + FontManager.throwFontNotFound(alias, null, style, location.toString()); + + if (Trace.font) + Trace.trace("Initializing font at '" + location.toString() + "' as '" + alias + "'"); + + this.defaultFace = fontFace; + + init(alias); + } + + private void init(String alias) + { + fontHeight = defaultFace.getPointSize(); + + if (!TagType.DefineFont.equals(tag.getTagType())) + { + tag.setFontName(alias); + tag.setFontFlagsBold(defaultFace.isBold()); + tag.setFontFlagsItalic(defaultFace.isItalic()); + + if (tag.isFontFlagsHasLayout()) + { + tag.setFontAscent(defaultFace.getAscent()); + tag.setFontDescent(defaultFace.getDescent()); + tag.setFontLeading(defaultFace.getLineGap()); + + if (Trace.font) + { + Trace.trace("\tBold: " + tag.isFontFlagsBold()); + Trace.trace("\tItalic: " + tag.isFontFlagsItalic()); + Trace.trace("\tAscent: " + tag.getFontAscent()); + Trace.trace("\tDescent: " + tag.getFontDescent()); + Trace.trace("\tLeading: " + tag.getFontLeading()); + } + } + } + + // If flashType enabled we must have z, Z, l, L + if (flashType) + { + GlyphEntry adfGE = defaultFace.getGlyphEntry('z'); + if (adfGE == null) + flashType = false; + + adfGE = defaultFace.getGlyphEntry('Z'); + if (adfGE == null) + flashType = false; + + adfGE = defaultFace.getGlyphEntry('l'); + if (adfGE == null) + flashType = false; + + adfGE = defaultFace.getGlyphEntry('L'); + if (adfGE == null) + flashType = false; + } + + if (flashType) + { + zoneRecordBuilder = ZoneRecordBuilder.createInstance(); + if (zoneRecordBuilder != null) + { + zoneRecordBuilder.setFontAlias(alias); + zoneRecordBuilder.setFontBuilder(this); + zoneRecordBuilder.setFontFace(defaultFace); + } + else + { + // FlashType Zone Records are not available, so we should + // disable flashType + flashType = false; + } + } + + addChar(' '); // Add at least a space char by default + } + + /** + * Creates a DefineFont2 or DefineFont3 tag depending on the code specified + * on construction. + */ + public DefineFontTag build() + { + int count = glyphEntryMap.size(); + + if (Trace.font) + Trace.trace("Building font '" + tag.getFontName() + "' with " + count + " characters."); + + if (flashType && tag instanceof DefineFont3Tag) + { + DefineFont3Tag df3 = (DefineFont3Tag)tag; + DefineFontAlignZonesTag zones = new DefineFontAlignZonesTag(); + zones.setFontTag(df3); + zones.setZoneTable(new ZoneRecord[count]); + zones.setCsmTableHint(1); + df3.setZones(zones); + } + + tag.setGlyphShapeTable(new Shape[count]); + + if (!TagType.DefineFont.equals(tag.getTagType())) + { + tag.setCodeTable(new int[count]); + tag.setNumGlyphs(count); + + if (tag.isFontFlagsHasLayout()) + { + tag.setFontAdvanceTable(new int[count]); + tag.setFontBoundsTable(new Rect[count]); + } + } + + // Process each GlyphEntry + Iterator it = glyphEntryMap.iterator(); + int i = 0; + + // long flashTypeTime = 0; + while (it.hasNext() && i < count) + { + GlyphEntry ge = (GlyphEntry)((Map.Entry)it.next()).getValue(); + + if (flashType && tag instanceof DefineFont3Tag) + { + ((DefineFont3Tag)tag).getZones().getZoneTable()[i] = ge.zoneRecord; + } + + // Note: offsets to shape table entries calculated on encoding + tag.getGlyphShapeTable()[i] = ge.shape; + + // IMPORTANT! Update GlyphEntry Index + ge.setGlyphIndex(i); + + // DEFINEFONT2/3 specific properties + if (!TagType.DefineFont.equals(tag.getTagType())) + { + tag.getCodeTable()[i] = ge.character; // unsigned code point + + // Layout information + if (tag.isFontFlagsHasLayout()) + { + tag.getFontAdvanceTable()[i] = (short)ge.getGlyphAdvance(); //advance in emScale + // The player doesn't need ge.bounds, so we ignore it. + // We must still generate this value, however, for ADF use. + tag.getFontBoundsTable()[i] = IDENTITY_RECT; + } + else + { + if (Trace.font) + Trace.trace("Warning: font tag created without layout information."); + } + } + + i++; + } + + if (tag.isFontFlagsHasLayout()) + { + tag.setFontKerningTable(new KerningRecord[0]); + } + + if (TagType.DefineFont3.equals(tag.getTagType())) + { + tag.setFontFlagsWideCodes(true); + } + else if (!tag.isFontFlagsWideCodes()) + { + int[] codeTable = tag.getCodeTable(); + for (int j = 0; j < codeTable.length; j++) + { + if (codeTable[i] > 255) + { + tag.setFontFlagsWideCodes(true); + break; + } + } + } + + // FIXME: we should allow the user to set the language code + //tag.langCode = 1; + + // if we have any license info, create a DefineFontName tag + if (useLicenseTag && ((getFSType() != null && ! getFSType().installable) || getCopyright() != null || getName() != null)) + { + DefineFontNameTag license = new DefineFontNameTag(); + license.setFontTag(tag); + license.setFontName(getName()); + license.setFontCopyright(getCopyright()); + tag.setLicense(license); + } + + return tag; + } + + /** + * Adds all supported characters from 0 to the highest glyph + * contained in the default font face. + */ + public void addAllChars() + { + addAllChars(defaultFace); + } + + /** + * Adds all supported characters from 0 to the highest glyph + * contained in the given font face. + * + * @param face + */ + public void addAllChars(FontFace face) + { + int min = face.getFirstChar(); + int count = face.getNumGlyphs(); + + if (Trace.font) + Trace.trace("\tAdding " + count + " chars, starting from " + min); + + addCharset(min, count); + } + + /** + * Adds supported characters in the specified range from the default + * font face. + * + * @param fromChar + * @param count + */ + public void addCharset(int fromChar, int count) + { + addCharset(defaultFace, fromChar, count); + } + + /** + * Adds supported characters in the specified range from the given + * font face. + * + * @param face + * @param fromChar + * @param count + */ + public void addCharset(FontFace face, int fromChar, int count) + { + int remaining = count; + + for (int i = fromChar; remaining > 0 && i < Character.MAX_VALUE; i++) + { + char c = (char)i; + GlyphEntry ge = addChar(face, c); + if (ge != null) + { + remaining--; + } + } + } + + /** + * Adds all supported characters in the given array from the + * default font face. + * + * @param chars + */ + public void addCharset(char[] chars) + { + addCharset(defaultFace, chars); + } + + /** + * Adds all supported characters in array from the given font face. + * + * @param face + * @param chars + */ + public void addCharset(FontFace face, char[] chars) + { + //TODO: Sort before adding to optimize IntMap addition + for (int i = 0; i < chars.length; i++) + { + char c = chars[i]; + addChar(face, c); + } + } + + /** + * If supported, includes a given character from the default font face. + * + * @param c + */ + public void addChar(char c) + { + addChar(defaultFace, c); + } + + /** + * If supported, includes a character from the given font face. + * + * @param c + */ + public GlyphEntry addChar(FontFace face, char c) + { + GlyphEntry ge = (GlyphEntry)glyphEntryMap.get(c); + + if (ge == null) + { + ge = face.getGlyphEntry(c); + + if (ge != null) + { + //Add to this tag's collection + glyphEntryMap.put(c, ge); + } + } + + if (flashType && ge != null && ge.zoneRecord == null && zoneRecordBuilder != null) + { + ge.zoneRecord = zoneRecordBuilder.build(c); + } + + return ge; + } + + public String getCopyright() + { + return defaultFace.getCopyright(); + } + + public String getName() + { + return defaultFace.getFamily(); + } + + public FSType getFSType() + { + return defaultFace.getFSType(); + } + + public void setLangcode(int code) + { + if (code >= 0 && code < 6) + tag.setLanguageCode(code); + } + + public GlyphEntry getGlyph(char c) + { + return (GlyphEntry)glyphEntryMap.get(c); + } + + public double getFontHeight() + { + return fontHeight; + } + + public int size() + { + return glyphEntryMap.size(); + } + + public static final class NoFontManagerException extends RuntimeException + { + private static final long serialVersionUID = 755054716704678420L; + + public NoFontManagerException() + { + super("No FontManager provided. Cannot build font."); + } + } + + public static final class SWFFontNotSupportedException extends RuntimeException + { + private static final long serialVersionUID = -7381079883711386211L; + + public SWFFontNotSupportedException(String message) + { + super(message); + } + } +} diff --git a/compiler/src/main/java/org/apache/royale/compiler/problems/FontEmbeddingNotSupported.java b/compiler/src/main/java/org/apache/royale/swf/builders/PathIteratorWrapper.java similarity index 56% rename from compiler/src/main/java/org/apache/royale/compiler/problems/FontEmbeddingNotSupported.java rename to compiler/src/main/java/org/apache/royale/swf/builders/PathIteratorWrapper.java index 1af2ff38f..c463cfdf1 100644 --- a/compiler/src/main/java/org/apache/royale/compiler/problems/FontEmbeddingNotSupported.java +++ b/compiler/src/main/java/org/apache/royale/swf/builders/PathIteratorWrapper.java @@ -17,26 +17,37 @@ * */ -package org.apache.royale.compiler.problems; +package org.apache.royale.swf.builders; -import org.apache.royale.compiler.common.ISourceLocation; +import java.awt.geom.PathIterator; /** - * Problem created when a user tried to embed a font. + * This class wraps a PathIterator and adds a ShapeIterator + * implemenation. */ -public class FontEmbeddingNotSupported extends CompilerProblem +public class PathIteratorWrapper implements IShapeIterator { - public static final String DESCRIPTION = - "The direct embedding of fonts is not supported. Use the ${FONTSWF} utility and embed the resulting ${SWF}."; + private PathIterator pi; - public static final int errorCode = 5034; + public PathIteratorWrapper(PathIterator pi) + { + this.pi = pi; + } - public FontEmbeddingNotSupported(ISourceLocation site) - { - super(site); - } + public short currentSegment(double[] coords) + { + int code = pi.currentSegment(coords); + return (short) code; + } + + public boolean isDone() + { + return pi.isDone(); + } + + public void next() + { + pi.next(); + } - // Prevent these from being localized. - public final String FONTSWF = "fontswf"; - public final String SWF = "SWF"; } diff --git a/compiler/src/main/java/org/apache/royale/swf/builders/ZoneRecordBuilder.java b/compiler/src/main/java/org/apache/royale/swf/builders/ZoneRecordBuilder.java new file mode 100644 index 000000000..af4c467c8 --- /dev/null +++ b/compiler/src/main/java/org/apache/royale/swf/builders/ZoneRecordBuilder.java @@ -0,0 +1,84 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.royale.swf.builders; + +import org.apache.royale.compiler.fonts.FontFace; +import org.apache.royale.swf.types.ZoneRecord; +import org.apache.royale.utils.Trace; + +/** + * A simple class to decouple FlashType ZoneRecord construction from + * FontBuilder. + */ +public class ZoneRecordBuilder +{ + private static final String DEFAULT_BUILDER = "flash.fonts.flashtype.FlashTypeZoneRecordBuilder"; + + protected String fontAlias; + protected FontBuilder fontBuilder; + protected FontFace fontFace; + + protected ZoneRecordBuilder() + { + } + + public void setFontAlias(String alias) + { + fontAlias = alias; + } + + public void setFontBuilder(FontBuilder builder) + { + fontBuilder = builder; + } + + public void setFontFace(FontFace face) + { + fontFace = face; + } + + /** + * This no-op method returns an empty ZoneRecord. Subclasses should + * override this method. + */ + public ZoneRecord build(int character) + { + // Return an empty Zone Record... + ZoneRecord zoneRecord = new ZoneRecord(); + return zoneRecord; + } + + public static ZoneRecordBuilder createInstance() + { + try + { + Class c = Class.forName(DEFAULT_BUILDER); + ZoneRecordBuilder builder = (ZoneRecordBuilder)c.newInstance(); + return builder; + } + catch (Throwable t) + { + if (Trace.error) + Trace.trace("ZoneRecordBuilder implementation not found '" + DEFAULT_BUILDER + "'"); + } + + return null; + } +} diff --git a/compiler/src/main/java/org/apache/royale/swf/io/SWFWriter.java b/compiler/src/main/java/org/apache/royale/swf/io/SWFWriter.java index a5cbc3f6b..5d087883e 100644 --- a/compiler/src/main/java/org/apache/royale/swf/io/SWFWriter.java +++ b/compiler/src/main/java/org/apache/royale/swf/io/SWFWriter.java @@ -2160,8 +2160,22 @@ public class SWFWriter implements ISWFWriter private void writeDefineFontName(DefineFontNameTag tag) { tagBuffer.writeUI16(tag.getFontTag().getCharacterID()); - tagBuffer.writeString(tag.getFontName()); - tagBuffer.writeString(tag.getFontCopyright()); + if (tag.getFontName() != null) + { + tagBuffer.writeString(tag.getFontName()); + } + else + { + tagBuffer.writeString(""); + } + if (tag.getFontCopyright() != null) + { + tagBuffer.writeString(tag.getFontCopyright()); + } + else + { + tagBuffer.writeString(""); + } } private void writeDefineFontAlignZones(DefineFontAlignZonesTag tag) diff --git a/compiler/src/main/java/org/apache/royale/utils/AbstractCache.java b/compiler/src/main/java/org/apache/royale/utils/AbstractCache.java new file mode 100644 index 000000000..6058328ab --- /dev/null +++ b/compiler/src/main/java/org/apache/royale/utils/AbstractCache.java @@ -0,0 +1,134 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.royale.utils; + +import java.io.Serializable; + +/** + * Subclasses of AbstractCache implement various cache policies. + * <p> + * Users of these subclasses should override fetch() or fetchSerial() + * to provide the logic for retreiving entries that are not in cache. + * <p> + * If you override fetchSerial(), only one thread will enter fetch() + * at the same time. + */ +public abstract class AbstractCache implements Serializable +{ + private static class Lock {} + private Lock lock = new Lock(); + private Thread busyThread; + + /** + * subclasses override fetch() to fetch values that are not in + * cache. + * + * The default implementation uses a semaphore to serialize + * access to the fetchSerial method. + * + * @return the value to cache. null's are okay. + */ + protected Object fetch(Object key) + { + synchronized(lock) + { + Thread currentThread = Thread.currentThread(); + if (busyThread == currentThread) + { + throw new IllegalStateException("AbstractCache.fetch is not re-entrant"); + } + + while (busyThread != null) + { + try + { + lock.wait(); + } + catch (InterruptedException e) + { + } + } + + busyThread = currentThread; + } + + try + { + return fetchSerial(key); + } + finally + { + synchronized (lock) + { + busyThread = null; + lock.notify(); + } + } + } + + /** + * users can override this hook to add logic for fetching + * values that is automatically serialized by the base class. + */ + protected Object fetchSerial(Object key) + { + return null; + } + + /** + * retreive a value from the cache, invoking fetch() if the value is + * not there. this method is final -- implementations should + * override fetch(). + * + * Note: null values can be stored in the cache. This is useful + * for resource caches that fail-fast, when a resource doesn't + * exist. fetch() will return null, and null will be stored in the + * cache under the given key, like any other value. + */ + public abstract Object get(Object key); + + abstract public void remove(Object key); + + abstract public void put(Object key, Object value); + + abstract public void setSize(int size); + + abstract public void clear(); + + // statistics + long hits; + long misses; + long missPenalty; + + static public boolean verbose = false; + + final void report() + { + if (verbose) + { + double tries = misses+hits; + double penalty = missPenalty/(double)misses; + + System.out.println(this + " hit rate: " + (hits*100)/tries + + " " + hits + "/" + misses + + " penalty " + penalty); + } + } +} \ No newline at end of file diff --git a/compiler/src/main/java/org/apache/royale/utils/LRUCache.java b/compiler/src/main/java/org/apache/royale/utils/LRUCache.java new file mode 100644 index 000000000..e5e48d59f --- /dev/null +++ b/compiler/src/main/java/org/apache/royale/utils/LRUCache.java @@ -0,0 +1,365 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.royale.utils; + +import java.util.HashMap; +import java.util.Set; + +/** + * Caching data structure that uses a Least Recently Used (LRU) + * algorithm. This cache has a max size associated with it that is + * used to determine when objects should be purged from the cache. + * Once the max size is reached, the least recently used element will + * be purged. + * <p> + * This class is thread-safe. + */ +public abstract class LRUCache extends AbstractCache +{ + private HashMap<Object, LRUListEntry> map; + private LRUListEntry head;//the MRU element + private LRUListEntry tail;//the LRU element + private int maxSize; //the max size of this cache. If this size is exceeded, LRU elements will be purged to free cache space. + private int purgeSize = 1;//number of objects to purge when the max size is reached. + + /** + * Create a new LRU cache. + * + * @param initialSize the initial size of the IntMap + * @param maxSize the maximum number of elements this cache can hold before purging LRU elements. + */ + public LRUCache(int initialSize, int maxSize) + { + this(initialSize, maxSize, 1); + } + + /** + * Create a new LRU cache. + * This constructor takes a purgeSize parameter that is used to increase the number of LRU + * elements to when the cache exceeds its max size. Increasing the purge size can reduce the + * overhead of using the cache if the cache size is maxed out frequently. + * + * @param initialSize the initial size of the IntMap + * @param maxSize the maximum number of elements this cache can hold before purging LRU elements. + * @param purgeSize the number of LRU elements to purge once the max size is exceeded. + */ + public LRUCache(int initialSize, int maxSize, int purgeSize) + { + super(); + this.maxSize = maxSize; + this.purgeSize = purgeSize; + map = new HashMap<Object, LRUListEntry>(initialSize); + head = null; + tail = null; + } + + /** + * Retrieve the complete set of entries from this LRUCache. + * + * @return a Set of Map.Entry + */ + public synchronized Set entrySet() + { + return map.entrySet(); + } + + /** + * Retrieve an object from the cache using the specified key. + * Use of this method will make the retrieved object the + * most recently used object. + */ + public Object get(Object key) + { + Object value = null; + + synchronized (this) + { + //use a fast compare to see if this key matches the head object. + //This trick improves performance drastically in situations where + //the same object is looked up consecutively. This trick is especially + //effective on quoted string keys because the vm optimizes them to use the + //same memory location and therefore the == operator returns true. + if (head != null && key == head.key) + { + return head.value; + } + + LRUListEntry entry = map.get(key); + + if (entry != null /* && (value = entry.value) != null */) + { + hits++; + entry.hits++; + + //move this key to the front of the use list + setMostRecentlyUsed(entry); + return entry.value; + } + else + { + // not in cache, go fetch it + misses++; + rmStats(entry); + } + } + + long before = System.currentTimeMillis(); + + // don't hold the lock while fetching + value = fetch(key); + + int penalty = (int)(System.currentTimeMillis() - before); + missPenalty += penalty; + + put(key, value); + + return value; + } + + /** + * Insert an object into the cache. + */ + public void put(Object key, Object value) + { + //create a new list entry + LRUListEntry entry = new LRUListEntry(); + + //set the entry's value to be the new key. + entry.value = value; + entry.key = key; + + synchronized (this) + { + //insert the entry into the table + remove(key); + map.put(key, entry); + + //move the new entry to the front of the list + setMostRecentlyUsed(entry); + if (tail == null) + { + tail = entry; + } + + //purge condition + if (map.size() > maxSize) + { + //purge the LRU elements to free up space for more elements. + purgeLRUElements(); + } + + rmStats(entry); + } + } + + /** + * Remove an object from the cache. + */ + public void remove(Object key) + { + synchronized (this) + { + LRUListEntry entry = map.remove(key); + if (entry != null) + { + if (entry == head) + { + head = entry.next; + } + if (entry == tail) + { + tail = entry.prev; + } + if (entry.prev != null) + { + entry.prev.next = entry.next; + assert (entry.prev.next != entry.prev); + } + if (entry.next != null) + { + entry.next.prev = entry.prev; + assert (entry.next.prev != entry.next); + } + } + + rmStats(entry); + } + } + + public void setSize(int size) + { +//HashMap grows independently, can not control? + } + + public synchronized void clear() + { + map.clear(); + head = null; + tail = null; + } + + /** + * Returns the number of elements currently in the cache. + */ + public int size() + { + int s = map.size(); + LRUListEntry e = head; + int num = 0; + while (e != null) + { + num += 1; + e = e.next; + } + if (s != num) + { + assert false : ("Memory leak in LRUCache!"); + } + return s; + } + + /** + * Returns the number of elements that this cache can hold. + * Once more than this number of elements is added to the cache, + * the least recently used element will automatically be removed. + */ + public int getMaxSize() + { + return maxSize; + } + + /** + * Returns the number of LRU elements to purge when the max size is reached. + */ + public int getPurgeSize() + { + return purgeSize; + } + + /** + * Handler hook to signal subclasses that the LRU element has been purged from the cache. + * This method is triggered when the cache's max size has been exceeded and the LRU + * element must be removed from the cache. This method is invoked after the LRU element + * has been removed from the cache. + * + * @param key the insertion key that was originally used to add value to the cache. + * @param value the value element bound to the key. + */ + protected void handleLRUElementPurged(Object key, Object value) + { + } + + private void rmStats(LRUListEntry ref) + { + if (ref != null) + { + hits -= ref.hits; + misses--; + if (ref.penalty != -1) + missPenalty -= ref.penalty; + } + } + + /* + * Remove the least recently used elements to make space for another element. + * This purge will dump + */ + private void purgeLRUElements() + { + //purge the number of LRU elements specified by the purgeSize. + for (int i = 0; i < purgeSize && tail != null; i++) + { + Object key = tail.key; + Object value = tail.value; + remove(tail.key); + + //signal the subclass that the LRU element has been purged. + handleLRUElementPurged(key, value); + } + } + + + /* + * Set the specified entry as the most recently used entry. + */ + private void setMostRecentlyUsed(LRUListEntry entry) + { + if (entry == head) return; + + //replace the current position of the entry with its next entry + if (entry.prev != null) + { + entry.prev.next = entry.next; + assert (entry.prev.next != entry.prev); + if (entry == tail) + { + tail = entry.prev; + tail.next = null; + } + } + + if (entry.next != null) + { + entry.next.prev = entry.prev; + assert (entry.next.prev != entry.next); + } + + //set the entry as the head + entry.prev = null; + entry.next = head; + assert (entry.next != entry); + if (head != null) + { + head.prev = entry; + assert (head.prev != head); + } + head = entry; + } + + /** + * Linked list element for the LRU list. + */ + protected class LRUListEntry extends Object + { + LRUListEntry next; + LRUListEntry prev; + Object value; + Object key; + int hits; + final int penalty = -1; + + public String toString() + { + return key + "=" + value; + } + + public Object getKey() + { + return key; + } + + public Object getValue() + { + return value; + } + } +} + +
