| /* |
| * |
| * 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 flash.css; |
| |
| import flex2.compiler.util.CompilerMessage.CompilerError; |
| import flex2.compiler.Transcoder; |
| import flex2.compiler.media.FontTranscoder; |
| import flex2.compiler.util.MimeMappings; |
| import flex2.compiler.util.ThreadLocalToolkit; |
| import flash.fonts.FontFace; |
| import flash.util.Trace; |
| import org.w3c.css.sac.CSSException; |
| import org.w3c.css.sac.LexicalUnit; |
| |
| import java.util.*; |
| |
| /** |
| * Represents an @font-face {} CSS rule. These rules are used to embed |
| * font definitions into SWFs. |
| * |
| * The rule must be initialized with a StyleParser before it can be |
| * used to turn the Rule into a DefineFont2 SWF tag. |
| * |
| * @author Peter Farland |
| */ |
| public class FontFaceRule extends Rule |
| { |
| private String family = null; |
| private int style = -1; |
| |
| /** |
| * List<{LocalSource,URLSource}> |
| */ |
| private List<Object> locations = null; |
| |
| /** |
| * String->{String, List<URLSource>, List<LocalSource>} |
| */ |
| private Map<String, Object> embedParams = null; |
| |
| public FontFaceRule(String path, int lineNumber) |
| { |
| super(Rule.FONT_FACE_RULE, path, lineNumber); |
| this.declaration = new StyleDeclaration(path, lineNumber); |
| } |
| |
| public Map<String, Object> getEmbedParams() |
| { |
| return embedParams; |
| } |
| |
| /** |
| * This method validates the specified family and style information and |
| * attempts to pre-load the font before the DefineFont2 tag can be built. |
| * |
| * @param parser The current style parser. This is only used to report a warning for |
| * any ignored descriptors. See checkForIgnoredDescriptors(). |
| */ |
| public void initialize(StyleParser parser) |
| { |
| String alias = getFamily(); |
| List<Object> locations = getLocations(); |
| |
| embedParams = new HashMap<String, Object>(); |
| embedParams.put(Transcoder.MIMETYPE, "application/x-font"); |
| if (locations == null && alias != null) |
| { |
| embedParams.put( FontTranscoder.FONTNAME, alias ); |
| // systemfont change, remove below |
| embedParams.put( FontTranscoder.SYSTEMFONT, alias ); |
| } |
| else if (alias != null) |
| { |
| embedParams.put( FontTranscoder.FONTNAME, alias ); |
| if (locations.size() == 1) |
| { |
| Object source = locations.get(0); |
| if (source instanceof URLSource) |
| { |
| URLSource urlSource = (URLSource) source; |
| embedParams.put( Transcoder.SOURCE, urlSource.getValue() ); |
| if (urlSource.getValue().toLowerCase().endsWith( ".swf" )) |
| { |
| embedParams.put( Transcoder.MIMETYPE, MimeMappings.FLASH ); |
| } |
| } |
| else // if (locations.get(0) instanceof LocalSource) |
| { |
| LocalSource localSource = (LocalSource) source; |
| embedParams.put( FontTranscoder.SYSTEMFONT, localSource.getValue() ); |
| } |
| } |
| else |
| { |
| embedParams.put( FontTranscoder.SOURCELIST, locations ); |
| } |
| } |
| |
| String unicodeRange = getUnicodeRange(); |
| |
| if (unicodeRange != null) |
| { |
| embedParams.put( FontTranscoder.UNICODERANGE, unicodeRange ); |
| } |
| |
| String flashType = getFlashType(); |
| if (flashType != null) |
| { |
| embedParams.put(FontTranscoder.ADVANTIALIASING, flashType); |
| } |
| |
| String cff = getCompactFontFormat(); |
| if (cff != null) |
| { |
| embedParams.put(FontTranscoder.EMBEDASCFF, cff); |
| } |
| |
| checkForIgnoredDescriptors(parser); |
| |
| if (isBold()) |
| embedParams.put( FontTranscoder.FONTWEIGHT, "bold" ); |
| if (isItalic()) |
| embedParams.put( FontTranscoder.FONTSTYLE, "italic" ); |
| |
| if (Trace.font) |
| Trace.trace("@fontface rule parsed for family '" + alias + "'"); |
| |
| // todo - Record FILE and LINE? |
| } |
| |
| /** |
| * The font-family is used as an alias to bind the embedded DefineFont2 tag with a |
| * corresponding DefineEditText. This binding may be applied at runtime via |
| * deferred instantiation using the ActionScript TextFormat API. |
| */ |
| public String getFamily() |
| { |
| if (family == null) |
| { |
| Descriptor f = declaration.getDescriptorValue("font-family"); |
| |
| if (f == null) |
| { |
| // [preilly] This is required to be consistent with the other descriptor |
| // names which we allow to be hyphenated or camel case. |
| f = declaration.getDescriptorValue("fontFamily"); |
| } |
| |
| if (f != null) |
| family = f.getIdentAsString(); //TODO: This MUST NOT be null. |
| } |
| |
| return family; |
| } |
| |
| /** |
| * This class parses the values for the src descriptor in a font-face rule. |
| * It is required for referencing actual font data, whether downloadable or |
| * locally installed. |
| * <p> |
| * |
| * <code> |
| * src: local("T-26 Typeka Mix"), url("fonts/magda-extra") |
| * </code> |
| * </p> |
| * |
| * @return List<{LocalSource,URLSource}>, a list of locations to search for font data |
| */ |
| public List<Object> getLocations() |
| { |
| if (locations == null) |
| { |
| Descriptor src = declaration.getDescriptorValue("src"); |
| List<Object> result = null; |
| |
| if (src != null) |
| { |
| LexicalUnit lu = src.getValue(); |
| result = new ArrayList<Object>(2); |
| |
| while (lu != null) |
| { |
| if (lu.getLexicalUnitType() == LexicalUnit.SAC_FUNCTION) //local("...") |
| { |
| result.add(new LocalSource(lu.getParameters().getStringValue().trim())); |
| } |
| else if (lu.getLexicalUnitType() == LexicalUnit.SAC_URI) //url("...") |
| { |
| result.add(new URLSource(lu.getStringValue())); |
| } |
| |
| lu = lu.getNextLexicalUnit(); |
| } |
| } |
| |
| locations = result; |
| } |
| |
| return locations; |
| } |
| |
| /** |
| * This method attempts to map CSS font-styles and font-weights to java.awt.Font styles. |
| * Only the first style and weight in a list is considered as java will always |
| * attempt to derive a style in a font, even if it is not supported. |
| * |
| * The default style will be plain, which is equivalent to "normal" in CSS. |
| */ |
| public String formatFontStyle() |
| { |
| Descriptor s = declaration.getDescriptorValue("font-style"); |
| |
| if (s == null) |
| { |
| s = declaration.getDescriptorValue("fontStyle"); |
| } |
| |
| return (s == null)? "" : "fontStyle='" + s + "'"; |
| } |
| |
| public String formatFontWeight() |
| { |
| Descriptor w = declaration.getDescriptorValue("font-weight"); |
| |
| if (w == null) |
| { |
| w = declaration.getDescriptorValue("fontWeight"); |
| } |
| |
| return (w == null)? "" : "fontWeight='" + w + "'"; |
| } |
| |
| public int getFontStyle() |
| { |
| if (style < 0) |
| { |
| style = FontFace.PLAIN; //Default to plain |
| |
| Descriptor s = declaration.getDescriptorValue("font-style"); |
| |
| if (s == null) |
| { |
| s = declaration.getDescriptorValue("fontStyle"); |
| } |
| |
| Descriptor w = declaration.getDescriptorValue("font-weight"); |
| |
| if (w == null) |
| { |
| w = declaration.getDescriptorValue("fontWeight"); |
| } |
| |
| if (s != null) |
| { |
| StringTokenizer styles = new StringTokenizer(s.getIdentAsString(), ","); |
| |
| //Examine the first token only, as Java matches any style |
| String thisStyle = ((String)styles.nextElement()).trim().toLowerCase(); |
| |
| if (isItalic(thisStyle)) |
| style += FontFace.ITALIC; |
| } |
| |
| if (w != null) |
| { |
| StringTokenizer weights = new StringTokenizer(w.getIdentAsString(), ","); |
| |
| //Examine the first token only, as Java matches any style |
| String thisWeight = ((String)weights.nextElement()).trim().toLowerCase(); |
| |
| if (isBold(thisWeight)) |
| style += FontFace.BOLD; |
| } |
| } |
| |
| return style; |
| } |
| |
| public boolean isBold() |
| { |
| if (style < 0) |
| getFontStyle(); |
| |
| return style == FontFace.BOLD || style == (FontFace.BOLD + FontFace.ITALIC); |
| } |
| |
| public boolean isItalic() |
| { |
| if (style < 0) |
| getFontStyle(); |
| |
| return style == FontFace.ITALIC || style == (FontFace.BOLD + FontFace.ITALIC); |
| } |
| |
| /** |
| * Determines whether a CSS font-style descriptor is specifying an ITALIC font. |
| * Styles specified as "italic" or "oblique" will be |
| * interpreted as true. |
| * |
| * @param value |
| */ |
| public static boolean isItalic(String value) |
| { |
| boolean italic = false; |
| |
| if (value != null) |
| { |
| String ital = value.trim().toLowerCase(); |
| if (ital.startsWith( "\"" )) |
| { |
| ital = ital.substring( 1 ); |
| if (ital.endsWith( "\"" )) |
| ital = ital.substring( 0, ital.length() - 1 ); |
| } |
| if (ital.equals("italic") || ital.equals("oblique")) |
| italic = true; |
| } |
| |
| return italic; |
| } |
| |
| /** |
| * Determines whether a CSS font-weight descriptor is specifying a BOLD font. |
| * Weights specified as integers will be interpreted as bold for values equal to or |
| * greater than 700. |
| * |
| * @param value |
| */ |
| public static boolean isBold(String value) |
| { |
| boolean bold = false; |
| |
| if (value != null) |
| { |
| String b = value.trim().toLowerCase(); |
| if (b.startsWith( "\"" )) |
| { |
| b = b.substring( 1 ); |
| if (b.endsWith( "\"" )) |
| b = b.substring( 0, b.length() - 1 ); |
| } |
| |
| if (b.startsWith("bold")) |
| { |
| bold = true; |
| } |
| else |
| { |
| try |
| { |
| int w = Integer.parseInt(b); |
| if (w >= 700) |
| bold = true; |
| } |
| catch (Throwable t) |
| { |
| } |
| } |
| } |
| |
| return bold; |
| } |
| |
| /** |
| * Unicode ranges allow the user to specify ranges of characters to embed for a given Font. |
| * The range is restricted to the Basic Multilingual Plane (BMP), which sets character codepoints |
| * between U+0000 and U+FFFF, because a Java char primitive is restricted to this range. |
| * |
| * <p> |
| * <code> |
| * unicode-range: U+AC00-U+D7FF |
| * </code> |
| * </p> |
| */ |
| public String getUnicodeRange() |
| { |
| Descriptor property = declaration.getDescriptorValue("unicode-range"); |
| if (property == null) |
| { |
| property = declaration.getDescriptorValue("unicodeRange"); |
| } |
| |
| if (property != null) |
| { |
| String unicodeRange = property.getIdentAsString(); |
| |
| // A single unicode character makes Batik freak out. Work around it: |
| unicodeRange = unicodeRange.trim(); |
| int semicolon = unicodeRange.indexOf(";"); |
| if (semicolon != -1) |
| { |
| unicodeRange = unicodeRange.substring( 0, semicolon ); |
| } |
| return unicodeRange; |
| } |
| return null; |
| } |
| |
| |
| private String getFlashType() |
| { |
| String flashType = null; |
| Descriptor flashTypeDesc = declaration.getDescriptorValue("advanced-anti-aliasing"); |
| |
| if (flashTypeDesc == null) |
| { |
| flashTypeDesc = declaration.getDescriptorValue("advancedAntiAliasing"); |
| } |
| |
| if (flashTypeDesc == null) |
| { |
| flashTypeDesc = declaration.getDescriptorValue("flash-type"); |
| } |
| |
| if (flashTypeDesc == null) |
| { |
| flashTypeDesc = declaration.getDescriptorValue("flashType"); |
| } |
| |
| if (flashTypeDesc != null) |
| { |
| flashType = flashTypeDesc.getIdentAsString(); |
| } |
| return flashType; |
| } |
| |
| private String getCompactFontFormat() |
| { |
| String cff = null; |
| |
| Descriptor cffDesc = declaration.getDescriptorValue(FontTranscoder.EMBEDASCFF); |
| // Check for alternate CSS-like form 'embed-as-cff' |
| if (cffDesc == null) |
| cffDesc = declaration.getDescriptorValue("embed-as-cff"); |
| |
| if (cffDesc != null) |
| cff = cffDesc.getIdentAsString(); |
| |
| return cff; |
| } |
| |
| |
| private void checkForIgnoredDescriptors(StyleParser parser) |
| { |
| String descriptor = null; |
| |
| if (declaration.getDescriptorValue("font-size") != null) |
| { |
| descriptor = "font-size"; |
| } |
| else if (declaration.getDescriptorValue("fontSize") != null) |
| { |
| descriptor = "fontSize"; |
| } |
| else if (declaration.getDescriptorValue("panose-1") != null) |
| { |
| descriptor = "panose-1"; |
| } |
| else if (declaration.getDescriptorValue("stemv") != null) |
| { |
| descriptor = "stemv"; |
| } |
| else if (declaration.getDescriptorValue("stemh") != null) |
| { |
| descriptor = "stemh"; |
| } |
| else if (declaration.getDescriptorValue("slope") != null) |
| { |
| descriptor = "slope"; |
| } |
| else if (declaration.getDescriptorValue("cap-height") != null) |
| { |
| descriptor = "cap-height"; |
| } |
| else if (declaration.getDescriptorValue("capHeight") != null) |
| { |
| descriptor = "capHeight"; |
| } |
| else if (declaration.getDescriptorValue("x-height") != null) |
| { |
| descriptor = "x-height"; |
| } |
| else if (declaration.getDescriptorValue("xHeight") != null) |
| { |
| descriptor = "xHeight"; |
| } |
| else if (declaration.getDescriptorValue("ascent") != null) |
| { |
| descriptor = "ascent"; |
| } |
| else if (declaration.getDescriptorValue("descent") != null) |
| { |
| descriptor = "descent"; |
| } |
| else if (declaration.getDescriptorValue("widths") != null) |
| { |
| descriptor = "widths"; |
| } |
| else if (declaration.getDescriptorValue("bbox") != null) |
| { |
| descriptor = "bbox"; |
| } |
| else if (declaration.getDescriptorValue("definition-src") != null) |
| { |
| descriptor = "definition-src"; |
| } |
| else if (declaration.getDescriptorValue("definitionSrc") != null) |
| { |
| descriptor = "definitionSrc"; |
| } |
| else if (declaration.getDescriptorValue("baseline") != null) |
| { |
| descriptor = "baseline"; |
| } |
| else if (declaration.getDescriptorValue("centerline") != null) |
| { |
| descriptor = "centerline"; |
| } |
| else if (declaration.getDescriptorValue("mathline") != null) |
| { |
| descriptor = "mathline"; |
| } |
| else if (declaration.getDescriptorValue("topline") != null) |
| { |
| descriptor = "topline"; |
| } |
| |
| if (descriptor != null) |
| { |
| IgnoredDescriptor ignoredDescriptor = new IgnoredDescriptor(descriptor); |
| |
| String text = ThreadLocalToolkit.getLocalizationManager().getLocalizedTextString(ignoredDescriptor); |
| |
| parser.warning(new CSSException(CSSException.SAC_UNSPECIFIED_ERR, text, null)); |
| } |
| } |
| |
| /** |
| * |
| */ |
| public static FontFaceRule getRule(Collection<FontFaceRule> rules, String family, boolean bold, boolean italic) |
| { |
| Iterator<FontFaceRule> it = rules.iterator(); |
| while (it.hasNext()) |
| { |
| FontFaceRule ffr = it.next(); |
| if (family.equals(ffr.getFamily())) |
| { |
| if (bold != ffr.isBold()) |
| continue; |
| |
| if (italic != ffr.isItalic()) |
| continue; |
| |
| return ffr; |
| } |
| } |
| |
| return null; |
| } |
| |
| /* |
| public static boolean hasFamily(Collection<FontFaceRule> rules, String family) |
| { |
| Iterator<FontFaceRule> it = rules.iterator(); |
| while (it.hasNext()) |
| { |
| FontFaceRule ffr = it.next(); |
| if (family.equals(ffr.getFamily())) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| */ |
| |
| public static class IgnoredDescriptor extends CompilerError |
| { |
| private static final long serialVersionUID = -4627590989999828547L; |
| public String descriptor; |
| |
| public IgnoredDescriptor(String descriptor) |
| { |
| this.descriptor = descriptor; |
| } |
| } |
| |
| public static class UnicodeRangeNotSupported extends CompilerError |
| { |
| |
| private static final long serialVersionUID = -7817266835618855999L; |
| } |
| } |