From 5feb2f8d04c7ef475316235c097a726290b6729a Mon Sep 17 00:00:00 2001 From: "(no author)" <(no author)@unknown> Date: Sat, 6 Mar 2004 05:53:16 +0000 Subject: [PATCH] This commit was manufactured by cvs2svn to create branch 'FOP_0-20-0_Alt-Design'. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/FOP_0-20-0_Alt-Design@197413 13f79535-47bb-0310-9956-ffa450edef68 --- src/java/org/apache/fop/apps/Document.java | 390 +++++ .../org/apache/fop/apps/FOFileHandler.java | 113 ++ src/java/org/apache/fop/apps/FOUserAgent.java | 157 ++ .../org/apache/fop/apps/XSLTInputHandler.java | 180 +++ src/java/org/apache/fop/area/LineArea.java | 115 ++ src/java/org/apache/fop/area/Page.java | 136 ++ src/java/org/apache/fop/fonts/BFEntry.java | 66 + src/java/org/apache/fop/fonts/CIDFont.java | 93 ++ .../org/apache/fop/fonts/CIDFontType.java | 78 + src/java/org/apache/fop/fonts/CustomFont.java | 338 +++++ .../org/apache/fop/fonts/EmbedFontInfo.java | 80 + src/java/org/apache/fop/fonts/FontReader.java | 286 ++++ src/java/org/apache/fop/fonts/FontSetup.java | 236 +++ .../org/apache/fop/fonts/FontTriplet.java | 71 + src/java/org/apache/fop/fonts/FontUtil.java | 59 + src/java/org/apache/fop/fonts/LazyFont.java | 256 ++++ .../org/apache/fop/fonts/MultiByteFont.java | 294 ++++ .../org/apache/fop/fonts/MutableFont.java | 126 ++ .../org/apache/fop/fonts/SingleByteFont.java | 96 ++ src/java/org/apache/fop/fonts/Typeface.java | 51 + .../org/apache/fop/fonts/apps/PFMReader.java | 389 +++++ .../org/apache/fop/fonts/apps/TTFReader.java | 514 +++++++ .../fop/fonts/truetype/FontFileReader.java | 335 +++++ .../fop/fonts/truetype/TTFCmapEntry.java | 106 ++ .../fop/fonts/truetype/TTFDirTabEntry.java | 108 ++ .../apache/fop/fonts/truetype/TTFFile.java | 1312 +++++++++++++++++ .../fop/fonts/truetype/TTFMtxEntry.java | 194 +++ .../fop/fonts/truetype/TTFSubSetFile.java | 854 +++++++++++ .../org/apache/fop/fonts/type1/PFBData.java | 160 ++ .../org/apache/fop/fonts/type1/PFBParser.java | 235 +++ .../org/apache/fop/fonts/type1/PFMFile.java | 437 ++++++ .../fop/fonts/type1/PFMInputStream.java | 107 ++ .../apache/fop/render/AbstractRenderer.java | 758 ++++++++++ .../org/apache/fop/render/PrintRenderer.java | 48 + src/java/org/apache/fop/render/Renderer.java | 190 +++ .../apache/fop/render/RendererContext.java | 95 ++ .../org/apache/fop/render/XMLHandler.java | 45 + .../apache/fop/render/awt/AWTFontMetrics.java | 283 ++++ .../fop/render/awt/AWTPrintRenderer.java | 121 ++ .../apache/fop/render/awt/AWTRenderer.java | 465 ++++++ .../fop/render/awt/FontMetricsMapper.java | 166 +++ .../org/apache/fop/render/awt/FontSetup.java | 182 +++ .../apache/fop/render/awt/viewer/Command.java | 82 ++ .../fop/render/awt/viewer/GoToPageDialog.java | 128 ++ .../fop/render/awt/viewer/PreviewDialog.java | 571 +++++++ .../awt/viewer/PreviewDialogAboutBox.java | 118 ++ .../fop/render/awt/viewer/Translator.java | 59 + 47 files changed, 11283 insertions(+) create mode 100644 src/java/org/apache/fop/apps/Document.java create mode 100644 src/java/org/apache/fop/apps/FOFileHandler.java create mode 100644 src/java/org/apache/fop/apps/FOUserAgent.java create mode 100644 src/java/org/apache/fop/apps/XSLTInputHandler.java create mode 100644 src/java/org/apache/fop/area/LineArea.java create mode 100644 src/java/org/apache/fop/area/Page.java create mode 100644 src/java/org/apache/fop/fonts/BFEntry.java create mode 100644 src/java/org/apache/fop/fonts/CIDFont.java create mode 100644 src/java/org/apache/fop/fonts/CIDFontType.java create mode 100644 src/java/org/apache/fop/fonts/CustomFont.java create mode 100644 src/java/org/apache/fop/fonts/EmbedFontInfo.java create mode 100644 src/java/org/apache/fop/fonts/FontReader.java create mode 100644 src/java/org/apache/fop/fonts/FontSetup.java create mode 100644 src/java/org/apache/fop/fonts/FontTriplet.java create mode 100644 src/java/org/apache/fop/fonts/FontUtil.java create mode 100644 src/java/org/apache/fop/fonts/LazyFont.java create mode 100644 src/java/org/apache/fop/fonts/MultiByteFont.java create mode 100644 src/java/org/apache/fop/fonts/MutableFont.java create mode 100644 src/java/org/apache/fop/fonts/SingleByteFont.java create mode 100644 src/java/org/apache/fop/fonts/Typeface.java create mode 100644 src/java/org/apache/fop/fonts/apps/PFMReader.java create mode 100644 src/java/org/apache/fop/fonts/apps/TTFReader.java create mode 100644 src/java/org/apache/fop/fonts/truetype/FontFileReader.java create mode 100644 src/java/org/apache/fop/fonts/truetype/TTFCmapEntry.java create mode 100644 src/java/org/apache/fop/fonts/truetype/TTFDirTabEntry.java create mode 100644 src/java/org/apache/fop/fonts/truetype/TTFFile.java create mode 100644 src/java/org/apache/fop/fonts/truetype/TTFMtxEntry.java create mode 100644 src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java create mode 100644 src/java/org/apache/fop/fonts/type1/PFBData.java create mode 100644 src/java/org/apache/fop/fonts/type1/PFBParser.java create mode 100644 src/java/org/apache/fop/fonts/type1/PFMFile.java create mode 100644 src/java/org/apache/fop/fonts/type1/PFMInputStream.java create mode 100644 src/java/org/apache/fop/render/AbstractRenderer.java create mode 100644 src/java/org/apache/fop/render/PrintRenderer.java create mode 100644 src/java/org/apache/fop/render/Renderer.java create mode 100644 src/java/org/apache/fop/render/RendererContext.java create mode 100644 src/java/org/apache/fop/render/XMLHandler.java create mode 100644 src/java/org/apache/fop/render/awt/AWTFontMetrics.java create mode 100644 src/java/org/apache/fop/render/awt/AWTPrintRenderer.java create mode 100644 src/java/org/apache/fop/render/awt/AWTRenderer.java create mode 100644 src/java/org/apache/fop/render/awt/FontMetricsMapper.java create mode 100644 src/java/org/apache/fop/render/awt/FontSetup.java create mode 100644 src/java/org/apache/fop/render/awt/viewer/Command.java create mode 100644 src/java/org/apache/fop/render/awt/viewer/GoToPageDialog.java create mode 100644 src/java/org/apache/fop/render/awt/viewer/PreviewDialog.java create mode 100644 src/java/org/apache/fop/render/awt/viewer/PreviewDialogAboutBox.java create mode 100644 src/java/org/apache/fop/render/awt/viewer/Translator.java diff --git a/src/java/org/apache/fop/apps/Document.java b/src/java/org/apache/fop/apps/Document.java new file mode 100644 index 000000000..2329050cb --- /dev/null +++ b/src/java/org/apache/fop/apps/Document.java @@ -0,0 +1,390 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.apps; + +// Java +import java.util.Map; +import java.io.IOException; +import java.util.Set; +import java.util.HashSet; + + +// FOP +import org.apache.fop.apps.FOUserAgent; + +import org.apache.fop.area.AreaTree; +import org.apache.fop.area.AreaTreeControl; +import org.apache.fop.area.AreaTreeModel; + +import org.apache.fop.fo.extensions.Bookmarks; +import org.apache.fop.fo.FOInputHandler; +import org.apache.fop.fo.FOTreeControl; +import org.apache.fop.fo.FOTreeEvent; +import org.apache.fop.fo.FOTreeListener; +import org.apache.fop.fo.pagination.PageSequence; +import org.apache.fop.fonts.Font; +import org.apache.fop.fonts.FontMetrics; +import org.apache.fop.layout.LayoutStrategy; + +// SAX +import org.xml.sax.SAXException; + +// Avalon +import org.apache.avalon.framework.logger.Logger; + +/** + * Class storing information for the FOP Document being processed, and managing + * the processing of it. + */ +public class Document implements FOTreeControl, FOTreeListener, + AreaTreeControl { + + /** The parent Driver object */ + private Driver driver; + + /** Map containing fonts that have been used */ + private Map usedFonts; + + /** look up a font-triplet to find a font-name */ + private Map triplets; + + /** look up a font-name to get a font (that implements FontMetrics at least) */ + private Map fonts; + + /** + * the LayoutStrategy to be used to process this document + * TODO: this actually belongs in the RenderContext class, when it is + * created + */ + private LayoutStrategy layoutStrategy = null; + + /** The current AreaTree for the PageSequence being rendered. */ + public AreaTree areaTree; + /** The AreaTreeModel for the PageSequence being rendered. */ + public AreaTreeModel atModel; + + private Bookmarks bookmarks = null; + + /** + * The current set of id's in the FO tree. + * This is used so we know if the FO tree contains duplicates. + */ + private Set idReferences = new HashSet(); + + /** + * Structure handler used to notify structure events + * such as start end element. + */ + public FOInputHandler foInputHandler; + + /** + * Main constructor + * @param driver the Driver object that is the "parent" of this Document + */ + public Document(Driver driver) { + this.driver = driver; + this.triplets = new java.util.HashMap(); + this.fonts = new java.util.HashMap(); + this.usedFonts = new java.util.HashMap(); + } + + /** + * Checks if the font setup is valid (At least the ultimate fallback font + * must be registered.) + * @return True if valid + */ + public boolean isSetupValid() { + return triplets.containsKey(Font.DEFAULT_FONT); + } + + /** + * Adds a new font triplet. + * @param name internal key + * @param family font family name + * @param style font style (normal, italic, oblique...) + * @param weight font weight + */ + public void addFontProperties(String name, String family, String style, + int weight) { + /* + * add the given family, style and weight as a lookup for the font + * with the given name + */ + + String key = createFontKey(family, style, weight); + this.triplets.put(key, name); + } + + /** + * Adds font metrics for a specific font. + * @param name internal key + * @param metrics metrics to register + */ + public void addMetrics(String name, FontMetrics metrics) { + // add the given metrics as a font with the given name + + this.fonts.put(name, metrics); + } + + /** + * Lookup a font. + *
+ * Locate the font name for a given family, style and weight. + * The font name can then be used as a key as it is unique for + * the associated document. + * This also adds the font to the list of used fonts. + * @param family font family + * @param style font style + * @param weight font weight + * @return internal key + */ + public String fontLookup(String family, String style, + int weight) { + String key; + // first try given parameters + key = createFontKey(family, style, weight); + String f = (String)triplets.get(key); + if (f == null) { + // then adjust weight, favouring normal or bold + f = findAdjustWeight(family, style, weight); + + // then try any family with orig weight + if (f == null) { + key = createFontKey("any", style, weight); + f = (String)triplets.get(key); + } + + // then try any family with adjusted weight + if (f == null) { + f = findAdjustWeight(family, style, weight); + } + + // then use default + if (f == null) { + f = (String)triplets.get(Font.DEFAULT_FONT); + } + + } + + usedFonts.put(f, fonts.get(f)); + return f; + } + + /** + * Find a font with a given family and style by trying + * different font weights according to the spec. + * @param family font family + * @param style font style + * @param weight font weight + * @return internal key + */ + public String findAdjustWeight(String family, String style, + int weight) { + String key; + String f = null; + int newWeight = weight; + if (newWeight < 400) { + while (f == null && newWeight > 0) { + newWeight -= 100; + key = createFontKey(family, style, newWeight); + f = (String)triplets.get(key); + } + } else if (newWeight == 500) { + key = createFontKey(family, style, 400); + f = (String)triplets.get(key); + } else if (newWeight > 500) { + while (f == null && newWeight < 1000) { + newWeight += 100; + key = createFontKey(family, style, newWeight); + f = (String)triplets.get(key); + } + newWeight = weight; + while (f == null && newWeight > 400) { + newWeight -= 100; + key = createFontKey(family, style, newWeight); + f = (String)triplets.get(key); + } + } + if (f == null) { + key = createFontKey(family, style, 400); + f = (String)triplets.get(key); + } + + return f; + } + + /** + * Determines if a particular font is available. + * @param family font family + * @param style font style + * @param weight font weight + * @return True if available + */ + public boolean hasFont(String family, String style, int weight) { + String key = createFontKey(family, style, weight); + return this.triplets.containsKey(key); + } + + /** + * Creates a key from the given strings. + * @param family font family + * @param style font style + * @param weight font weight + * @return internal key + */ + public static String createFontKey(String family, String style, + int weight) { + return family + "," + style + "," + weight; + } + + /** + * Gets a Map of all registred fonts. + * @return a read-only Map with font key/FontMetrics pairs + */ + public Map getFonts() { + return java.util.Collections.unmodifiableMap(this.fonts); + } + + /** + * This is used by the renderers to retrieve all the + * fonts used in the document. + * This is for embedded font or creating a list of used fonts. + * @return a read-only Map with font key/FontMetrics pairs + */ + public Map getUsedFonts() { + return this.usedFonts; + } + + /** + * Returns the FontMetrics for a particular font + * @param fontName internal key + * @return font metrics + */ + public FontMetrics getMetricsFor(String fontName) { + usedFonts.put(fontName, fonts.get(fontName)); + return (FontMetrics)fonts.get(fontName); + } + + /** + * Set the LayoutStrategy to be used to process this Document + * @param ls the LayoutStrategy object to be used to process this Document + */ + public void setLayoutStrategy(LayoutStrategy ls) { + this.layoutStrategy = ls; + } + + /** + * @return this Document's LayoutStrategy object + */ + public LayoutStrategy getLayoutStrategy () { + return layoutStrategy; + } + + /** + * Public accessor for the parent Driver of this Document + * @return the parent Driver for this Document + */ + public Driver getDriver() { + return driver; + } + + /** + * Required by the FOTreeListener interface. It handles an + * FOTreeEvent that is fired when a PageSequence object has been completed. + * @param event the FOTreeEvent that was fired + * @throws FOPException for errors in building the PageSequence + */ + public void foPageSequenceComplete (FOTreeEvent event) throws FOPException { + PageSequence pageSeq = event.getPageSequence(); + areaTree.addBookmarksToAreaTree(); + layoutStrategy.format(pageSeq, areaTree); + } + + /** + * Required by the FOTreeListener interface. It handles an FOTreeEvent that + * is fired when the Document has been completely parsed. + * @param event the FOTreeEvent that was fired + * @throws SAXException for parsing errors + */ + public void foDocumentComplete (FOTreeEvent event) throws SAXException { + //processAreaTree(atModel); + try { + areaTree.endDocument(); + driver.getRenderer().stopRenderer(); + } catch (IOException ex) { + throw new SAXException(ex); + } + } + + /** + * Get the area tree for this layout handler. + * + * @return the area tree for this document + */ + public AreaTree getAreaTree() { + return areaTree; + } + + /** + * Set the Bookmarks object for this Document + * @param bookmarks the Bookmarks object containing the bookmarks for this + * Document + */ + public void setBookmarks(Bookmarks bookmarks) { + this.bookmarks = bookmarks; + } + + /** + * Public accessor for the Bookmarks for this Document + * @return the Bookmarks for this Document + */ + public Bookmarks getBookmarks() { + return bookmarks; + } + + /** + * Retuns the set of ID references. + * @return the ID references + */ + public Set getIDReferences() { + return idReferences; + } + + /** + * @return the FOInputHandler for parsing this FO Tree + */ + public FOInputHandler getFOInputHandler() { + return foInputHandler; + } + + /** + * @return the Logger to be used for processing this Document + */ + public Logger getLogger() { + return getDriver().getLogger(); + } + + /** + * @return the FOUserAgent used for processing this document + */ + public FOUserAgent getUserAgent() { + return getDriver().getUserAgent(); + } + +} diff --git a/src/java/org/apache/fop/apps/FOFileHandler.java b/src/java/org/apache/fop/apps/FOFileHandler.java new file mode 100644 index 000000000..28226e677 --- /dev/null +++ b/src/java/org/apache/fop/apps/FOFileHandler.java @@ -0,0 +1,113 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.apps; + +// Imported SAX classes +import org.xml.sax.InputSource; +import org.xml.sax.XMLReader; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotSupportedException; + +// java +import javax.xml.parsers.SAXParserFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.File; +import java.net.URL; + + +/** + * Manages input if it is an XSL-FO file. + */ +public class FOFileHandler extends InputHandler { + + private File fofile = null; + private URL foURL = null; + + /** + * Create a FOFileHandler for a file. + * @param fofile the file to read the FO document. + */ + public FOFileHandler(File fofile) { + this.fofile = fofile; + } + + /** + * Create a FOFileHandler for an URL. + * @param url the URL to read the FO document. + */ + public FOFileHandler(URL url) { + this.foURL = url; + } + + + /** + * @see org.apache.fop.apps.InputHandler#getInputSource() + */ + public InputSource getInputSource () { + if (fofile != null) { + return super.fileInputSource(fofile); + } + return super.urlInputSource(foURL); + } + + /** + * @see org.apache.fop.apps.InputHandler#getParser() + */ + public XMLReader getParser() throws FOPException { + return createParser(); + } + + /** + * Creates XMLReader object using default + * SAXParserFactory + * @return the created XMLReader + * @throws FOPException if the parser couldn't be created or configured for proper operation. + */ + protected static XMLReader createParser() throws FOPException { + try { + SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setNamespaceAware(true); + factory.setFeature( + "http://xml.org/sax/features/namespace-prefixes", true); + return factory.newSAXParser().getXMLReader(); + } catch (SAXNotSupportedException se) { + throw new FOPException("Error: You need a parser which allows the" + + " http://xml.org/sax/features/namespace-prefixes" + + " feature to be set to true to support namespaces", se); + } catch (SAXException se) { + throw new FOPException("Couldn't create XMLReader", se); + } catch (ParserConfigurationException pce) { + throw new FOPException("Couldn't create XMLReader", pce); + } + } + + /** + * Returns the fully qualified classname of the standard XML parser for FOP + * to use. + * @return the XML parser classname + */ + public static final String getParserClassName() { + try { + return createParser().getClass().getName(); + } catch (FOPException e) { + return null; + } + } +} + diff --git a/src/java/org/apache/fop/apps/FOUserAgent.java b/src/java/org/apache/fop/apps/FOUserAgent.java new file mode 100644 index 000000000..024c5ad37 --- /dev/null +++ b/src/java/org/apache/fop/apps/FOUserAgent.java @@ -0,0 +1,157 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.apps; + +// Java +import java.util.Map; +import java.io.IOException; +import java.io.InputStream; + +// Avalon +import org.apache.avalon.framework.logger.LogEnabled; +import org.apache.avalon.framework.logger.Logger; + +// FOP +import org.apache.fop.pdf.PDFEncryptionParams; + +/** + * The User Agent for fo. + * This user agent is used by the processing to obtain user configurable + * options. + *

+ * Renderer specific extensions (that do not produce normal areas on + * the output) will be done like so: + *
+ * The extension will create an area, custom if necessary + *
+ * this area will be added to the user agent with a key + *
+ * the renderer will know keys for particular extensions + *
+ * eg. bookmarks will be held in a special hierarchical area representing + * the title and bookmark structure + *
+ * These areas may contain resolveable areas that will be processed + * with other resolveable areas + */ +public class FOUserAgent implements LogEnabled { + + private Logger log; + /** Map containing various default values */ + public Map defaults = new java.util.HashMap(); + /** Map containing XML handlers for various document types */ + public Map handlers = new java.util.HashMap(); + private String baseURL; + private PDFEncryptionParams pdfEncryptionParams; + private float px2mm = 0.35277777777777777778f; //72dpi (=25.4/dpi) + + /** + * Sets the logger. + * @param log Logger to use + * @see org.apache.avalon.framework.logger.LogEnabled#enableLogging(Logger) + */ + public void enableLogging(Logger log) { + this.log = log; + } + + /** + * Returns the logger to use. + * @see org.apache.avalon.framework.logger.AbstractLogEnabled#getLogger() + * (todo) This breaks IoC/SoC. Should be improved. + */ + public Logger getLogger() { + return this.log; + } + + /** + * Sets the base URL. + * @param baseURL base URL + */ + public void setBaseURL(String baseURL) { + this.baseURL = baseURL; + } + + /** + * Returns the base URL. + * @return the base URL + */ + public String getBaseURL() { + if ((this.baseURL == null) || (this.baseURL.trim().equals(""))) { + return "file:."; + } else { + return this.baseURL; + } + } + + /** + * Returns the parameters for PDF encryption. + * @return the PDF encryption parameters, null if not applicable + */ + public PDFEncryptionParams getPDFEncryptionParams() { + return pdfEncryptionParams; + } + + /** + * Sets the parameters for PDF encryption. + * @param pdfEncryptionParams the PDF encryption parameters, null to + * disable PDF encryption + */ + public void setPDFEncryptionParams(PDFEncryptionParams pdfEncryptionParams) { + this.pdfEncryptionParams = pdfEncryptionParams; + } + + + /** + * Get an input stream for a reference. + * Temporary solution until API better. + * @param uri URI to access + * @return InputStream for accessing the resource. + * @throws IOException in case of an I/O problem + */ + public InputStream getStream(String uri) throws IOException { + return null; + } + + /** + * Returns the conversion factor from pixel units to millimeters. This + * depends on the desired reolution. + * @return float conversion factor + */ + public float getPixelUnitToMillimeter() { + return this.px2mm; + } + + /** + * Sets the resolution in dpi. + * @param dpi resolution in dpi + */ + public void setResolution(int dpi) { + this.px2mm = (float)(25.4 / dpi); + } + + /** + * If to create hot links to footnotes and before floats. + * @return True if hot links dhould be created + */ + public boolean linkToFootnotes() { + return true; + } + +} + diff --git a/src/java/org/apache/fop/apps/XSLTInputHandler.java b/src/java/org/apache/fop/apps/XSLTInputHandler.java new file mode 100644 index 000000000..9634f073f --- /dev/null +++ b/src/java/org/apache/fop/apps/XSLTInputHandler.java @@ -0,0 +1,180 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.apps; + +// Imported java.io classes +import java.io.File; +import java.util.Vector; + +// Imported TraX classes +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamSource; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.sax.SAXTransformerFactory; + +// Imported SAX classes +import org.xml.sax.InputSource; +import org.xml.sax.XMLReader; +import org.xml.sax.XMLFilter; + +/** + * XSLTInputHandler basically takes an XML file and transforms it with an XSLT + * file and the resulting XSL-FO document is input for FOP. + */ +public class XSLTInputHandler extends InputHandler { + + private StreamSource xmlSource; + private Source xsltSource; + private Vector xsltParams = null; // not yet implemented + + /** + * Constructor for files as input + * @param xmlfile XML file + * @param xsltfile XSLT file + * @param params Vector of command-line parameters (name, value, + * name, value, ...) for XSL stylesheet + * @throws FOPException if initializing the Transformer fails + */ + public XSLTInputHandler(File xmlfile, File xsltfile, Vector params) throws FOPException { + this.xmlSource = new StreamSource(xmlfile); + this.xsltSource = new StreamSource(xsltfile); + xsltParams = params; + } + + /** + * Constructor for files as input + * @param xmlfile XML file + * @param xsltfile XSLT file + * @throws FOPException if initializing the Transformer fails + * @deprecated Use JAXP instead. + */ + public XSLTInputHandler(File xmlfile, File xsltfile) throws FOPException { + this.xmlSource = new StreamSource(xmlfile); + this.xsltSource = new StreamSource(xsltfile); + } + + /** + * Constructor with URIs/URLs as input. + * @param xmlURL XML URL + * @param xsltURL XSLT URL + * @throws FOPException if initializing the Transformer fails + * @deprecated Use JAXP instead. + */ + public XSLTInputHandler(String xmlURL, String xsltURL) throws FOPException { + this.xmlSource = new StreamSource(xmlURL); + this.xsltSource = new StreamSource(xsltURL); + } + + /** + * Constructor with InputSources as input. + * @param xmlSource XML InputSource + * @param xsltSource XSLT InputSource + * @throws FOPException if initializing the Transformer fails + * @deprecated Use JAXP instead. + */ + public XSLTInputHandler(InputSource xmlSource, InputSource xsltSource) + throws FOPException { + this.xmlSource = new StreamSource(xmlSource.getByteStream(), + xmlSource.getSystemId()); + this.xsltSource = new StreamSource(xsltSource.getByteStream(), + xsltSource.getSystemId()); + } + + /** + * @see org.apache.fop.apps.InputHandler#getInputSource() + */ + public InputSource getInputSource() { + InputSource is = new InputSource(); + is.setByteStream(xmlSource.getInputStream()); + is.setSystemId(xmlSource.getSystemId()); + return is; + } + + /** + * Overwrites this method of the super class and returns an XMLFilter + * instead of a simple XMLReader which allows chaining of transformations. + * @see org.apache.fop.apps.InputHandler#getParser() + */ + public XMLReader getParser() throws FOPException { + return getXMLFilter(xsltSource, xsltParams); + } + + /** + * Creates from the transformer an instance of an XMLFilter which + * then can be used in a chain with the XMLReader passed to Driver. This way + * during the conversion of the xml file + xslt stylesheet the resulting + * data is fed into Fop. This should help to avoid memory problems + * @param xsltSource An xslt stylesheet + * @return an XMLFilter which can be chained together with other + * XMLReaders or XMLFilters + * @throws FOPException if setting up the XMLFilter fails + */ + public static XMLFilter getXMLFilter(Source xsltSource, Vector inParams) throws FOPException { + try { + // Instantiate a TransformerFactory. + TransformerFactory tFactory = TransformerFactory.newInstance(); + // Determine whether the TransformerFactory supports The use of SAXSource + // and SAXResult + if (tFactory.getFeature(SAXSource.FEATURE) + && tFactory.getFeature(SAXResult.FEATURE)) { + // Cast the TransformerFactory to SAXTransformerFactory. + SAXTransformerFactory saxTFactory = + ((SAXTransformerFactory)tFactory); + // Create an XMLFilter for each stylesheet. + XMLFilter xmlfilter = + saxTFactory.newXMLFilter(xsltSource); + +/* if (inParams != null) { + Transformer transformer = ??? how to obtain from an XMLFilter? + int nParams = inParams.size(); + + for (int i = 0; i < nParams; i += 2) { + transformer.setParameter((String) inParams.elementAt(i), + (String) inParams.elementAt(i + 1)); + } + } +*/ + + // Create an XMLReader. + XMLReader parser = FOFileHandler.createParser(); + if (parser == null) { + throw new FOPException("Unable to create SAX parser"); + } + + // xmlFilter1 uses the XMLReader as its reader. + xmlfilter.setParent(parser); + return xmlfilter; + } else { + throw new FOPException("Your parser doesn't support the " + + "features SAXSource and SAXResult." + + "\nMake sure you are using an XSLT engine which " + + "supports TrAX"); + } + } catch (FOPException fe) { + throw fe; + } catch (Exception ex) { + throw new FOPException(ex); + } + } + +} + diff --git a/src/java/org/apache/fop/area/LineArea.java b/src/java/org/apache/fop/area/LineArea.java new file mode 100644 index 000000000..fa1d586db --- /dev/null +++ b/src/java/org/apache/fop/area/LineArea.java @@ -0,0 +1,115 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.area; + +import org.apache.fop.area.inline.InlineArea; + +import java.util.ArrayList; +import java.util.List; + +/** + * The line area. + * This is a line area that contains inline areas. + */ +public class LineArea extends Area { + private int stacking = LR; + // contains inline areas + // has start indent and length, dominant baseline, height + private int startIndent; + private int length; + + private int lineHeight; + // this is the offset for the dominant baseline + private int baseLine; + + // this class can contain the dominant char styling info + // this means that many renderers can optimise a bit + + private List inlineAreas = new ArrayList(); + + /** + * Set the height of this line area. + * + * @param height the height of the line area + */ + public void setHeight(int height) { + lineHeight = height; + } + + /** + * Get the height of this line area. + * + * @return the height of the line area + */ + public int getHeight() { + return lineHeight; + } + + /** + * Add a child area to this line area. + * + * @param childArea the inline child area to add + */ + public void addChild(Area childArea) { + if (childArea instanceof InlineArea) { + addInlineArea((InlineArea)childArea); + } + } + + /** + * Add an inline child area to this line area. + * + * @param area the inline child area to add + */ + public void addInlineArea(InlineArea area) { + inlineAreas.add(area); + } + + /** + * Get the inline child areas of this line area. + * + * @return the list of inline areas + */ + public List getInlineAreas() { + return inlineAreas; + } + + /** + * Set the start indent of this line area. + * The start indent is used for offsetting the start of + * the inline areas for alignment or other indents. + * + * @param si the start indent value + */ + public void setStartIndent(int si) { + startIndent = si; + } + + /** + * Get the start indent of this line area. + * The start indent is used for offsetting the start of + * the inline areas for alignment or other indents. + * + * @return the start indent value + */ + public int getStartIndent() { + return startIndent; + } +} + diff --git a/src/java/org/apache/fop/area/Page.java b/src/java/org/apache/fop/area/Page.java new file mode 100644 index 000000000..49d74d7f2 --- /dev/null +++ b/src/java/org/apache/fop/area/Page.java @@ -0,0 +1,136 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.area; + +import java.io.Serializable; +import java.util.Map; + +import org.apache.fop.fo.pagination.Region; + +/** + * The page. + * This holds the contents of the page. Each region is added. + * The unresolved references area added so that if the page is + * serialized then it will handle the resolving properly after + * being reloaded. + * This is serializable so it can be saved to cache to save + * memory if there are forward references. + * The page is cloneable so the page master can make copies of + * the top level page and regions. + */ +public class Page implements Serializable, Cloneable { + // contains before, start, body, end and after regions + private RegionViewport regionBefore = null; + private RegionViewport regionStart = null; + private RegionViewport regionBody = null; + private RegionViewport regionEnd = null; + private RegionViewport regionAfter = null; + + // temporary map of unresolved objects used when serializing the page + private Map unresolved = null; + + /** + * Set the region on this page. + * + * @param areaclass the area class of the region to set + * @param port the region viewport to set + */ + public void setRegionViewport(int areaclass, RegionViewport port) { + if (areaclass == Region.BEFORE_CODE) { + regionBefore = port; + } else if (areaclass == Region.START_CODE) { + regionStart = port; + } else if (areaclass == Region.BODY_CODE) { + regionBody = port; + } else if (areaclass == Region.END_CODE) { + regionEnd = port; + } else if (areaclass == Region.AFTER_CODE) { + regionAfter = port; + } + } + + /** + * Get the region from this page. + * + * @param areaclass the region area class + * @return the region viewport or null if none + */ + public RegionViewport getRegionViewport(int areaclass) { + if (areaclass == Region.BEFORE_CODE) { + return regionBefore; + } else if (areaclass == Region.START_CODE) { + return regionStart; + } else if (areaclass == Region.BODY_CODE) { + return regionBody; + } else if (areaclass == Region.END_CODE) { + return regionEnd; + } else if (areaclass == Region.AFTER_CODE) { + return regionAfter; + } + return null; + } + + /** + * Clone this page. + * This returns a new page with a clone of all the regions. + * + * @return a new clone of this page + */ + public Object clone() { + Page p = new Page(); + if (regionBefore != null) { + p.regionBefore = (RegionViewport)regionBefore.clone(); + } + if (regionStart != null) { + p.regionStart = (RegionViewport)regionStart.clone(); + } + if (regionBody != null) { + p.regionBody = (RegionViewport)regionBody.clone(); + } + if (regionEnd != null) { + p.regionEnd = (RegionViewport)regionEnd.clone(); + } + if (regionAfter != null) { + p.regionAfter = (RegionViewport)regionAfter.clone(); + } + + return p; + } + + /** + * Set the unresolved references on this page for serializing. + * + * @param unres the map of unresolved objects + */ + public void setUnresolvedReferences(Map unres) { + unresolved = unres; + } + + /** + * Get the map unresolved references from this page. + * This should be called after deserializing to retrieve + * the map of unresolved references that were serialized. + * + * @return the de-serialized map of unresolved objects + */ + public Map getUnresolvedReferences() { + return unresolved; + } +} + diff --git a/src/java/org/apache/fop/fonts/BFEntry.java b/src/java/org/apache/fop/fonts/BFEntry.java new file mode 100644 index 000000000..a367c91c1 --- /dev/null +++ b/src/java/org/apache/fop/fonts/BFEntry.java @@ -0,0 +1,66 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts; + +/** + * This is just a holder class for bfentries. + */ +public class BFEntry { + + private int unicodeStart; + private int unicodeEnd; + private int glyphStartIndex; + + /** + * Main constructor. + * @param unicodeStart Unicode start index + * @param unicodeEnd Unicode end index + * @param glyphStartIndex glyph start index + */ + public BFEntry(int unicodeStart, int unicodeEnd, int glyphStartIndex) { + this.unicodeStart = unicodeStart; + this.unicodeEnd = unicodeEnd; + this.glyphStartIndex = glyphStartIndex; + } + + /** + * Returns the unicodeStart. + * @return the Unicode start index + */ + public int getUnicodeStart() { + return unicodeStart; + } + + /** + * Returns the unicodeEnd. + * @return the Unicode end index + */ + public int getUnicodeEnd() { + return unicodeEnd; + } + + /** + * Returns the glyphStartIndex. + * @return the glyph start index + */ + public int getGlyphStartIndex() { + return glyphStartIndex; + } + +} diff --git a/src/java/org/apache/fop/fonts/CIDFont.java b/src/java/org/apache/fop/fonts/CIDFont.java new file mode 100644 index 000000000..3214e484b --- /dev/null +++ b/src/java/org/apache/fop/fonts/CIDFont.java @@ -0,0 +1,93 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts; + +//Java +import java.util.Map; + +/** + * Abstract base class for CID fonts. + */ +public abstract class CIDFont extends CustomFont { + + /** + * usedGlyphs contains orginal, new glyph index + */ + public Map usedGlyphs = new java.util.HashMap(); + + /** + * usedGlyphsIndex contains new glyph, original index + */ + public Map usedGlyphsIndex = new java.util.HashMap(); + public int usedGlyphsCount = 0; + + //private PDFWArray warray = new PDFWArray(); + public int width[] = null; + + // ---- Required ---- + /** + * Returns the name of the base font. + * @return the name of the base font + */ + public abstract String getCidBaseFont(); + + /** + * Returns the type of the CID font. + * @return the type of the CID font + */ + public abstract CIDFontType getCIDType(); + + /** + * Returns the name of the issuer of the font. + * @return a String identifying an issuer of character collections - + * for example, Adobe + */ + public abstract String getRegistry(); + + /** + * Returns a font name for use within a registry. + * @return a String that uniquely names a character collection issued by + * a specific registry - for example, Japan1. + */ + public abstract String getOrdering(); + + /** + * Returns the supplement number of the character collection. + * @return the supplement number + */ + public abstract int getSupplement(); + + + // ---- Optional ---- + /** + * Returns the default width for this font. + * @return the default width + */ + public int getDefaultWidth() { + return 0; + } + + /** + * @see org.apache.fop.fonts.Typeface#isMultiByte() + */ + public boolean isMultiByte() { + return true; + } + +} \ No newline at end of file diff --git a/src/java/org/apache/fop/fonts/CIDFontType.java b/src/java/org/apache/fop/fonts/CIDFontType.java new file mode 100644 index 000000000..4a1ecff71 --- /dev/null +++ b/src/java/org/apache/fop/fonts/CIDFontType.java @@ -0,0 +1,78 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts; + +import org.apache.avalon.framework.ValuedEnum; + +/** + * This class enumerates all supported CID font types. + */ +public class CIDFontType extends ValuedEnum { + + /** + * CID Font Type 0 + */ + public static final CIDFontType CIDTYPE0 = new CIDFontType("CIDFontType0", 0); + + /** + * CID Font Type 2 + */ + public static final CIDFontType CIDTYPE2 = new CIDFontType("CIDFontType2", 1); + + + /** + * @see org.apache.avalon.framework.Enum#Enum(String) + */ + protected CIDFontType(String name, int value) { + super(name, value); + } + + + /** + * Returns the CIDFontType by name. + * @param name Name of the CID font type to look up + * @return FontType the CID font type + */ + public static CIDFontType byName(String name) { + if (name.equalsIgnoreCase(CIDFontType.CIDTYPE0.getName())) { + return CIDFontType.CIDTYPE0; + } else if (name.equalsIgnoreCase(CIDFontType.CIDTYPE2.getName())) { + return CIDFontType.CIDTYPE2; + } else { + throw new IllegalArgumentException("Invalid CID font type: " + name); + } + } + + + /** + * Returns the CID FontType by value. + * @param value Value of the CID font type to look up + * @return FontType the CID font type + */ + public static CIDFontType byValue(int value) { + if (value == CIDFontType.CIDTYPE0.getValue()) { + return CIDFontType.CIDTYPE0; + } else if (value == CIDFontType.CIDTYPE2.getValue()) { + return CIDFontType.CIDTYPE2; + } else { + throw new IllegalArgumentException("Invalid CID font type: " + value); + } + } + +} diff --git a/src/java/org/apache/fop/fonts/CustomFont.java b/src/java/org/apache/fop/fonts/CustomFont.java new file mode 100644 index 000000000..cc29b77d6 --- /dev/null +++ b/src/java/org/apache/fop/fonts/CustomFont.java @@ -0,0 +1,338 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts; + +import java.util.Map; + + +/** + * Abstract base class for custom fonts loaded from files, for example. + */ +public abstract class CustomFont extends Typeface + implements FontDescriptor, MutableFont { + + private String fontName = null; + private String embedFileName = null; + private String embedResourceName = null; + + private int capHeight = 0; + private int xHeight = 0; + private int ascender = 0; + private int descender = 0; + private int[] fontBBox = {0, 0, 0, 0}; + private int flags = 4; + private int stemV = 0; + private int italicAngle = 0; + private int missingWidth = 0; + private FontType fontType = FontType.TYPE1; + private int firstChar = 0; + private int lastChar = 255; + + private Map kerning = new java.util.HashMap(); + + + private boolean useKerning = true; + + + /** + * @see org.apache.fop.fonts.FontMetrics#getFontName() + */ + public String getFontName() { + return fontName; + } + + /** + * Returns an URI representing an embeddable font file. The URI will often + * be a filename or an URL. + * @return URI to an embeddable font file or null if not available. + */ + public String getEmbedFileName() { + return embedFileName; + } + + /** + * Returns the lookup name to an embeddable font file available as a + * resource. + * (todo) Remove this method, this should be done using a resource: URI. + * @return the lookup name + */ + public String getEmbedResourceName() { + return embedResourceName; + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getAscender() + */ + public int getAscender() { + return ascender; + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getDescender() + */ + public int getDescender() { + return descender; + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getCapHeight() + */ + public int getCapHeight() { + return capHeight; + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getAscender(int) + */ + public int getAscender(int size) { + return size * ascender; + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getDescender(int) + */ + public int getDescender(int size) { + return size * descender; + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getCapHeight(int) + */ + public int getCapHeight(int size) { + return size * capHeight; + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getXHeight(int) + */ + public int getXHeight(int size) { + return size * xHeight; + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getFontBBox() + */ + public int[] getFontBBox() { + return fontBBox; + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getFlags() + */ + public int getFlags() { + return flags; + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getStemV() + */ + public int getStemV() { + return stemV; + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getItalicAngle() + */ + public int getItalicAngle() { + return italicAngle; + } + + /** + * Returns the width to be used when no width is available. + * @return a character width + */ + public int getMissingWidth() { + return missingWidth; + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getFontType() + */ + public FontType getFontType() { + return fontType; + } + + /** + * Returns the index of the first character defined in this font. + * @return the index of the first character + */ + public int getFirstChar() { + return 0; + // return firstChar; + /**(todo) Why is this hardcoded??? This code was in SingleByteFont.java */ + } + + /** + * Returns the index of the last character defined in this font. + * @return the index of the last character + */ + public int getLastChar() { + return lastChar; + } + + /** + * Used to determine if kerning is enabled. + * @return True if kerning is enabled. + */ + public boolean isKerningEnabled() { + return useKerning; + } + + /** + * @see org.apache.fop.fonts.FontMetrics#hasKerningInfo() + */ + public final boolean hasKerningInfo() { + return (isKerningEnabled() & kerning.isEmpty()); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getKerningInfo() + */ + public final Map getKerningInfo() { + if (isKerningEnabled()) { + return kerning; + } else { + return java.util.Collections.EMPTY_MAP; + } + } + + + /* ---- MutableFont interface ---- */ + + /** + * @see org.apache.fop.fonts.MutableFont#setFontName(String) + */ + public void setFontName(String name) { + this.fontName = name; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setEmbedFileName(String) + */ + public void setEmbedFileName(String path) { + this.embedFileName = path; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setEmbedResourceName(String) + */ + public void setEmbedResourceName(String name) { + this.embedResourceName = name; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setCapHeight(int) + */ + public void setCapHeight(int capHeight) { + this.capHeight = capHeight; + } + + /** + * Returns the XHeight value of the font. + * @param xHeight the XHeight value + */ + public void setXHeight(int xHeight) { + this.xHeight = xHeight; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setAscender(int) + */ + public void setAscender(int ascender) { + this.ascender = ascender; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setDescender(int) + */ + public void setDescender(int descender) { + this.descender = descender; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setFontBBox(int[]) + */ + public void setFontBBox(int[] bbox) { + this.fontBBox = bbox; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setFlags(int) + */ + public void setFlags(int flags) { + this.flags = flags; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setStemV(int) + */ + public void setStemV(int stemV) { + this.stemV = stemV; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setItalicAngle(int) + */ + public void setItalicAngle(int italicAngle) { + this.italicAngle = italicAngle; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setMissingWidth(int) + */ + public void setMissingWidth(int width) { + this.missingWidth = width; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setFontType(FontType) + */ + public void setFontType(FontType fontType) { + this.fontType = fontType; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setFirstChar(int) + */ + public void setFirstChar(int index) { + this.firstChar = index; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setLastChar(int) + */ + public void setLastChar(int index) { + this.lastChar = index; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setKerningEnabled(boolean) + */ + public void setKerningEnabled(boolean enabled) { + this.useKerning = enabled; + } + + /** + * @see org.apache.fop.fonts.MutableFont#putKerningEntry(Integer, Map) + */ + public void putKerningEntry(Integer key, Map value) { + this.kerning.put(key, value); + } + +} diff --git a/src/java/org/apache/fop/fonts/EmbedFontInfo.java b/src/java/org/apache/fop/fonts/EmbedFontInfo.java new file mode 100644 index 000000000..63f3d3b0d --- /dev/null +++ b/src/java/org/apache/fop/fonts/EmbedFontInfo.java @@ -0,0 +1,80 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts; + +import java.util.List; + +/** + * FontInfo contains meta information on fonts (where is the metrics file etc.) + */ +public class EmbedFontInfo { + + private String metricsFile, embedFile; + private boolean kerning; + private List fontTriplets; + + /** + * Main constructor + * @param metricsFile Path to the xml file containing font metrics + * @param kerning True if kerning should be enabled + * @param fontTriplets List of font triplets to associate with this font + * @param embedFile Path to the embeddable font file (may be null) + */ + public EmbedFontInfo(String metricsFile, boolean kerning, + List fontTriplets, String embedFile) { + this.metricsFile = metricsFile; + this.embedFile = embedFile; + this.kerning = kerning; + this.fontTriplets = fontTriplets; + } + + /** + * Returns the path to the metrics file + * @return the metrics file path + */ + public String getMetricsFile() { + return metricsFile; + } + + /** + * Returns the path to the embeddable font file + * @return the font file path + */ + public String getEmbedFile() { + return embedFile; + } + + /** + * Determines if kerning is enabled + * @return True if enabled + */ + public boolean getKerning() { + return kerning; + } + + /** + * Returns the list of font triplets associated with this font. + * @return List of font triplets + */ + public List getFontTriplets() { + return fontTriplets; + } + +} + diff --git a/src/java/org/apache/fop/fonts/FontReader.java b/src/java/org/apache/fop/fonts/FontReader.java new file mode 100644 index 000000000..e5d5ecf32 --- /dev/null +++ b/src/java/org/apache/fop/fonts/FontReader.java @@ -0,0 +1,286 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts; + +//Java +import java.util.List; +import java.util.Map; +import java.io.IOException; + +import javax.xml.parsers.SAXParserFactory; + +//SAX +import org.xml.sax.XMLReader; +import org.xml.sax.SAXException; +import org.xml.sax.Locator; +import org.xml.sax.Attributes; +import org.xml.sax.helpers.DefaultHandler; + +//FOP +import org.apache.fop.apps.FOPException; + +/** + * Class for reading a metric.xml file and creating a font object. + * Typical usage: + *

+ * FontReader reader = new FontReader();
+ * reader.setFontEmbedPath();
+ * reader.useKerning(true);
+ * Font f = reader.getFont();
+ * 
+ */ +public class FontReader extends DefaultHandler { + + private Locator locator = null; + private boolean isCID = false; + private CustomFont returnFont = null; + private MultiByteFont multiFont = null; + private SingleByteFont singleFont = null; + private StringBuffer text = new StringBuffer(); + + private List cidWidths = null; + private int cidWidthIndex = 0; + + private Map currentKerning = null; + + private List bfranges = null; + + private void createFont(String path) throws FOPException { + XMLReader parser = null; + + try { + final SAXParserFactory factory = javax.xml.parsers.SAXParserFactory.newInstance(); + factory.setNamespaceAware(true); + parser = factory.newSAXParser().getXMLReader(); + } catch (Exception e) { + throw new FOPException(e); + } + if (parser == null) { + throw new FOPException("Unable to create SAX parser"); + } + + try { + parser.setFeature("http://xml.org/sax/features/namespace-prefixes", + false); + } catch (SAXException e) { + throw new FOPException("You need a SAX parser which supports SAX version 2", + e); + } + + parser.setContentHandler(this); + + try { + parser.parse(path); + } catch (SAXException e) { + throw new FOPException(e); + } catch (IOException e) { + throw new FOPException(e); + } + + } + + /** + * Sets the path to embed a font. A null value disables font embedding. + * @param path URI for the embeddable file + */ + public void setFontEmbedPath(String path) { + returnFont.setEmbedFileName(path); + } + + /** + * Enable/disable use of kerning for the font + * @param enabled true to enable kerning, false to disable + */ + public void setKerningEnabled(boolean enabled) { + returnFont.setKerningEnabled(enabled); + } + + + /** + * Get the generated font object + * @return the font + */ + public Typeface getFont() { + return returnFont; + } + + /** + * Construct a FontReader object from a path to a metric.xml file + * and read metric data + * @param path URI to the font metric file + * @throws FOPException if loading the font fails + */ + public FontReader(String path) throws FOPException { + createFont(path); + } + + /** + * @see org.xml.sax.ContentHandler#startDocument() + */ + public void startDocument() { + } + + /** + * @see org.xml.sax.ContentHandler#setDocumentLocator(Locator) + */ + public void setDocumentLocator(Locator locator) { + this.locator = locator; + } + + /** + * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes) + */ + public void startElement(String uri, String localName, String qName, + Attributes attributes) { + if (localName.equals("font-metrics")) { + if ("TYPE0".equals(attributes.getValue("type"))) { + multiFont = new MultiByteFont(); + returnFont = multiFont; + isCID = true; + } else if ("TRUETYPE".equals(attributes.getValue("type"))) { + singleFont = new SingleByteFont(); + singleFont.setFontType(FontType.TRUETYPE); + returnFont = singleFont; + isCID = false; + } else { + singleFont = new SingleByteFont(); + singleFont.setFontType(FontType.TYPE1); + returnFont = singleFont; + isCID = false; + } + } else if ("embed".equals(localName)) { + returnFont.setEmbedFileName(attributes.getValue("file")); + returnFont.setEmbedResourceName(attributes.getValue("class")); + } else if ("cid-widths".equals(localName)) { + cidWidthIndex = getInt(attributes.getValue("start-index")); + cidWidths = new java.util.ArrayList(); + } else if ("kerning".equals(localName)) { + currentKerning = new java.util.HashMap(); + returnFont.putKerningEntry(new Integer(attributes.getValue("kpx1")), + currentKerning); + } else if ("bfranges".equals(localName)) { + bfranges = new java.util.ArrayList(); + } else if ("bf".equals(localName)) { + BFEntry entry = new BFEntry(getInt(attributes.getValue("us")), + getInt(attributes.getValue("ue")), + getInt(attributes.getValue("gi"))); + bfranges.add(entry); + } else if ("wx".equals(localName)) { + cidWidths.add(new Integer(attributes.getValue("w"))); + } else if ("widths".equals(localName)) { + //singleFont.width = new int[256]; + } else if ("char".equals(localName)) { + try { + singleFont.setWidth(Integer.parseInt(attributes.getValue("idx")), + Integer.parseInt(attributes.getValue("wdt"))); + } catch (NumberFormatException ne) { + System.out.println("Malformed width in metric file: " + + ne.getMessage()); + } + } else if ("pair".equals(localName)) { + currentKerning.put(new Integer(attributes.getValue("kpx2")), + new Integer(attributes.getValue("kern"))); + } + } + + private int getInt(String str) { + int ret = 0; + try { + ret = Integer.parseInt(str); + } catch (Exception e) { + /**@todo log this exception */ + } + return ret; + } + + /** + * @see org.xml.sax.ContentHandler#endElement(String, String, String) + */ + public void endElement(String uri, String localName, String qName) { + if ("font-name".equals(localName)) { + returnFont.setFontName(text.toString()); + } else if ("ttc-name".equals(localName) && isCID) { + multiFont.setTTCName(text.toString()); + } else if ("cap-height".equals(localName)) { + returnFont.setCapHeight(getInt(text.toString())); + } else if ("x-height".equals(localName)) { + returnFont.setXHeight(getInt(text.toString())); + } else if ("ascender".equals(localName)) { + returnFont.setAscender(getInt(text.toString())); + } else if ("descender".equals(localName)) { + returnFont.setDescender(getInt(text.toString())); + } else if ("left".equals(localName)) { + int[] bbox = returnFont.getFontBBox(); + bbox[0] = getInt(text.toString()); + returnFont.setFontBBox(bbox); + } else if ("bottom".equals(localName)) { + int[] bbox = returnFont.getFontBBox(); + bbox[1] = getInt(text.toString()); + returnFont.setFontBBox(bbox); + } else if ("right".equals(localName)) { + int[] bbox = returnFont.getFontBBox(); + bbox[2] = getInt(text.toString()); + returnFont.setFontBBox(bbox); + } else if ("top".equals(localName)) { + int[] bbox = returnFont.getFontBBox(); + bbox[3] = getInt(text.toString()); + returnFont.setFontBBox(bbox); + } else if ("first-char".equals(localName)) { + returnFont.setFirstChar(getInt(text.toString())); + } else if ("last-char".equals(localName)) { + returnFont.setLastChar(getInt(text.toString())); + } else if ("flags".equals(localName)) { + returnFont.setFlags(getInt(text.toString())); + } else if ("stemv".equals(localName)) { + returnFont.setStemV(getInt(text.toString())); + } else if ("italic-angle".equals(localName)) { + returnFont.setItalicAngle(getInt(text.toString())); + } else if ("missing-width".equals(localName)) { + returnFont.setMissingWidth(getInt(text.toString())); + } else if ("cid-type".equals(localName)) { + multiFont.setCIDType(CIDFontType.byName(text.toString())); + } else if ("default-width".equals(localName)) { + multiFont.setDefaultWidth(getInt(text.toString())); + } else if ("cid-widths".equals(localName)) { + int[] wds = new int[cidWidths.size()]; + int j = 0; + for (int count = 0; count < cidWidths.size(); count++) { + Integer i = (Integer)cidWidths.get(count); + wds[j++] = i.intValue(); + } + + //multiFont.addCIDWidthEntry(cidWidthIndex, wds); + multiFont.setWidthArray(wds); + + } else if ("bfranges".equals(localName)) { + multiFont.setBFEntries((BFEntry[])bfranges.toArray(new BFEntry[0])); + } + text.setLength(0); //Reset text buffer (see characters()) + } + + /** + * @see org.xml.sax.ContentHandler#characters(char[], int, int) + */ + public void characters(char[] ch, int start, int length) { + text.append(ch, start, length); + } + +} + + diff --git a/src/java/org/apache/fop/fonts/FontSetup.java b/src/java/org/apache/fop/fonts/FontSetup.java new file mode 100644 index 000000000..9c0c1e6f6 --- /dev/null +++ b/src/java/org/apache/fop/fonts/FontSetup.java @@ -0,0 +1,236 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts; + +// FOP +import org.apache.fop.apps.Document; + +// FOP (base 14 fonts) +import org.apache.fop.fonts.base14.Helvetica; +import org.apache.fop.fonts.base14.HelveticaBold; +import org.apache.fop.fonts.base14.HelveticaOblique; +import org.apache.fop.fonts.base14.HelveticaBoldOblique; +import org.apache.fop.fonts.base14.TimesRoman; +import org.apache.fop.fonts.base14.TimesBold; +import org.apache.fop.fonts.base14.TimesItalic; +import org.apache.fop.fonts.base14.TimesBoldItalic; +import org.apache.fop.fonts.base14.Courier; +import org.apache.fop.fonts.base14.CourierBold; +import org.apache.fop.fonts.base14.CourierOblique; +import org.apache.fop.fonts.base14.CourierBoldOblique; +import org.apache.fop.fonts.base14.Symbol; +import org.apache.fop.fonts.base14.ZapfDingbats; + +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.ConfigurationException; + +// Java +import java.util.List; + +/** + * Default fonts for FOP application; currently this uses PDF's fonts + * by default. + * + * Assigns the font (with metrics) to internal names like "F1" and + * assigns family-style-weight triplets to the fonts + */ +public class FontSetup { + + /** + * Sets up the font info object. + * + * Adds metrics for basic fonts and useful family-style-weight + * triplets for lookup. + * + * @param fontInfo the font info object to set up + * @param embedList ??? + */ + public static void setup(Document fontInfo, List embedList) { + + fontInfo.addMetrics("F1", new Helvetica()); + fontInfo.addMetrics("F2", new HelveticaOblique()); + fontInfo.addMetrics("F3", new HelveticaBold()); + fontInfo.addMetrics("F4", new HelveticaBoldOblique()); + fontInfo.addMetrics("F5", new TimesRoman()); + fontInfo.addMetrics("F6", new TimesItalic()); + fontInfo.addMetrics("F7", new TimesBold()); + fontInfo.addMetrics("F8", new TimesBoldItalic()); + fontInfo.addMetrics("F9", new Courier()); + fontInfo.addMetrics("F10", new CourierOblique()); + fontInfo.addMetrics("F11", new CourierBold()); + fontInfo.addMetrics("F12", new CourierBoldOblique()); + fontInfo.addMetrics("F13", new Symbol()); + fontInfo.addMetrics("F14", new ZapfDingbats()); + + // Custom type 1 fonts step 1/2 + // fontInfo.addMetrics("F15", new OMEP()); + // fontInfo.addMetrics("F16", new GaramondLightCondensed()); + // fontInfo.addMetrics("F17", new BauerBodoniBoldItalic()); + + /* any is treated as serif */ + fontInfo.addFontProperties("F5", "any", "normal", Font.NORMAL); + fontInfo.addFontProperties("F6", "any", "italic", Font.NORMAL); + fontInfo.addFontProperties("F6", "any", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F7", "any", "normal", Font.BOLD); + fontInfo.addFontProperties("F8", "any", "italic", Font.BOLD); + fontInfo.addFontProperties("F8", "any", "oblique", Font.BOLD); + + fontInfo.addFontProperties("F1", "sans-serif", "normal", Font.NORMAL); + fontInfo.addFontProperties("F2", "sans-serif", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F2", "sans-serif", "italic", Font.NORMAL); + fontInfo.addFontProperties("F3", "sans-serif", "normal", Font.BOLD); + fontInfo.addFontProperties("F4", "sans-serif", "oblique", Font.BOLD); + fontInfo.addFontProperties("F4", "sans-serif", "italic", Font.BOLD); + fontInfo.addFontProperties("F5", "serif", "normal", Font.NORMAL); + fontInfo.addFontProperties("F6", "serif", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F6", "serif", "italic", Font.NORMAL); + fontInfo.addFontProperties("F7", "serif", "normal", Font.BOLD); + fontInfo.addFontProperties("F8", "serif", "oblique", Font.BOLD); + fontInfo.addFontProperties("F8", "serif", "italic", Font.BOLD); + fontInfo.addFontProperties("F9", "monospace", "normal", Font.NORMAL); + fontInfo.addFontProperties("F10", "monospace", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F10", "monospace", "italic", Font.NORMAL); + fontInfo.addFontProperties("F11", "monospace", "normal", Font.BOLD); + fontInfo.addFontProperties("F12", "monospace", "oblique", Font.BOLD); + fontInfo.addFontProperties("F12", "monospace", "italic", Font.BOLD); + + fontInfo.addFontProperties("F1", "Helvetica", "normal", Font.NORMAL); + fontInfo.addFontProperties("F2", "Helvetica", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F2", "Helvetica", "italic", Font.NORMAL); + fontInfo.addFontProperties("F3", "Helvetica", "normal", Font.BOLD); + fontInfo.addFontProperties("F4", "Helvetica", "oblique", Font.BOLD); + fontInfo.addFontProperties("F4", "Helvetica", "italic", Font.BOLD); + fontInfo.addFontProperties("F5", "Times", "normal", Font.NORMAL); + fontInfo.addFontProperties("F6", "Times", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F6", "Times", "italic", Font.NORMAL); + fontInfo.addFontProperties("F7", "Times", "normal", Font.BOLD); + fontInfo.addFontProperties("F8", "Times", "oblique", Font.BOLD); + fontInfo.addFontProperties("F8", "Times", "italic", Font.BOLD); + fontInfo.addFontProperties("F9", "Courier", "normal", Font.NORMAL); + fontInfo.addFontProperties("F10", "Courier", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F10", "Courier", "italic", Font.NORMAL); + fontInfo.addFontProperties("F11", "Courier", "normal", Font.BOLD); + fontInfo.addFontProperties("F12", "Courier", "oblique", Font.BOLD); + fontInfo.addFontProperties("F12", "Courier", "italic", Font.BOLD); + fontInfo.addFontProperties("F13", "Symbol", "normal", Font.NORMAL); + fontInfo.addFontProperties("F14", "ZapfDingbats", "normal", Font.NORMAL); + + // Custom type 1 fonts step 2/2 + // fontInfo.addFontProperties("F15", "OMEP", "normal", FontInfo.NORMAL); + // fontInfo.addFontProperties("F16", "Garamond-LightCondensed", "normal", FontInfo.NORMAL); + // fontInfo.addFontProperties("F17", "BauerBodoni", "italic", FontInfo.BOLD); + + /* for compatibility with PassiveTex */ + fontInfo.addFontProperties("F5", "Times-Roman", "normal", Font.NORMAL); + fontInfo.addFontProperties("F6", "Times-Roman", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F6", "Times-Roman", "italic", Font.NORMAL); + fontInfo.addFontProperties("F7", "Times-Roman", "normal", Font.BOLD); + fontInfo.addFontProperties("F8", "Times-Roman", "oblique", Font.BOLD); + fontInfo.addFontProperties("F8", "Times-Roman", "italic", Font.BOLD); + fontInfo.addFontProperties("F5", "Times Roman", "normal", Font.NORMAL); + fontInfo.addFontProperties("F6", "Times Roman", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F6", "Times Roman", "italic", Font.NORMAL); + fontInfo.addFontProperties("F7", "Times Roman", "normal", Font.BOLD); + fontInfo.addFontProperties("F8", "Times Roman", "oblique", Font.BOLD); + fontInfo.addFontProperties("F8", "Times Roman", "italic", Font.BOLD); + fontInfo.addFontProperties("F9", "Computer-Modern-Typewriter", + "normal", Font.NORMAL); + + /* Add configured fonts */ + addConfiguredFonts(fontInfo, embedList, 15); + } + + /** + * Add fonts from configuration file starting with + * internalnames F + * @param fontInfo the font info object to set up + * @param fontInfos ??? + * @param num starting index for internal font numbering + */ + public static void addConfiguredFonts(Document fontInfo, List fontInfos, int num) { + if (fontInfos == null) { + return; //No fonts to process + } + + String internalName = null; + //FontReader reader = null; + + for (int i = 0; i < fontInfos.size(); i++) { + EmbedFontInfo configFontInfo = (EmbedFontInfo)fontInfos.get(i); + + String metricsFile = configFontInfo.getMetricsFile(); + if (metricsFile != null) { + internalName = "F" + num; + num++; + /* + reader = new FontReader(metricsFile); + reader.useKerning(configFontInfo.getKerning()); + reader.setFontEmbedPath(configFontInfo.getEmbedFile()); + fontInfo.addMetrics(internalName, reader.getFont()); + */ + LazyFont font = new LazyFont(configFontInfo.getEmbedFile(), + metricsFile, + configFontInfo.getKerning()); + fontInfo.addMetrics(internalName, font); + + List triplets = configFontInfo.getFontTriplets(); + for (int c = 0; c < triplets.size(); c++) { + FontTriplet triplet = (FontTriplet)triplets.get(c); + + int weight = FontUtil.parseCSS2FontWeight(triplet.getWeight()); + //System.out.println("Registering: "+triplet+" weight="+weight); + fontInfo.addFontProperties(internalName, + triplet.getName(), + triplet.getStyle(), + weight); + } + } + } + } + + /** + * Builds a list of EmbedFontInfo objects for use with the setup() method. + * @param cfg Configuration object + * @return List the newly created list of fonts + * @throws ConfigurationException if something's wrong with the config data + */ + public static List buildFontListFromConfiguration(Configuration cfg) + throws ConfigurationException { + List fontList = new java.util.ArrayList(); + Configuration[] font = cfg.getChildren("font"); + for (int i = 0; i < font.length; i++) { + Configuration[] triple = font[i].getChildren("font-triplet"); + List tripleList = new java.util.ArrayList(); + for (int j = 0; j < triple.length; j++) { + tripleList.add(new FontTriplet(triple[j].getAttribute("name"), + triple[j].getAttribute("weight"), + triple[j].getAttribute("style"))); + } + + EmbedFontInfo efi; + efi = new EmbedFontInfo(font[i].getAttribute("metrics-url"), + font[i].getAttributeAsBoolean("kerning", false), + tripleList, font[i].getAttribute("embed-url", null)); + + fontList.add(efi); + } + return fontList; + } +} + diff --git a/src/java/org/apache/fop/fonts/FontTriplet.java b/src/java/org/apache/fop/fonts/FontTriplet.java new file mode 100644 index 000000000..ed7e13f83 --- /dev/null +++ b/src/java/org/apache/fop/fonts/FontTriplet.java @@ -0,0 +1,71 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts; + +/** + * FontTriplet contains information on name, weight, style of one font + */ +public class FontTriplet { + + private String name, weight, style; + + /** + * Creates a new font triplet. + * @param name font name + * @param weight font weight (normal, bold etc.) + * @param style font style (normal, italic etc.) + */ + public FontTriplet(String name, String weight, String style) { + this.name = name; + this.weight = weight; + this.style = style; + } + + /** + * Returns the font name. + * @return the font name + */ + public String getName() { + return name; + } + + /** + * Returns the font weight. + * @return the font weight + */ + public String getWeight() { + return weight; + } + + /** + * Returns the font style. + * @return the font style + */ + public String getStyle() { + return style; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + return getName() + "," + getStyle() + "," + getWeight(); + } +} + diff --git a/src/java/org/apache/fop/fonts/FontUtil.java b/src/java/org/apache/fop/fonts/FontUtil.java new file mode 100644 index 000000000..999836d8f --- /dev/null +++ b/src/java/org/apache/fop/fonts/FontUtil.java @@ -0,0 +1,59 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts; + +/** + * Font utilities. + */ +public class FontUtil { + + /** + * Parses an CSS2 (SVG and XSL-FO) font weight (normal, bold, 100-900) to + * an integer. + * See http://www.w3.org/TR/REC-CSS2/fonts.html#propdef-font-weight + * TODO: Implement "lighter" and "bolder". + * @param text the font weight to parse + * @return an integer between 100 and 900 (100, 200, 300...) + */ + public static int parseCSS2FontWeight(String text) { + int weight = 400; + try { + weight = Integer.parseInt(text); + weight = ((int)weight / 100) * 100; + weight = Math.max(weight, 100); + weight = Math.min(weight, 900); + } catch (NumberFormatException nfe) { + //weight is no number, so convert symbolic name to number + if (text.equals("normal")) { + weight = 400; + } else if (text.equals("bold")) { + weight = 700; + } else { + throw new IllegalArgumentException( + "Illegal value for font weight: '" + + text + + "'. Use one of: 100, 200, 300, " + + "400, 500, 600, 700, 800, 900, " + + "normal (=400), bold (=700)"); + } + } + return weight; + } + +} diff --git a/src/java/org/apache/fop/fonts/LazyFont.java b/src/java/org/apache/fop/fonts/LazyFont.java new file mode 100644 index 000000000..b16f6b027 --- /dev/null +++ b/src/java/org/apache/fop/fonts/LazyFont.java @@ -0,0 +1,256 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts; + +//Java +import java.util.Map; + +//FOP + + +/** + * This class is used to defer the loading of a font until it is really used. + */ +public class LazyFont extends Typeface implements FontDescriptor { + + private String metricsFileName = null; + private String fontEmbedPath = null; + private boolean useKerning = false; + + private boolean isMetricsLoaded = false; + private Typeface realFont = null; + private FontDescriptor realFontDescriptor = null; + + /** + * Main constructor + * @param fontEmbedPath path to embeddable file (may be null) + * @param metricsFileName path to the metrics XML file + * @param useKerning True, if kerning should be enabled + */ + public LazyFont(String fontEmbedPath, String metricsFileName, boolean useKerning) { + this.metricsFileName = metricsFileName; + this.fontEmbedPath = fontEmbedPath; + this.useKerning = useKerning; + } + + private void load() { + if (!isMetricsLoaded) { + isMetricsLoaded = true; + try { + /**@todo Possible thread problem here */ + + FontReader reader = new FontReader(metricsFileName); + reader.setKerningEnabled(useKerning); + reader.setFontEmbedPath(fontEmbedPath); + realFont = reader.getFont(); + if (realFont instanceof FontDescriptor) { + realFontDescriptor = (FontDescriptor) realFont; + } + // System.out.println("Metrics " + metricsFileName + " loaded."); + } catch (Exception ex) { + ex.printStackTrace(); + /**@todo Log this exception */ + //log.error("Failed to read font metrics file " + // + metricsFileName + // + " : " + ex.getMessage()); + } + } + } + + /** + * Gets the real font. + * @return the real font + */ + public Typeface getRealFont() { + load(); + return realFont; + } + + // ---- Font ---- + /** + * @see org.apache.fop.fonts.Typeface#getEncoding() + */ + public String getEncoding() { + load(); + return realFont.getEncoding(); + } + + /** + * @see org.apache.fop.fonts.Font#mapChar(char) + */ + public char mapChar(char c) { + load(); + return realFont.mapChar(c); + } + + /** + * @see org.apache.fop.fonts.Typeface#isMultiByte() + */ + public boolean isMultiByte() { + return realFont.isMultiByte(); + } + + // ---- FontMetrics interface ---- + /** + * @see org.apache.fop.fonts.FontMetrics#getFontName() + */ + public String getFontName() { + load(); + return realFont.getFontName(); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getAscender(int) + */ + public int getAscender(int size) { + load(); + return realFont.getAscender(size); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getCapHeight(int) + */ + public int getCapHeight(int size) { + load(); + return realFont.getCapHeight(size); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getDescender(int) + */ + public int getDescender(int size) { + load(); + return realFont.getDescender(size); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getXHeight(int) + */ + public int getXHeight(int size) { + load(); + return realFont.getXHeight(size); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getWidth(int, int) + */ + public int getWidth(int i, int size) { + load(); + return realFont.getWidth(i, size); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getWidths() + */ + public int[] getWidths() { + load(); + return realFont.getWidths(); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#hasKerningInfo() + */ + public boolean hasKerningInfo() { + load(); + return realFont.hasKerningInfo(); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getKerningInfo() + */ + public Map getKerningInfo() { + load(); + return realFont.getKerningInfo(); + } + + // ---- FontDescriptor interface ---- + /** + * @see org.apache.fop.fonts.FontDescriptor#getCapHeight() + */ + public int getCapHeight() { + load(); + return realFontDescriptor.getCapHeight(); + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getDescender() + */ + public int getDescender() { + load(); + return realFontDescriptor.getDescender(); + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getAscender() + */ + public int getAscender() { + load(); + return realFontDescriptor.getAscender(); + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getFlags() + */ + public int getFlags() { + load(); + return realFontDescriptor.getFlags(); + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getFontBBox() + */ + public int[] getFontBBox() { + load(); + return realFontDescriptor.getFontBBox(); + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getItalicAngle() + */ + public int getItalicAngle() { + load(); + return realFontDescriptor.getItalicAngle(); + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getStemV() + */ + public int getStemV() { + load(); + return realFontDescriptor.getStemV(); + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getFontType() + */ + public FontType getFontType() { + load(); + return realFontDescriptor.getFontType(); + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#isEmbeddable() + */ + public boolean isEmbeddable() { + load(); + return realFontDescriptor.isEmbeddable(); + } + +} + diff --git a/src/java/org/apache/fop/fonts/MultiByteFont.java b/src/java/org/apache/fop/fonts/MultiByteFont.java new file mode 100644 index 000000000..62f5be961 --- /dev/null +++ b/src/java/org/apache/fop/fonts/MultiByteFont.java @@ -0,0 +1,294 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts; + +//Java +import java.util.Map; + +/** + * Generic MultiByte (CID) font + */ +public class MultiByteFont extends CIDFont { + + private static int uniqueCounter = 1; + + + private String ttcName = null; + private String encoding = "Identity-H"; + + private String embedResourceName = null; + + private int defaultWidth = 0; + private CIDFontType cidType = CIDFontType.CIDTYPE2; + + private String namePrefix = null; // Quasi unique prefix + + private BFEntry[] bfentries = null; + + /** + * Default constructor + */ + public MultiByteFont() { + // Make sure that the 3 first glyphs are included + usedGlyphs.put(new Integer(0), new Integer(0)); + usedGlyphsIndex.put(new Integer(0), new Integer(0)); + usedGlyphsCount++; + usedGlyphs.put(new Integer(1), new Integer(1)); + usedGlyphsIndex.put(new Integer(1), new Integer(1)); + usedGlyphsCount++; + usedGlyphs.put(new Integer(2), new Integer(2)); + usedGlyphsIndex.put(new Integer(2), new Integer(2)); + usedGlyphsCount++; + + // Create a quasiunique prefix for fontname + int cnt = 0; + synchronized (this.getClass()) { + cnt = uniqueCounter++; + } + int ctm = (int)(System.currentTimeMillis() & 0xffff); + namePrefix = new String(cnt + "E" + Integer.toHexString(ctm)); + + setFontType(FontType.TYPE0); + } + + /** + * @see org.apache.fop.fonts.CIDFont#getDefaultWidth() + */ + public int getDefaultWidth() { + return defaultWidth; + } + + /** + * @see org.apache.fop.fonts.CIDFont#getRegistry() + */ + public String getRegistry() { + return "Adobe"; + } + + /** + * @see org.apache.fop.fonts.CIDFont#getOrdering() + */ + public String getOrdering() { + return "UCS"; + } + + /** + * @see org.apache.fop.fonts.CIDFont#getSupplement() + */ + public int getSupplement() { + return 0; + } + + /** + * @see org.apache.fop.fonts.CIDFont#getCIDType() + */ + public CIDFontType getCIDType() { + return cidType; + } + + /** + * Sets the CIDType. + * @param cidType The cidType to set + */ + public void setCIDType(CIDFontType cidType) { + this.cidType = cidType; + } + + /** + * @see org.apache.fop.fonts.CIDFont#getCidBaseFont() + */ + public String getCidBaseFont() { + if (isEmbeddable()) { + return namePrefix + super.getFontName(); + } else { + return super.getFontName(); + } + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#isEmbeddable() + */ + public boolean isEmbeddable() { + if (getEmbedFileName() == null + && embedResourceName == null) { + return false; + } else { + return true; + } + } + + /** + * @see org.apache.fop.fonts.Typeface#getEncoding() + */ + public String getEncoding() { + return encoding; + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getFontName() + */ + public String getFontName() { + if (isEmbeddable()) { + return namePrefix + super.getFontName(); + } else { + return super.getFontName(); + } + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getWidth(int, int) + */ + public int getWidth(int i, int size) { + if (isEmbeddable()) { + Integer idx = (Integer)usedGlyphsIndex.get(new Integer(i)); + return size * width[idx.intValue()]; + } else { + return size * width[i]; + } + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getWidths() + */ + public int[] getWidths() { + int[] arr = new int[width.length]; + System.arraycopy(width, 0, arr, 0, width.length - 1); + /* + for (int i = 0; i < arr.length; i++) + arr[i] *= size; + */ + return arr; + } + + /** + * Remaps a codepoint based. + * @param i codepoint to remap + * @return new codepoint + */ +/* unused + public Integer reMap(Integer i) { + if (isEmbeddable()) { + Integer ret = (Integer)usedGlyphsIndex.get(i); + if (ret == null) { + ret = i; + } + return ret; + } else { + return i; + } + + } +*/ + + /** + * @see org.apache.fop.fonts.Font#mapChar(char) + */ + public char mapChar(char c) { + int idx = (int)c; + int retIdx = 0; + + for (int i = 0; (i < bfentries.length) && retIdx == 0; i++) { + if (bfentries[i].getUnicodeStart() <= idx + && bfentries[i].getUnicodeEnd() >= idx) { + retIdx = bfentries[i].getGlyphStartIndex() + idx + - bfentries[i].getUnicodeStart(); + } + } + + if (isEmbeddable()) { + // Reencode to a new subset font or get + // the reencoded value + Integer newIdx = (Integer)usedGlyphs.get(new Integer(retIdx)); + if (newIdx == null) { + usedGlyphs.put(new Integer(retIdx), + new Integer(usedGlyphsCount)); + usedGlyphsIndex.put(new Integer(usedGlyphsCount), + new Integer(retIdx)); + retIdx = usedGlyphsCount; + // System.out.println(c+"("+(int)c+") = "+retIdx); + usedGlyphsCount++; + } else { + retIdx = newIdx.intValue(); + } + } + + return (char)retIdx; + } + + /** + * Sets the bfentries. + * @param bfentries The bfentries to set + */ + public void setBFEntries(BFEntry[] bfentries) { + this.bfentries = bfentries; + } + + /** + * Sets the defaultWidth. + * @param defaultWidth The defaultWidth to set + */ + public void setDefaultWidth(int defaultWidth) { + this.defaultWidth = defaultWidth; + } + + /** + * Returns the TrueType Collection Name. + * @return the TrueType Collection Name + */ + public String getTTCName() { + return ttcName; + } + + /** + * Sets the the TrueType Collection Name. + * @param ttcName the TrueType Collection Name + */ + public void setTTCName(String ttcName) { + this.ttcName = ttcName; + } + + /** + * Adds a new CID width entry to the font. + * @param cidWidthIndex index + * @param wds array of widths + */ + /* + public void addCIDWidthEntry(int cidWidthIndex, int[] wds) { + this.warray.addEntry(cidWidthIndex, wds); + }*/ + + + /** + * Sets the width array. + * @param wds array of widths. + */ + public void setWidthArray(int[] wds) { + this.width = wds; + } + + /** + * Returns a Map of used Glyphs. + * @return Map Map of used Glyphs + */ + public Map getUsedGlyphs() { + return usedGlyphs; + } + +} + diff --git a/src/java/org/apache/fop/fonts/MutableFont.java b/src/java/org/apache/fop/fonts/MutableFont.java new file mode 100644 index 000000000..42b5b54e5 --- /dev/null +++ b/src/java/org/apache/fop/fonts/MutableFont.java @@ -0,0 +1,126 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts; + +import java.util.Map; + + +/** + * This interface is used to set the values of a font during configuration time. + */ +public interface MutableFont { + + /** + * Sets the font name. + * @param name font name + */ + void setFontName(String name); + + /** + * Sets the path to the embeddable font file. + * @param path URI to the file + */ + void setEmbedFileName(String path); + + /** + * Sets the resource name of the embeddable font file. + * @param name resource name + */ + void setEmbedResourceName(String name); + + /** + * Sets the capital height value. + * @param capHeight capital height + */ + void setCapHeight(int capHeight); + + /** + * Sets the ascent value. + * @param ascender ascent height + */ + void setAscender(int ascender); + + /** + * Sets the descent value. + * @param descender descent value + */ + void setDescender(int descender); + + /** + * Sets the font's bounding box + * @param bbox bounding box + */ + void setFontBBox(int[] bbox); + + /** + * Sets the font's flags + * @param flags flags + */ + void setFlags(int flags); + + /** + * Sets the font's StemV value. + * @param stemV StemV + */ + void setStemV(int stemV); + + /** + * Sets the font's italic angle. + * @param italicAngle italic angle + */ + void setItalicAngle(int italicAngle); + + /** + * Sets the font's default width + * @param width default width + */ + void setMissingWidth(int width); + + /** + * Sets the font type. + * @param fontType font type + */ + void setFontType(FontType fontType); + + /** + * Sets the index of the first character in the character table. + * @param index index of first character + */ + void setFirstChar(int index); + + /** + * Sets the index of the last character in the character table. + * @param index index of the last character + */ + void setLastChar(int index); + + /** + * Enables/disabled kerning. + * @param enabled True if kerning should be enabled if available + */ + void setKerningEnabled(boolean enabled); + + /** + * Adds an entry to the kerning table. + * @param key Kerning key + * @param value Kerning value + */ + void putKerningEntry(Integer key, Map value); + +} diff --git a/src/java/org/apache/fop/fonts/SingleByteFont.java b/src/java/org/apache/fop/fonts/SingleByteFont.java new file mode 100644 index 000000000..e28bc653a --- /dev/null +++ b/src/java/org/apache/fop/fonts/SingleByteFont.java @@ -0,0 +1,96 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts; + +/** + * Generic SingleByte font + */ +public class SingleByteFont extends CustomFont { + + private final CodePointMapping mapping + = CodePointMapping.getMapping("WinAnsiEncoding"); + + private String encoding = "WinAnsiEncoding"; + + private int width[] = null; + + + /** + * @see org.apache.fop.fonts.FontDescriptor#isEmbeddable() + */ + public boolean isEmbeddable() { + return (getEmbedFileName() == null && getEmbedResourceName() == null) ? false + : true; + } + + /** + * @see org.apache.fop.fonts.Typeface#getEncoding() + */ + public String getEncoding() { + return encoding; + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getWidth(int, int) + */ + public int getWidth(int i, int size) { + return size * width[i]; + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getWidths() + */ + public int[] getWidths() { + int[] arr = new int[width.length]; + System.arraycopy(width, 0, arr, 0, width.length - 1); + /* + for (int i = 0; i < arr.length; i++) + arr[i] *= size; + */ + return arr; + } + + /** + * @see org.apache.fop.fonts.Font#mapChar(char) + */ + public char mapChar(char c) { + char d = mapping.mapChar(c); + if (d != 0) { + return d; + } else { + return '#'; + } + } + + /* ---- single byte font specific setters --- */ + + /** + * Sets a width for a character. + * @param index index of the character + * @param width the width of the character + */ + public void setWidth(int index, int width) { + if (this.width == null) { + this.width = new int[256]; + } + this.width[index] = width; + } + +} + diff --git a/src/java/org/apache/fop/fonts/Typeface.java b/src/java/org/apache/fop/fonts/Typeface.java new file mode 100644 index 000000000..f3e24cf98 --- /dev/null +++ b/src/java/org/apache/fop/fonts/Typeface.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts; + +// FOP + + +/** + * Base class for PDF font classes + */ +public abstract class Typeface implements FontMetrics { + + /** + * Get the encoding of the font. + * @return the encoding + */ + public abstract String getEncoding(); + + /** + * Map a Unicode character to a code point in the font. + * @param c character to map + * @return the mapped character + */ + public abstract char mapChar(char c); + + /** + * Determines whether the font is a multibyte font. + * @return True if it is multibyte + */ + public boolean isMultiByte() { + return false; + } + +} + diff --git a/src/java/org/apache/fop/fonts/apps/PFMReader.java b/src/java/org/apache/fop/fonts/apps/PFMReader.java new file mode 100644 index 000000000..1aa8e105c --- /dev/null +++ b/src/java/org/apache/fop/fonts/apps/PFMReader.java @@ -0,0 +1,389 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts.apps; + +import java.io.File; +import java.io.InputStream; +import java.io.IOException; +import java.util.Map; +import java.util.List; +import java.util.Iterator; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +//Avalon +import org.apache.avalon.framework.logger.AbstractLogEnabled; +import org.apache.avalon.framework.logger.Logger; +import org.apache.avalon.framework.logger.ConsoleLogger; + +//FOP +import org.apache.fop.fonts.type1.PFMFile; + +/** + * A tool which reads PFM files from Adobe Type 1 fonts and creates + * XML font metrics file for use in FOP. + */ +public class PFMReader extends AbstractLogEnabled { + + /** + * Parse commandline arguments. put options in the HashMap and return + * arguments in the String array + * the arguments: -fn Perpetua,Bold -cn PerpetuaBold per.ttf Perpetua.xml + * returns a String[] with the per.ttf and Perpetua.xml. The hash + * will have the (key, value) pairs: (-fn, Perpetua) and (-cn, PerpetuaBold) + */ + private static String[] parseArguments(Map options, String[] args) { + List arguments = new java.util.ArrayList(); + for (int i = 0; i < args.length; i++) { + if (args[i].startsWith("-")) { + if ((i + 1) < args.length && !args[i + 1].startsWith("-")) { + options.put(args[i], args[i + 1]); + i++; + } else { + options.put(args[i], ""); + } + } else { + arguments.add(args[i]); + } + } + + return (String[])arguments.toArray(new String[arguments.size()]); + } + + private void displayUsage() { + getLogger().info( + " java org.apache.fop.fonts.apps.PFMReader [options] metricfile.pfm xmlfile.xml"); + getLogger().info(" where options can be:"); + getLogger().info(" -fn "); + getLogger().info(" default is to use the fontname in the .pfm file, but"); + getLogger().info(" you can override that name to make sure that the"); + getLogger().info(" embedded font is used (if you're embedding fonts)"); + getLogger().info(" instead of installed fonts when viewing documents "); + getLogger().info(" with Acrobat Reader."); + } + + + /** + * The main method for the PFM reader tool. + * + * @param args Command-line arguments: [options] metricfile.pfm xmlfile.xml + * where options can be: + * -fn + * default is to use the fontname in the .pfm file, but you can override + * that name to make sure that the embedded font is used instead of installed + * fonts when viewing documents with Acrobat Reader. + * -cn + * default is to use the fontname + * -ef + * will add the possibility to embed the font. When running fop, fop will look + * for this file to embed it + * -er + * you can also include the fontfile in the fop.jar file when building fop. + * You can use both -ef and -er. The file specified in -ef will be searched first, + * then the -er file. + */ + public static void main(String[] args) { + String embFile = null; + String embResource = null; + String className = null; + String fontName = null; + + Map options = new java.util.HashMap(); + String[] arguments = parseArguments(options, args); + + PFMReader app = new PFMReader(); + Logger log; + if (options.get("-d") != null) { + log = new ConsoleLogger(ConsoleLogger.LEVEL_DEBUG); + } else { + log = new ConsoleLogger(ConsoleLogger.LEVEL_INFO); + } + app.enableLogging(log); + + log.info("PFM Reader v1.1a"); + log.info(""); + + if (options.get("-ef") != null) { + embFile = (String)options.get("-ef"); + } + + if (options.get("-er") != null) { + embResource = (String)options.get("-er"); + } + + if (options.get("-fn") != null) { + fontName = (String)options.get("-fn"); + } + + if (options.get("-cn") != null) { + className = (String)options.get("-cn"); + } + + if (arguments.length != 2 || options.get("-h") != null + || options.get("-help") != null || options.get("--help") != null) { + app.displayUsage(); + } else { + try { + PFMFile pfm = app.loadPFM(arguments[0]); + if (pfm != null) { + app.preview(pfm); + + Document doc = app.constructFontXML(pfm, + fontName, className, embResource, embFile); + + app.writeFontXML(doc, arguments[1]); + } + } catch (Exception e) { + log.error("Error while building XML font metrics file", e); + System.exit(-1); + } + } + } + + + /** + * Read a PFM file and returns it as an object. + * + * @param filename The filename of the PFM file. + * @return The PFM as an object. + * @throws IOException In case of an I/O problem + */ + public PFMFile loadPFM(String filename) throws IOException { + getLogger().info("Reading " + filename + "..."); + getLogger().info(""); + InputStream in = new java.io.FileInputStream(filename); + try { + PFMFile pfm = new PFMFile(); + setupLogger(pfm); + pfm.load(in); + return pfm; + } finally { + in.close(); + } + } + + /** + * Displays a preview of the PFM file on the console. + * + * @param pfm The PFM file to preview. + */ + public void preview(PFMFile pfm) { + getLogger().info("Font: " + pfm.getWindowsName()); + getLogger().info("Name: " + pfm.getPostscriptName()); + getLogger().info("CharSet: " + pfm.getCharSetName()); + getLogger().info("CapHeight: " + pfm.getCapHeight()); + getLogger().info("XHeight: " + pfm.getXHeight()); + getLogger().info("LowerCaseAscent: " + pfm.getLowerCaseAscent()); + getLogger().info("LowerCaseDescent: " + pfm.getLowerCaseDescent()); + getLogger().info("Having widths for " + (pfm.getLastChar() - pfm.getFirstChar()) + + " characters (" + pfm.getFirstChar() + + "-" + pfm.getLastChar() + ")."); + getLogger().info("for example: Char " + pfm.getFirstChar() + + " has a width of " + pfm.getCharWidth(pfm.getFirstChar())); + getLogger().info(""); + } + + /** + * Writes the generated DOM Document to a file. + * + * @param doc The DOM Document to save. + * @param target The target filename for the XML file. + * @throws TransformerException if an error occurs during serialization + */ + public void writeFontXML(org.w3c.dom.Document doc, String target) + throws TransformerException { + getLogger().info("Writing xml font file " + target + "..."); + getLogger().info(""); + + TransformerFactory factory = TransformerFactory.newInstance(); + Transformer transformer = factory.newTransformer(); + transformer.transform( + new javax.xml.transform.dom.DOMSource(doc), + new javax.xml.transform.stream.StreamResult(new File(target))); + } + + /** + * Generates the font metrics file from the PFM file. + * + * @param pfm The PFM file to generate the font metrics from. + * @param fontName name of the font + * @param className class name for the font + * @param resource path to the font as embedded resource + * @param file path to the font as file + * @return The DOM document representing the font metrics file. + */ + public org.w3c.dom.Document constructFontXML(PFMFile pfm, + String fontName, String className, String resource, String file) { + getLogger().info("Creating xml font file..."); + getLogger().info(""); + + Document doc; + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + doc = factory.newDocumentBuilder().newDocument(); + } catch (javax.xml.parsers.ParserConfigurationException e) { + getLogger().error("Can't create DOM implementation", e); + return null; + } + Element root = doc.createElement("font-metrics"); + doc.appendChild(root); + root.setAttribute("type", "TYPE1"); + + Element el = doc.createElement("font-name"); + root.appendChild(el); + el.appendChild(doc.createTextNode(pfm.getPostscriptName())); + + String s = pfm.getPostscriptName(); + int pos = s.indexOf("-"); + if (pos >= 0) { + char sb[] = new char[s.length() - 1]; + s.getChars(0, pos, sb, 0); + s.getChars(pos + 1, s.length(), sb, pos); + s = new String(sb); + } + + el = doc.createElement("embed"); + root.appendChild(el); + if (file != null) { + el.setAttribute("file", file); + } + if (resource != null) { + el.setAttribute("class", resource); + } + + el = doc.createElement("encoding"); + root.appendChild(el); + el.appendChild(doc.createTextNode(pfm.getCharSetName() + "Encoding")); + + el = doc.createElement("cap-height"); + root.appendChild(el); + Integer value = new Integer(pfm.getCapHeight()); + el.appendChild(doc.createTextNode(value.toString())); + + el = doc.createElement("x-height"); + root.appendChild(el); + value = new Integer(pfm.getXHeight()); + el.appendChild(doc.createTextNode(value.toString())); + + el = doc.createElement("ascender"); + root.appendChild(el); + value = new Integer(pfm.getLowerCaseAscent()); + el.appendChild(doc.createTextNode(value.toString())); + + el = doc.createElement("descender"); + root.appendChild(el); + value = new Integer(-pfm.getLowerCaseDescent()); + el.appendChild(doc.createTextNode(value.toString())); + + Element bbox = doc.createElement("bbox"); + root.appendChild(bbox); + int[] bb = pfm.getFontBBox(); + final String[] names = {"left", "bottom", "right", "top"}; + for (int i = 0; i < names.length; i++) { + el = doc.createElement(names[i]); + bbox.appendChild(el); + value = new Integer(bb[i]); + el.appendChild(doc.createTextNode(value.toString())); + } + + el = doc.createElement("flags"); + root.appendChild(el); + value = new Integer(pfm.getFlags()); + el.appendChild(doc.createTextNode(value.toString())); + + el = doc.createElement("stemv"); + root.appendChild(el); + value = new Integer(pfm.getStemV()); + el.appendChild(doc.createTextNode(value.toString())); + + el = doc.createElement("italicangle"); + root.appendChild(el); + value = new Integer(pfm.getItalicAngle()); + el.appendChild(doc.createTextNode(value.toString())); + + el = doc.createElement("first-char"); + root.appendChild(el); + value = new Integer(pfm.getFirstChar()); + el.appendChild(doc.createTextNode(value.toString())); + + el = doc.createElement("last-char"); + root.appendChild(el); + value = new Integer(pfm.getLastChar()); + el.appendChild(doc.createTextNode(value.toString())); + + Element widths = doc.createElement("widths"); + root.appendChild(widths); + + for (short i = pfm.getFirstChar(); i <= pfm.getLastChar(); i++) { + el = doc.createElement("char"); + widths.appendChild(el); + el.setAttribute("idx", Integer.toString(i)); + el.setAttribute("wdt", + new Integer(pfm.getCharWidth(i)).toString()); + } + + + // Get kerning + Iterator enum = pfm.getKerning().keySet().iterator(); + while (enum.hasNext()) { + Integer kpx1 = (Integer)enum.next(); + el = doc.createElement("kerning"); + el.setAttribute("kpx1", kpx1.toString()); + root.appendChild(el); + Element el2 = null; + + Map h2 = (Map)pfm.getKerning().get(kpx1); + Iterator enum2 = h2.keySet().iterator(); + while (enum2.hasNext()) { + Integer kpx2 = (Integer)enum2.next(); + el2 = doc.createElement("pair"); + el2.setAttribute("kpx2", kpx2.toString()); + Integer val = (Integer)h2.get(kpx2); + el2.setAttribute("kern", val.toString()); + el.appendChild(el2); + } + } + return doc; + } + + + private String escapeString(String str) { + StringBuffer esc = new StringBuffer(); + + for (int i = 0; i < str.length(); i++) { + if (str.charAt(i) == '\\') { + esc.append("\\\\"); + } else { + esc.append(str.charAt(i)); + } + } + + return esc.toString(); + } + +} + + + + diff --git a/src/java/org/apache/fop/fonts/apps/TTFReader.java b/src/java/org/apache/fop/fonts/apps/TTFReader.java new file mode 100644 index 000000000..d6636610b --- /dev/null +++ b/src/java/org/apache/fop/fonts/apps/TTFReader.java @@ -0,0 +1,514 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts.apps; + +import java.io.File; +import java.io.IOException; +import java.util.Map; +import java.util.List; +import java.util.Iterator; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +//Avalon +import org.apache.avalon.framework.logger.AbstractLogEnabled; +import org.apache.avalon.framework.logger.ConsoleLogger; +import org.apache.avalon.framework.logger.Logger; + +//FOP +import org.apache.fop.fonts.truetype.FontFileReader; +import org.apache.fop.fonts.truetype.TTFCmapEntry; +import org.apache.fop.fonts.truetype.TTFFile; + +/** + * A tool which reads TTF files and generates + * XML font metrics file for use in FOP. + */ +public class TTFReader extends AbstractLogEnabled { + + /** + * Parse commandline arguments. put options in the HashMap and return + * arguments in the String array + * the arguments: -fn Perpetua,Bold -cn PerpetuaBold per.ttf Perpetua.xml + * returns a String[] with the per.ttf and Perpetua.xml. The hash + * will have the (key, value) pairs: (-fn, Perpetua) and (-cn, PerpetuaBold) + */ + private static String[] parseArguments(Map options, String[] args) { + List arguments = new java.util.ArrayList(); + for (int i = 0; i < args.length; i++) { + if (args[i].startsWith("-")) { + if ((i + 1) < args.length && !args[i + 1].startsWith("-")) { + options.put(args[i], args[i + 1]); + i++; + } else { + options.put(args[i], ""); + } + } else { + arguments.add(args[i]); + } + } + + return (String[])arguments.toArray(new String[0]); + } + + + private void displayUsage() { + getLogger().info( + " java org.apache.fop.fonts.apps.TTFReader [options] fontfile.ttf xmlfile.xml"); + getLogger().info(" where options can be:"); + getLogger().info("-d "); + getLogger().info(" Set debug level (default: INFO)."); + getLogger().info("-enc ansi"); + getLogger().info(" With this option you create a WinAnsi encoded font."); + getLogger().info(" The default is to create a CID keyed font."); + getLogger().info(" If you're not going to use characters outside the"); + getLogger().info(" pdfencoding range (almost the same as iso-8889-1)"); + getLogger().info(" you can add this option."); + getLogger().info("-ttcname "); + getLogger().info(" If you're reading data from a TrueType Collection"); + getLogger().info(" (.ttc file) you must specify which font from the"); + getLogger().info(" collection you will read metrics from. If you read"); + getLogger().info(" from a .ttc file without this option, the fontnames"); + getLogger().info(" will be listed for you."); + getLogger().info(" -fn "); + getLogger().info(" default is to use the fontname in the .ttf file, but"); + getLogger().info(" you can override that name to make sure that the"); + getLogger().info(" embedded font is used (if you're embedding fonts)"); + getLogger().info(" instead of installed fonts when viewing documents "); + getLogger().info(" with Acrobat Reader."); + } + + + /** + * The main method for the TTFReader tool. + * + * @param args Command-line arguments: [options] fontfile.ttf xmlfile.xml + * where options can be: + * -fn + * default is to use the fontname in the .ttf file, but you can override + * that name to make sure that the embedded font is used instead of installed + * fonts when viewing documents with Acrobat Reader. + * -cn + * default is to use the fontname + * -ef + * will add the possibility to embed the font. When running fop, fop will look + * for this file to embed it + * -er + * you can also include the fontfile in the fop.jar file when building fop. + * You can use both -ef and -er. The file specified in -ef will be searched first, + * then the -er file. + */ + public static void main(String[] args) { + String embFile = null; + String embResource = null; + String className = null; + String fontName = null; + String ttcName = null; + boolean isCid = true; + + Map options = new java.util.HashMap(); + String[] arguments = parseArguments(options, args); + + int level = ConsoleLogger.LEVEL_INFO; + if (options.get("-d") != null) { + String lev = (String)options.get("-d"); + if (lev.equals("DEBUG")) { + level = ConsoleLogger.LEVEL_DEBUG; + } else if (lev.equals("INFO")) { + level = ConsoleLogger.LEVEL_INFO; + } + } + Logger log = new ConsoleLogger(level); + + TTFReader app = new TTFReader(); + app.enableLogging(log); + + log.info("TTF Reader v1.1.4"); + + if (options.get("-enc") != null) { + String enc = (String)options.get("-enc"); + if ("ansi".equals(enc)) { + isCid = false; + } + } + + if (options.get("-ttcname") != null) { + ttcName = (String)options.get("-ttcname"); + } + + if (options.get("-ef") != null) { + embFile = (String)options.get("-ef"); + } + + if (options.get("-er") != null) { + embResource = (String)options.get("-er"); + } + + if (options.get("-fn") != null) { + fontName = (String)options.get("-fn"); + } + + if (options.get("-cn") != null) { + className = (String)options.get("-cn"); + } + + if (arguments.length != 2 || options.get("-h") != null + || options.get("-help") != null || options.get("--help") != null) { + app.displayUsage(); + } else { + try { + TTFFile ttf = app.loadTTF(arguments[0], ttcName); + if (ttf != null) { + org.w3c.dom.Document doc = app.constructFontXML(ttf, + fontName, className, embResource, embFile, isCid, + ttcName); + + if (isCid) { + log.info("Creating CID encoded metrics"); + } else { + log.info("Creating WinAnsi encoded metrics"); + } + + if (doc != null) { + app.writeFontXML(doc, arguments[1]); + } + + if (ttf.isEmbeddable()) { + log.info("This font contains no embedding license restrictions"); + } else { + log.info("** Note: This font contains license retrictions for\n" + + " embedding. This font shouldn't be embedded."); + } + } + } catch (Exception e) { + log.error("Error while building XML font metrics file", e); + System.exit(-1); + } + } + } + + /** + * Read a TTF file and returns it as an object. + * + * @param fileName The filename of the TTF file. + * @param fontName The name of the font + * @return The TTF as an object, null if the font is incompatible. + * @throws IOException In case of an I/O problem + */ + public TTFFile loadTTF(String fileName, String fontName) throws IOException { + TTFFile ttfFile = new TTFFile(); + setupLogger(ttfFile); + getLogger().info("Reading " + fileName + "..."); + + FontFileReader reader = new FontFileReader(fileName); + boolean supported = ttfFile.readFont(reader, fontName); + if (!supported) { + return null; + } + return ttfFile; + } + + + /** + * Writes the generated DOM Document to a file. + * + * @param doc The DOM Document to save. + * @param target The target filename for the XML file. + * @throws TransformerException if an error occurs during serialization + */ + public void writeFontXML(org.w3c.dom.Document doc, String target) + throws TransformerException { + getLogger().info("Writing xml font file " + target + "..."); + + TransformerFactory factory = TransformerFactory.newInstance(); + Transformer transformer = factory.newTransformer(); + transformer.transform( + new javax.xml.transform.dom.DOMSource(doc), + new javax.xml.transform.stream.StreamResult(new File(target))); + } + + /** + * Generates the font metrics file from the TTF/TTC file. + * + * @param ttf The PFM file to generate the font metrics from. + * @param fontName Name of the font + * @param className Class name for the font + * @param resource path to the font as embedded resource + * @param file path to the font as file + * @param isCid True if the font is CID encoded + * @param ttcName Name of the TrueType Collection + * @return The DOM document representing the font metrics file. + */ + public org.w3c.dom.Document constructFontXML(TTFFile ttf, + String fontName, String className, String resource, String file, + boolean isCid, String ttcName) { + getLogger().info("Creating xml font file..."); + + Document doc; + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + doc = factory.newDocumentBuilder().newDocument(); + } catch (javax.xml.parsers.ParserConfigurationException e) { + getLogger().error("Can't create DOM implementation", e); + return null; + } + Element root = doc.createElement("font-metrics"); + doc.appendChild(root); + if (isCid) { + root.setAttribute("type", "TYPE0"); + } else { + root.setAttribute("type", "TRUETYPE"); + } + + Element el = doc.createElement("font-name"); + root.appendChild(el); + + // Note that the PostScript name usually is something like + // "Perpetua-Bold", but the TrueType spec says that in the ttf file + // it should be "Perpetua,Bold". + + String s = stripWhiteSpace(ttf.getPostScriptName()); + + if (fontName != null) { + el.appendChild(doc.createTextNode(stripWhiteSpace(fontName))); + } else { + el.appendChild(doc.createTextNode(s)); + } + + el = doc.createElement("embed"); + root.appendChild(el); + if (file != null && ttf.isEmbeddable()) { + el.setAttribute("file", file); + } + if (resource != null && ttf.isEmbeddable()) { + el.setAttribute("class", resource); + } + + el = doc.createElement("cap-height"); + root.appendChild(el); + el.appendChild(doc.createTextNode(String.valueOf(ttf.getCapHeight()))); + + el = doc.createElement("x-height"); + root.appendChild(el); + el.appendChild(doc.createTextNode(String.valueOf(ttf.getXHeight()))); + + el = doc.createElement("ascender"); + root.appendChild(el); + el.appendChild(doc.createTextNode(String.valueOf(ttf.getLowerCaseAscent()))); + + el = doc.createElement("descender"); + root.appendChild(el); + el.appendChild(doc.createTextNode(String.valueOf(ttf.getLowerCaseDescent()))); + + Element bbox = doc.createElement("bbox"); + root.appendChild(bbox); + int[] bb = ttf.getFontBBox(); + final String[] names = {"left", "bottom", "right", "top"}; + for (int i = 0; i < names.length; i++) { + el = doc.createElement(names[i]); + bbox.appendChild(el); + el.appendChild(doc.createTextNode(String.valueOf(bb[i]))); + } + + el = doc.createElement("flags"); + root.appendChild(el); + el.appendChild(doc.createTextNode(String.valueOf(ttf.getFlags()))); + + el = doc.createElement("stemv"); + root.appendChild(el); + el.appendChild(doc.createTextNode(ttf.getStemV())); + + el = doc.createElement("italicangle"); + root.appendChild(el); + el.appendChild(doc.createTextNode(ttf.getItalicAngle())); + + if (ttcName != null) { + el = doc.createElement("ttc-name"); + root.appendChild(el); + el.appendChild(doc.createTextNode(ttcName)); + } + + el = doc.createElement("subtype"); + root.appendChild(el); + + // Fill in extras for CID keyed fonts + if (isCid) { + el.appendChild(doc.createTextNode("TYPE0")); + + generateDOM4MultiByteExtras(root, ttf, isCid); + } else { + // Fill in extras for singlebyte fonts + el.appendChild(doc.createTextNode("TRUETYPE")); + + generateDOM4SingleByteExtras(root, ttf, isCid); + } + + generateDOM4Kerning(root, ttf, isCid); + + return doc; + } + + private void generateDOM4MultiByteExtras(Element parent, TTFFile ttf, boolean isCid) { + Element el; + Document doc = parent.getOwnerDocument(); + + Element mel = doc.createElement("multibyte-extras"); + parent.appendChild(mel); + + el = doc.createElement("cid-type"); + mel.appendChild(el); + el.appendChild(doc.createTextNode("CIDFontType2")); + + el = doc.createElement("default-width"); + mel.appendChild(el); + el.appendChild(doc.createTextNode("0")); + + el = doc.createElement("bfranges"); + mel.appendChild(el); + Iterator e = ttf.getCMaps().listIterator(); + while (e.hasNext()) { + TTFCmapEntry ce = (TTFCmapEntry)e.next(); + Element el2 = doc.createElement("bf"); + el.appendChild(el2); + el2.setAttribute("us", String.valueOf(ce.getUnicodeStart())); + el2.setAttribute("ue", String.valueOf(ce.getUnicodeEnd())); + el2.setAttribute("gi", String.valueOf(ce.getGlyphStartIndex())); + } + + el = doc.createElement("cid-widths"); + el.setAttribute("start-index", "0"); + mel.appendChild(el); + + int[] wx = ttf.getWidths(); + for (int i = 0; i < wx.length; i++) { + Element wxel = doc.createElement("wx"); + wxel.setAttribute("w", String.valueOf(wx[i])); + el.appendChild(wxel); + } + } + + private void generateDOM4SingleByteExtras(Element parent, TTFFile ttf, boolean isCid) { + Element el; + Document doc = parent.getOwnerDocument(); + + Element sel = doc.createElement("singlebyte-extras"); + parent.appendChild(sel); + + el = doc.createElement("encoding"); + sel.appendChild(el); + el.appendChild(doc.createTextNode(ttf.getCharSetName())); + + el = doc.createElement("first-char"); + sel.appendChild(el); + el.appendChild(doc.createTextNode(String.valueOf(ttf.getFirstChar()))); + + el = doc.createElement("last-char"); + sel.appendChild(el); + el.appendChild(doc.createTextNode(String.valueOf(ttf.getLastChar()))); + + Element widths = doc.createElement("widths"); + sel.appendChild(widths); + + for (short i = ttf.getFirstChar(); i <= ttf.getLastChar(); i++) { + el = doc.createElement("char"); + widths.appendChild(el); + el.setAttribute("idx", String.valueOf(i)); + el.setAttribute("wdt", String.valueOf(ttf.getCharWidth(i))); + } + } + + private void generateDOM4Kerning(Element parent, TTFFile ttf, boolean isCid) { + Element el; + Document doc = parent.getOwnerDocument(); + + // Get kerning + Iterator enum; + if (isCid) { + enum = ttf.getKerning().keySet().iterator(); + } else { + enum = ttf.getAnsiKerning().keySet().iterator(); + } + + while (enum.hasNext()) { + Integer kpx1 = (Integer)enum.next(); + + el = doc.createElement("kerning"); + el.setAttribute("kpx1", kpx1.toString()); + parent.appendChild(el); + Element el2 = null; + + Map h2; + if (isCid) { + h2 = (Map)ttf.getKerning().get(kpx1); + } else { + h2 = (Map)ttf.getAnsiKerning().get(kpx1); + } + + Iterator enum2 = h2.keySet().iterator(); + while (enum2.hasNext()) { + Integer kpx2 = (Integer)enum2.next(); + if (isCid || kpx2.intValue() < 256) { + el2 = doc.createElement("pair"); + el2.setAttribute("kpx2", kpx2.toString()); + Integer val = (Integer)h2.get(kpx2); + el2.setAttribute("kern", val.toString()); + el.appendChild(el2); + } + } + } + } + + + private String stripWhiteSpace(String s) { + char[] ch = new char[s.length()]; + s.getChars(0, s.length(), ch, 0); + StringBuffer stb = new StringBuffer(); + for (int i = 0; i < ch.length; i++) { + if (ch[i] != ' ' + && ch[i] != '\r' + && ch[i] != '\n' + && ch[i] != '\t') { + stb.append(ch[i]); + } + } + + return stb.toString(); + } + + private String escapeString(String str) { + StringBuffer esc = new StringBuffer(); + + for (int i = 0; i < str.length(); i++) { + if (str.charAt(i) == '\\') { + esc.append("\\\\"); + } else { + esc.append(str.charAt(i)); + } + } + + return esc.toString(); + } + +} + diff --git a/src/java/org/apache/fop/fonts/truetype/FontFileReader.java b/src/java/org/apache/fop/fonts/truetype/FontFileReader.java new file mode 100644 index 000000000..f83b597ca --- /dev/null +++ b/src/java/org/apache/fop/fonts/truetype/FontFileReader.java @@ -0,0 +1,335 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts.truetype; + +import java.io.InputStream; +import java.io.File; +import java.io.IOException; + +import org.apache.commons.io.IOUtils; + +/** + * Reads a TrueType font file into a byte array and + * provides file like functions for array access. + */ +public class FontFileReader { + + private int fsize; // file size + private int current; // current position in file + private byte[] file; + + /** + * Initializes class and reads stream. Init does not close stream. + * + * @param in InputStream to read from new array with size + inc + * @throws IOException In case of an I/O problem + */ + private void init(InputStream in) throws java.io.IOException { + this.file = IOUtils.toByteArray(in); + this.fsize = this.file.length; + this.current = 0; + } + + /** + * Constructor + * + * @param fileName filename to read + * @throws IOException In case of an I/O problem + */ + public FontFileReader(String fileName) throws IOException { + final File f = new File(fileName); + InputStream in = new java.io.FileInputStream(f); + try { + init(in); + } finally { + in.close(); + } + } + + + /** + * Constructor + * + * @param in InputStream to read from + * @throws IOException In case of an I/O problem + */ + public FontFileReader(InputStream in) throws IOException { + init(in); + } + + + /** + * Set current file position to offset + * + * @param offset The new offset to set + * @throws IOException In case of an I/O problem + */ + public void seekSet(long offset) throws IOException { + if (offset > fsize || offset < 0) { + throw new java.io.EOFException("Reached EOF, file size=" + fsize + + " offset=" + offset); + } + current = (int)offset; + } + + /** + * Set current file position to offset + * + * @param add The number of bytes to advance + * @throws IOException In case of an I/O problem + */ + public void seekAdd(long add) throws IOException { + seekSet(current + add); + } + + /** + * Skip a given number of bytes. + * + * @param add The number of bytes to advance + * @throws IOException In case of an I/O problem + */ + public void skip(long add) throws IOException { + seekAdd(add); + } + + /** + * Returns current file position. + * + * @return int The current position. + */ + public int getCurrentPos() { + return current; + } + + /** + * Returns the size of the file. + * + * @return int The filesize + */ + public int getFileSize() { + return fsize; + } + + /** + * Read 1 byte. + * + * @return One byte + * @throws IOException If EOF is reached + */ + public byte read() throws IOException { + if (current > fsize) { + throw new java.io.EOFException("Reached EOF, file size=" + fsize); + } + + final byte ret = file[current++]; + return ret; + } + + /** + * Read 1 signed byte. + * + * @return One byte + * @throws IOException If EOF is reached + */ + public final byte readTTFByte() throws IOException { + return read(); + } + + /** + * Read 1 unsigned byte. + * + * @return One unsigned byte + * @throws IOException If EOF is reached + */ + public final int readTTFUByte() throws IOException { + final byte buf = read(); + + if (buf < 0) { + return (int)(256 + buf); + } else { + return (int)buf; + } + } + + /** + * Read 2 bytes signed. + * + * @return One signed short + * @throws IOException If EOF is reached + */ + public final short readTTFShort() throws IOException { + final int ret = (readTTFUByte() << 8) + readTTFUByte(); + final short sret = (short)ret; + return sret; + } + + /** + * Read 2 bytes unsigned. + * + * @return One unsigned short + * @throws IOException If EOF is reached + */ + public final int readTTFUShort() throws IOException { + final int ret = (readTTFUByte() << 8) + readTTFUByte(); + return (int)ret; + } + + /** + * Write a USHort at a given position. + * + * @param pos The absolute position to write to + * @param val The value to write + * @throws IOException If EOF is reached + */ + public final void writeTTFUShort(int pos, int val) throws IOException { + if ((pos + 2) > fsize) { + throw new java.io.EOFException("Reached EOF"); + } + final byte b1 = (byte)((val >> 8) & 0xff); + final byte b2 = (byte)(val & 0xff); + file[pos] = b1; + file[pos + 1] = b2; + } + + /** + * Read 2 bytes signed at position pos without changing current position. + * + * @param pos The absolute position to read from + * @return One signed short + * @throws IOException If EOF is reached + */ + public final short readTTFShort(long pos) throws IOException { + final long cp = getCurrentPos(); + seekSet(pos); + final short ret = readTTFShort(); + seekSet(cp); + return ret; + } + + /** + * Read 2 bytes unsigned at position pos without changing current position. + * + * @param pos The absolute position to read from + * @return One unsigned short + * @throws IOException If EOF is reached + */ + public final int readTTFUShort(long pos) throws IOException { + long cp = getCurrentPos(); + seekSet(pos); + int ret = readTTFUShort(); + seekSet(cp); + return ret; + } + + /** + * Read 4 bytes. + * + * @return One signed integer + * @throws IOException If EOF is reached + */ + public final int readTTFLong() throws IOException { + long ret = readTTFUByte(); // << 8; + ret = (ret << 8) + readTTFUByte(); + ret = (ret << 8) + readTTFUByte(); + ret = (ret << 8) + readTTFUByte(); + + return (int)ret; + } + + /** + * Read 4 bytes. + * + * @return One unsigned integer + * @throws IOException If EOF is reached + */ + public final long readTTFULong() throws IOException { + long ret = readTTFUByte(); + ret = (ret << 8) + readTTFUByte(); + ret = (ret << 8) + readTTFUByte(); + ret = (ret << 8) + readTTFUByte(); + + return ret; + } + + /** + * Read a NUL terminated ISO-8859-1 string. + * + * @return A String + * @throws IOException If EOF is reached + */ + public final String readTTFString() throws IOException { + int i = current; + while (file[i++] != 0) { + if (i > fsize) { + throw new java.io.EOFException("Reached EOF, file size=" + + fsize); + } + } + + byte[] tmp = new byte[i - current]; + System.arraycopy(file, current, tmp, 0, i - current); + return new String(tmp, "ISO-8859-1"); + } + + + /** + * Read an ISO-8859-1 string of len bytes. + * + * @param len The length of the string to read + * @return A String + * @throws IOException If EOF is reached + */ + public final String readTTFString(int len) throws IOException { + if ((len + current) > fsize) { + throw new java.io.EOFException("Reached EOF, file size=" + fsize); + } + + byte[] tmp = new byte[len]; + System.arraycopy(file, current, tmp, 0, len); + current += len; + final String encoding; + if ((tmp.length > 0) && (tmp[0] == 0)) { + encoding = "UnicodeBig"; + } else { + encoding = "ISO-8859-1"; + } + return new String(tmp, encoding); + } + + /** + * Return a copy of the internal array + * + * @param offset The absolute offset to start reading from + * @param length The number of bytes to read + * @return An array of bytes + * @throws IOException if out of bounds + */ + public byte[] getBytes(int offset, + int length) throws IOException { + if ((offset + length) > fsize) { + throw new java.io.IOException("Reached EOF"); + } + + byte[] ret = new byte[length]; + System.arraycopy(file, offset, ret, 0, length); + return ret; + } + + +} \ No newline at end of file diff --git a/src/java/org/apache/fop/fonts/truetype/TTFCmapEntry.java b/src/java/org/apache/fop/fonts/truetype/TTFCmapEntry.java new file mode 100644 index 000000000..164dd5766 --- /dev/null +++ b/src/java/org/apache/fop/fonts/truetype/TTFCmapEntry.java @@ -0,0 +1,106 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts.truetype; + +/** + * The CMap entry contains information of a Unicode range and the + * the glyph indexes related to the range + */ +public class TTFCmapEntry { + + private int unicodeStart; + private int unicodeEnd; + private int glyphStartIndex; + + TTFCmapEntry() { + unicodeStart = 0; + unicodeEnd = 0; + glyphStartIndex = 0; + } + + TTFCmapEntry(int unicodeStart, int unicodeEnd, int glyphStartIndex) { + this.unicodeStart = unicodeStart; + this.unicodeEnd = unicodeEnd; + this.glyphStartIndex = glyphStartIndex; + } + + /** + * @see java.lang.Object#equals(Object) + */ + public boolean equals(Object o) { + if (o instanceof TTFCmapEntry) { + TTFCmapEntry ce = (TTFCmapEntry)o; + if (ce.unicodeStart == this.unicodeStart + && ce.unicodeEnd == this.unicodeEnd + && ce.glyphStartIndex == this.glyphStartIndex) { + return true; + } + } + return false; + } + + /** + * Returns the glyphStartIndex. + * @return int + */ + public int getGlyphStartIndex() { + return glyphStartIndex; + } + + /** + * Returns the unicodeEnd. + * @return int + */ + public int getUnicodeEnd() { + return unicodeEnd; + } + + /** + * Returns the unicodeStart. + * @return int + */ + public int getUnicodeStart() { + return unicodeStart; + } + + /** + * Sets the glyphStartIndex. + * @param glyphStartIndex The glyphStartIndex to set + */ + public void setGlyphStartIndex(int glyphStartIndex) { + this.glyphStartIndex = glyphStartIndex; + } + + /** + * Sets the unicodeEnd. + * @param unicodeEnd The unicodeEnd to set + */ + public void setUnicodeEnd(int unicodeEnd) { + this.unicodeEnd = unicodeEnd; + } + + /** + * Sets the unicodeStart. + * @param unicodeStart The unicodeStart to set + */ + public void setUnicodeStart(int unicodeStart) { + this.unicodeStart = unicodeStart; + } + +} diff --git a/src/java/org/apache/fop/fonts/truetype/TTFDirTabEntry.java b/src/java/org/apache/fop/fonts/truetype/TTFDirTabEntry.java new file mode 100644 index 000000000..46d837589 --- /dev/null +++ b/src/java/org/apache/fop/fonts/truetype/TTFDirTabEntry.java @@ -0,0 +1,108 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts.truetype; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; + + +/** + * This class represents an entry to a TrueType font's Dir Tab. + */ +class TTFDirTabEntry { + + private byte[] tag = new byte[4]; + private int checksum; + private long offset; + private long length; + + /** + * Read Dir Tab, return tag name + */ + public String read(FontFileReader in) throws IOException { + tag[0] = in.readTTFByte(); + tag[1] = in.readTTFByte(); + tag[2] = in.readTTFByte(); + tag[3] = in.readTTFByte(); + + in.skip(4); // Skip checksum + + offset = in.readTTFULong(); + length = in.readTTFULong(); + String tagStr = new String(tag, "ISO-8859-1"); + // System.err.println("tag='" + tagStr + "'"); + + //System.out.println(this.toString()); + return tagStr; + } + + + public String toString() { + return "Read dir tab [" + + tag[0] + " " + tag[1] + " " + tag[2] + " " + tag[3] + "]" + + " offset: " + offset + + " length: " + length + + " name: " + tag; + } + + /** + * Returns the checksum. + * @return int + */ + public int getChecksum() { + return checksum; + } + + /** + * Returns the length. + * @return long + */ + public long getLength() { + return length; + } + + /** + * Returns the offset. + * @return long + */ + public long getOffset() { + return offset; + } + + /** + * Returns the tag bytes. + * @return byte[] + */ + public byte[] getTag() { + return tag; + } + + /** + * Returns the tag bytes. + * @return byte[] + */ + public String getTagString() { + try { + return new String(tag, "ISO-8859-1"); + } catch (UnsupportedEncodingException e) { + return this.toString(); // Should never happen. + } + } + +} diff --git a/src/java/org/apache/fop/fonts/truetype/TTFFile.java b/src/java/org/apache/fop/fonts/truetype/TTFFile.java new file mode 100644 index 000000000..e56297d5f --- /dev/null +++ b/src/java/org/apache/fop/fonts/truetype/TTFFile.java @@ -0,0 +1,1312 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts.truetype; + +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; +import java.util.List; + +import org.apache.avalon.framework.logger.AbstractLogEnabled; +import org.apache.avalon.framework.logger.ConsoleLogger; +import org.apache.avalon.framework.logger.Logger; +import org.apache.fop.fonts.Glyphs; + +/** + * Reads a TrueType file or a TrueType Collection. + * The TrueType spec can be found at the Microsoft. + * Typography site: http://www.microsoft.com/truetype/ + */ +public class TTFFile extends AbstractLogEnabled { + + static final byte NTABS = 24; + static final int NMACGLYPHS = 258; + static final int MAX_CHAR_CODE = 255; + static final int ENC_BUF_SIZE = 1024; + + /** Set to true to get even more debug output than with level DEBUG */ + public static final boolean TRACE_ENABLED = false; + + private String encoding = "WinAnsiEncoding"; // Default encoding + + private short firstChar = 0; + private boolean isEmbeddable = true; + private boolean hasSerifs = true; + /** + * Table directory + */ + protected Map dirTabs; + private Map kerningTab; // for CIDs + private Map ansiKerningTab; // For winAnsiEncoding + private List cmaps; + private List unicodeMapping; + + private int upem; // unitsPerEm from "head" table + private int nhmtx; // Number of horizontal metrics + private int postFormat; + private int locaFormat; + /** + * Offset to last loca + */ + protected long lastLoca = 0; + private int numberOfGlyphs; // Number of glyphs in font (read from "maxp" table) + private int nmGlyphs; // Used in fixWidths - remove? + + /** + * Contains glyph data + */ + protected TTFMtxEntry mtxTab[]; // Contains glyph data + private int[] mtxEncoded = null; + + private String fontName = ""; + private String fullName = ""; + private String notice = ""; + private String familyName = ""; + private String subFamilyName = ""; + + private long italicAngle = 0; + private long isFixedPitch = 0; + private int fontBBox1 = 0; + private int fontBBox2 = 0; + private int fontBBox3 = 0; + private int fontBBox4 = 0; + private int capHeight = 0; + private int underlinePosition = 0; + private int underlineThickness = 0; + private int xHeight = 0; + private int ascender = 0; + private int descender = 0; + + private short lastChar = 0; + + private int ansiWidth[]; + private Map ansiIndex; + + private TTFDirTabEntry currentDirTab; + + /** + * Position inputstream to position indicated + * in the dirtab offset + offset + */ + void seekTab(FontFileReader in, String name, + long offset) throws IOException { + TTFDirTabEntry dt = (TTFDirTabEntry)dirTabs.get(name); + if (dt == null) { + getLogger().error("Dirtab " + name + " not found."); + } else { + in.seekSet(dt.getOffset() + offset); + this.currentDirTab = dt; + } + } + + /** + * Convert from truetype unit to pdf unit based on the + * unitsPerEm field in the "head" table + * @param n truetype unit + * @return pdf unit + */ + public int convertTTFUnit2PDFUnit(int n) { + int ret; + if (n < 0) { + long rest1 = n % upem; + long storrest = 1000 * rest1; + long ledd2 = rest1 / storrest; + ret = -((-1000 * n) / upem - (int)ledd2); + } else { + ret = (n / upem) * 1000 + ((n % upem) * 1000) / upem; + } + + return ret; + } + + /** + * Read the cmap table, + * return false if the table is not present or only unsupported + * tables are present. Currently only unicode cmaps are supported. + * Set the unicodeIndex in the TTFMtxEntries and fills in the + * cmaps vector. + */ + private boolean readCMAP(FontFileReader in) throws IOException { + + unicodeMapping = new java.util.ArrayList(); + + //Read CMAP table and correct mtxTab.index + int mtxPtr = 0; + + seekTab(in, "cmap", 2); + int numCMap = in.readTTFUShort(); // Number of cmap subtables + long cmapUniOffset = 0; + + getLogger().info(numCMap + " cmap tables"); + + //Read offset for all tables. We are only interested in the unicode table + for (int i = 0; i < numCMap; i++) { + int cmapPID = in.readTTFUShort(); + int cmapEID = in.readTTFUShort(); + long cmapOffset = in.readTTFULong(); + + getLogger().debug("Platform ID: " + cmapPID + + " Encoding: " + cmapEID); + + if (cmapPID == 3 && cmapEID == 1) { + cmapUniOffset = cmapOffset; + } + } + + if (cmapUniOffset <= 0) { + getLogger().fatalError("Unicode cmap table not present"); + getLogger().fatalError("Unsupported format: Aborting"); + return false; + } + + // Read unicode cmap + seekTab(in, "cmap", cmapUniOffset); + int cmapFormat = in.readTTFUShort(); + /*int cmap_length =*/ in.readTTFUShort(); //skip cmap length + + getLogger().info("CMAP format: " + cmapFormat); + if (cmapFormat == 4) { + in.skip(2); // Skip version number + int cmapSegCountX2 = in.readTTFUShort(); + int cmapSearchRange = in.readTTFUShort(); + int cmapEntrySelector = in.readTTFUShort(); + int cmapRangeShift = in.readTTFUShort(); + + if (getLogger().isDebugEnabled()) { + getLogger().debug("segCountX2 : " + cmapSegCountX2); + getLogger().debug("searchRange : " + cmapSearchRange); + getLogger().debug("entrySelector: " + cmapEntrySelector); + getLogger().debug("rangeShift : " + cmapRangeShift); + } + + + int cmapEndCounts[] = new int[cmapSegCountX2 / 2]; + int cmapStartCounts[] = new int[cmapSegCountX2 / 2]; + int cmapDeltas[] = new int[cmapSegCountX2 / 2]; + int cmapRangeOffsets[] = new int[cmapSegCountX2 / 2]; + + for (int i = 0; i < (cmapSegCountX2 / 2); i++) { + cmapEndCounts[i] = in.readTTFUShort(); + } + + in.skip(2); // Skip reservedPad + + for (int i = 0; i < (cmapSegCountX2 / 2); i++) { + cmapStartCounts[i] = in.readTTFUShort(); + } + + for (int i = 0; i < (cmapSegCountX2 / 2); i++) { + cmapDeltas[i] = in.readTTFShort(); + } + + //int startRangeOffset = in.getCurrentPos(); + + for (int i = 0; i < (cmapSegCountX2 / 2); i++) { + cmapRangeOffsets[i] = in.readTTFUShort(); + } + + int glyphIdArrayOffset = in.getCurrentPos(); + + // Insert the unicode id for the glyphs in mtxTab + // and fill in the cmaps ArrayList + + for (int i = 0; i < cmapStartCounts.length; i++) { + + getLogger().debug(i + ": " + cmapStartCounts[i] + + " - " + cmapEndCounts[i]); + + for (int j = cmapStartCounts[i]; j <= cmapEndCounts[i]; j++) { + + // Update lastChar + if (j < 256 && j > lastChar) { + lastChar = (short)j; + } + + if (mtxPtr < mtxTab.length) { + int glyphIdx; + // the last character 65535 = .notdef + // may have a range offset + if (cmapRangeOffsets[i] != 0 && j != 65535) { + int glyphOffset = glyphIdArrayOffset + + ((cmapRangeOffsets[i] / 2) + + (j - cmapStartCounts[i]) + + (i) + - cmapSegCountX2 / 2) * 2; + in.seekSet(glyphOffset); + glyphIdx = (in.readTTFUShort() + cmapDeltas[i]) + & 0xffff; + + unicodeMapping.add(new UnicodeMapping(glyphIdx, j)); + mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j)); + + + // Also add winAnsiWidth + List v = (List)ansiIndex.get(new Integer(j)); + if (v != null) { + Iterator e = v.listIterator(); + while (e.hasNext()) { + Integer aIdx = (Integer)e.next(); + ansiWidth[aIdx.intValue()] = + mtxTab[glyphIdx].getWx(); + + if (getLogger().isDebugEnabled()) { + getLogger().debug("Added width " + + mtxTab[glyphIdx].getWx() + + " uni: " + j + + " ansi: " + aIdx.intValue()); + } + } + } + + if (getLogger().isDebugEnabled()) { + getLogger().debug("Idx: " + + glyphIdx + + " Delta: " + cmapDeltas[i] + + " Unicode: " + j + + " name: " + mtxTab[glyphIdx].getName()); + } + } else { + glyphIdx = (j + cmapDeltas[i]) & 0xffff; + + if (glyphIdx < mtxTab.length) { + mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j)); + } else { + if (getLogger().isDebugEnabled()) { + getLogger().debug("Glyph " + glyphIdx + + " out of range: " + + mtxTab.length); + } + } + + unicodeMapping.add(new UnicodeMapping(glyphIdx, j)); + if (glyphIdx < mtxTab.length) { + mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j)); + } else { + if (getLogger().isDebugEnabled()) { + getLogger().debug("Glyph " + glyphIdx + + " out of range: " + + mtxTab.length); + } + } + + // Also add winAnsiWidth + List v = (List)ansiIndex.get(new Integer(j)); + if (v != null) { + Iterator e = v.listIterator(); + while (e.hasNext()) { + Integer aIdx = (Integer)e.next(); + ansiWidth[aIdx.intValue()] = mtxTab[glyphIdx].getWx(); + } + } + + //getLogger().debug("IIdx: " + + // mtxPtr + + // " Delta: " + cmap_deltas[i] + + // " Unicode: " + j + + // " name: " + + // mtxTab[(j+cmap_deltas[i]) & 0xffff].name); + + } + if (glyphIdx < mtxTab.length) { + if (mtxTab[glyphIdx].getUnicodeIndex().size() < 2) { + mtxPtr++; + } + } + } + } + } + } + return true; + } + + /** + * Print first char/last char + */ + private void printMaxMin() { + int min = 255; + int max = 0; + for (int i = 0; i < mtxTab.length; i++) { + if (mtxTab[i].getIndex() < min) { + min = mtxTab[i].getIndex(); + } + if (mtxTab[i].getIndex() > max) { + max = mtxTab[i].getIndex(); + } + } + getLogger().info("Min: " + min); + getLogger().info("Max: " + max); + } + + + /** + * Reads the font using a FontFileReader. + * + * @param in The FontFileReader to use + * @throws IOException In case of an I/O problem + */ + public void readFont(FontFileReader in) throws IOException { + readFont(in, (String)null); + } + + /** + * initialize the ansiWidths array (for winAnsiEncoding) + * and fill with the missingwidth + */ + private void initAnsiWidths() { + ansiWidth = new int[256]; + for (int i = 0; i < 256; i++) { + ansiWidth[i] = mtxTab[0].getWx(); + } + + // Create an index hash to the ansiWidth + // Can't just index the winAnsiEncoding when inserting widths + // same char (eg bullet) is repeated more than one place + ansiIndex = new java.util.HashMap(); + for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) { + Integer ansi = new Integer(i); + Integer uni = new Integer((int)Glyphs.WINANSI_ENCODING[i]); + + List v = (List)ansiIndex.get(uni); + if (v == null) { + v = new java.util.ArrayList(); + ansiIndex.put(uni, v); + } + v.add(ansi); + } + } + + /** + * Read the font data. + * If the fontfile is a TrueType Collection (.ttc file) + * the name of the font to read data for must be supplied, + * else the name is ignored. + * + * @param in The FontFileReader to use + * @param name The name of the font + * @return boolean Returns true if the font is valid + * @throws IOException In case of an I/O problem + */ + public boolean readFont(FontFileReader in, String name) throws IOException { + + /* + * Check if TrueType collection, and that the name + * exists in the collection + */ + if (!checkTTC(in, name)) { + if (name == null) { + throw new IllegalArgumentException( + "For TrueType collection you must specify which font " + + "to select (-ttcname)"); + } else { + throw new IOException( + "Name does not exist in the TrueType collection: " + name); + } + } + + readDirTabs(in); + readFontHeader(in); + getNumGlyphs(in); + getLogger().info("Number of glyphs in font: " + numberOfGlyphs); + readHorizontalHeader(in); + readHorizontalMetrics(in); + initAnsiWidths(); + readPostScript(in); + readOS2(in); + readIndexToLocation(in); + readGlyf(in); + readName(in); + readPCLT(in); + // Read cmap table and fill in ansiwidths + boolean valid = readCMAP(in); + if (!valid) { + return false; + } + // Create cmaps for bfentries + createCMaps(); + // print_max_min(); + + readKerning(in); + return true; + } + + private void createCMaps() { + cmaps = new java.util.ArrayList(); + TTFCmapEntry tce = new TTFCmapEntry(); + + Iterator e = unicodeMapping.listIterator(); + UnicodeMapping um = (UnicodeMapping)e.next(); + UnicodeMapping lastMapping = um; + + tce.setUnicodeStart(um.getUIdx()); + tce.setGlyphStartIndex(um.getGIdx()); + + while (e.hasNext()) { + um = (UnicodeMapping)e.next(); + if (((lastMapping.getUIdx() + 1) != um.getUIdx()) + || ((lastMapping.getGIdx() + 1) != um.getGIdx())) { + tce.setUnicodeEnd(lastMapping.getUIdx()); + cmaps.add(tce); + + tce = new TTFCmapEntry(); + tce.setUnicodeStart(um.getUIdx()); + tce.setGlyphStartIndex(um.getGIdx()); + } + lastMapping = um; + } + + tce.setUnicodeEnd(um.getUIdx()); + cmaps.add(tce); + } + + /** + * Returns the Windows name of the font. + * @return String The Windows name + */ + public String getWindowsName() { + return familyName + "," + subFamilyName; + } + + /** + * Returns the PostScript name of the font. + * @return String The PostScript name + */ + public String getPostScriptName() { + if ("Regular".equals(subFamilyName) || "Roman".equals(subFamilyName)) { + return familyName; + } else { + return familyName + "," + subFamilyName; + } + } + + /** + * Returns the font family name of the font. + * @return String The family name + */ + public String getFamilyName() { + return familyName; + } + + /** + * Returns the name of the character set used. + * @return String The caracter set + */ + public String getCharSetName() { + return encoding; + } + + /** + * Returns the CapHeight attribute of the font. + * @return int The CapHeight + */ + public int getCapHeight() { + return (int)convertTTFUnit2PDFUnit(capHeight); + } + + /** + * Returns the XHeight attribute of the font. + * @return int The XHeight + */ + public int getXHeight() { + return (int)convertTTFUnit2PDFUnit(xHeight); + } + + /** + * Returns the Flags attribute of the font. + * @return int The Flags + */ + public int getFlags() { + int flags = 32; // Use Adobe Standard charset + if (italicAngle != 0) { + flags = flags | 64; + } + if (isFixedPitch != 0) { + flags = flags | 2; + } + if (hasSerifs) { + flags = flags | 1; + } + return flags; + } + + + /** + * Returns the StemV attribute of the font. + * @return String The StemV + */ + public String getStemV() { + return "0"; + } + + /** + * Returns the ItalicAngle attribute of the font. + * @return String The ItalicAngle + */ + public String getItalicAngle() { + String ia = Short.toString((short)(italicAngle / 0x10000)); + + // This is the correct italic angle, however only int italic + // angles are supported at the moment so this is commented out. + /* + * if ((italicAngle % 0x10000) > 0 ) + * ia=ia+(comma+Short.toString((short)((short)((italicAngle % 0x10000)*1000)/0x10000))); + */ + return ia; + } + + /** + * Returns the font bounding box. + * @return int[] The font bbox + */ + public int[] getFontBBox() { + final int[] fbb = new int[4]; + fbb[0] = (int)convertTTFUnit2PDFUnit(fontBBox1); + fbb[1] = (int)convertTTFUnit2PDFUnit(fontBBox2); + fbb[2] = (int)convertTTFUnit2PDFUnit(fontBBox3); + fbb[3] = (int)convertTTFUnit2PDFUnit(fontBBox4); + + return fbb; + } + + /** + * Returns the LowerCaseAscent attribute of the font. + * @return int The LowerCaseAscent + */ + public int getLowerCaseAscent() { + return (int)convertTTFUnit2PDFUnit(ascender); + } + + /** + * Returns the LowerCaseDescent attribute of the font. + * @return int The LowerCaseDescent + */ + public int getLowerCaseDescent() { + return (int)convertTTFUnit2PDFUnit(descender); + } + + /** + * Returns the index of the last character, but this is for WinAnsiEncoding + * only, so the last char is < 256. + * @return short Index of the last character (<256) + */ + public short getLastChar() { + return lastChar; + } + + /** + * Returns the index of the first character. + * @return short Index of the first character + */ + public short getFirstChar() { + return firstChar; + } + + /** + * Returns an array of character widths. + * @return int[] The character widths + */ + public int[] getWidths() { + int[] wx = new int[mtxTab.length]; + for (int i = 0; i < wx.length; i++) { + wx[i] = (int)convertTTFUnit2PDFUnit(mtxTab[i].getWx()); + } + + return wx; + } + + /** + * Returns the width of a given character. + * @param idx Index of the character + * @return int Standard width + */ + public int getCharWidth(int idx) { + return (int)convertTTFUnit2PDFUnit(ansiWidth[idx]); + } + + /** + * Returns the kerning table. + * @return Map The kerning table + */ + public Map getKerning() { + return kerningTab; + } + + /** + * Returns the ANSI kerning table. + * @return Map The ANSI kerning table + */ + public Map getAnsiKerning() { + return ansiKerningTab; + } + + /** + * Indicates if the font may be embedded. + * @return boolean True if it may be embedded + */ + public boolean isEmbeddable() { + return isEmbeddable; + } + + + /** + * Read Table Directory from the current position in the + * FontFileReader and fill the global HashMap dirTabs + * with the table name (String) as key and a TTFDirTabEntry + * as value. + * @param in FontFileReader to read the table directory from + * @throws IOException in case of an I/O problem + */ + protected void readDirTabs(FontFileReader in) throws IOException { + in.skip(4); // TTF_FIXED_SIZE + int ntabs = in.readTTFUShort(); + in.skip(6); // 3xTTF_USHORT_SIZE + + dirTabs = new java.util.HashMap(); + TTFDirTabEntry[] pd = new TTFDirTabEntry[ntabs]; + getLogger().debug("Reading " + ntabs + " dir tables"); + for (int i = 0; i < ntabs; i++) { + pd[i] = new TTFDirTabEntry(); + dirTabs.put(pd[i].read(in), pd[i]); + } + } + + /** + * Read the "head" table, this reads the bounding box and + * sets the upem (unitsPerEM) variable + * @param in FontFileReader to read the header from + * @throws IOException in case of an I/O problem + */ + protected void readFontHeader(FontFileReader in) throws IOException { + seekTab(in, "head", 2 * 4 + 2 * 4 + 2); + upem = in.readTTFUShort(); + + in.skip(16); + + fontBBox1 = in.readTTFShort(); + fontBBox2 = in.readTTFShort(); + fontBBox3 = in.readTTFShort(); + fontBBox4 = in.readTTFShort(); + + in.skip(2 + 2 + 2); + + locaFormat = in.readTTFShort(); + } + + /** + * Read the number of glyphs from the "maxp" table + * @param in FontFileReader to read the number of glyphs from + * @throws IOException in case of an I/O problem + */ + protected void getNumGlyphs(FontFileReader in) throws IOException { + seekTab(in, "maxp", 4); + numberOfGlyphs = in.readTTFUShort(); + } + + + /** + * Read the "hhea" table to find the ascender and descender and + * size of "hmtx" table, as a fixed size font might have only + * one width. + * @param in FontFileReader to read the hhea table from + * @throws IOException in case of an I/O problem + */ + protected void readHorizontalHeader(FontFileReader in) + throws IOException { + seekTab(in, "hhea", 4); + ascender = in.readTTFShort(); // Use sTypoAscender in "OS/2" table? + descender = in.readTTFShort(); // Use sTypoDescender in "OS/2" table? + + in.skip(2 + 2 + 3 * 2 + 8 * 2); + nhmtx = in.readTTFUShort(); + getLogger().debug("Number of horizontal metrics: " + nhmtx); + + //Check OS/2 table for ascender/descender if necessary + if (ascender == 0 || descender == 0) { + seekTab(in, "OS/2", 68); + if (this.currentDirTab.getLength() >= 78) { + ascender = in.readTTFShort(); //sTypoAscender + descender = in.readTTFShort(); //sTypoDescender + } + } + + } + + /** + * Read "hmtx" table and put the horizontal metrics + * in the mtxTab array. If the number of metrics is less + * than the number of glyphs (eg fixed size fonts), extend + * the mtxTab array and fill in the missing widths + * @param in FontFileReader to read the hmtx table from + * @throws IOException in case of an I/O problem + */ + protected void readHorizontalMetrics(FontFileReader in) + throws IOException { + seekTab(in, "hmtx", 0); + + int mtxSize = Math.max(numberOfGlyphs, nhmtx); + mtxTab = new TTFMtxEntry[mtxSize]; + + if (TRACE_ENABLED) { + getLogger().debug("*** Widths array: \n"); + } + for (int i = 0; i < mtxSize; i++) { + mtxTab[i] = new TTFMtxEntry(); + } + for (int i = 0; i < nhmtx; i++) { + mtxTab[i].setWx(in.readTTFUShort()); + mtxTab[i].setLsb(in.readTTFUShort()); + + if (TRACE_ENABLED) { + if (getLogger().isDebugEnabled()) { + getLogger().debug(" width[" + i + "] = " + + convertTTFUnit2PDFUnit(mtxTab[i].getWx()) + ";"); + } + } + } + + if (nhmtx < mtxSize) { + // Fill in the missing widths + int lastWidth = mtxTab[nhmtx - 1].getWx(); + for (int i = nhmtx; i < mtxSize; i++) { + mtxTab[i].setWx(lastWidth); + mtxTab[i].setLsb(in.readTTFUShort()); + } + } + } + + + /** + * Read the "post" table + * containing the PostScript names of the glyphs. + */ + private final void readPostScript(FontFileReader in) throws IOException { + seekTab(in, "post", 0); + postFormat = in.readTTFLong(); + italicAngle = in.readTTFULong(); + underlinePosition = in.readTTFShort(); + underlineThickness = in.readTTFShort(); + isFixedPitch = in.readTTFULong(); + + //Skip memory usage values + in.skip(4 * 4); + + getLogger().debug("PostScript format: " + postFormat); + switch (postFormat) { + case 0x00010000: + getLogger().debug("PostScript format 1"); + for (int i = 0; i < Glyphs.MAC_GLYPH_NAMES.length; i++) { + mtxTab[i].setName(Glyphs.MAC_GLYPH_NAMES[i]); + } + break; + case 0x00020000: + getLogger().debug("PostScript format 2"); + int numGlyphStrings = 0; + + // Read Number of Glyphs + int l = in.readTTFUShort(); + + // Read indexes + for (int i = 0; i < l; i++) { + mtxTab[i].setIndex(in.readTTFUShort()); + + if (mtxTab[i].getIndex() > 257) { + //Index is not in the Macintosh standard set + numGlyphStrings++; + } + + if (getLogger().isDebugEnabled()) { + getLogger().debug("PostScript index: " + mtxTab[i].getIndexAsString()); + } + } + + // firstChar=minIndex; + String[] psGlyphsBuffer = new String[numGlyphStrings]; + if (getLogger().isDebugEnabled()) { + getLogger().debug("Reading " + numGlyphStrings + + " glyphnames, that are not in the standard Macintosh" + + " set. Total number of glyphs=" + l); + } + for (int i = 0; i < psGlyphsBuffer.length; i++) { + psGlyphsBuffer[i] = in.readTTFString(in.readTTFUByte()); + } + + //Set glyph names + for (int i = 0; i < l; i++) { + if (mtxTab[i].getIndex() < NMACGLYPHS) { + mtxTab[i].setName(Glyphs.MAC_GLYPH_NAMES[mtxTab[i].getIndex()]); + } else { + if (!mtxTab[i].isIndexReserved()) { + int k = mtxTab[i].getIndex() - NMACGLYPHS; + + if (getLogger().isDebugEnabled()) { + getLogger().debug(k + " i=" + i + " mtx=" + mtxTab.length + + " ps=" + psGlyphsBuffer.length); + } + + mtxTab[i].setName(psGlyphsBuffer[k]); + } + } + } + + break; + case 0x00030000: + // PostScript format 3 contains no glyph names + getLogger().debug("PostScript format 3"); + break; + default: + getLogger().error("Unknown PostScript format: " + postFormat); + } + } + + + /** + * Read the "OS/2" table + */ + private final void readOS2(FontFileReader in) throws IOException { + // Check if font is embeddable + if (dirTabs.get("OS/2") != null) { + seekTab(in, "OS/2", 2 * 4); + int fsType = in.readTTFUShort(); + if (fsType == 2) { + isEmbeddable = false; + } else { + isEmbeddable = true; + } + } else { + isEmbeddable = true; + } + } + + /** + * Read the "loca" table. + * @param in FontFileReader to read from + * @throws IOException In case of a I/O problem + */ + protected final void readIndexToLocation(FontFileReader in) + throws IOException { + seekTab(in, "loca", 0); + for (int i = 0; i < numberOfGlyphs; i++) { + mtxTab[i].setOffset(locaFormat == 1 ? in.readTTFULong() + : (in.readTTFUShort() << 1)); + } + lastLoca = (locaFormat == 1 ? in.readTTFULong() + : (in.readTTFUShort() << 1)); + } + + /** + * Read the "glyf" table to find the bounding boxes. + * @param in FontFileReader to read from + * @throws IOException In case of a I/O problem + */ + private final void readGlyf(FontFileReader in) throws IOException { + TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get("glyf"); + for (int i = 0; i < (numberOfGlyphs - 1); i++) { + if (mtxTab[i].getOffset() != mtxTab[i + 1].getOffset()) { + in.seekSet(dirTab.getOffset() + mtxTab[i].getOffset()); + in.skip(2); + final int[] bbox = { + in.readTTFShort(), + in.readTTFShort(), + in.readTTFShort(), + in.readTTFShort()}; + mtxTab[i].setBoundingBox(bbox); + } else { + mtxTab[i].setBoundingBox(mtxTab[0].getBoundingBox()); + } + } + + + long n = ((TTFDirTabEntry)dirTabs.get("glyf")).getOffset(); + for (int i = 0; i < numberOfGlyphs; i++) { + if ((i + 1) >= mtxTab.length + || mtxTab[i].getOffset() != mtxTab[i + 1].getOffset()) { + in.seekSet(n + mtxTab[i].getOffset()); + in.skip(2); + final int[] bbox = { + in.readTTFShort(), + in.readTTFShort(), + in.readTTFShort(), + in.readTTFShort()}; + mtxTab[i].setBoundingBox(bbox); + } else { + /**@todo Verify that this is correct, looks like a copy/paste bug (jm)*/ + final int bbox0 = mtxTab[0].getBoundingBox()[0]; + final int[] bbox = {bbox0, bbox0, bbox0, bbox0}; + mtxTab[i].setBoundingBox(bbox); + /* Original code + mtxTab[i].bbox[0] = mtxTab[0].bbox[0]; + mtxTab[i].bbox[1] = mtxTab[0].bbox[0]; + mtxTab[i].bbox[2] = mtxTab[0].bbox[0]; + mtxTab[i].bbox[3] = mtxTab[0].bbox[0]; */ + } + getLogger().debug(mtxTab[i].toString(this)); + } + } + + /** + * Read the "name" table. + * @param in FontFileReader to read from + * @throws IOException In case of a I/O problem + */ + private final void readName(FontFileReader in) throws IOException { + seekTab(in, "name", 2); + int i = in.getCurrentPos(); + int n = in.readTTFUShort(); + int j = in.readTTFUShort() + i - 2; + i += 2 * 2; + + while (n-- > 0) { + // getLogger().debug("Iteration: " + n); + in.seekSet(i); + final int platformID = in.readTTFUShort(); + final int encodingID = in.readTTFUShort(); + final int languageID = in.readTTFUShort(); + + int k = in.readTTFUShort(); + int l = in.readTTFUShort(); + + if (((platformID == 1 || platformID == 3) + && (encodingID == 0 || encodingID == 1)) + && (k == 1 || k == 2 || k == 0 || k == 4 || k == 6)) { + in.seekSet(j + in.readTTFUShort()); + String txt = in.readTTFString(l); + + getLogger().debug(platformID + " " + + encodingID + " " + + languageID + " " + + k + " " + txt); + switch (k) { + case 0: + notice = txt; + break; + case 1: + familyName = txt; + break; + case 2: + subFamilyName = txt; + break; + case 4: + fullName = txt; + break; + case 6: + fontName = txt; + break; + } + if (!notice.equals("") + && !fullName.equals("") + && !fontName.equals("") + && !familyName.equals("") + && !subFamilyName.equals("")) { + break; + } + } + i += 6 * 2; + } + } + + /** + * Read the "PCLT" table to find xHeight and capHeight. + * @param in FontFileReader to read from + * @throws IOException In case of a I/O problem + */ + private final void readPCLT(FontFileReader in) throws IOException { + TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get("PCLT"); + if (dirTab != null) { + in.seekSet(dirTab.getOffset() + 4 + 4 + 2); + xHeight = in.readTTFUShort(); + in.skip(2 * 2); + capHeight = in.readTTFUShort(); + in.skip(2 + 16 + 8 + 6 + 1 + 1); + + int serifStyle = in.readTTFUByte(); + serifStyle = serifStyle >> 6; + serifStyle = serifStyle & 3; + if (serifStyle == 1) { + hasSerifs = false; + } else { + hasSerifs = true; + } + } else { + // Approximate capHeight from height of "H" + // It's most unlikly that a font misses the PCLT table + // This also assumes that psocriptnames exists ("H") + // Should look it up int the cmap (that wouldn't help + // for charsets without H anyway...) + for (int i = 0; i < mtxTab.length; i++) { + if ("H".equals(mtxTab[i].getName())) { + capHeight = mtxTab[i].getBoundingBox()[3] - mtxTab[i].getBoundingBox()[1]; + } + } + } + } + + /** + * Read the kerning table, create a table for both CIDs and + * winAnsiEncoding. + * @param in FontFileReader to read from + * @throws IOException In case of a I/O problem + */ + private final void readKerning(FontFileReader in) throws IOException { + // Read kerning + kerningTab = new java.util.HashMap(); + ansiKerningTab = new java.util.HashMap(); + TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get("kern"); + if (dirTab != null) { + seekTab(in, "kern", 2); + for (int n = in.readTTFUShort(); n > 0; n--) { + in.skip(2 * 2); + int k = in.readTTFUShort(); + if (!((k & 1) != 0) || (k & 2) != 0 || (k & 4) != 0) { + return; + } + if ((k >> 8) != 0) { + continue; + } + + k = in.readTTFUShort(); + in.skip(3 * 2); + while (k-- > 0) { + int i = in.readTTFUShort(); + int j = in.readTTFUShort(); + int kpx = in.readTTFShort(); + if (kpx != 0) { + // CID table + Integer iObj = new Integer(i); + Map adjTab = (Map)kerningTab.get(iObj); + if (adjTab == null) { + adjTab = new java.util.HashMap(); + } + adjTab.put(new Integer(j), + new Integer((int)convertTTFUnit2PDFUnit(kpx))); + kerningTab.put(iObj, adjTab); + } + } + } + // getLogger().debug(kerningTab.toString()); + + // Create winAnsiEncoded kerning table + Iterator ae = kerningTab.keySet().iterator(); + while (ae.hasNext()) { + Integer cidKey = (Integer)ae.next(); + Map akpx = new java.util.HashMap(); + Map ckpx = (Map)kerningTab.get(cidKey); + + Iterator aee = ckpx.keySet().iterator(); + while (aee.hasNext()) { + Integer cidKey2 = (Integer)aee.next(); + Integer kern = (Integer)ckpx.get(cidKey2); + + Iterator uniMap = mtxTab[cidKey2.intValue()].getUnicodeIndex().listIterator(); + while (uniMap.hasNext()) { + Integer unicodeKey = (Integer)uniMap.next(); + Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue()); + for (int u = 0; u < ansiKeys.length; u++) { + akpx.put(ansiKeys[u], kern); + } + } + } + + if (akpx.size() > 0) { + Iterator uniMap = mtxTab[cidKey.intValue()].getUnicodeIndex().listIterator(); + while (uniMap.hasNext()) { + Integer unicodeKey = (Integer)uniMap.next(); + Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue()); + for (int u = 0; u < ansiKeys.length; u++) { + ansiKerningTab.put(ansiKeys[u], akpx); + } + } + } + } + } + } + + /** + * Return a List with TTFCmapEntry. + * @return A list of TTFCmapEntry objects + */ + public List getCMaps() { + return cmaps; + } + + /** + * Check if this is a TrueType collection and that the given + * name exists in the collection. + * If it does, set offset in fontfile to the beginning of + * the Table Directory for that font. + * @param in FontFileReader to read from + * @param name The name to check + * @return True if not collection or font name present, false otherwise + * @throws IOException In case of an I/O problem + */ + protected final boolean checkTTC(FontFileReader in, String name) throws IOException { + String tag = in.readTTFString(4); + + if ("ttcf".equals(tag)) { + // This is a TrueType Collection + in.skip(4); + + // Read directory offsets + int numDirectories = (int)in.readTTFULong(); + // int numDirectories=in.readTTFUShort(); + long[] dirOffsets = new long[numDirectories]; + for (int i = 0; i < numDirectories; i++) { + dirOffsets[i] = in.readTTFULong(); + } + + getLogger().info("This is a TrueType collection file with " + + numDirectories + " fonts"); + getLogger().info("Containing the following fonts: "); + // Read all the directories and name tables to check + // If the font exists - this is a bit ugly, but... + boolean found = false; + + // Iterate through all name tables even if font + // Is found, just to show all the names + long dirTabOffset = 0; + for (int i = 0; (i < numDirectories); i++) { + in.seekSet(dirOffsets[i]); + readDirTabs(in); + + readName(in); + + if (fullName.equals(name)) { + found = true; + dirTabOffset = dirOffsets[i]; + getLogger().info(fullName + " <-- selected"); + } else { + getLogger().info(fullName); + } + + // Reset names + notice = ""; + fullName = ""; + familyName = ""; + fontName = ""; + subFamilyName = ""; + } + + in.seekSet(dirTabOffset); + return found; + } else { + in.seekSet(0); + return true; + } + } + + /* + * Helper classes, they are not very efficient, but that really + * doesn't matter... + */ + private Integer[] unicodeToWinAnsi(int unicode) { + List ret = new java.util.ArrayList(); + for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) { + if (unicode == Glyphs.WINANSI_ENCODING[i]) { + ret.add(new Integer(i)); + } + } + return (Integer[])ret.toArray(new Integer[0]); + } + + /** + * Dumps a few informational values to System.out. + */ + public void printStuff() { + System.out.println("Font name: " + fontName); + System.out.println("Full name: " + fullName); + System.out.println("Family name: " + familyName); + System.out.println("Subfamily name: " + subFamilyName); + System.out.println("Notice: " + notice); + System.out.println("xHeight: " + (int)convertTTFUnit2PDFUnit(xHeight)); + System.out.println("capheight: " + (int)convertTTFUnit2PDFUnit(capHeight)); + + int italic = (int)(italicAngle >> 16); + System.out.println("Italic: " + italic); + System.out.print("ItalicAngle: " + (short)(italicAngle / 0x10000)); + if ((italicAngle % 0x10000) > 0) { + System.out.print("." + + (short)((italicAngle % 0x10000) * 1000) + / 0x10000); + } + System.out.println(); + System.out.println("Ascender: " + convertTTFUnit2PDFUnit(ascender)); + System.out.println("Descender: " + convertTTFUnit2PDFUnit(descender)); + System.out.println("FontBBox: [" + (int)convertTTFUnit2PDFUnit(fontBBox1) + + " " + (int)convertTTFUnit2PDFUnit(fontBBox2) + " " + + (int)convertTTFUnit2PDFUnit(fontBBox3) + " " + + (int)convertTTFUnit2PDFUnit(fontBBox4) + "]"); + } + + /** + * Static main method to get info about a TrueType font. + * @param args The command line arguments + */ + public static void main(String[] args) { + int level = ConsoleLogger.LEVEL_WARN; + Logger log = new ConsoleLogger(level); + try { + TTFFile ttfFile = new TTFFile(); + ttfFile.enableLogging(log); + + FontFileReader reader = new FontFileReader(args[0]); + + String name = null; + if (args.length >= 2) { + name = args[1]; + } + + ttfFile.readFont(reader, name); + ttfFile.printStuff(); + + } catch (IOException ioe) { + log.error("Problem reading font: " + ioe.toString(), ioe); + } + } + +} + + +/** + * Key-value helper class + */ +class UnicodeMapping { + + private int uIdx; + private int gIdx; + + UnicodeMapping(int gIdx, int uIdx) { + this.uIdx = uIdx; + this.gIdx = gIdx; + } + + /** + * Returns the gIdx. + * @return int + */ + public int getGIdx() { + return gIdx; + } + + /** + * Returns the uIdx. + * @return int + */ + public int getUIdx() { + return uIdx; + } + +} diff --git a/src/java/org/apache/fop/fonts/truetype/TTFMtxEntry.java b/src/java/org/apache/fop/fonts/truetype/TTFMtxEntry.java new file mode 100644 index 000000000..cc5466830 --- /dev/null +++ b/src/java/org/apache/fop/fonts/truetype/TTFMtxEntry.java @@ -0,0 +1,194 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts.truetype; + +import java.util.List; + +/** + * This class represents a TrueType Mtx Entry. + */ +class TTFMtxEntry { + + private int wx; + private int lsb; + private String name = ""; + private int index; + private List unicodeIndex = new java.util.ArrayList(); + private int[] boundingBox = new int[4]; + private long offset; + private byte found = 0; + + /** + * Returns a String representation of this object. + * + * @param t TTFFile to use for unit conversion + * @return String String representation + */ + public String toString(TTFFile t) { + return "Glyph " + name + " index: " + getIndexAsString() + " bbox [" + + t.convertTTFUnit2PDFUnit(boundingBox[0]) + " " + + t.convertTTFUnit2PDFUnit(boundingBox[1]) + " " + + t.convertTTFUnit2PDFUnit(boundingBox[2]) + " " + + t.convertTTFUnit2PDFUnit(boundingBox[3]) + "] wx: " + + t.convertTTFUnit2PDFUnit(wx); + } + + /** + * Returns the boundingBox. + * @return int[] + */ + public int[] getBoundingBox() { + return boundingBox; + } + + /** + * Sets the boundingBox. + * @param boundingBox The boundingBox to set + */ + public void setBoundingBox(int[] boundingBox) { + this.boundingBox = boundingBox; + } + + /** + * Returns the found. + * @return byte + */ + public byte getFound() { + return found; + } + + /** + * Returns the index. + * @return int + */ + public int getIndex() { + return index; + } + + /** + * Determines whether this index represents a reserved character. + * @return True if it is reserved + */ + public boolean isIndexReserved() { + return (getIndex() >= 32768) && (getIndex() <= 65535); + } + + /** + * Returns a String representation of the index taking into account if + * the index is in the reserved range. + * @return index as String + */ + public String getIndexAsString() { + if (isIndexReserved()) { + return Integer.toString(getIndex()) + " (reserved)"; + } else { + return Integer.toString(getIndex()); + } + } + + /** + * Returns the lsb. + * @return int + */ + public int getLsb() { + return lsb; + } + + /** + * Returns the name. + * @return String + */ + public String getName() { + return name; + } + + /** + * Returns the offset. + * @return long + */ + public long getOffset() { + return offset; + } + + /** + * Returns the unicodeIndex. + * @return List + */ + public List getUnicodeIndex() { + return unicodeIndex; + } + + /** + * Returns the wx. + * @return int + */ + public int getWx() { + return wx; + } + + /** + * Sets the found. + * @param found The found to set + */ + public void setFound(byte found) { + this.found = found; + } + + /** + * Sets the index. + * @param index The index to set + */ + public void setIndex(int index) { + this.index = index; + } + + /** + * Sets the lsb. + * @param lsb The lsb to set + */ + public void setLsb(int lsb) { + this.lsb = lsb; + } + + /** + * Sets the name. + * @param name The name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * Sets the offset. + * @param offset The offset to set + */ + public void setOffset(long offset) { + this.offset = offset; + } + + /** + * Sets the wx. + * @param wx The wx to set + */ + public void setWx(int wx) { + this.wx = wx; + } + + +} diff --git a/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java b/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java new file mode 100644 index 000000000..5d7812eab --- /dev/null +++ b/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java @@ -0,0 +1,854 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts.truetype; + +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; +import java.util.List; + + +/** + * Reads a TrueType file and generates a subset + * that can be used to embed a TrueType CID font. + * TrueType tables needed for embedded CID fonts are: + * "head", "hhea", "loca", "maxp", "cvt ", "prep", "glyf", "hmtx" and "fpgm". + * The TrueType spec can be found at the Microsoft + * Typography site: http://www.microsoft.com/truetype/ + */ +public class TTFSubSetFile extends TTFFile { + + private byte[] output = null; + private int realSize = 0; + private int currentPos = 0; + + /* + * Offsets in name table to be filled out by table. + * The offsets are to the checkSum field + */ + private int cvtDirOffset = 0; + private int fpgmDirOffset = 0; + private int glyfDirOffset = 0; + private int headDirOffset = 0; + private int hheaDirOffset = 0; + private int hmtxDirOffset = 0; + private int locaDirOffset = 0; + private int maxpDirOffset = 0; + private int prepDirOffset = 0; + + private int checkSumAdjustmentOffset = 0; + private int locaOffset = 0; + + /** + * Initalize the output array + */ + private void init(int size) { + output = new byte[size]; + realSize = 0; + currentPos = 0; + + // createDirectory() + } + + /** + * Create the directory table + */ + private void createDirectory() { + int numTables = 9; + // Create the TrueType header + writeByte((byte)0); + writeByte((byte)1); + writeByte((byte)0); + writeByte((byte)0); + realSize += 4; + + writeUShort(numTables); + realSize += 2; + + // Create searchRange, entrySelector and rangeShift + int maxPow = maxPow2(numTables); + int searchRange = maxPow * 16; + writeUShort(searchRange); + realSize += 2; + + writeUShort(maxPow); + realSize += 2; + + writeUShort((numTables * 16) - searchRange); + realSize += 2; + + // Create space for the table entries + writeString("cvt "); + cvtDirOffset = currentPos; + currentPos += 12; + realSize += 16; + + if (hasFpgm()) { + writeString("fpgm"); + fpgmDirOffset = currentPos; + currentPos += 12; + realSize += 16; + } + + writeString("glyf"); + glyfDirOffset = currentPos; + currentPos += 12; + realSize += 16; + + writeString("head"); + headDirOffset = currentPos; + currentPos += 12; + realSize += 16; + + writeString("hhea"); + hheaDirOffset = currentPos; + currentPos += 12; + realSize += 16; + + writeString("hmtx"); + hmtxDirOffset = currentPos; + currentPos += 12; + realSize += 16; + + writeString("loca"); + locaDirOffset = currentPos; + currentPos += 12; + realSize += 16; + + writeString("maxp"); + maxpDirOffset = currentPos; + currentPos += 12; + realSize += 16; + + writeString("prep"); + prepDirOffset = currentPos; + currentPos += 12; + realSize += 16; + } + + + /** + * Copy the cvt table as is from original font to subset font + */ + private void createCvt(FontFileReader in) throws IOException { + TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("cvt "); + if (entry != null) { + pad4(); + seekTab(in, "cvt ", 0); + System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()), + 0, output, currentPos, (int)entry.getLength()); + + int checksum = getCheckSum(currentPos, (int)entry.getLength()); + writeULong(cvtDirOffset, checksum); + writeULong(cvtDirOffset + 4, currentPos); + writeULong(cvtDirOffset + 8, (int)entry.getLength()); + currentPos += (int)entry.getLength(); + realSize += (int)entry.getLength(); + } else { + throw new IOException("Can't find cvt table"); + } + } + + + private boolean hasFpgm() { + return (dirTabs.get("fpgm") != null); + } + + + /** + * Copy the fpgm table as is from original font to subset font + */ + private void createFpgm(FontFileReader in) throws IOException { + TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("fpgm"); + if (entry != null) { + pad4(); + seekTab(in, "fpgm", 0); + System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()), + 0, output, currentPos, (int)entry.getLength()); + int checksum = getCheckSum(currentPos, (int)entry.getLength()); + writeULong(fpgmDirOffset, checksum); + writeULong(fpgmDirOffset + 4, currentPos); + writeULong(fpgmDirOffset + 8, (int)entry.getLength()); + currentPos += (int)entry.getLength(); + realSize += (int)entry.getLength(); + } else { + //fpgm table is optional + //throw new IOException("Can't find fpgm table"); + } + } + + + + /** + * Create an empty loca table without updating checksum + */ + private void createLoca(int size) throws IOException { + pad4(); + locaOffset = currentPos; + writeULong(locaDirOffset + 4, currentPos); + writeULong(locaDirOffset + 8, size * 4 + 4); + currentPos += size * 4 + 4; + realSize += size * 4 + 4; + } + + + /** + * Copy the maxp table as is from original font to subset font + * and set num glyphs to size + */ + private void createMaxp(FontFileReader in, int size) throws IOException { + TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("maxp"); + if (entry != null) { + pad4(); + seekTab(in, "maxp", 0); + System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()), + 0, output, currentPos, (int)entry.getLength()); + writeUShort(currentPos + 4, size); + + int checksum = getCheckSum(currentPos, (int)entry.getLength()); + writeULong(maxpDirOffset, checksum); + writeULong(maxpDirOffset + 4, currentPos); + writeULong(maxpDirOffset + 8, (int)entry.getLength()); + currentPos += (int)entry.getLength(); + realSize += (int)entry.getLength(); + } else { + throw new IOException("Can't find maxp table"); + } + } + + + /** + * Copy the prep table as is from original font to subset font + */ + private void createPrep(FontFileReader in) throws IOException { + TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("prep"); + if (entry != null) { + pad4(); + seekTab(in, "prep", 0); + System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()), + 0, output, currentPos, (int)entry.getLength()); + + int checksum = getCheckSum(currentPos, (int)entry.getLength()); + writeULong(prepDirOffset, checksum); + writeULong(prepDirOffset + 4, currentPos); + writeULong(prepDirOffset + 8, (int)entry.getLength()); + currentPos += (int)entry.getLength(); + realSize += (int)entry.getLength(); + } else { + throw new IOException("Can't find prep table"); + } + } + + + /** + * Copy the hhea table as is from original font to subset font + * and fill in size of hmtx table + */ + private void createHhea(FontFileReader in, int size) throws IOException { + TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("hhea"); + if (entry != null) { + pad4(); + seekTab(in, "hhea", 0); + System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()), + 0, output, currentPos, (int)entry.getLength()); + writeUShort((int)entry.getLength() + currentPos - 2, size); + + int checksum = getCheckSum(currentPos, (int)entry.getLength()); + writeULong(hheaDirOffset, checksum); + writeULong(hheaDirOffset + 4, currentPos); + writeULong(hheaDirOffset + 8, (int)entry.getLength()); + currentPos += (int)entry.getLength(); + realSize += (int)entry.getLength(); + } else { + throw new IOException("Can't find hhea table"); + } + } + + + /** + * Copy the head table as is from original font to subset font + * and set indexToLocaFormat to long and set + * checkSumAdjustment to 0, store offset to checkSumAdjustment + * in checkSumAdjustmentOffset + */ + private void createHead(FontFileReader in) throws IOException { + TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("head"); + if (entry != null) { + pad4(); + seekTab(in, "head", 0); + System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()), + 0, output, currentPos, (int)entry.getLength()); + + checkSumAdjustmentOffset = currentPos + 8; + output[currentPos + 8] = 0; // Set checkSumAdjustment to 0 + output[currentPos + 9] = 0; + output[currentPos + 10] = 0; + output[currentPos + 11] = 0; + output[currentPos + 50] = 0; // long locaformat + output[currentPos + 51] = 1; // long locaformat + + int checksum = getCheckSum(currentPos, (int)entry.getLength()); + writeULong(headDirOffset, checksum); + writeULong(headDirOffset + 4, currentPos); + writeULong(headDirOffset + 8, (int)entry.getLength()); + + currentPos += (int)entry.getLength(); + realSize += (int)entry.getLength(); + } else { + throw new IOException("Can't find head table"); + } + } + + + /** + * Create the glyf table and fill in loca table + */ + private void createGlyf(FontFileReader in, + Map glyphs) throws IOException { + TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("glyf"); + int size = 0; + int start = 0; + int endOffset = 0; // Store this as the last loca + if (entry != null) { + pad4(); + start = currentPos; + + /* Loca table must be in order by glyph index, so build + * an array first and then write the glyph info and + * location offset. + */ + int[] origIndexes = new int[glyphs.size()]; + + Iterator e = glyphs.keySet().iterator(); + while (e.hasNext()) { + Integer origIndex = (Integer)e.next(); + Integer subsetIndex = (Integer)glyphs.get(origIndex); + origIndexes[subsetIndex.intValue()] = origIndex.intValue(); + } + + for (int i = 0; i < origIndexes.length; i++) { + int glyphLength = 0; + int nextOffset = 0; + int origGlyphIndex = origIndexes[i]; + if (origGlyphIndex >= (mtxTab.length - 1)) { + nextOffset = (int)lastLoca; + } else { + nextOffset = (int)mtxTab[origGlyphIndex + 1].getOffset(); + } + glyphLength = nextOffset - (int)mtxTab[origGlyphIndex].getOffset(); + + // Copy glyph + System.arraycopy( + in.getBytes((int)entry.getOffset() + (int)mtxTab[origGlyphIndex].getOffset(), + glyphLength), 0, + output, currentPos, + glyphLength); + + + // Update loca table + writeULong(locaOffset + i * 4, currentPos - start); + if ((currentPos - start + glyphLength) > endOffset) { + endOffset = (currentPos - start + glyphLength); + } + + currentPos += glyphLength; + realSize += glyphLength; + + } + + size = currentPos - start; + + int checksum = getCheckSum(start, size); + writeULong(glyfDirOffset, checksum); + writeULong(glyfDirOffset + 4, start); + writeULong(glyfDirOffset + 8, size); + currentPos += 12; + realSize += 12; + + // Update loca checksum and last loca index + writeULong(locaOffset + glyphs.size() * 4, endOffset); + + checksum = getCheckSum(locaOffset, glyphs.size() * 4 + 4); + writeULong(locaDirOffset, checksum); + } else { + throw new IOException("Can't find glyf table"); + } + } + + + /** + * Create the hmtx table by copying metrics from original + * font to subset font. The glyphs Map contains an + * Integer key and Integer value that maps the original + * metric (key) to the subset metric (value) + */ + private void createHmtx(FontFileReader in, + Map glyphs) throws IOException { + TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("hmtx"); + + int longHorMetricSize = glyphs.size() * 2; + int leftSideBearingSize = glyphs.size() * 2; + int hmtxSize = longHorMetricSize + leftSideBearingSize; + + if (entry != null) { + pad4(); + //int offset = (int)entry.offset; + Iterator e = glyphs.keySet().iterator(); + while (e.hasNext()) { + Integer origIndex = (Integer)e.next(); + Integer subsetIndex = (Integer)glyphs.get(origIndex); + + writeUShort(currentPos + subsetIndex.intValue() * 4, + mtxTab[origIndex.intValue()].getWx()); + writeUShort(currentPos + subsetIndex.intValue() * 4 + 2, + mtxTab[origIndex.intValue()].getLsb()); + } + + int checksum = getCheckSum(currentPos, hmtxSize); + writeULong(hmtxDirOffset, checksum); + writeULong(hmtxDirOffset + 4, currentPos); + writeULong(hmtxDirOffset + 8, hmtxSize); + currentPos += hmtxSize; + realSize += hmtxSize; + } else { + throw new IOException("Can't find hmtx table"); + } + } + + /** + * Returns a List containing the glyph itself plus all glyphs + * that this composite glyph uses + */ + private List getIncludedGlyphs(FontFileReader in, int glyphOffset, + Integer glyphIdx) throws IOException { + List ret = new java.util.ArrayList(); + ret.add(glyphIdx); + int offset = glyphOffset + (int)mtxTab[glyphIdx.intValue()].getOffset() + 10; + Integer compositeIdx = null; + int flags = 0; + boolean moreComposites = true; + while (moreComposites) { + flags = in.readTTFUShort(offset); + compositeIdx = new Integer(in.readTTFUShort(offset + 2)); + ret.add(compositeIdx); + + offset += 4; + if ((flags & 1) > 0) { + // ARG_1_AND_ARG_2_ARE_WORDS + offset += 4; + } else { + offset += 2; + } + + if ((flags & 8) > 0) { + offset += 2; // WE_HAVE_A_SCALE + } else if ((flags & 64) > 0) { + offset += 4; // WE_HAVE_AN_X_AND_Y_SCALE + } else if ((flags & 128) > 0) { + offset += 8; // WE_HAVE_A_TWO_BY_TWO + } + + if ((flags & 32) > 0) { + moreComposites = true; + } else { + moreComposites = false; + } + } + + return ret; + } + + + /** + * Rewrite all compositepointers in glyphindex glyphIdx + * + */ + private void remapComposite(FontFileReader in, Map glyphs, + int glyphOffset, + Integer glyphIdx) throws IOException { + int offset = glyphOffset + (int)mtxTab[glyphIdx.intValue()].getOffset() + + 10; + + Integer compositeIdx = null; + int flags = 0; + boolean moreComposites = true; + + while (moreComposites) { + flags = in.readTTFUShort(offset); + compositeIdx = new Integer(in.readTTFUShort(offset + 2)); + Integer newIdx = (Integer)glyphs.get(compositeIdx); + if (newIdx == null) { + // This errormessage would look much better + // if the fontname was printed to + //log.error("An embedded font " + // + "contains bad glyph data. " + // + "Characters might not display " + // + "correctly."); + moreComposites = false; + continue; + } + + in.writeTTFUShort(offset + 2, newIdx.intValue()); + + offset += 4; + + if ((flags & 1) > 0) { + // ARG_1_AND_ARG_2_ARE_WORDS + offset += 4; + } else { + offset += 2; + } + + if ((flags & 8) > 0) { + offset += 2; // WE_HAVE_A_SCALE + } else if ((flags & 64) > 0) { + offset += 4; // WE_HAVE_AN_X_AND_Y_SCALE + } else if ((flags & 128) > 0) { + offset += 8; // WE_HAVE_A_TWO_BY_TWO + } + + if ((flags & 32) > 0) { + moreComposites = true; + } else { + moreComposites = false; + } + } + } + + + /** + * Scan all the original glyphs for composite glyphs and add those glyphs + * to the glyphmapping also rewrite the composite glyph pointers to the new + * mapping + */ + private void scanGlyphs(FontFileReader in, + Map glyphs) throws IOException { + TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("glyf"); + Map newComposites = null; + Map allComposites = new java.util.HashMap(); + + int newIndex = glyphs.size(); + + if (entry != null) { + while (newComposites == null || newComposites.size() > 0) { + // Inefficient to iterate through all glyphs + newComposites = new java.util.HashMap(); + + Iterator e = glyphs.keySet().iterator(); + while (e.hasNext()) { + Integer origIndex = (Integer)e.next(); + + if (in.readTTFShort(entry.getOffset() + + mtxTab[origIndex.intValue()].getOffset()) < 0) { + // origIndex is a composite glyph + allComposites.put(origIndex, glyphs.get(origIndex)); + List composites = + getIncludedGlyphs(in, (int)entry.getOffset(), + origIndex); + + // Iterate through all composites pointed to + // by this composite and check if they exists + // in the glyphs map, add them if not. + Iterator cps = composites.iterator(); + while (cps.hasNext()) { + Integer cIdx = (Integer)cps.next(); + if (glyphs.get(cIdx) == null + && newComposites.get(cIdx) == null) { + newComposites.put(cIdx, + new Integer(newIndex)); + newIndex++; + } + } + } + } + + // Add composites to glyphs + Iterator m = newComposites.keySet().iterator(); + while (m.hasNext()) { + Integer im = (Integer)m.next(); + glyphs.put(im, newComposites.get(im)); + } + } + + // Iterate through all composites to remap their composite index + Iterator ce = allComposites.keySet().iterator(); + while (ce.hasNext()) { + remapComposite(in, glyphs, (int)entry.getOffset(), + (Integer)ce.next()); + } + + } else { + throw new IOException("Can't find glyf table"); + } + } + + + + /** + * Returns a subset of the original font. + * + * @param in FontFileReader to read from + * @param name Name to be checked for in the font file + * @param glyphs Map of glyphs (glyphs has old index as (Integer) key and + * new index as (Integer) value) + * @return A subset of the original font + * @throws IOException in case of an I/O problem + */ + public byte[] readFont(FontFileReader in, String name, + Map glyphs) throws IOException { + + //Check if TrueType collection, and that the name exists in the collection + if (!checkTTC(in, name)) { + throw new IOException("Failed to read font"); + } + + output = new byte[in.getFileSize()]; + + readDirTabs(in); + readFontHeader(in); + getNumGlyphs(in); + readHorizontalHeader(in); + readHorizontalMetrics(in); + readIndexToLocation(in); + + scanGlyphs(in, glyphs); + + createDirectory(); // Create the TrueType header and directory + + createHead(in); + createHhea(in, glyphs.size()); // Create the hhea table + createHmtx(in, glyphs); // Create hmtx table + createMaxp(in, glyphs.size()); // copy the maxp table + + try { + createCvt(in); // copy the cvt table + } catch (IOException ex) { + // Cvt is optional (only required for OpenType (MS) fonts) + //log.error("TrueType warning: " + ex.getMessage()); + } + + try { + createFpgm(in); // copy fpgm table + } catch (IOException ex) { + // Fpgm is optional (only required for OpenType (MS) fonts) + //log.error("TrueType warning: " + ex.getMessage()); + } + + try { + createPrep(in); // copy prep table + } catch (IOException ex) { + // Prep is optional (only required for OpenType (MS) fonts) + //log.error("TrueType warning: " + ex.getMessage()); + } + + try { + createLoca(glyphs.size()); // create empty loca table + } catch (IOException ex) { + // Loca is optional (only required for OpenType (MS) fonts) + //log.error("TrueType warning: " + ex.getMessage()); + } + + try { + createGlyf(in, glyphs); + } catch (IOException ex) { + // Glyf is optional (only required for OpenType (MS) fonts) + //log.error("TrueType warning: " + ex.getMessage()); + } + + pad4(); + createCheckSumAdjustment(); + + byte[] ret = new byte[realSize]; + System.arraycopy(output, 0, ret, 0, realSize); + + return ret; + } + + /** + * writes a ISO-8859-1 string at the currentPosition + * updates currentPosition but not realSize + * @return number of bytes written + */ + private int writeString(String str) { + int length = 0; + try { + byte[] buf = str.getBytes("ISO-8859-1"); + System.arraycopy(buf, 0, output, currentPos, buf.length); + length = buf.length; + currentPos += length; + } catch (java.io.UnsupportedEncodingException e) { + // This should never happen! + } + + return length; + } + + /** + * Appends a byte to the output array, + * updates currentPost but not realSize + */ + private void writeByte(byte b) { + output[currentPos++] = b; + } + + /** + * Appends a USHORT to the output array, + * updates currentPost but not realSize + */ + private void writeUShort(int s) { + byte b1 = (byte)((s >> 8) & 0xff); + byte b2 = (byte)(s & 0xff); + writeByte(b1); + writeByte(b2); + } + + /** + * Appends a USHORT to the output array, + * at the given position without changing currentPos + */ + private void writeUShort(int pos, int s) { + byte b1 = (byte)((s >> 8) & 0xff); + byte b2 = (byte)(s & 0xff); + output[pos] = b1; + output[pos + 1] = b2; + } + + /** + * Appends a ULONG to the output array, + * updates currentPos but not realSize + */ + private void writeULong(int s) { + byte b1 = (byte)((s >> 24) & 0xff); + byte b2 = (byte)((s >> 16) & 0xff); + byte b3 = (byte)((s >> 8) & 0xff); + byte b4 = (byte)(s & 0xff); + writeByte(b1); + writeByte(b2); + writeByte(b3); + writeByte(b4); + } + + /** + * Appends a ULONG to the output array, + * at the given position without changing currentPos + */ + private void writeULong(int pos, int s) { + byte b1 = (byte)((s >> 24) & 0xff); + byte b2 = (byte)((s >> 16) & 0xff); + byte b3 = (byte)((s >> 8) & 0xff); + byte b4 = (byte)(s & 0xff); + output[pos] = b1; + output[pos + 1] = b2; + output[pos + 2] = b3; + output[pos + 3] = b4; + } + + /** + * Read a signed short value at given position + */ + private short readShort(int pos) { + int ret = readUShort(pos); + return (short)ret; + } + + /** + * Read a unsigned short value at given position + */ + private int readUShort(int pos) { + int ret = (int)output[pos]; + if (ret < 0) { + ret += 256; + } + ret = ret << 8; + if ((int)output[pos + 1] < 0) { + ret |= (int)output[pos + 1] + 256; + } else { + ret |= (int)output[pos + 1]; + } + + return ret; + } + + /** + * Create a padding in the fontfile to align + * on a 4-byte boundary + */ + private void pad4() { + int padSize = currentPos % 4; + for (int i = 0; i < padSize; i++) { + output[currentPos++] = 0; + realSize++; + } + } + + /** + * Returns the maximum power of 2 <= max + */ + private int maxPow2(int max) { + int i = 0; + while (Math.pow(2, (double)i) < max) { + i++; + } + + return (i - 1); + } + + private int log2(int num) { + return (int)(Math.log((double)num) / Math.log(2)); + } + + + private int getCheckSum(int start, int size) { + return (int)getLongCheckSum(start, size); + } + + private long getLongCheckSum(int start, int size) { + // All the tables here are aligned on four byte boundaries + // Add remainder to size if it's not a multiple of 4 + int remainder = size % 4; + if (remainder != 0) { + size += remainder; + } + + long sum = 0; + + for (int i = 0; i < size; i += 4) { + int l = (int)(output[start + i] << 24); + l += (int)(output[start + i + 1] << 16); + l += (int)(output[start + i + 2] << 16); + l += (int)(output[start + i + 3] << 16); + sum += l; + if (sum > 0xffffffff) { + sum = sum - 0xffffffff; + } + } + + return sum; + } + + private void createCheckSumAdjustment() { + long sum = getLongCheckSum(0, realSize); + int checksum = (int)(0xb1b0afba - sum); + writeULong(checkSumAdjustmentOffset, checksum); + } + +} + + + diff --git a/src/java/org/apache/fop/fonts/type1/PFBData.java b/src/java/org/apache/fop/fonts/type1/PFBData.java new file mode 100644 index 000000000..5e552a8fc --- /dev/null +++ b/src/java/org/apache/fop/fonts/type1/PFBData.java @@ -0,0 +1,160 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts.type1; + +import java.io.OutputStream; +import java.io.IOException; + +/** + * Class that represents the contents of a PFB file. + * + * @see PFBParser + */ +public class PFBData { + + /** + * Raw format, no special file structure + */ + public static final int PFB_RAW = 0; + + /** + * PC format + */ + public static final int PFB_PC = 1; + + /** + * MAC Format (unsupported, yet) + */ + public static final int PFB_MAC = 2; + + private int pfbFormat; //One of the PFB_* constants + private byte[] headerSegment; + private byte[] encryptedSegment; + private byte[] trailerSegment; + + + /** + * Sets the PFB format the font was loaded with. + * @param format one of the PFB_* constants + */ + public void setPFBFormat(int format) { + switch (format) { + case PFB_RAW: + case PFB_PC: + this.pfbFormat = format; + break; + case PFB_MAC: + throw new UnsupportedOperationException("Mac format is not yet implemented"); + default: + throw new IllegalArgumentException("Invalid value for PFB format: " + format); + } + } + + + /** + * Returns the format the font was loaded with. + * @return int one of the PFB_* constants + */ + public int getPFBFormat() { + return this.pfbFormat; + } + + /** + * Sets the header segment of the font file. + * @param headerSeg the header segment + */ + public void setHeaderSegment(byte[] headerSeg) { + this.headerSegment = headerSeg; + } + + /** + * Sets the encrypted segment of the font file. + * @param encryptedSeg the encrypted segment + */ + public void setEncryptedSegment(byte[] encryptedSeg) { + this.encryptedSegment = encryptedSeg; + } + + /** + * Sets the trailer segment of the font file. + * @param trailerSeg the trailer segment + */ + public void setTrailerSegment(byte[] trailerSeg) { + this.trailerSegment = trailerSeg; + } + + /** + * Returns the full length of the raw font file. + * @return int the raw file length + */ + public int getLength() { + return getLength1() + getLength2() + getLength3(); + } + + + /** + * Returns the Length1 (length of the header segment). + * @return int Length1 + */ + public int getLength1() { + return this.headerSegment.length; + } + + + /** + * Returns the Length2 (length of the encrypted segment). + * @return int Length2 + */ + public int getLength2() { + return this.encryptedSegment.length; + } + + + /** + * Returns the Length3 (length of the trailer segment). + * @return int Length3 + */ + public int getLength3() { + return this.trailerSegment.length; + } + + + /** + * Writes the PFB file in raw format to an OutputStream. + * @param out the OutputStream to write to + * @throws IOException In case of an I/O problem + */ + public void outputAllParts(OutputStream out) throws IOException { + out.write(this.headerSegment); + out.write(this.encryptedSegment); + out.write(this.trailerSegment); + } + + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + return "PFB: format=" + getPFBFormat() + + " len1=" + getLength1() + + " len2=" + getLength2() + + " len3=" + getLength3(); + } + +} \ No newline at end of file diff --git a/src/java/org/apache/fop/fonts/type1/PFBParser.java b/src/java/org/apache/fop/fonts/type1/PFBParser.java new file mode 100644 index 000000000..ce10ba223 --- /dev/null +++ b/src/java/org/apache/fop/fonts/type1/PFBParser.java @@ -0,0 +1,235 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts.type1; + +import java.io.IOException; +import java.io.InputStream; +import java.io.DataInputStream; +import java.io.BufferedInputStream; + +//Commons +import org.apache.commons.io.IOUtils; + +/** + * This class represents a parser for Adobe Type 1 PFB files. + * + * @see PFBData + */ +public class PFBParser { + + private static final byte[] CURRENTFILE_EEXEC; + private static final byte[] CLEARTOMARK; + + static { + try { + CURRENTFILE_EEXEC = "currentfile eexec".getBytes("US-ASCII"); + CLEARTOMARK = "cleartomark".getBytes("US-ASCII"); + } catch (java.io.UnsupportedEncodingException e) { + throw new RuntimeException("Incompatible VM. It doesn't support the US-ASCII encoding"); + } + } + + + /** + * Parses a PFB file into a PFBData object. + * @param url URL to load the PFB file from + * @return PFBData memory representation of the font + * @throws IOException In case of an I/O problem + */ + public PFBData parsePFB(java.net.URL url) throws IOException { + InputStream in = url.openStream(); + try { + return parsePFB(in); + } finally { + in.close(); + } + } + + + /** + * Parses a PFB file into a PFBData object. + * @param pfbFile File to load the PFB file from + * @return PFBData memory representation of the font + * @throws IOException In case of an I/O problem + */ + public PFBData parsePFB(java.io.File pfbFile) throws IOException { + InputStream in = new java.io.FileInputStream(pfbFile); + try { + return parsePFB(in); + } finally { + in.close(); + } + } + + + /** + * Parses a PFB file into a PFBData object. + * @param in InputStream to load the PFB file from + * @return PFBData memory representation of the font + * @throws IOException In case of an I/O problem + */ + public PFBData parsePFB(InputStream in) throws IOException { + PFBData pfb = new PFBData(); + BufferedInputStream bin = new BufferedInputStream(in); + DataInputStream din = new DataInputStream(bin); + din.mark(32); + int firstByte = din.readUnsignedByte(); + din.reset(); + if (firstByte == 128) { + pfb.setPFBFormat(PFBData.PFB_PC); + parsePCFormat(pfb, din); + } else { + pfb.setPFBFormat(PFBData.PFB_RAW); + parseRAWFormat(pfb, bin); + } + return pfb; + } + + + private static int swapInteger(final int value) { + return (((value >> 0) & 0xff) << 24) + + (((value >> 8) & 0xff) << 16) + + (((value >> 16) & 0xff) << 8) + + (((value >> 24) & 0xff) << 0); + } + + + private void parsePCFormat(PFBData pfb, DataInputStream din) throws IOException { + int segmentHead; + int segmentType; + int bytesRead; + + //Read first segment + segmentHead = din.readUnsignedByte(); + if (segmentHead != 128) { + throw new IOException("Invalid file format. Expected ASCII 80hex"); + } + segmentType = din.readUnsignedByte(); //Read + int len1 = swapInteger(din.readInt()); + byte[] headerSegment = new byte[len1]; + din.readFully(headerSegment); + pfb.setHeaderSegment(headerSegment); + + //Read second segment + segmentHead = din.readUnsignedByte(); + if (segmentHead != 128) { + throw new IOException("Invalid file format. Expected ASCII 80hex"); + } + segmentType = din.readUnsignedByte(); + int len2 = swapInteger(din.readInt()); + byte[] encryptedSegment = new byte[len2]; + din.readFully(encryptedSegment); + pfb.setEncryptedSegment(encryptedSegment); + + //Read third segment + segmentHead = din.readUnsignedByte(); + if (segmentHead != 128) { + throw new IOException("Invalid file format. Expected ASCII 80hex"); + } + segmentType = din.readUnsignedByte(); + int len3 = swapInteger(din.readInt()); + byte[] trailerSegment = new byte[len3]; + din.readFully(trailerSegment); + pfb.setTrailerSegment(trailerSegment); + + //Read EOF indicator + segmentHead = din.readUnsignedByte(); + if (segmentHead != 128) { + throw new IOException("Invalid file format. Expected ASCII 80hex"); + } + segmentType = din.readUnsignedByte(); + if (segmentType != 3) { + throw new IOException("Expected segment type 3, but found: " + segmentType); + } + } + + + private static final boolean byteCmp(byte[] src, int srcOffset, byte[] cmp) { + for (int i = 0; i < cmp.length; i++) { + // System.out.println("Compare: " + src[srcOffset + i] + " " + cmp[i]); + if (src[srcOffset + i] != cmp[i]) { + return false; + } + } + return true; + } + + private void calcLengths(PFBData pfb, byte[] originalData) { + // Calculate length 1 and 3 + // System.out.println ("Checking font, size = "+originalData.length); + + // Length1 is the size of the initial ascii portion + // search for "currentfile eexec" + // Get the first binary number and search backwards for "eexec" + int len1 = 30; + + // System.out.println("Length1="+len1); + while (!byteCmp(originalData, len1 - CURRENTFILE_EEXEC.length, CURRENTFILE_EEXEC)) { + len1++; + } + + // Skip newline + len1++; + + // Length3 is length of the last portion of the file + int len3 = 0; + len3 -= CLEARTOMARK.length; + while (!byteCmp(originalData, originalData.length + len3, CLEARTOMARK)) { + len3--; + // System.out.println("Len3="+len3); + } + len3 = -len3; + len3++; + // Eat 512 zeroes + int numZeroes = 0; + byte[] ws1 = new byte[]{0x0D}; //CR + byte[] ws2 = new byte[]{0x0A}; //LF + byte[] ws3 = new byte[]{0x30}; //"0" + while ((originalData[originalData.length - len3] == ws1[0] + || originalData[originalData.length - len3] == ws2[0] + || originalData[originalData.length - len3] == ws3[0]) + && numZeroes < 512) { + len3++; + if (originalData[originalData.length - len3] == ws3[0]) { + numZeroes++; + } + } + // System.out.println("Length3="+len3); + + //Create the 3 segments + byte[] buffer = new byte[len1]; + System.arraycopy(originalData, 0, buffer, 0, len1); + pfb.setHeaderSegment(buffer); + + int len2 = originalData.length - len3 - len1; + buffer = new byte[len2]; + System.arraycopy(originalData, len1, buffer, 0, len2); + pfb.setEncryptedSegment(buffer); + + buffer = new byte[len3]; + System.arraycopy(originalData, len1 + len2, buffer, 0, len3); + pfb.setTrailerSegment(buffer); + } + + private void parseRAWFormat(PFBData pfb, BufferedInputStream bin) + throws IOException { + calcLengths(pfb, IOUtils.toByteArray(bin)); + } + +} \ No newline at end of file diff --git a/src/java/org/apache/fop/fonts/type1/PFMFile.java b/src/java/org/apache/fop/fonts/type1/PFMFile.java new file mode 100644 index 000000000..c0ff6e415 --- /dev/null +++ b/src/java/org/apache/fop/fonts/type1/PFMFile.java @@ -0,0 +1,437 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts.type1; + +// Java +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +// Apache libs +import org.apache.avalon.framework.logger.AbstractLogEnabled; +import org.apache.commons.io.IOUtils; + +// FOP +import org.apache.fop.fonts.Glyphs; + +/** + * This class represents a PFM file (or parts of it) as a Java object. + */ +public class PFMFile extends AbstractLogEnabled { + + // Header stuff + private String windowsName; + private String postscriptName; + private short dfItalic; + private int dfWeight; + private short dfCharSet; + private short dfPitchAndFamily; + private int dfAvgWidth; + private int dfMaxWidth; + private int dfMinWidth; + private short dfFirstChar; + private short dfLastChar; + + // Extension stuff + // --- + + // Extend Text Metrics + private int etmCapHeight; + private int etmXHeight; + private int etmLowerCaseAscent; + private int etmLowerCaseDescent; + + // Extent table + private int[] extentTable; + + private Map kerningTab = new java.util.HashMap(); + + /** + * Parses a PFM file + * + * @param inStream The stream from which to read the PFM file. + * @throws IOException In case of an I/O problem + */ + public void load(InputStream inStream) throws IOException { + final byte[] buf = IOUtils.toByteArray(inStream); + final InputStream bufin = new java.io.ByteArrayInputStream(buf); + PFMInputStream in = new PFMInputStream(bufin); + /*final int version =*/ in.readShort(); + final long filesize = in.readInt(); + if (filesize != buf.length) { + getLogger().warn("Effective file size is not the same as indicated in the header."); + } + bufin.reset(); + + loadHeader(in); + loadExtension(in); + } + + /** + * Parses the header of the PFM file. + * + * @param inStream The stream from which to read the PFM file. + * @throws IOException In case of an I/O problem + */ + private void loadHeader(PFMInputStream inStream) throws IOException { + inStream.skip(80); + dfItalic = inStream.readByte(); + inStream.skip(2); + dfWeight = inStream.readShort(); + dfCharSet = inStream.readByte(); + inStream.skip(4); + dfPitchAndFamily = inStream.readByte(); + dfAvgWidth = inStream.readShort(); + dfMaxWidth = inStream.readShort(); + dfFirstChar = inStream.readByte(); + dfLastChar = inStream.readByte(); + inStream.skip(8); + long faceOffset = inStream.readInt(); + + inStream.reset(); + inStream.skip(faceOffset); + windowsName = inStream.readString(); + + inStream.reset(); + inStream.skip(117); + } + + /** + * Parses the extension part of the PFM file. + * + * @param inStream The stream from which to read the PFM file. + */ + private void loadExtension(PFMInputStream inStream) throws IOException { + final int size = inStream.readShort(); + if (size != 30) { + getLogger().warn("Size of extension block was expected to be " + + "30 bytes, but was " + size + " bytes."); + } + final long extMetricsOffset = inStream.readInt(); + final long extentTableOffset = inStream.readInt(); + inStream.skip(4); //Skip dfOriginTable + final long kernPairOffset = inStream.readInt(); + inStream.skip(4); //Skip dfTrackKernTable + long driverInfoOffset = inStream.readInt(); + + if (kernPairOffset > 0) { + inStream.reset(); + inStream.skip(kernPairOffset); + loadKernPairs(inStream); + } + + inStream.reset(); + inStream.skip(driverInfoOffset); + postscriptName = inStream.readString(); + + if (extMetricsOffset != 0) { + inStream.reset(); + inStream.skip(extMetricsOffset); + loadExtMetrics(inStream); + } + if (extentTableOffset != 0) { + inStream.reset(); + inStream.skip(extentTableOffset); + loadExtentTable(inStream); + } + + } + + /** + * Parses the kernPairs part of the pfm file + * + * @param inStream The stream from which to read the PFM file. + */ + private void loadKernPairs(PFMInputStream inStream) throws IOException { + int i = inStream.readShort(); + + + getLogger().info(i + " kerning pairs"); + while (i > 0) { + int g1 = (int)inStream.readByte(); + i--; + + int g2 = (int)inStream.readByte(); + + int adj = inStream.readShort(); + if (adj > 0x8000) { + adj = -(0x10000 - adj); + } + getLogger().debug("Char no: (" + g1 + ", " + g2 + ") kern: " + adj); + + if (getLogger().isDebugEnabled()) { + final String glyph1 = Glyphs.TEX8R_GLYPH_NAMES[g1]; + final String glyph2 = Glyphs.TEX8R_GLYPH_NAMES[g2]; + getLogger().debug("glyphs: " + glyph1 + ", " + glyph2); + } + + Map adjTab = (Map)kerningTab.get(new Integer(g1)); + if (adjTab == null) { + adjTab = new java.util.HashMap(); + } + adjTab.put(new Integer(g2), new Integer(adj)); + kerningTab.put(new Integer(g1), adjTab); + } + } + + /** + * Parses the extended metrics part of the PFM file. + * + * @param inStream The stream from which to read the PFM file. + */ + private void loadExtMetrics(PFMInputStream inStream) throws IOException { + final int size = inStream.readShort(); + if (size != 52) { + getLogger().warn("Size of extension block was expected to be " + + "52 bytes, but was " + size + " bytes."); + } + inStream.skip(12); //Skip etmPointSize, etmOrientation, etmMasterHeight, + //etmMinScale, etmMaxScale, emtMasterUnits + etmCapHeight = inStream.readShort(); + etmXHeight = inStream.readShort(); + etmLowerCaseAscent = inStream.readShort(); + etmLowerCaseDescent = inStream.readShort(); + //Ignore the rest of the values + } + + /** + * Parses the extent table of the PFM file. + * + * @param inStream The stream from which to read the PFM file. + */ + private void loadExtentTable(PFMInputStream inStream) throws IOException { + extentTable = new int[dfLastChar - dfFirstChar + 1]; + dfMinWidth = dfMaxWidth; + for (short i = dfFirstChar; i <= dfLastChar; i++) { + extentTable[i - dfFirstChar] = inStream.readShort(); + if (extentTable[i - dfFirstChar] < dfMinWidth) { + dfMinWidth = extentTable[i - dfFirstChar]; + } + } + } + + /** + * Returns the Windows name of the font. + * + * @return The Windows name. + */ + public String getWindowsName() { + return windowsName; + } + + /** + * Return the kerning table. The kerning table is a Map with + * strings with glyphnames as keys, containing Maps as value. + * The value map contains a glyph name string key and an Integer value + * + * @return A Map containing the kerning table + */ + public Map getKerning() { + return kerningTab; + } + + /** + * Returns the Postscript name of the font. + * + * @return The Postscript name. + */ + public String getPostscriptName() { + return postscriptName; + } + + /** + * Returns the charset used for the font. + * + * @return The charset (0=WinAnsi). + */ + public short getCharSet() { + return dfCharSet; + } + + /** + * Returns the charset of the font as a string. + * + * @return The name of the charset. + */ + public String getCharSetName() { + switch (dfCharSet) { + case 0: + return "WinAnsi"; + case 128: + return "Shift-JIS (Japanese)"; + default: + return "Unknown"; + } + } + + /** + * Returns the number of the character that defines + * the first entry in the widths list. + * + * @return The number of the first character. + */ + public short getFirstChar() { + return dfFirstChar; + } + + /** + * Returns the number of the character that defines + * the last entry in the widths list. + * + * @return The number of the last character. + */ + public short getLastChar() { + return dfLastChar; + } + + /** + * Returns the CapHeight parameter for the font (height of uppercase H). + * + * @return The CapHeight parameter. + */ + public int getCapHeight() { + return etmCapHeight; + } + + /** + * Returns the XHeight parameter for the font (height of lowercase x). + * + * @return The CapHeight parameter. + */ + public int getXHeight() { + return etmXHeight; + } + + /** + * Returns the LowerCaseAscent parameter for the font (height of lowercase d). + * + * @return The LowerCaseAscent parameter. + */ + public int getLowerCaseAscent() { + return etmLowerCaseAscent; + } + + /** + * Returns the LowerCaseDescent parameter for the font (height of lowercase p). + * + * @return The LowerCaseDescent parameter. + */ + public int getLowerCaseDescent() { + return etmLowerCaseDescent; + } + + /** + * Tells whether the font has proportional character spacing. + * + * @return ex. true for Times, false for Courier. + */ + public boolean getIsProportional() { + return ((dfPitchAndFamily & 1) == 1); + } + + /** + * Returns the bounding box for the font. + * Note: this value is just an approximation, + * it does not really exist in the PFM file. + * + * @return The calculated Font BBox. + */ + public int[] getFontBBox() { + int[] bbox = new int[4]; + + // Just guessing.... + if (!getIsProportional() && (dfAvgWidth == dfMaxWidth)) { + bbox[0] = -20; + } else { + bbox[0] = -100; + } + bbox[1] = -(getLowerCaseDescent() + 5); + bbox[2] = dfMaxWidth + 10; + bbox[3] = getLowerCaseAscent() + 5; + return bbox; + } + + /** + * Returns the characteristics flags for the font as + * needed for a PDF font descriptor (See PDF specs). + * + * @return The characteristics flags. + */ + public int getFlags() { + int flags = 0; + if (!getIsProportional()) { + flags |= 1; + } + if ((dfPitchAndFamily & 16) == 16) { + flags |= 2; + } + if ((dfPitchAndFamily & 64) == 64) { + flags |= 4; + } + if (dfCharSet == 0) { + flags |= 6; + } + if (dfItalic != 0) { + flags |= 7; + } + return flags; + } + + /** + * Returns the width of the dominant vertical stems of the font. + * Note: this value is just an approximation, + * it does not really exist in the PFM file. + * + * @return The vertical stem width. + */ + public int getStemV() { + // Just guessing.... + if (dfItalic != 0) { + return (int)Math.round(dfMinWidth * 0.25); + } else { + return (int)Math.round(dfMinWidth * 0.6); + } + } + + /** + * Returns the italic angle of the font. + * Note: this value is just an approximation, + * it does not really exist in the PFM file. + * + * @return The italic angle. + */ + public int getItalicAngle() { + if (dfItalic != 0) { + return -16; // Just guessing.... + } else { + return 0; + } + } + + /** + * Returns the width of a character + * + * @param which The number of the character for which the width is requested. + * @return The width of a character. + */ + public int getCharWidth(short which) { + return extentTable[which - dfFirstChar]; + } + +} diff --git a/src/java/org/apache/fop/fonts/type1/PFMInputStream.java b/src/java/org/apache/fop/fonts/type1/PFMInputStream.java new file mode 100644 index 000000000..5f5a17432 --- /dev/null +++ b/src/java/org/apache/fop/fonts/type1/PFMInputStream.java @@ -0,0 +1,107 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.fonts.type1; + +import java.io.IOException; +import java.io.InputStream; +import java.io.DataInputStream; +import java.io.InputStreamReader; + +/** + * This is a helper class for reading PFM files. It defines functions for + * extracting specific values out of the stream. + */ +public class PFMInputStream extends java.io.FilterInputStream { + + private DataInputStream datain; + + /** + * Constructs a PFMInputStream based on an InputStream representing the + * PFM file. + * + * @param in The stream from which to read the PFM file + */ + public PFMInputStream(InputStream in) { + super(in); + datain = new DataInputStream(in); + } + + /** + * Parses a one byte value out of the stream. + * + * @return The value extracted + * @throws IOException In case of an I/O problem + */ + public short readByte() throws IOException { + short s = datain.readByte(); + // Now, we've got to trick Java into forgetting the sign + int s1 = (((s & 0xF0) >>> 4) << 4) + (s & 0x0F); + return (short)s1; + } + + /** + * Parses a two byte value out of the stream. + * + * @return The value extracted + * @throws IOException In case of an I/O problem + */ + public int readShort() throws IOException { + int i = datain.readShort(); + + // Change byte order + int high = (i & 0xFF00) >>> 8; + int low = (i & 0x00FF) << 8; + return low + high; + } + + /** + * Parses a four byte value out of the stream. + * + * @return The value extracted + * @throws IOException In case of an I/O problem + */ + public long readInt() throws IOException { + int i = datain.readInt(); + + // Change byte order + int i1 = (i & 0xFF000000) >>> 24; + int i2 = (i & 0x00FF0000) >>> 8; + int i3 = (i & 0x0000FF00) << 8; + int i4 = (i & 0x000000FF) << 24; + return i1 + i2 + i3 + i4; + } + + /** + * Parses a zero-terminated string out of the stream. + * + * @return The value extracted + * @throws IOException In case of an I/O problem + */ + public String readString() throws IOException { + InputStreamReader reader = new InputStreamReader(in, "ISO-8859-1"); + StringBuffer buf = new StringBuffer(); + int ch = reader.read(); + while (ch != 0) { + buf.append((char)ch); + ch = reader.read(); + } + return buf.toString(); + } + +} diff --git a/src/java/org/apache/fop/render/AbstractRenderer.java b/src/java/org/apache/fop/render/AbstractRenderer.java new file mode 100644 index 000000000..8f5a96f7f --- /dev/null +++ b/src/java/org/apache/fop/render/AbstractRenderer.java @@ -0,0 +1,758 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.render; + +// Java +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Date; +import java.util.Map; +import java.util.List; +import java.util.Iterator; + +// XML +import org.w3c.dom.Document; + +// FOP +import org.apache.fop.apps.FOPException; +import org.apache.fop.area.Area; +import org.apache.fop.area.BeforeFloat; +import org.apache.fop.area.Block; +import org.apache.fop.area.BlockViewport; +import org.apache.fop.area.BodyRegion; +import org.apache.fop.area.CTM; +import org.apache.fop.area.Flow; +import org.apache.fop.area.Footnote; +import org.apache.fop.area.LineArea; +import org.apache.fop.area.MainReference; +import org.apache.fop.area.Span; +import org.apache.fop.area.Page; +import org.apache.fop.area.PageViewport; +import org.apache.fop.area.RegionViewport; +import org.apache.fop.area.RegionReference; +import org.apache.fop.area.Title; +import org.apache.fop.area.TreeExt; +import org.apache.fop.area.inline.Container; +import org.apache.fop.area.inline.ForeignObject; +import org.apache.fop.area.inline.Image; +import org.apache.fop.area.inline.InlineArea; +import org.apache.fop.area.inline.InlineParent; +import org.apache.fop.area.inline.Leader; +import org.apache.fop.area.inline.Space; +import org.apache.fop.area.inline.Viewport; +import org.apache.fop.area.inline.TextArea; +import org.apache.fop.area.inline.Character; +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.fo.Constants; +import org.apache.fop.fo.FOTreeControl; +import org.apache.fop.fo.pagination.Region; + +// Avalon +import org.apache.avalon.framework.logger.AbstractLogEnabled; +import org.apache.avalon.framework.configuration.Configurable; +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.ConfigurationException; + +/** + * Abstract base class for all renderers. The Abstract renderer does all the + * top level processing of the area tree and adds some abstract methods to + * handle viewports. This keeps track of the current block and inline position. + */ +public abstract class AbstractRenderer extends AbstractLogEnabled + implements Renderer, Configurable, Constants { + + /** + * user agent + */ + protected FOUserAgent userAgent; + + /** + * producer (usually "FOP") + */ + protected String producer = "FOP"; + + /** + * creator of document + */ + protected String creator = null; + + /** + * creation time + */ + protected Date creationDate = null; + + /** + * renderer configuration + */ + protected Map options; + + /** + * block progression position + */ + protected int currentBPPosition = 0; + + /** + * inline progression position + */ + protected int currentIPPosition = 0; + + /** + * current inline progression position in block + */ + protected int currentBlockIPPosition = 0; + + /** + * the block progression position of the containing block used for + * absolutely positioned blocks + */ + protected int containingBPPosition = 0; + + /** + * the inline progression position of the containing block used for + * absolutely positioned blocks + */ + protected int containingIPPosition = 0; + + /** + * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration) + */ + public void configure(Configuration conf) throws ConfigurationException { + } + + /** @see org.apache.fop.render.Renderer */ + public void setProducer(String inProducer) { + producer = inProducer; + } + + /** @see org.apache.fop.render.Renderer */ + public void setCreator(String inCreator) { + creator = inCreator; + } + + /** + * @see org.apache.fop.render.Renderer + */ + public abstract void setupFontInfo(FOTreeControl foTreeControl); + + /** + * @see org.apache.fop.render.Renderer + */ + public void setUserAgent(FOUserAgent agent) { + userAgent = agent; + } + + /** + * @param date + */ + public void setCreationDate(Date date) { + creationDate = date; + } + + /** @see org.apache.fop.render.Renderer */ + public void setOptions(Map opt) { + options = opt; + } + + /** @see org.apache.fop.render.Renderer */ + public void startRenderer(OutputStream outputStream) + throws IOException { } + + /** @see org.apache.fop.render.Renderer */ + public void stopRenderer() + throws IOException { } + + /** + * Check if this renderer supports out of order rendering. If this renderer + * supports out of order rendering then it means that the pages that are + * not ready will be prepared and a future page will be rendered. + * + * @return True if the renderer supports out of order rendering + * @see org.apache.fop.render.Renderer + */ + public boolean supportsOutOfOrder() { + return false; + } + + /** + * @param ext (todo) Description of the Parameter + * @see org.apache.fop.render.Renderer + */ + public void renderExtension(TreeExt ext) { } + + /** + * Prepare a page for rendering. This is called if the renderer supports + * out of order rendering. The renderer should prepare the page so that a + * page further on in the set of pages can be rendered. The body of the + * page should not be rendered. The page will be rendered at a later time + * by the call to render page. + * + * @see org.apache.fop.render.Renderer + */ + public void preparePage(PageViewport page) { } + + /** + * Utility method to convert a page sequence title to a string. Some + * renderers may only be able to use a string title. A title is a sequence + * of inline areas that this method attempts to convert to an equivalent + * string. + * + * @param title The Title to convert + * @return An expanded string representing the title + */ + protected String convertTitleToString(Title title) { + List children = title.getInlineAreas(); + String str = convertToString(children); + return str.trim(); + } + + private String convertToString(List children) { + StringBuffer sb = new StringBuffer(); + for (int count = 0; count < children.size(); count++) { + InlineArea inline = (InlineArea) children.get(count); + if (inline instanceof Character) { + sb.append(((Character) inline).getChar()); + } else if (inline instanceof TextArea) { + sb.append(((TextArea) inline).getTextArea()); + } else if (inline instanceof InlineParent) { + sb.append(convertToString( + ((InlineParent) inline).getChildAreas())); + } else { + sb.append(" "); + } + } + return sb.toString(); + } + + /** @see org.apache.fop.render.Renderer */ + public void startPageSequence(Title seqTitle) { + //do nothing + } + + // normally this would be overriden to create a page in the + // output + /** @see org.apache.fop.render.Renderer */ + public void renderPage(PageViewport page) + throws IOException, FOPException { + + Page p = page.getPage(); + renderPageAreas(p); + } + + /** + * Renders page areas. + * + * @param page The page whos page areas are to be rendered + */ + protected void renderPageAreas(Page page) { + /* Spec does not appear to specify whether fo:region-body should + appear above or below side regions in cases of overlap. FOP + decision is to have fo:region-body on top, hence it is rendered + last here. */ + RegionViewport viewport; + viewport = page.getRegionViewport(Region.BEFORE_CODE); + renderRegionViewport(viewport); + viewport = page.getRegionViewport(Region.START_CODE); + renderRegionViewport(viewport); + viewport = page.getRegionViewport(Region.END_CODE); + renderRegionViewport(viewport); + viewport = page.getRegionViewport(Region.AFTER_CODE); + renderRegionViewport(viewport); + viewport = page.getRegionViewport(Region.BODY_CODE); + renderRegionViewport(viewport); + } + + /** + * Renders a region viewport.

+ * + * The region may clip the area and it establishes a position from where + * the region is placed.

+ * + * @param port The region viewport to be rendered + */ + protected void renderRegionViewport(RegionViewport port) { + if (port != null) { + Rectangle2D view = port.getViewArea(); + // The CTM will transform coordinates relative to + // this region-reference area into page coords, so + // set origin for the region to 0,0. + currentBPPosition = 0; + currentIPPosition = 0; + currentBlockIPPosition = currentIPPosition; + + RegionReference region = port.getRegion(); + handleRegionTraits(port); + + // shouldn't the viewport have the CTM + startVParea(region.getCTM()); + // do after starting viewport area + if (region.getRegionClass() == Region.BODY_CODE) { + renderBodyRegion((BodyRegion) region); + } else { + renderRegion(region); + } + endVParea(); + } + } + + /** + * (todo) Description of the Method + * + * @param ctm The coordinate transformation matrix to use + */ + protected void startVParea(CTM ctm) { } + + /** + * Handle the traits for a region + * This is used to draw the traits for the given page region. + * (See Sect. 6.4.1.2 of XSL-FO spec.) + * @param rv the RegionViewport whose region is to be drawn + */ + protected void handleRegionTraits(RegionViewport rv) { + // draw border and background + } + + /** + * (todo) Description of the Method + */ + protected void endVParea() { } + + /** + * Renders a region reference area. + * + * @param region The region reference area + */ + protected void renderRegion(RegionReference region) { + List blocks = region.getBlocks(); + + renderBlocks(null, blocks); + + } + + /** + * Renders a body region area. + * + * @param region The body region + */ + protected void renderBodyRegion(BodyRegion region) { + BeforeFloat bf = region.getBeforeFloat(); + if (bf != null) { + renderBeforeFloat(bf); + } + MainReference mr = region.getMainReference(); + if (mr != null) { + renderMainReference(mr); + } + Footnote foot = region.getFootnote(); + if (foot != null) { + renderFootnote(foot); + } + } + + /** + * Renders a before float area. + * + * @param bf The before float area + */ + protected void renderBeforeFloat(BeforeFloat bf) { + List blocks = bf.getChildAreas(); + if (blocks != null) { + renderBlocks(null, blocks); + Block sep = bf.getSeparator(); + if (sep != null) { + renderBlock(sep); + } + } + } + + /** + * Renders a footnote + * + * @param footnote The footnote + */ + protected void renderFootnote(Footnote footnote) { + List blocks = footnote.getChildAreas(); + if (blocks != null) { + Block sep = footnote.getSeparator(); + if (sep != null) { + renderBlock(sep); + } + renderBlocks(null, blocks); + } + } + + /** + * Renders the main reference area. + *

+ * The main reference area contains a list of spans that are + * stacked on the page. + * The spans contain a list of normal flow reference areas + * that are positioned into columns. + *

+ * + * @param mr The main reference area + */ + protected void renderMainReference(MainReference mr) { + int saveIPPos = currentIPPosition; + + Span span = null; + List spans = mr.getSpans(); + for (int count = 0; count < spans.size(); count++) { + span = (Span) spans.get(count); + int offset = (mr.getWidth() + - (span.getColumnCount() - 1) * mr.getColumnGap()) + / span.getColumnCount() + mr.getColumnGap(); + for (int c = 0; c < span.getColumnCount(); c++) { + Flow flow = (Flow) span.getFlow(c); + + renderFlow(flow); + currentIPPosition += offset; + } + currentIPPosition = saveIPPos; + currentBPPosition += span.getHeight(); + } + } + + /** + * Renders a flow reference area. + * + * @param flow The flow reference area + */ + protected void renderFlow(Flow flow) { + // the normal flow reference area contains stacked blocks + List blocks = flow.getChildAreas(); + if (blocks != null) { + renderBlocks(null, blocks); + } + } + + /** + * Handle block traits. + * This method is called when the correct ip and bp posiiton is + * set. This should be overridden to draw border and background + * traits for the block area. + * + * @param block the block area + */ + protected void handleBlockTraits(Block block) { + // draw border and background + } + + /** + * Renders a block viewport. + * + * @param bv The block viewport + * @param children The children to render within the block viewport + */ + protected void renderBlockViewport(BlockViewport bv, List children) { + // clip and position viewport if necessary + if (bv.getPositioning() == Block.ABSOLUTE) { + // save positions + int saveIP = currentIPPosition; + int saveBP = currentBPPosition; + + CTM ctm = bv.getCTM(); + currentIPPosition = 0; + currentBPPosition = 0; + + startVParea(ctm); + handleBlockTraits(bv); + renderBlocks(bv, children); + endVParea(); + + // clip if necessary + + currentIPPosition = saveIP; + currentBPPosition = saveBP; + } else { + // save position and offset + int saveIP = currentIPPosition; + int saveBP = currentBPPosition; + + handleBlockTraits(bv); + renderBlocks(bv, children); + + currentIPPosition = saveIP; + currentBPPosition = saveBP + bv.getHeight(); + } + } + + /** + * Renders a list of block areas. + * + * @param parent the parent block if the parent is a block, otherwise + * a null value. + * @param blocks The block areas + */ + protected void renderBlocks(Block parent, List blocks) { + // the position of the containing block is used for + // absolutely positioned areas + int contBP = currentBPPosition; + int contIP = currentIPPosition; + containingBPPosition = contBP; + containingIPPosition = contIP; + + for (int count = 0; count < blocks.size(); count++) { + Object obj = blocks.get(count); + if (obj instanceof Block) { + containingBPPosition = contBP; + containingIPPosition = contIP; + renderBlock((Block) obj); + containingBPPosition = contBP; + containingIPPosition = contIP; + } else { + // a line area is rendered from the top left position + // of the line, each inline object is offset from there + LineArea line = (LineArea) obj; + currentBlockIPPosition = + currentIPPosition + line.getStartIndent(); + renderLineArea(line); + currentBPPosition += line.getHeight(); + } + } + } + + /** + * Renders a block area. + * + * @param block The block area + */ + protected void renderBlock(Block block) { + List children = block.getChildAreas(); + if (children == null) { + handleBlockTraits(block); + // simply move position + currentBPPosition += block.getHeight(); + } else if (block instanceof BlockViewport) { + renderBlockViewport((BlockViewport) block, children); + } else { + // save position and offset + int saveIP = currentIPPosition; + int saveBP = currentBPPosition; + + if (block.getPositioning() == Block.ABSOLUTE) { + currentIPPosition = containingIPPosition + block.getXOffset(); + currentBPPosition = containingBPPosition + block.getYOffset(); + + handleBlockTraits(block); + + renderBlocks(block, children); + + // absolute blocks do not effect the layout + currentBPPosition = saveBP; + } else { + // relative blocks are offset + currentIPPosition += block.getXOffset(); + currentBPPosition += block.getYOffset(); + + handleBlockTraits(block); + + renderBlocks(block, children); + + // stacked and relative blocks effect stacking + currentBPPosition = saveBP + block.getHeight(); + } + currentIPPosition = saveIP; + } + } + + /** + * Renders a line area.

+ * + * A line area may have grouped styling for its children such as underline, + * background.

+ * + * @param line The line area + */ + protected void renderLineArea(LineArea line) { + List children = line.getInlineAreas(); + + for (int count = 0; count < children.size(); count++) { + InlineArea inline = (InlineArea) children.get(count); + renderInlineArea(inline); + } + } + + protected void renderInlineArea(InlineArea inlineArea) { + if (inlineArea instanceof TextArea) { + renderText((TextArea) inlineArea); + } else if (inlineArea instanceof InlineParent) { + renderInlineParent((InlineParent) inlineArea); + } else if (inlineArea instanceof Space) { + renderInlineSpace((Space) inlineArea); + } else if (inlineArea instanceof Character) { + renderCharacter((Character) inlineArea); + } else if (inlineArea instanceof Viewport) { + renderViewport((Viewport) inlineArea); + } else if (inlineArea instanceof Leader) { + renderLeader((Leader) inlineArea); + } + } + + + /** @see org.apache.fop.render.Renderer */ + protected void renderCharacter(Character ch) { + currentBlockIPPosition += ch.getWidth(); + } + + /** @see org.apache.fop.render.Renderer */ + protected void renderInlineSpace(Space space) { + // an inline space moves the inline progression position + // for the current block by the width or height of the space + // it may also have styling (only on this object) that needs + // handling + currentBlockIPPosition += space.getWidth(); + } + + /** @see org.apache.fop.render.Renderer */ + protected void renderLeader(Leader area) { + currentBlockIPPosition += area.getWidth(); + } + + /** @see org.apache.fop.render.Renderer */ + protected void renderText(TextArea text) { + currentBlockIPPosition += text.getWidth(); + } + + /** @see org.apache.fop.render.Renderer */ + protected void renderInlineParent(InlineParent ip) { + int saveIP = currentBlockIPPosition; + Iterator iter = ip.getChildAreas().iterator(); + while (iter.hasNext()) { + renderInlineArea((InlineArea) iter.next()); + } + currentBlockIPPosition = saveIP + ip.getWidth(); + } + + /** @see org.apache.fop.render.Renderer */ + protected void renderViewport(Viewport viewport) { + Area content = viewport.getContent(); + int saveBP = currentBPPosition; + currentBPPosition += viewport.getOffset(); + Rectangle2D contpos = viewport.getContentPosition(); + if (content instanceof Image) { + renderImage((Image) content, contpos); + } else if (content instanceof Container) { + renderContainer((Container) content); + } else if (content instanceof ForeignObject) { + renderForeignObject((ForeignObject) content, contpos); + } + currentBlockIPPosition += viewport.getWidth(); + currentBPPosition = saveBP; + } + + /** + * Renders an image area. + * + * @param image The image + * @param pos The target position of the image + * (todo) Make renderImage() protected + */ + public void renderImage(Image image, Rectangle2D pos) { + // Default: do nothing. + // Some renderers (ex. Text) don't support images. + } + + /** @see org.apache.fop.render.Renderer */ + public void renderContainer(Container cont) { + int saveIP = currentIPPosition; + currentIPPosition = currentBlockIPPosition; + int saveBlockIP = currentBlockIPPosition; + int saveBP = currentBPPosition; + + List blocks = cont.getBlocks(); + renderBlocks(null, blocks); + currentIPPosition = saveIP; + currentBlockIPPosition = saveBlockIP; + currentBPPosition = saveBP; + } + + /** + * Renders a foreign object area. + * + * @param fo The foreign object area + * @param pos The target position of the foreign object + * (todo) Make renderForeignObject() protected + */ + public void renderForeignObject(ForeignObject fo, Rectangle2D pos) { + // Default: do nothing. + // Some renderers (ex. Text) don't support foreign objects. + } + + /** + * Set the default xml handler for the given mime type. + * @param mime MIME type + * @param handler XMLHandler to use + */ + public void setDefaultXMLHandler(FOUserAgent foua, String mime, + XMLHandler handler) { + foua.defaults.put(mime, handler); + } + + /** + * Add an xml handler for the given mime type and xml namespace. + * @param mime MIME type + * @param ns Namespace URI + * @param handler XMLHandler to use + */ + public void addXMLHandler(FOUserAgent foua, String mime, String ns, + XMLHandler handler) { + Map mh = (Map) foua.handlers.get(mime); + if (mh == null) { + mh = new java.util.HashMap(); + foua.handlers.put(mime, mh); + } + mh.put(ns, handler); + } + + /** + * Render the xml document with the given xml namespace. + * The Render Context is by the handle to render into the current + * rendering target. + * @param ctx rendering context + * @param doc DOM Document containing the source document + * @param namespace Namespace URI of the document + */ + public void renderXML(FOUserAgent foua, RendererContext ctx, Document doc, + String namespace) { + String mime = ctx.getMimeType(); + Map mh = (Map) foua.handlers.get(mime); + XMLHandler handler = null; + if (mh != null) { + handler = (XMLHandler) mh.get(namespace); + } + if (handler == null) { + handler = (XMLHandler) foua.defaults.get(mime); + } + if (handler != null) { + try { + handler.handleXML(ctx, doc, namespace); + } catch (Throwable t) { + // could not handle document + getLogger().error("Some XML content will be ignored. " + + "Could not render XML", t); + } + } else { + // no handler found for document + getLogger().warn("Some XML content will be ignored. " + + "No handler defined for XML: " + namespace); + } + } +} + diff --git a/src/java/org/apache/fop/render/PrintRenderer.java b/src/java/org/apache/fop/render/PrintRenderer.java new file mode 100644 index 000000000..edeb1b542 --- /dev/null +++ b/src/java/org/apache/fop/render/PrintRenderer.java @@ -0,0 +1,48 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.render; + +// FOP +import org.apache.fop.apps.Document; +import org.apache.fop.fo.FOTreeControl; +import org.apache.fop.fonts.FontSetup; + +// Java +import java.util.List; + +/** Abstract base class of "Print" type renderers. */ +public abstract class PrintRenderer extends AbstractRenderer { + + /** Font configuration */ + protected FOTreeControl fontInfo; + + /** list of fonts */ + protected List fontList = null; + + /** + * Set up the font info + * + * @param fontInfo font info to set up + */ + public void setupFontInfo(FOTreeControl foTreeControl) { + this.fontInfo = foTreeControl; + FontSetup.setup((Document)fontInfo, fontList); + } + +} diff --git a/src/java/org/apache/fop/render/Renderer.java b/src/java/org/apache/fop/render/Renderer.java new file mode 100644 index 000000000..ef5f4526f --- /dev/null +++ b/src/java/org/apache/fop/render/Renderer.java @@ -0,0 +1,190 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.render; + +// Java +import java.io.OutputStream; +import java.io.IOException; +import java.util.Date; +import java.util.Map; + +// FOP +import org.apache.fop.apps.FOPException; +import org.apache.fop.area.PageViewport; +import org.apache.fop.area.Title; +import org.apache.fop.area.TreeExt; +import org.apache.fop.area.inline.Container; +import org.apache.fop.area.inline.InlineParent; +import org.apache.fop.area.inline.Leader; +import org.apache.fop.area.inline.Space; +import org.apache.fop.area.inline.Viewport; +import org.apache.fop.area.inline.TextArea; +import org.apache.fop.fo.FOTreeControl; +import org.apache.fop.apps.FOUserAgent; + +/** + * Interface implemented by all renderers. This interface is used to control + * the rendering of pages and to let block and inline level areas call the + * appropriate method to render themselves.

+ * + * A Renderer implementation takes areas/spaces and produces output in some + * format.

+ * + * Typically, most renderers are subclassed from FOP's abstract implementations + * ({@link AbstractRenderer}, {@link PrintRenderer}) which already handle a lot + * of things letting you concentrate on the details of the output format. + */ +public interface Renderer { + + /** + * Role constant for Avalon. + */ + String ROLE = Renderer.class.getName(); + + + /** + * Initiates the rendering phase. + * This must only be called once for a rendering. If + * stopRenderer is called then this may be called again + * for a new document rendering. + * + * @param outputStream The OutputStream to use for output + * @exception IOException If an I/O error occurs + */ + void startRenderer(OutputStream outputStream) + throws IOException; + + /** + * Signals the end of the rendering phase. + * The renderer should reset to an initial state and dispose of + * any resources for the completed rendering. + * + * @exception IOException If an I/O error occurs + */ + void stopRenderer() + throws IOException; + + /** + * Set the User Agent. + * + * @param agent The User Agent + */ + void setUserAgent(FOUserAgent agent); + + /** + * Set up the given FontInfo. + * + * @param fontInfo The fonts + */ + void setupFontInfo(FOTreeControl foTreeControl); + + /** + * Set up renderer options. + * + * @param options The Configuration for the renderer + */ + void setOptions(Map options); + + /** + * Set the producer of the rendering. If this method isn't called the + * renderer uses a default. Note: Not all renderers support this feature. + * + * @param producer The name of the producer (normally "FOP") to be + * embedded in the generated file. + */ + void setProducer(String producer); + + /** + * Set the creator of the document to be rendered. + * If this method isn't called the renderer uses a default. + * Note: Not all renderers support this feature. + * + * @param creator The name of the document creator + */ + void setCreator(String creator); + + /** + * Set the creator date/timeof the document to be rendered. + * If this method isn't called the renderer uses the current date/time + * as default. + * Note: Not all renderers support this feature. + * + * @param date The name of the document creator + */ + void setCreationDate(Date date); + + /** + * Reports if out of order rendering is supported.

+ * + * Normally, all pages of a document are rendered in their natural order + * (page 1, page 2, page 3 etc.). Some output formats (such as PDF) allow + * pages to be output in random order. This is helpful to reduce resource + * strain on the system because a page that cannot be fully resolved + * doesn't block subsequent pages that are already fully resolved.

+ * + * @return True if this renderer supports out of order rendering. + */ + boolean supportsOutOfOrder(); + + /** + * Tells the renderer to render an extension element. + * + * @param ext The extension element to be rendered + */ + void renderExtension(TreeExt ext); + + /** + * This is called if the renderer supports out of order rendering. The + * renderer should prepare the page so that a page further on in the set of + * pages can be rendered. The body of the page should not be rendered. The + * page will be rendered at a later time by the call to {@link + * #renderPage(PageViewport)}. + * + * @param page The page viewport to use + */ + void preparePage(PageViewport page); + + /** + * Tells the renderer that a new page sequence starts. + * + * @param seqTitle The title of the page sequence + */ + void startPageSequence(Title seqTitle); + + /** + * Tells the renderer to render a particular page. A renderer typically + * reponds by packing up the current page and writing it immediately to the + * output device. + * + * @param page The page to be rendered + * @exception IOException if an I/O error occurs + * @exception FOPException if a FOP interal error occurs. + */ + void renderPage(PageViewport page) + throws IOException, FOPException; + + /** + * Tells the renderer to render an inline container. + * + * @param cont The inline container area + */ + void renderContainer(Container cont); + +} + diff --git a/src/java/org/apache/fop/render/RendererContext.java b/src/java/org/apache/fop/render/RendererContext.java new file mode 100644 index 000000000..998a53701 --- /dev/null +++ b/src/java/org/apache/fop/render/RendererContext.java @@ -0,0 +1,95 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.render; + +//Java +import java.util.Map; + +//FOP +import org.apache.fop.apps.FOUserAgent; + +/** + * The Render Context for external handlers. This provides a rendering context + * so that external handlers can get information to be able to render to the + * render target. + */ +public class RendererContext { + + private String mime; + private FOUserAgent userAgent; + private Map props = new java.util.HashMap(); + + /** + * Contructor for this class. It takes a MIME type as parameter. + * + * @param m The MIME type of the output that's generated. + */ + public RendererContext(String m) { + mime = m; + } + + /** + * Returns the MIME type associated with this RendererContext. + * + * @return The MIME type (ex. application/pdf) + */ + public String getMimeType() { + return mime; + } + + /** + * Sets the user agent. + * + * @param ua The user agent + */ + public void setUserAgent(FOUserAgent ua) { + userAgent = ua; + } + + /** + * Returns the user agent. + * + * @return The user agent + */ + public FOUserAgent getUserAgent() { + return userAgent; + } + + /** + * Sets a property on the RendererContext. + * + * @param name The key of the property + * @param val The value of the property + */ + public void setProperty(String name, Object val) { + props.put(name, val); + } + + /** + * Returns a property from the RendererContext. + * + * @param prop The key of the property to return. + * @return The requested value, null if it doesn't exist. + */ + public Object getProperty(String prop) { + return props.get(prop); + } + +} + diff --git a/src/java/org/apache/fop/render/XMLHandler.java b/src/java/org/apache/fop/render/XMLHandler.java new file mode 100644 index 000000000..4942a5ae3 --- /dev/null +++ b/src/java/org/apache/fop/render/XMLHandler.java @@ -0,0 +1,45 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.render; + +import org.w3c.dom.Document; + +/** + * This interface is implemented by classes that can handle a certain type + * of foreign objects. + */ +public interface XMLHandler { + + /** + * Handle an external xml document inside a Foreign Object Area.

+ * + * This may throw an exception if for some reason it cannot be handled. The + * caller is expected to deal with this exception.

+ * + * @param context The RendererContext (contains the user agent) + * @param doc A DOM containing the foreign object to be + * processed + * @param ns The Namespace of the foreign object + * @exception Exception If an error occurs during processing. + */ + void handleXML(RendererContext context, Document doc, String ns) + throws Exception; + +} + diff --git a/src/java/org/apache/fop/render/awt/AWTFontMetrics.java b/src/java/org/apache/fop/render/awt/AWTFontMetrics.java new file mode 100644 index 000000000..4d0ec8d08 --- /dev/null +++ b/src/java/org/apache/fop/render/awt/AWTFontMetrics.java @@ -0,0 +1,283 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.render.awt; + +// Java +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.geom.Rectangle2D; +import java.awt.FontMetrics; +import java.awt.font.TextLayout; + +/** + * This is a FontMetrics to be used for AWT rendering. + * It instanciates a font, depening on family and style + * values. The java.awt.FontMetrics for this font is then + * created to be used for the actual measurement. + * Since layout is word by word and since it is expected that + * two subsequent words often share the same style, the + * Font and FontMetrics is buffered and only changed if needed. + *

+ * Since FontState and FontInfo multiply all factors by + * size, we assume a "standard" font of FONT_SIZE. + */ +public class AWTFontMetrics { + + /** + * Font size standard used for metric measurements + */ + public static final int FONT_SIZE = 1; + + /** + * This factor multiplies the calculated values to scale + * to FOP internal measurements + */ + public static final int FONT_FACTOR = (1000 * 1000) / FONT_SIZE; + + /** + * The width of all 256 character, if requested + */ + private int width[] = null; + + /** + * The typical height of a small cap latter + */ + private int xHeight = 0; + + /** + * Buffered font. + * f1 is bufferd for metric measurements during layout. + * fSized is buffered for display purposes + */ + private Font f1 = null; // , fSized = null; + + /** + * The family type of the font last used + */ + private String family = ""; + + /** + * The style of the font last used + */ + private int style = 0; + + /** + * The size of the font last used + */ + private float size = 0; + + /** + * The FontMetrics object used to calculate character width etc. + */ + private FontMetrics fmt = null; + + /** + * Temp graphics object needed to get the font metrics + */ + private Graphics2D graphics; + + /** + * Constructs a new Font-metrics. + * @param graphics a temp graphics object - this is needed so + * that we can get an instance of java.awt.FontMetrics + */ + public AWTFontMetrics(Graphics2D graphics) { + this.graphics = graphics; + } + + /** + * Determines the font ascent of the Font described by this + * FontMetrics object + * @param family font family (java name) to use + * @param style font style (java def.) to use + * @param size font size + * @return ascent in milliponts + */ + public int getAscender(String family, int style, int size) { + setFont(family, style, size); + // return (int)(FONT_FACTOR * fmt.getAscent()); + + // workaround for sun bug on FontMetrics.getAscent() + // http://developer.java.sun.com/developer/bugParade/bugs/4399887.html + int realAscent = fmt.getAscent() + - (fmt.getDescent() + fmt.getLeading()); + return FONT_FACTOR * realAscent; + } + + + /** + * The size of a capital letter measured from the font's baseline + * @param family font family + * @param style font style + * @param size font size + * @return capital height in millipoints + */ + public int getCapHeight(String family, int style, int size) { + // currently just gets Ascent value but maybe should use + // getMaxAcent() at some stage + return getAscender(family, style, size); + } + + /** + * Determines the font descent of the Font described by this + * FontMetrics object + * @param family font family (jave name) to use + * @param style font style (jave def.) to use + * @param size font size + * @return descent in milliponts + */ + public int getDescender(String family, int style, int size) { + setFont(family, style, size); + return (-1 * FONT_FACTOR * fmt.getDescent()); + } + + /** + * Determines the typical font height of a small cap letter + * FontMetrics object + * @param family font family (jave name) to use + * @param style font style (jave def.) to use + * @param size font size + * @return font height in milliponts + */ + public int getXHeight(String family, int style, int size) { + setFont(family, style, size); + return (int)(FONT_FACTOR * xHeight); + } + + /** + * Returns width (in 1/1000ths of point size) of character at + * code point i + * @param i the character for which to get the width + * @param family font family (jave name) to use + * @param style font style (jave def.) to use + * @param size font size + * @return character width in millipoints + */ + public int width(int i, String family, int style, int size) { + int w; + setFont(family, style, size); + // the output seems to look a little better if the + // space is rendered larger than given by + // the FontMetrics object + if (i <= 32) { + w = (int)(1.4 * fmt.charWidth(i) * FONT_FACTOR); + } else { + w = (int)(fmt.charWidth(i) * FONT_FACTOR); + } + return w; + } + + /** + * Return widths (in 1/1000ths of point size) of all + * characters + * @param family font family (jave name) to use + * @param style font style (jave def.) to use + * @param size font size + * @return array of character widths in millipoints + */ + public int[] getWidths(String family, int style, int size) { + int i; + + if (width == null) { + width = new int[256]; + } + setFont(family, style, size); + for (i = 0; i < 256; i++) { + width[i] = FONT_FACTOR * fmt.charWidth(i); + } + return width; + } + + /** + * Checks whether the font for which values are + * requested is the one used immediately before or + * whether it is a new one + * @param family font family (jave name) to use + * @param style font style (jave def.) to use + * @param size font size + * @return true if the font was changed, false otherwise + */ + private boolean setFont(String family, int style, int size) { + boolean changed = false; + Rectangle2D rect; + TextLayout layout; + int s = (int)(size / 1000f); + + if (f1 == null) { + f1 = new Font(family, style, s); + fmt = graphics.getFontMetrics(f1); + changed = true; + } else { + if ((this.style != style) || !this.family.equals(family) + || this.size != s) { + if (family.equals(this.family)) { + f1 = f1.deriveFont(style, (float)s); + } else { + f1 = new Font(family, style, s); + } + fmt = graphics.getFontMetrics(f1); + changed = true; + } + // else the font is unchanged from last time + } + if (changed) { + layout = new TextLayout("m", f1, graphics.getFontRenderContext()); + rect = layout.getBounds(); + xHeight = (int)rect.getHeight(); + } + // save the family and style for later comparison + this.family = family; + this.style = style; + this.size = s; + return changed; + } + + + /** + * Returns a java.awt.Font instance for the desired + * family, style and size type. + * This is here, so that the font-mapping + * of FOP-defined fonts to java-fonts can be done + * in one place and does not need to occur in + * AWTFontRenderer. + * @param family font family (jave name) to use + * @param style font style (jave def.) to use + * @param size font size + * @return font with the desired characeristics. + */ + public java.awt.Font getFont(String family, int style, int size) { + setFont(family, style, size); + return f1; + /* + * if( setFont(family,style, size) ) fSized = null; + * if( fSized == null || this.size != size ) { + * fSized = f1.deriveFont( size / 1000f ); + * } + * this.size = size; + * return fSized; + */ + } + +} + + + + + + diff --git a/src/java/org/apache/fop/render/awt/AWTPrintRenderer.java b/src/java/org/apache/fop/render/awt/AWTPrintRenderer.java new file mode 100644 index 000000000..5b057959a --- /dev/null +++ b/src/java/org/apache/fop/render/awt/AWTPrintRenderer.java @@ -0,0 +1,121 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.render.awt; + +import java.awt.print.PrinterException; +import java.awt.print.PrinterJob; +import java.io.IOException; +import java.util.Vector; + +public class AWTPrintRenderer extends AWTRenderer { + + private static final int EVEN_AND_ALL = 0; + private static final int EVEN = 1; + private static final int ODD = 2; + + private int startNumber; + private int endNumber; + private int mode = EVEN_AND_ALL; + private int copies = 1; + private PrinterJob printerJob; + + public AWTPrintRenderer() { + initialize(); + } + + private void initialize() throws IllegalArgumentException { + // read from command-line options + copies = getIntProperty("copies", 1); + startNumber = getIntProperty("start", 1) - 1; + endNumber = getIntProperty("end", -1); + String str = System.getProperty("even"); + if (str != null) { + mode = Boolean.valueOf(str).booleanValue() ? EVEN : ODD; + } + + printerJob = PrinterJob.getPrinterJob(); + printerJob.setJobName("FOP Document"); + printerJob.setCopies(copies); + if (System.getProperty("dialog") != null) { + if (!printerJob.printDialog()) { + throw new IllegalArgumentException("Printing cancelled by operator"); + } + } + printerJob.setPageable(this); + } + + public void stopRenderer() throws IOException { + super.stopRenderer(); + + if (endNumber == -1) { + endNumber = getNumberOfPages(); + } + + Vector numbers = getInvalidPageNumbers(); + for (int i = numbers.size() - 1; i > -1; i--) { + // removePage(Integer.parseInt((String)numbers.elementAt(i))); + } + + try { + printerJob.print(); + } catch (PrinterException e) { + e.printStackTrace(); + throw new IOException("Unable to print: " + + e.getClass().getName() + + ": " + e.getMessage()); + } + } + + public static int getIntProperty(String name, int def) { + String propValue = System.getProperty(name); + if (propValue != null) { + try { + return Integer.parseInt(propValue); + } catch (Exception e) { + return def; + } + } else { + return def; + } + } + + private Vector getInvalidPageNumbers() { + Vector vec = new Vector(); + int max = getNumberOfPages(); + boolean isValid; + for (int i = 0; i < max; i++) { + isValid = true; + if (i < startNumber || i > endNumber) { + isValid = false; + } else if (mode != EVEN_AND_ALL) { + if (mode == EVEN && ((i + 1) % 2 != 0)) { + isValid = false; + } else if (mode == ODD && ((i + 1) % 2 != 1)) { + isValid = false; + } + } + + if (!isValid) { + vec.add(i + ""); + } + } + return vec; + } +} // class AWTPrintRenderer + diff --git a/src/java/org/apache/fop/render/awt/AWTRenderer.java b/src/java/org/apache/fop/render/awt/AWTRenderer.java new file mode 100644 index 000000000..684c2a549 --- /dev/null +++ b/src/java/org/apache/fop/render/awt/AWTRenderer.java @@ -0,0 +1,465 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.render.awt; + +/* + * originally contributed by + * Juergen Verwohlt: Juergen.Verwohlt@jCatalog.com, + * Rainer Steinkuhle: Rainer.Steinkuhle@jCatalog.com, + * Stanislav Gorkhover: Stanislav.Gorkhover@jCatalog.com + */ + +// Java +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.Toolkit; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.awt.print.PageFormat; +import java.awt.print.Pageable; +import java.awt.print.Printable; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Map; +import java.util.Vector; + +import org.apache.fop.apps.Document; +import org.apache.fop.apps.FOPException; +import org.apache.fop.apps.InputHandler; +import org.apache.fop.area.Area; +import org.apache.fop.area.Page; +import org.apache.fop.area.PageViewport; +import org.apache.fop.area.RegionViewport; +import org.apache.fop.area.Trait; +import org.apache.fop.area.inline.TextArea; +import org.apache.fop.datatypes.ColorType; +import org.apache.fop.fo.FOTreeControl; +import org.apache.fop.image.FopImage; +import org.apache.fop.image.ImageFactory; +import org.apache.fop.render.AbstractRenderer; +import org.apache.fop.traits.BorderProps; +import org.apache.fop.render.awt.FontMetricsMapper; +import org.apache.fop.render.awt.viewer.PreviewDialog; +import org.apache.fop.render.awt.viewer.Translator; + +/** + * This is FOP's AWT renderer. + */ +public class AWTRenderer extends AbstractRenderer implements Printable, Pageable { + + protected double scaleFactor = 100.0; + protected int pageNumber = 0; + private int pageWidth = 0; + private int pageHeight = 0; + private Vector pageViewportList = new java.util.Vector(); + private Vector pageList = new java.util.Vector(); + private Vector bufferedImageList = new java.util.Vector(); + private BufferedImage currentPageImage = null; + + /** Font configuration */ + protected Document fontInfo; + + /** + The InputHandler associated with this Renderer. + Sent to the PreviewDialog for document reloading. + */ + private InputHandler inputHandler; + + /** + * The resource bundle used for AWT messages. + */ + protected Translator translator = null; + + private Map fontNames = new java.util.Hashtable(); + private Map fontStyles = new java.util.Hashtable(); + private Color saveColor = null; + + /** + * The preview dialog frame used for display of the documents. + * Also used as the AWT Component for FontSetup in generating + * valid font measures. + */ + protected PreviewDialog frame; + + public AWTRenderer(InputHandler handler) { + inputHandler = handler; + translator = new Translator(); + createPreviewDialog(inputHandler); + } + + public AWTRenderer() { + translator = new Translator(); + createPreviewDialog(null); + } + + /** + * @see org.apache.fop.render.Renderer + */ + public boolean supportsOutOfOrder() { + return false; + } + + public Translator getTranslator() { + return translator; + } + + public void setupFontInfo(FOTreeControl foTreeControl) { + // create a temp Image to test font metrics on + fontInfo = (Document) foTreeControl; + BufferedImage fontImage = + new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); + FontSetup.setup(fontInfo, fontImage.createGraphics()); + } + + public int getPageNumber() { + return pageNumber; + } + + public void setPageNumber(int aValue) { + pageNumber = aValue; + } + + public void setScaleFactor(double newScaleFactor) { + scaleFactor = newScaleFactor; + } + + public double getScaleFactor() { + return scaleFactor; + } + + public void startRenderer(OutputStream out) + throws IOException { + // empty pageViewportList, in case of a reload from PreviewDialog + pageViewportList.removeAllElements(); + pageList.removeAllElements(); + bufferedImageList.removeAllElements(); + System.out.println("\nRegion Types: 0-Before/Top, 1-Start/Left, 2-Body, 3-End/Right, 4-After/Bottom"); + } + + public void stopRenderer() + throws IOException { + frame.setStatus(translator.getString("Status.Show")); + frame.showPage(); + } + + // Printable Interface + public PageFormat getPageFormat(int pos) { + return null; + } + + public Printable getPrintable(int pos) { + return null; + } + + public int getNumberOfPages() { + return pageViewportList.size(); + } + + public int print(Graphics g, PageFormat format, int pos) { + return 0; + } + + private PreviewDialog createPreviewDialog(InputHandler handler) { + frame = new PreviewDialog(this, handler); + frame.addWindowListener(new WindowAdapter() { + public void windowClosed(WindowEvent we) { + System.exit(0); + } + }); + + //Centers the window + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + Dimension frameSize = frame.getSize(); + if (frameSize.height > screenSize.height) { + frameSize.height = screenSize.height; + } + if (frameSize.width > screenSize.width) { + frameSize.width = screenSize.width; + } + frame.setLocation((screenSize.width - frameSize.width) / 2, + (screenSize.height - frameSize.height) / 2); + frame.setVisible(true); + frame.setStatus(translator.getString("Status.Build.FO.tree")); + return frame; + } + + /** This method override only stores the PageViewport in a vector. + * No actual rendering performed -- this is done by getPageImage(pageNum) instead. + * @param pageViewport the PageViewport object supplied by the Area Tree + * @see org.apache.fop.render.Renderer + */ + public void renderPage(PageViewport pageViewport) throws IOException, FOPException { + pageViewportList.add(pageViewport); + pageList.add(pageViewport.getPage().clone()); + bufferedImageList.add(getPageImage(pageViewport)); + } + + public BufferedImage getBufferedPageImage(int pageNum) throws FOPException { + return (BufferedImage) bufferedImageList.get(pageNum); + } + + /** Generates a desired page from the renderer's page viewport vector. + * @param pageNum the 0-based page number to generate + * @return the java.awt.image.BufferedImage corresponding to the page + * @throws FOPException in case of an out-of-range page number requested + */ + public BufferedImage getPageImage(PageViewport pageViewport) throws FOPException { + Page page = pageViewport.getPage(); + + Rectangle2D bounds = pageViewport.getViewArea(); + pageWidth = (int) Math.round(bounds.getWidth() / 1000f ); + pageHeight = (int) Math.round(bounds.getHeight() / 1000f ); +/* + System.out.println("(Page) X, Y, Width, Height: " + bounds.getX() + + " " + bounds.getY() + + " " + bounds.getWidth() + + " " + bounds.getHeight()); +*/ + currentPageImage = + new BufferedImage((int)((pageWidth * (int)scaleFactor) / 100), + (int)((pageHeight * (int)scaleFactor) / 100), + BufferedImage.TYPE_INT_RGB); + + Graphics2D graphics = currentPageImage.createGraphics(); + graphics.setRenderingHint (RenderingHints.KEY_FRACTIONALMETRICS, + RenderingHints.VALUE_FRACTIONALMETRICS_ON); + + // transform page based on scale factor supplied + AffineTransform at = graphics.getTransform(); + at.scale(scaleFactor / 100.0, scaleFactor / 100.0); + graphics.setTransform(at); + + // draw page frame + graphics.setColor(Color.white); + graphics.fillRect(0, 0, pageWidth, pageHeight); + graphics.setColor(Color.black); + graphics.drawRect(-1, -1, pageWidth + 2, pageHeight + 2); + graphics.drawLine(pageWidth + 2, 0, pageWidth + 2, pageHeight + 2); + graphics.drawLine(pageWidth + 3, 1, pageWidth + 3, pageHeight + 3); + graphics.drawLine(0, pageHeight + 2, pageWidth + 2, pageHeight + 2); + graphics.drawLine(1, pageHeight + 3, pageWidth + 3, pageHeight + 3); + + renderPageAreas(page); + return currentPageImage; + } + + /** Generates a desired page from the renderer's page viewport vector. + * @param pageNum the 0-based page number to generate + * @return the java.awt.image.BufferedImage corresponding to the page + * @throws FOPException in case of an out-of-range page number requested + */ + public BufferedImage getPageImage(int pageNum) throws FOPException { + if (pageNum < 0 || pageNum >= pageViewportList.size()) { + throw new FOPException("out-of-range page number (" + pageNum + + ") requested; only " + pageViewportList.size() + + " page(s) available."); + } + PageViewport pageViewport = (PageViewport) pageViewportList.get(pageNum); + Page page = (Page) pageList.get(pageNum); + + Rectangle2D bounds = pageViewport.getViewArea(); + pageWidth = (int) Math.round(bounds.getWidth() / 1000f ); + pageHeight = (int) Math.round(bounds.getHeight() / 1000f ); +/* + System.out.println("(Page) X, Y, Width, Height: " + bounds.getX() + + " " + bounds.getY() + + " " + bounds.getWidth() + + " " + bounds.getHeight()); +*/ + currentPageImage = + new BufferedImage((int)((pageWidth * (int)scaleFactor) / 100), + (int)((pageHeight * (int)scaleFactor) / 100), + BufferedImage.TYPE_INT_RGB); + + Graphics2D graphics = currentPageImage.createGraphics(); + graphics.setRenderingHint (RenderingHints.KEY_FRACTIONALMETRICS, + RenderingHints.VALUE_FRACTIONALMETRICS_ON); + + // transform page based on scale factor supplied + AffineTransform at = graphics.getTransform(); + at.scale(scaleFactor / 100.0, scaleFactor / 100.0); + graphics.setTransform(at); + + // draw page frame + graphics.setColor(Color.white); + graphics.fillRect(0, 0, pageWidth, pageHeight); + graphics.setColor(Color.black); + graphics.drawRect(-1, -1, pageWidth + 2, pageHeight + 2); + graphics.drawLine(pageWidth + 2, 0, pageWidth + 2, pageHeight + 2); + graphics.drawLine(pageWidth + 3, 1, pageWidth + 3, pageHeight + 3); + graphics.drawLine(0, pageHeight + 2, pageWidth + 2, pageHeight + 2); + graphics.drawLine(1, pageHeight + 3, pageWidth + 3, pageHeight + 3); + + renderPageAreas(page); + return currentPageImage; + } + + /** + * Handle the traits for a region + * This is used to draw the traits for the given page region. + * (See Sect. 6.4.1.2 of XSL-FO spec.) + * @param region the RegionViewport whose region is to be drawn + */ + protected void handleRegionTraits(RegionViewport region) { + Rectangle2D viewArea = region.getViewArea(); + + int startX = (int) Math.round((viewArea.getX() / 1000f) + * (scaleFactor / 100f)); + int startY = (int) Math.round((viewArea.getY() / 1000f) + * (scaleFactor / 100f)); + // for rounding to work correctly, need to take into account + // fractional portion of X and Y. + int width = (int) Math.round(((viewArea.getX() + viewArea.getWidth()) / 1000f) + * (scaleFactor / 100f)) - startX; + int height = (int) Math.round(((viewArea.getY() + viewArea.getHeight()) / 1000f) + * (scaleFactor / 100f)) - startY; + + if (region.getRegion() != null) { + System.out.print("\nRegion type = " + region.getRegion().getRegionClass()); + } + + System.out.println(" X, Width, Y, Height: " + startX + + " " + width + + " " + startY + + " " + height + ); + + drawBackAndBorders(region, startX, startY, width, height); + } + + /** + * Draw the background and borders. + * This draws the background and border traits for an area given + * the position. + * + * @param block the area to get the traits from + * @param startx the start x position + * @param starty the start y position + * @param width the width of the area + * @param height the height of the area + */ + protected void drawBackAndBorders(Area block, + int startx, int starty, + int width, int height) { + + // draw background then border + Graphics2D graphics = currentPageImage.createGraphics(); + Trait.Background back; + back = (Trait.Background) block.getTrait(Trait.BACKGROUND); + if (back != null) { + + if (back.getColor() != null) { + graphics.setColor(back.getColor().getAWTColor()); + graphics.fillRect(startx, starty, width, height); + } + if (back.getURL() != null) { // TODO: implement + ImageFactory fact = ImageFactory.getInstance(); + FopImage fopimage = fact.getImage(back.getURL(), userAgent); + if (fopimage != null && fopimage.load(FopImage.DIMENSIONS, userAgent.getLogger())) { + if (back.getRepeat() == BackgroundRepeat.REPEAT) { + // create a pattern for the image + } else { + // place once + Rectangle2D pos; + pos = new Rectangle2D.Float((startx + back.getHoriz()) * 1000, + (starty + back.getVertical()) * 1000, + fopimage.getWidth() * 1000, + fopimage.getHeight() * 1000); +// putImage(back.getURL(), pos); + } + } + } + } + + BorderProps bps = (BorderProps) block.getTrait(Trait.BORDER_BEFORE); + if (bps != null) { + int borderWidth = (int) Math.round((bps.width / 1000f) * (scaleFactor / 100f)); + graphics.setColor(bps.color.getAWTColor()); + graphics.fillRect(startx, starty, width, borderWidth); + } + bps = (BorderProps) block.getTrait(Trait.BORDER_AFTER); + if (bps != null) { + int borderWidth = (int) Math.round((bps.width / 1000f) * (scaleFactor / 100f)); + int sy = starty + height; + graphics.setColor(bps.color.getAWTColor()); + graphics.fillRect(startx, starty + height - borderWidth, + width, borderWidth); + } + bps = (BorderProps) block.getTrait(Trait.BORDER_START); + if (bps != null) { + int borderWidth = (int) Math.round((bps.width / 1000f) * (scaleFactor / 100f)); + graphics.setColor(bps.color.getAWTColor()); + graphics.fillRect(startx, starty, borderWidth, height); + } + bps = (BorderProps) block.getTrait(Trait.BORDER_END); + if (bps != null) { + int borderWidth = (int) Math.round((bps.width / 1000f) * (scaleFactor / 100f)); + int sx = startx + width; + graphics.setColor(bps.color.getAWTColor()); + graphics.fillRect(startx + width - borderWidth, starty, + borderWidth, height); + } + + } + + /** + * @see org.apache.fop.render.Renderer#renderText(TextArea) + */ + public void renderText(TextArea text) { + System.out.println("In render text: " + text.getTextArea()); + + Graphics2D graphics = currentPageImage.createGraphics(); + String fontName = (String) text.getTrait(Trait.FONT_NAME); + int size = ((Integer) text.getTrait(Trait.FONT_SIZE)).intValue(); +// Typeface f = (Typeface) fontInfo.getFonts().get(fontName); + ColorType ct = (ColorType) text.getTrait(Trait.COLOR); + + FontMetricsMapper mapper = (FontMetricsMapper) + fontInfo.getMetricsFor(fontName); + if (mapper == null) { + mapper = new FontMetricsMapper("MonoSpaced", java.awt.Font.PLAIN, + graphics); + } + +// graphics.setColor(ct.getAWTColor()); +// graphics.setFont(mapper.getFont(size)); + graphics.setColor(java.awt.Color.black); + graphics.setFont(new java.awt.Font("monospaced", java.awt.Font.PLAIN, + 10)); + + int rx = currentBlockIPPosition; + int bl = currentBPPosition + text.getOffset(); + + int newx = (int) (rx + 500) / 1000; + int newy = (int) (pageHeight - (bl + 500) / 1000); + + String s = text.getTextArea(); +// graphics.drawString(s, newx, newy); + graphics.drawString(s, 220, 200); + + // TODO: render text decorations + currentBlockIPPosition += text.getWidth(); + } +} diff --git a/src/java/org/apache/fop/render/awt/FontMetricsMapper.java b/src/java/org/apache/fop/render/awt/FontMetricsMapper.java new file mode 100644 index 000000000..6e0df2c66 --- /dev/null +++ b/src/java/org/apache/fop/render/awt/FontMetricsMapper.java @@ -0,0 +1,166 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.render.awt; + +// Java +import java.awt.Graphics2D; +import java.util.Map; + +// FOP +import org.apache.fop.fonts.FontMetrics; +import org.apache.fop.fonts.FontType; + + +/** + * This class implements org.apache.fop.layout.FontMetrics and + * is added to the hash table in FontInfo. It deferes the + * actual calculation of the metrics to + * AWTFontMetrics. It only keeps the java name and + * style as member varibles + */ + +public class FontMetricsMapper implements FontMetrics { + + /** + * The first and last non space-character + */ + private static final int FIRST_CHAR = 32; + private static final int LAST_CHAR = 255; + + /** + * This is a AWTFontMetrics that does the real calculation. + * It is only one class that dynamically determines the font-size. + */ + private static AWTFontMetrics metric = null; + + /** + * The java name of the font. + * # Make the family name immutable. + */ + private final String family; + + /** + * The java style of the font. + * # Make the style immutable. + */ + private final int style; + + /** + * Constructs a new Font-metrics. + * @param family the family name of the font (java value) + * @param style the java type style value of the font + * @param graphics a Graphics2D object - this is needed so + * that we can get an instance of java.awt.FontMetrics + */ + public FontMetricsMapper(String family, int style, Graphics2D graphics) { + this.family = family; + this.style = style; + if (metric == null) { + metric = new AWTFontMetrics(graphics); + } + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getFontName() + */ + public String getFontName() { + return family; + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getFontType() + */ + public FontType getFontType() { + return FontType.OTHER; + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getAscender(int) + */ + public int getAscender(int size) { + return metric.getAscender(family, style, size); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getCapHeight(int) + */ + public int getCapHeight(int size) { + return metric.getCapHeight(family, style, size); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getDescender(int) + */ + public int getDescender(int size) { + return metric.getDescender(family, style, size); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getXHeight(int) + */ + public int getXHeight(int size) { + return metric.getXHeight(family, style, size); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getWidth(int, int) + */ + public int getWidth(int i, int size) { + return metric.width(i, family, style, size); + } + + + /** + * @see org.apache.fop.fonts.FontMetrics#getWidths() + */ + public int[] getWidths() { + return metric.getWidths(family, style, AWTFontMetrics.FONT_SIZE); + } + + /** + * Gets a Font instance of the Font that this + * FontMetrics describes in the desired size. + * @param size font size + * @return font with the desired characeristics. + */ + public java.awt.Font getFont(int size) { + return metric.getFont(family, style, size); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getKerningInfo() + */ + public Map getKerningInfo() { + return java.util.Collections.EMPTY_MAP; + } + + /** + * @see org.apache.fop.fonts.FontMetrics#hasKerningInfo() + */ + public boolean hasKerningInfo() { + return false; + } + + +} + + + + + diff --git a/src/java/org/apache/fop/render/awt/FontSetup.java b/src/java/org/apache/fop/render/awt/FontSetup.java new file mode 100644 index 000000000..cdac2ddb8 --- /dev/null +++ b/src/java/org/apache/fop/render/awt/FontSetup.java @@ -0,0 +1,182 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.render.awt; + +// FOP +import org.apache.fop.apps.Document; +import org.apache.fop.fonts.Font; + +// Java +import java.awt.Graphics2D; + +/** + * Sets up the AWT fonts. It is similar to + * org.apache.fop.render.pdf.FontSetup. + * Assigns the font (with metrics) to internal names like "F1" and + * assigns family-style-weight triplets to the fonts. + */ +public class FontSetup { + + + /** + * Sets up the font info object. + * + * Adds metrics for basic fonts and useful family-style-weight + * triplets for lookup. + * + * @param fontInfo the font info object to set up + * @param graphics needed for acces to font metrics + */ + public static void setup(Document fontInfo, Graphics2D graphics) { + FontMetricsMapper metric; + int normal, bold, bolditalic, italic; + + /* + * available java fonts are: + * Serif - bold, normal, italic, bold-italic + * SansSerif - bold, normal, italic, bold-italic + * MonoSpaced - bold, normal, italic, bold-italic + */ + normal = java.awt.Font.PLAIN; + bold = java.awt.Font.BOLD; + italic = java.awt.Font.ITALIC; + bolditalic = java.awt.Font.BOLD + java.awt.Font.ITALIC; + + metric = new FontMetricsMapper("SansSerif", normal, graphics); + // --> goes to F1 + fontInfo.addMetrics("F1", metric); + metric = new FontMetricsMapper("SansSerif", italic, graphics); + // --> goes to F2 + fontInfo.addMetrics("F2", metric); + metric = new FontMetricsMapper("SansSerif", bold, graphics); + // --> goes to F3 + fontInfo.addMetrics("F3", metric); + metric = new FontMetricsMapper("SansSerif", bolditalic, graphics); + // --> goes to F4 + fontInfo.addMetrics("F4", metric); + + + metric = new FontMetricsMapper("Serif", normal, graphics); + // --> goes to F5 + fontInfo.addMetrics("F5", metric); + metric = new FontMetricsMapper("Serif", italic, graphics); + // --> goes to F6 + fontInfo.addMetrics("F6", metric); + metric = new FontMetricsMapper("Serif", bold, graphics); + // --> goes to F7 + fontInfo.addMetrics("F7", metric); + metric = new FontMetricsMapper("Serif", bolditalic, graphics); + // --> goes to F8 + fontInfo.addMetrics("F8", metric); + + metric = new FontMetricsMapper("MonoSpaced", normal, graphics); + // --> goes to F9 + fontInfo.addMetrics("F9", metric); + metric = new FontMetricsMapper("MonoSpaced", italic, graphics); + // --> goes to F10 + fontInfo.addMetrics("F10", metric); + metric = new FontMetricsMapper("MonoSpaced", bold, graphics); + // --> goes to F11 + fontInfo.addMetrics("F11", metric); + metric = new FontMetricsMapper("MonoSpaced", bolditalic, graphics); + // --> goes to F12 + fontInfo.addMetrics("F12", metric); + + metric = new FontMetricsMapper("Symbol", bolditalic, graphics); + // --> goes to F13 and F14 + fontInfo.addMetrics("F13", metric); + fontInfo.addMetrics("F14", metric); + + // Custom type 1 fonts step 1/2 + // fontInfo.addMetrics("F15", new OMEP()); + // fontInfo.addMetrics("F16", new GaramondLightCondensed()); + // fontInfo.addMetrics("F17", new BauerBodoniBoldItalic()); + + /* any is treated as serif */ + fontInfo.addFontProperties("F5", "any", "normal", Font.NORMAL); + fontInfo.addFontProperties("F6", "any", "italic", Font.NORMAL); + fontInfo.addFontProperties("F6", "any", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F7", "any", "normal", Font.BOLD); + fontInfo.addFontProperties("F8", "any", "italic", Font.BOLD); + fontInfo.addFontProperties("F8", "any", "oblique", Font.BOLD); + + fontInfo.addFontProperties("F1", "sans-serif", "normal", Font.NORMAL); + fontInfo.addFontProperties("F2", "sans-serif", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F2", "sans-serif", "italic", Font.NORMAL); + fontInfo.addFontProperties("F3", "sans-serif", "normal", Font.BOLD); + fontInfo.addFontProperties("F4", "sans-serif", "oblique", Font.BOLD); + fontInfo.addFontProperties("F4", "sans-serif", "italic", Font.BOLD); + fontInfo.addFontProperties("F5", "serif", "normal", Font.NORMAL); + fontInfo.addFontProperties("F6", "serif", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F6", "serif", "italic", Font.NORMAL); + fontInfo.addFontProperties("F7", "serif", "normal", Font.BOLD); + fontInfo.addFontProperties("F8", "serif", "oblique", Font.BOLD); + fontInfo.addFontProperties("F8", "serif", "italic", Font.BOLD); + fontInfo.addFontProperties("F9", "monospace", "normal", Font.NORMAL); + fontInfo.addFontProperties("F10", "monospace", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F10", "monospace", "italic", Font.NORMAL); + fontInfo.addFontProperties("F11", "monospace", "normal", Font.BOLD); + fontInfo.addFontProperties("F12", "monospace", "oblique", Font.BOLD); + fontInfo.addFontProperties("F12", "monospace", "italic", Font.BOLD); + + fontInfo.addFontProperties("F1", "Helvetica", "normal", Font.NORMAL); + fontInfo.addFontProperties("F2", "Helvetica", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F2", "Helvetica", "italic", Font.NORMAL); + fontInfo.addFontProperties("F3", "Helvetica", "normal", Font.BOLD); + fontInfo.addFontProperties("F4", "Helvetica", "oblique", Font.BOLD); + fontInfo.addFontProperties("F4", "Helvetica", "italic", Font.BOLD); + fontInfo.addFontProperties("F5", "Times", "normal", Font.NORMAL); + fontInfo.addFontProperties("F6", "Times", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F6", "Times", "italic", Font.NORMAL); + fontInfo.addFontProperties("F7", "Times", "normal", Font.BOLD); + fontInfo.addFontProperties("F8", "Times", "oblique", Font.BOLD); + fontInfo.addFontProperties("F8", "Times", "italic", Font.BOLD); + fontInfo.addFontProperties("F9", "Courier", "normal", Font.NORMAL); + fontInfo.addFontProperties("F10", "Courier", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F10", "Courier", "italic", Font.NORMAL); + fontInfo.addFontProperties("F11", "Courier", "normal", Font.BOLD); + fontInfo.addFontProperties("F12", "Courier", "oblique", Font.BOLD); + fontInfo.addFontProperties("F12", "Courier", "italic", Font.BOLD); + fontInfo.addFontProperties("F13", "Symbol", "normal", Font.NORMAL); + fontInfo.addFontProperties("F14", "ZapfDingbats", "normal", Font.NORMAL); + + // Custom type 1 fonts step 2/2 + // fontInfo.addFontProperties("F15", "OMEP", "normal", FontInfo.NORMAL); + // fontInfo.addFontProperties("F16", "Garamond-LightCondensed", "normal", FontInfo.NORMAL); + // fontInfo.addFontProperties("F17", "BauerBodoni", "italic", FontInfo.BOLD); + + /* for compatibility with PassiveTex */ + fontInfo.addFontProperties("F5", "Times-Roman", "normal", Font.NORMAL); + fontInfo.addFontProperties("F6", "Times-Roman", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F6", "Times-Roman", "italic", Font.NORMAL); + fontInfo.addFontProperties("F7", "Times-Roman", "normal", Font.BOLD); + fontInfo.addFontProperties("F8", "Times-Roman", "oblique", Font.BOLD); + fontInfo.addFontProperties("F8", "Times-Roman", "italic", Font.BOLD); + fontInfo.addFontProperties("F5", "Times Roman", "normal", Font.NORMAL); + fontInfo.addFontProperties("F6", "Times Roman", "oblique", Font.NORMAL); + fontInfo.addFontProperties("F6", "Times Roman", "italic", Font.NORMAL); + fontInfo.addFontProperties("F7", "Times Roman", "normal", Font.BOLD); + fontInfo.addFontProperties("F8", "Times Roman", "oblique", Font.BOLD); + fontInfo.addFontProperties("F8", "Times Roman", "italic", Font.BOLD); + fontInfo.addFontProperties("F9", "Computer-Modern-Typewriter", + "normal", Font.NORMAL); + } + +} + diff --git a/src/java/org/apache/fop/render/awt/viewer/Command.java b/src/java/org/apache/fop/render/awt/viewer/Command.java new file mode 100644 index 000000000..725255a0e --- /dev/null +++ b/src/java/org/apache/fop/render/awt/viewer/Command.java @@ -0,0 +1,82 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.render.awt.viewer; + +//Java +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import javax.swing.ImageIcon; +import java.net.URL; + +/** + * This class represents UI-commands, which can be used as menu or toolbar + * items
. + * When the Command object receives action event, that object's + * doit method is invoked. doit method by default + * does nothing and the class customer have to override it to implement + * any action handling logic. + * Originally contributed by: + * Juergen Verwohlt: Juergen.Verwohlt@jcatalog.com, + * Rainer Steinkuhle: Rainer.Steinkuhle@jcatalog.com, + * Stanislav Gorkhover: Stanislav.Gorkhover@jcatalog.com + */ +public class Command extends AbstractAction { + + private static final String IMAGE_DIR = "images/"; + + /** + * Creates Command object with a given name and + * sets the name as a tooltip text. No associated icon image. + * @param name of the command + */ + public Command(String name) { + super(name); + putValue(SHORT_DESCRIPTION, name); + } + + /** + * Creates Command object with a given name, the same + * tooltip text and icon image if appropriate image file is found. + * @param name name of the command + * @param iconName name of the icon + */ + public Command(String name, String iconName) { + super(name); + putValue(SHORT_DESCRIPTION, name); + URL url = getClass().getResource(IMAGE_DIR + iconName + ".gif"); + if (url != null) { + putValue(SMALL_ICON, new ImageIcon(url)); + } + } + + /** + * @see java.awt.event.ActionListener#actionPerformed(ActionEvent) + */ + public void actionPerformed(ActionEvent e) { + doit(); + } + + /** + * Action handler, have to be overrided by subclasses. + */ + public void doit() { + //Do nothing + } +} + diff --git a/src/java/org/apache/fop/render/awt/viewer/GoToPageDialog.java b/src/java/org/apache/fop/render/awt/viewer/GoToPageDialog.java new file mode 100644 index 000000000..d3db6979b --- /dev/null +++ b/src/java/org/apache/fop/render/awt/viewer/GoToPageDialog.java @@ -0,0 +1,128 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.render.awt.viewer; + +import java.awt.Dimension; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; + +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +/** + * Go to Page Dialog. + * Originally contributed by: + * Juergen Verwohlt: Juergen.Verwohlt@jCatalog.com, + * Rainer Steinkuhle: Rainer.Steinkuhle@jCatalog.com, + * Stanislav Gorkhover: Stanislav.Gorkhover@jCatalog.com + */ +public class GoToPageDialog extends JDialog { + + private JTextField pgNbField; + private int pageNumber = -1; + + /** + * Creates modal dialog with a given title, attached to a given frame. + * @param frame Frame to attach to + * @param title dialog title + * @param translator translator for localization + */ + public GoToPageDialog(Frame frame, String title, Translator translator) { + super(frame, title, true); + jbInit(translator); + pack(); + } + + private void jbInit(Translator translator) { + JPanel panel1 = new JPanel(); + GridBagLayout gridBagLayout1 = new GridBagLayout(); + JLabel pgNbLabel = new JLabel(); + pgNbField = new JTextField(); + JButton okButton = new JButton(); + JButton cancelButton = new JButton(); + panel1.setLayout(gridBagLayout1); + pgNbLabel.setText(translator.getString("Label.Page.number")); + okButton.setText(translator.getString("Button.Ok")); + okButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + okButtonActionPerformed(e); + } + }); + cancelButton.setText(translator.getString("Button.Cancel")); + cancelButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(ActionEvent e) { + cancelButtonActionPerformed(e); + } + }); + panel1.setMinimumSize(new Dimension(250, 78)); + getContentPane().add(panel1); + panel1.add(pgNbLabel, + new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.NONE, + new Insets(10, 10, 10, 5), 0, 0)); + panel1.add(pgNbField, + new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.BOTH, + new Insets(10, 5, 10, 10), 0, 0)); + panel1.add(okButton, + new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, + GridBagConstraints.EAST, + GridBagConstraints.NONE, + new Insets(0, 0, 10, 5), 0, 0)); + panel1.add(cancelButton, + new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.NONE, + new Insets(0, 10, 10, 10), 0, 0)); + } + + private void okButtonActionPerformed(ActionEvent e) { + try { + pageNumber = Integer.parseInt(pgNbField.getText()); + dispose(); + } catch (NumberFormatException nfe) { + pgNbField.setText("???"); + } + + } + + private void cancelButtonActionPerformed(ActionEvent e) { + pageNumber = -1; + dispose(); + } + + /** + * Returns page number, entered by user. + * @return the page number + */ + public int getPageNumber() { + return pageNumber; + } +} + diff --git a/src/java/org/apache/fop/render/awt/viewer/PreviewDialog.java b/src/java/org/apache/fop/render/awt/viewer/PreviewDialog.java new file mode 100644 index 000000000..c1d06615c --- /dev/null +++ b/src/java/org/apache/fop/render/awt/viewer/PreviewDialog.java @@ -0,0 +1,571 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.render.awt.viewer; + +//Java +import javax.swing.BorderFactory; +import javax.swing.ImageIcon; +import javax.swing.JComboBox; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JToolBar; +import javax.swing.SwingUtilities; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.image.BufferedImage; +import java.awt.print.PrinterJob; +import java.awt.print.PrinterException; + +//FOP +import org.apache.fop.apps.Driver; +import org.apache.fop.apps.InputHandler; +import org.apache.fop.apps.FOPException; +import org.apache.fop.render.awt.AWTRenderer; + +/** + * AWT Viewer main window. + * Originally contributed by: + * Juergen Verwohlt: Juergen.Verwohlt@jCatalog.com, + * Rainer Steinkuhle: Rainer.Steinkuhle@jCatalog.com, + * Stanislav Gorkhover: Stanislav.Gorkhover@jCatalog.com + */ +public class PreviewDialog extends JFrame { + + /** The Translator for localization */ + protected Translator translator; + /** The AWT renderer */ + protected AWTRenderer renderer; + /** The InputHandler associated with this window */ + protected InputHandler inputHandler; + /** The Driver used for refreshing/reloading the view */ + protected Driver driver; + + private int currentPage = 0; + private int pageCount = 0; + private Reloader reloader; + private JComboBox scale; + private JLabel processStatus; + private JLabel pageLabel; + private JLabel infoStatus; + + /** + * Creates a new PreviewDialog that uses the given renderer. + * @param aRenderer the to use renderer + */ + public PreviewDialog(AWTRenderer aRenderer, InputHandler handler) { + renderer = aRenderer; + inputHandler = handler; + translator = renderer.getTranslator(); + + //Commands aka Actions + Command printAction = new Command(translator.getString("Menu.Print"), "Print") { + public void doit() { + print(); + } + }; + Command firstPageAction = new Command(translator.getString("Menu.First.page"), + "firstpg") { + public void doit() { + goToFirstPage(); + } + }; + Command previousPageAction = new Command(translator.getString("Menu.Prev.page"), + "prevpg") { + public void doit() { + goToPreviousPage(); + } + }; + Command nextPageAction = new Command(translator.getString("Menu.Next.page"), "nextpg") { + public void doit() { + goToNextPage(); + } + + }; + Command lastPageAction = new Command(translator.getString("Menu.Last.page"), "lastpg") { + public void doit() { + goToLastPage(); + } + }; + Command reloadAction = new Command(translator.getString("Menu.Reload"), "reload") { + public void doit() { + reload(); + } + }; + + setTitle("FOP: AWT-" + translator.getString("Title.Preview")); + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + + //Sets size to be 61%x90% of the screen size + Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + //Rather frivolous size - fits A4 page width in 1024x768 screen on my desktop + setSize(screen.width * 61 / 100, screen.height * 9 / 10); + + //Page view stuff + pageLabel = new JLabel(); + JScrollPane previewArea = new JScrollPane(pageLabel); + previewArea.getViewport().setBackground(Color.gray); + previewArea.setMinimumSize(new Dimension(50, 50)); + getContentPane().add(previewArea, BorderLayout.CENTER); + + //Scaling combobox + scale = new JComboBox(); + scale.addItem("25%"); + scale.addItem("50%"); + scale.addItem("75%"); + scale.addItem("100%"); + scale.addItem("150%"); + scale.addItem("200%"); + scale.setMaximumSize(new Dimension(80, 24)); + scale.setPreferredSize(new Dimension(80, 24)); + scale.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + scaleActionPerformed(e); + } + }); + scale.setSelectedItem("100%"); + renderer.setScaleFactor(100.0); + + //Menu + setJMenuBar(setupMenu()); + + //Toolbar + JToolBar toolBar = new JToolBar(); + toolBar.add(printAction); + toolBar.add(reloadAction); + toolBar.addSeparator(); + toolBar.add(firstPageAction); + toolBar.add(previousPageAction); + toolBar.add(nextPageAction); + toolBar.add(lastPageAction); + toolBar.addSeparator(); + toolBar.add(new JLabel(translator.getString("Menu.Zoom"))); + toolBar.addSeparator(); + toolBar.add(scale); + getContentPane().add(toolBar, BorderLayout.NORTH); + //Status bar + JPanel statusBar = new JPanel(); + processStatus = new JLabel(); + processStatus.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createEtchedBorder(), + BorderFactory.createEmptyBorder(0, 3, 0, 0))); + infoStatus = new JLabel(); + infoStatus.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createEtchedBorder(), + BorderFactory.createEmptyBorder(0, 3, 0, 0))); + + statusBar.setLayout(new GridBagLayout()); + + processStatus.setPreferredSize(new Dimension(200, 21)); + processStatus.setMinimumSize(new Dimension(200, 21)); + + infoStatus.setPreferredSize(new Dimension(100, 21)); + infoStatus.setMinimumSize(new Dimension(100, 21)); + statusBar.add(processStatus, + new GridBagConstraints(0, 0, 1, 0, 2.0, 0.0, + GridBagConstraints.CENTER, + GridBagConstraints.HORIZONTAL, + new Insets(0, 0, 0, 3), 0, 0)); + statusBar.add(infoStatus, + new GridBagConstraints(1, 0, 1, 0, 1.0, 0.0, + GridBagConstraints.CENTER, + GridBagConstraints.HORIZONTAL, + new Insets(0, 0, 0, 0), 0, 0)); + getContentPane().add(statusBar, BorderLayout.SOUTH); + } + + /** + * Creates a new menubar to be shown in this window. + * @return the newly created menubar + */ + private JMenuBar setupMenu() { + JMenuBar menuBar = new JMenuBar(); + JMenu menu = new JMenu(translator.getString("Menu.File")); + + //Adds mostly the same actions, but without icons + menu.add(new Command(translator.getString("Menu.Print")) { + public void doit() { + print(); + } + }); + // inputHandler must be set to allow reloading + if (inputHandler != null) { + menu.add(new Command(translator.getString("Menu.Reload")) { + public void doit() { + reload(); + } + }); + } + menu.addSeparator(); + menu.add(new Command(translator.getString("Menu.Exit")) { + public void doit() { + dispose(); + } + }); + menuBar.add(menu); + menu = new JMenu(translator.getString("Menu.View")); + menu.add(new Command(translator.getString("Menu.First.page")) { + public void doit() { + goToFirstPage(); + } + }); + menu.add(new Command(translator.getString("Menu.Prev.page")) { + public void doit() { + goToPreviousPage(); + } + }); + menu.add(new Command(translator.getString("Menu.Next.page")) { + public void doit() { + goToNextPage(); + } + }); + menu.add(new Command(translator.getString("Menu.Last.page")) { + public void doit() { + goToLastPage(); + } + }); + menu.add(new Command(translator.getString("Menu.Go.to.Page") + " ...") { + public void doit() { + showGoToPageDialog(); + } + }); + menu.addSeparator(); + JMenu subMenu = new JMenu(translator.getString("Menu.Zoom")); + subMenu.add(new Command("25%") { + public void doit() { + setScale(25.0); + } + }); + subMenu.add(new Command("50%") { + public void doit() { + setScale(50.0); + } + }); + subMenu.add(new Command("75%") { + public void doit() { + setScale(75.0); + } + }); + subMenu.add(new Command("100%") { + public void doit() { + setScale(100.0); + } + }); + subMenu.add(new Command("150%") { + public void doit() { + setScale(150.0); + } + }); + subMenu.add(new Command("200%") { + public void doit() { + setScale(200.0); + } + }); + menu.add(subMenu); + menu.addSeparator(); + menu.add(new Command(translator.getString("Menu.Default.zoom")) { + public void doit() { + setScale(100.0); + } + }); + menuBar.add(menu); + menu = new JMenu(translator.getString("Menu.Help")); + menu.add(new Command(translator.getString("Menu.About")) { + public void doit() { + startHelpAbout(); + } + }); + menuBar.add(menu); + return menuBar; + } + + /** + * Shows the About box + */ + private void startHelpAbout() { + PreviewDialogAboutBox dlg = new PreviewDialogAboutBox(this, translator); + //Centers the box + Dimension dlgSize = dlg.getPreferredSize(); + Dimension frmSize = getSize(); + Point loc = getLocation(); + dlg.setLocation((frmSize.width - dlgSize.width) / 2 + loc.x, + (frmSize.height - dlgSize.height) / 2 + loc.y); + dlg.setVisible(true); + } + + /** + * Changes the current visible page + * @param number the page number to go to + */ + private void goToPage(int number) { + currentPage = number; + renderer.setPageNumber(number); + showPage(); + } + + /** + * Shows the previous page. + */ + private void goToPreviousPage() { + if (currentPage <= 0) { + return; + } + currentPage--; + goToPage(currentPage); + } + + + /** + * Shows the next page. + */ + private void goToNextPage() { + if (currentPage >= pageCount - 1) { + return; + } + currentPage++; + goToPage(currentPage); + } + + /** + * Shows the last page. + */ + private void goToLastPage() { + if (currentPage == pageCount - 1) { + return; + } + currentPage = pageCount - 1; + goToPage(currentPage); + } + + /** + * Reloads and reformats document. + */ + private synchronized void reload() { + if (reloader == null || !reloader.isAlive()) { + reloader = new Reloader(); + reloader.start(); + } + } + + /** + * This class is used to reload document in + * a thread safe way. + */ + private class Reloader extends Thread { + public void run() { + if (driver == null) { + driver = new Driver(); + driver.setRenderer(renderer); + } else { + driver.reset(); + } + + pageLabel.setIcon(null); + infoStatus.setText(""); + currentPage = 0; + + try { + setStatus(translator.getString("Status.Build.FO.tree")); + driver.render(inputHandler); + setStatus(translator.getString("Status.Show")); + } catch (FOPException e) { + reportException(e); + } + } + } + + /** + * Shows "go to page" dialog and then goes to the selected page + */ + private void showGoToPageDialog() { + GoToPageDialog d = new GoToPageDialog(this, + translator.getString("Menu.Go.to.Page"), translator); + d.setLocation((int)getLocation().getX() + 50, + (int)getLocation().getY() + 50); + d.setVisible(true); + currentPage = d.getPageNumber(); + if (currentPage < 1 || currentPage > pageCount) { + return; + } + currentPage--; + goToPage(currentPage); + } + + /** + * Shows the first page. + */ + private void goToFirstPage() { + if (currentPage == 0) { + return; + } + currentPage = 0; + goToPage(currentPage); + } + + /** + * Prints the document + */ + private void print() { + PrinterJob pj = PrinterJob.getPrinterJob(); + pj.setPageable(renderer); + if (pj.printDialog()) { + try { + pj.print(); + } catch (PrinterException pe) { + pe.printStackTrace(); + } + } + } + + /** + * Scales page image + */ + private void setScale(double scaleFactor) { + if (scaleFactor == 25.0) { + scale.setSelectedIndex(0); + } else if (scaleFactor == 50.0) { + scale.setSelectedIndex(1); + } else if (scaleFactor == 75.0) { + scale.setSelectedIndex(2); + } else if (scaleFactor == 100.0) { + scale.setSelectedIndex(3); + } else if (scaleFactor == 150.0) { + scale.setSelectedIndex(4); + } else if (scaleFactor == 200.0) { + scale.setSelectedIndex(5); + } + renderer.setScaleFactor(scaleFactor); + if (renderer.getNumberOfPages() != 0) { + showPage(); + } + } + + private void scaleActionPerformed(ActionEvent e) { + String item = (String)scale.getSelectedItem(); + setScale(Double.parseDouble(item.substring(0, item.indexOf('%')))); + } + + /** + * Sets message to be shown in the status bar in a thread safe way. + * @param message the message + */ + public void setStatus(String message) { + SwingUtilities.invokeLater(new ShowStatus(message)); + } + + /** + * This class is used to show status in a thread safe way. + */ + private class ShowStatus implements Runnable { + /** + * The message to display + */ + private String message; + /** + * Constructs ShowStatus thread + * @param message message to display + */ + public ShowStatus(String message) { + this.message = message; + } + + public void run() { + processStatus.setText(message.toString()); + } + } + + /** + * Starts rendering process and shows the current page. + */ + public void showPage() { + ShowPageImage viewer = new ShowPageImage(); + if (SwingUtilities.isEventDispatchThread()) { + viewer.run(); + } else { + SwingUtilities.invokeLater(viewer); + } + } + + + /** + * This class is used to update the page image + * in a thread safe way. + */ + private class ShowPageImage implements Runnable { + /** + * The run method that does the actual updating + */ + public void run() { + try { + BufferedImage pageImage = null; + Graphics graphics = null; + + pageImage = renderer.getPageImage(currentPage); + if (pageImage == null) + return; + graphics = pageImage.getGraphics(); + graphics.setColor(Color.black); + graphics.drawRect(0, 0, pageImage.getWidth() - 1, + pageImage.getHeight() - 1); + + pageLabel.setIcon(new ImageIcon(pageImage)); + pageCount = renderer.getNumberOfPages(); + + // Update status bar + infoStatus.setText(translator.getString("Status.Page") + " " + + (currentPage + 1) + " " + + translator.getString("Status.of") + " " + pageCount); + } catch (FOPException e) { + reportException(e); + } + } + } + + /** + * Opens standard Swing error dialog box and reports given exception details. + * @param e the Exception + */ + public void reportException(Exception e) { + String msg = translator.getString("Exception.Occured"); + setStatus(msg); + JOptionPane.showMessageDialog( + getContentPane(), + "" + msg + ":
" + + e.getClass().getName() + "
" + + e.getMessage() + "", + translator.getString("Exception.Error"), + JOptionPane.ERROR_MESSAGE + ); + } +} + diff --git a/src/java/org/apache/fop/render/awt/viewer/PreviewDialogAboutBox.java b/src/java/org/apache/fop/render/awt/viewer/PreviewDialogAboutBox.java new file mode 100644 index 000000000..f7052d9a4 --- /dev/null +++ b/src/java/org/apache/fop/render/awt/viewer/PreviewDialogAboutBox.java @@ -0,0 +1,118 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.render.awt.viewer; + +//Java +import java.awt.AWTEvent; +import java.awt.BorderLayout; +import java.awt.Dialog; +import java.awt.FlowLayout; +import java.awt.Frame; +import java.awt.GridLayout; + +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.border.EmptyBorder; +import java.awt.event.ActionListener; +import java.awt.event.WindowEvent; +import java.awt.event.ActionEvent; + +//FOP +import org.apache.fop.apps.Version; + +/** + * AWT Viewer's "About" dialog. + * Originally contributed by: + * Juergen Verwohlt: Juergen.Verwohlt@jCatalog.com, + * Rainer Steinkuhle: Rainer.Steinkuhle@jCatalog.com, + * Stanislav Gorkhover: Stanislav.Gorkhover@jCatalog.com + */ +public class PreviewDialogAboutBox extends Dialog implements ActionListener { + private JButton okButton; + + /** + * Creates modal "About" dialog, attached to a given parent frame. + * @param parent parent frame + * @param translator Translator for localization + */ + public PreviewDialogAboutBox(Frame parent, Translator translator) { + super(parent, true); + enableEvents(AWTEvent.WINDOW_EVENT_MASK); + setTitle(translator.getString("About.Title")); + setResizable(false); + JPanel panel1 = new JPanel(); + JPanel panel2 = new JPanel(); + JPanel insetsPanel1 = new JPanel(); + JPanel insetsPanel2 = new JPanel(); + JPanel insetsPanel3 = new JPanel(); + okButton = new JButton(); + JLabel imageControl1 = new JLabel(); + imageControl1.setIcon(new ImageIcon(getClass().getResource("images/fop.gif"))); + JLabel label1 = new JLabel(translator.getString("About.Product")); + JLabel label2 = new JLabel(translator.getString("About.Version") + + " " + Version.getVersion()); + JLabel label3 = new JLabel(translator.getString("About.Copyright")); + panel1.setLayout(new BorderLayout()); + panel2.setLayout(new BorderLayout()); + insetsPanel1.setLayout(new FlowLayout()); + insetsPanel2.setLayout(new FlowLayout()); + insetsPanel2.setBorder(new EmptyBorder(10, 10, 10, 10)); + insetsPanel3.setLayout(new GridLayout(3, 1)); + insetsPanel3.setBorder(new EmptyBorder(10, 10, 10, 10)); + okButton.setText(translator.getString("Button.Ok")); + okButton.addActionListener(this); + insetsPanel2.add(imageControl1, null); + panel2.add(insetsPanel2, BorderLayout.WEST); + insetsPanel3.add(label1); + insetsPanel3.add(label2); + insetsPanel3.add(label3); + panel2.add(insetsPanel3, BorderLayout.CENTER); + insetsPanel1.add(okButton); + panel1.add(insetsPanel1, BorderLayout.SOUTH); + panel1.add(panel2, BorderLayout.NORTH); + add(panel1); + pack(); + } + + /** + * @see java.awt.Window#processWindowEvent(WindowEvent) + */ + protected void processWindowEvent(WindowEvent e) { + if (e.getID() == WindowEvent.WINDOW_CLOSING) { + cancel(); + } + super.processWindowEvent(e); + } + + private void cancel() { + dispose(); + } + + /** + * @see java.awt.event.ActionListener#actionPerformed(ActionEvent) + */ + public void actionPerformed(ActionEvent e) { + if (e.getSource() == okButton) { + cancel(); + } + } +} + diff --git a/src/java/org/apache/fop/render/awt/viewer/Translator.java b/src/java/org/apache/fop/render/awt/viewer/Translator.java new file mode 100644 index 000000000..b9498fcc3 --- /dev/null +++ b/src/java/org/apache/fop/render/awt/viewer/Translator.java @@ -0,0 +1,59 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed 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. + */ + +/* $Id$ */ + +package org.apache.fop.render.awt.viewer; + +//Java +import java.util.ResourceBundle; +import java.util.Locale; + +/** + * AWT Viewer's localization class, backed up by java.util.ResourceBundle. + * Originally contributed by: + * Stanislav.Gorkhover@jCatalog.com + */ +public class Translator { + + private ResourceBundle bundle; + private static String bundleBaseName = "org/apache/fop/render/awt/viewer/resources/Viewer"; + + /** + * Default constructor, default Locale is used. + */ + public Translator() { + this(Locale.getDefault()); + } + + /** + * Constructor for a given Locale. + * @param locale Locale to use + */ + public Translator(Locale locale) { + bundle = ResourceBundle.getBundle(bundleBaseName, locale); + } + + /** + * Returns localized String for a given key. + * @param key the key + * @return the localized String + */ + public String getString(String key) { + return bundle.getString(key); + } +} + -- 2.39.5