| /* |
| * |
| * 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.fonts; |
| |
| import flash.swf.builder.types.PathIteratorWrapper; |
| import flash.swf.builder.types.ShapeBuilder; |
| import flash.swf.types.GlyphEntry; |
| import flash.swf.types.Shape; |
| import flash.swf.SwfConstants; |
| import flash.util.Trace; |
| |
| 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 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; |
| } |
| |
| // Now that we've processed the font with the JRE font manager, we now need to get the fsType |
| // and copyright info from from Batik. This is a bit strange, as one of the advantages of the |
| // JRE font manager is that it can process some fonts that Batik cannot. This check is needed, |
| // though, anytime we create a font. So it may mean we don't need the JRE font manager anymore, |
| // but since I'm not sure of that, it'll be kept in for awhile. |
| String locationStr = BatikFontManager.processLocation(location); |
| org.apache.flex.forks.batik.svggen.font.Font batikFont = org.apache.flex.forks.batik.svggen.font.Font.create(locationStr); |
| if (batikFont == null) |
| { |
| throw new FontFormatException ("Unable to create font." ); |
| } |
| FSType type = FSType.getFSType(batikFont); |
| if (! type.usableByFlex) |
| { |
| throw new BatikFontManager.UnusableFontLicense(location + "", type.description); |
| } |
| String copyright = batikFont.getNameTable().getRecord((short)0); |
| String trademark = batikFont.getNameTable().getRecord((short)7); |
| |
| // 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 ? SwfConstants.TWIPS_PER_PIXEL : 1)); |
| descent = (int)Math.rint(fontMetrics.getDescent() * emScale * (useTwips ? SwfConstants.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.advance = (int)(advance * emScale * (useTwips ? SwfConstants.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.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; |
| } |
| } |