From e15428a90dd3e0298e30fa84aa318f527b3bbec4 Mon Sep 17 00:00:00 2001 From: Jeremias Maerki Date: Fri, 28 Apr 2006 08:51:27 +0000 Subject: [PATCH] Introduced "ignored namespaces" list on FopFactory. Attributes from ignored namespaces are not complained about. Not done for elements, yet. Added support for foreign attributes (attributes in a non-FO namespace) on formatting objects, for example to specify additional (proprietary) hints for rendering i-f-o and e-g. First usage example is PCLRendererContext which the PCLGraphics2DAdapter uses to decide whether to paint natively using HP GL/2 or using a bitmap. PCL Renderer revived: Basic framework constructed based on the old one. Still incomplete (no border painting, incomplete Graphics2D implementation, problems with reference orientation, no kerning etc.). The PCL Renderer implements PCL5 (monochrome) and HP GL/2. Work in progress! Added UnitConv helper class which could also be useful elsewhere (Could be a candidate for Commons). git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@397806 13f79535-47bb-0310-9956-ffa450edef68 --- .../services/org.apache.fop.fo.ElementMapping | 1 + src/java/org/apache/fop/apps/FopFactory.java | 45 +- src/java/org/apache/fop/area/Area.java | 62 +- .../org/apache/fop/area/RegionViewport.java | 7 +- .../apache/fop/fo/ElementMappingRegistry.java | 8 + src/java/org/apache/fop/fo/FOTreeBuilder.java | 9 +- src/java/org/apache/fop/fo/FObj.java | 41 +- src/java/org/apache/fop/fo/PropertyList.java | 17 +- .../extensions/ExtensionElementMapping.java | 13 +- .../OldExtensionElementMapping.java | 36 + .../fop/layoutmgr/AbstractLayoutManager.java | 10 +- .../inline/AbstractGraphicsLayoutManager.java | 1 + .../fop/render/RendererContextConstants.java | 6 + .../apache/fop/render/xml/XMLRenderer.java | 18 + src/java/org/apache/fop/util/QName.java | 113 +++ src/java/org/apache/fop/util/UnitConv.java | 118 +++ .../services/org.apache.fop.render.XMLHandler | 1 + .../pcl/DefaultMonochromeBitmapConverter.java | 52 ++ .../pcl/JAIMonochromeBitmapConverter.java | 97 +++ .../render/pcl/MonochromeBitmapConverter.java | 43 + .../apache/fop/render/pcl/PCLGenerator.java | 502 +++++++++++ .../apache/fop/render/pcl/PCLGraphics2D.java | 430 +++++++++ .../fop/render/pcl/PCLGraphics2DAdapter.java | 137 +++ .../fop/render/pcl/PCLPageDefinition.java | 143 +++ .../apache/fop/render/pcl/PCLRenderer.java | 815 ++++++++++++++++-- .../fop/render/pcl/PCLRendererContext.java | 92 ++ .../apache/fop/render/pcl/PCLSVGHandler.java | 144 ++++ .../org/apache/fop/render/pcl/PCLStream.java | 55 -- .../org/apache/fop/render/pcl/package.html | 4 +- .../org/apache/fop/UtilityCodeTestSuite.java | 2 + .../org/apache/fop/util/UnitConvTestCase.java | 45 + 31 files changed, 2911 insertions(+), 156 deletions(-) create mode 100644 src/java/org/apache/fop/fo/extensions/OldExtensionElementMapping.java create mode 100644 src/java/org/apache/fop/util/QName.java create mode 100644 src/java/org/apache/fop/util/UnitConv.java create mode 100644 src/sandbox/META-INF/services/org.apache.fop.render.XMLHandler create mode 100644 src/sandbox/org/apache/fop/render/pcl/DefaultMonochromeBitmapConverter.java create mode 100644 src/sandbox/org/apache/fop/render/pcl/JAIMonochromeBitmapConverter.java create mode 100644 src/sandbox/org/apache/fop/render/pcl/MonochromeBitmapConverter.java create mode 100644 src/sandbox/org/apache/fop/render/pcl/PCLGenerator.java create mode 100644 src/sandbox/org/apache/fop/render/pcl/PCLGraphics2D.java create mode 100644 src/sandbox/org/apache/fop/render/pcl/PCLGraphics2DAdapter.java create mode 100644 src/sandbox/org/apache/fop/render/pcl/PCLPageDefinition.java create mode 100644 src/sandbox/org/apache/fop/render/pcl/PCLRendererContext.java create mode 100644 src/sandbox/org/apache/fop/render/pcl/PCLSVGHandler.java delete mode 100644 src/sandbox/org/apache/fop/render/pcl/PCLStream.java create mode 100644 test/java/org/apache/fop/util/UnitConvTestCase.java diff --git a/src/java/META-INF/services/org.apache.fop.fo.ElementMapping b/src/java/META-INF/services/org.apache.fop.fo.ElementMapping index b182c3a99..58bae87a3 100644 --- a/src/java/META-INF/services/org.apache.fop.fo.ElementMapping +++ b/src/java/META-INF/services/org.apache.fop.fo.ElementMapping @@ -2,6 +2,7 @@ org.apache.fop.fo.FOElementMapping org.apache.fop.fo.extensions.svg.SVGElementMapping org.apache.fop.fo.extensions.svg.BatikExtensionElementMapping org.apache.fop.fo.extensions.ExtensionElementMapping +org.apache.fop.fo.extensions.OldExtensionElementMapping org.apache.fop.fo.extensions.xmp.XMPElementMapping org.apache.fop.fo.extensions.xmp.RDFElementMapping org.apache.fop.render.ps.extensions.PSExtensionElementMapping \ No newline at end of file diff --git a/src/java/org/apache/fop/apps/FopFactory.java b/src/java/org/apache/fop/apps/FopFactory.java index a0acb36a3..152d75829 100644 --- a/src/java/org/apache/fop/apps/FopFactory.java +++ b/src/java/org/apache/fop/apps/FopFactory.java @@ -22,17 +22,23 @@ import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.net.MalformedURLException; -import java.util.List; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; import javax.xml.transform.Source; import javax.xml.transform.TransformerException; import javax.xml.transform.URIResolver; +import org.xml.sax.SAXException; + import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.apache.fop.fo.ElementMapping; import org.apache.fop.fo.ElementMappingRegistry; import org.apache.fop.hyphenation.HyphenationTreeResolver; @@ -41,7 +47,6 @@ import org.apache.fop.layoutmgr.LayoutManagerMaker; import org.apache.fop.render.RendererFactory; import org.apache.fop.render.XMLHandlerRegistry; import org.apache.fop.util.ContentHandlerFactoryRegistry; -import org.xml.sax.SAXException; /** * Factory class which instantiates new Fop and FOUserAgent instances. This class also holds @@ -110,6 +115,8 @@ public class FopFactory { /** Optional overriding LayoutManagerMaker */ private LayoutManagerMaker lmMakerOverride = null; + + private Set ignoredNamespaces = new java.util.HashSet(); /** * Main constructor. @@ -431,6 +438,40 @@ public class FopFactory { this.pageWidth = pageWidth; } + /** + * Adds a namespace to the set of ignored namespaces. + * If FOP encounters a namespace which it cannot handle, it issues a warning except if this + * namespace is in the ignored set. + * @param namespaceURI the namespace URI + */ + public void ignoreNamespace(String namespaceURI) { + this.ignoredNamespaces.add(namespaceURI); + } + + /** + * Adds a collection of namespaces to the set of ignored namespaces. + * If FOP encounters a namespace which it cannot handle, it issues a warning except if this + * namespace is in the ignored set. + * @param namespaceURIs the namespace URIs + */ + public void ignoreNamespaces(Collection namespaceURIs) { + this.ignoredNamespaces.addAll(namespaceURIs); + } + + /** + * Indicates whether a namespace URI is on the ignored list. + * @param namespaceURI the namespace URI + * @return true if the namespace is ignored by FOP + */ + public boolean isNamespaceIgnored(String namespaceURI) { + return this.ignoredNamespaces.contains(namespaceURI); + } + + /** @return the set of namespaces that are ignored by FOP */ + public Set getIgnoredNamespace() { + return Collections.unmodifiableSet(this.ignoredNamespaces); + } + //------------------------------------------- Configuration stuff /** diff --git a/src/java/org/apache/fop/area/Area.java b/src/java/org/apache/fop/area/Area.java index b0916abda..51c95f0ca 100644 --- a/src/java/org/apache/fop/area/Area.java +++ b/src/java/org/apache/fop/area/Area.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2005 The Apache Software Foundation. + * Copyright 1999-2006 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. @@ -20,13 +20,15 @@ package org.apache.fop.area; import java.io.Serializable; +import java.util.Collections; +import java.util.Iterator; import java.util.Map; -import java.util.HashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.fop.datatypes.ColorType; import org.apache.fop.traits.BorderProps; +import org.apache.fop.util.QName; // If the area appears more than once in the output // or if the area has external data it is cached @@ -129,8 +131,11 @@ public class Area implements Serializable { /** * Traits for this area stored in a HashMap */ - protected HashMap props = null; + protected Map props = null; + /** Foreign attributes */ + protected Map foreignAttributes = null; + /** * logging instance */ @@ -462,6 +467,57 @@ public class Area implements Serializable { } } + /** + * Sets a foreign attribute. + * @param name the qualified name of the attribute + * @param value the attribute value + */ + public void setForeignAttribute(QName name, String value) { + if (this.foreignAttributes == null) { + this.foreignAttributes = new java.util.HashMap(); + } + this.foreignAttributes.put(name, value); + } + + /** + * Set foreign attributes from a Map. + * @param atts a Map with attributes (keys: QName, values: String) + */ + public void setForeignAttributes(Map atts) { + if (atts.size() == 0) { + return; + } + Iterator iter = atts.keySet().iterator(); + while (iter.hasNext()) { + QName qName = (QName)iter.next(); + String value = (String)atts.get(qName); + //The casting is only to ensure type safety (too bad we can't use generics, yet) + setForeignAttribute(qName, value); + } + } + + /** + * Returns the value of a foreign attribute on the area. + * @param name the qualified name of the attribute + * @return the attribute value or null if it isn't set + */ + public String getForeignAttributeValue(QName name) { + if (this.foreignAttributes != null) { + return (String)this.foreignAttributes.get(name); + } else { + return null; + } + } + + /** @return the foreign attributes associated with this area */ + public Map getForeignAttributes() { + if (this.foreignAttributes != null) { + return Collections.unmodifiableMap(this.foreignAttributes); + } else { + return Collections.EMPTY_MAP; + } + } + /** @see java.lang.Object#toString() */ public String toString() { StringBuffer sb = new StringBuffer(super.toString()); diff --git a/src/java/org/apache/fop/area/RegionViewport.java b/src/java/org/apache/fop/area/RegionViewport.java index ff3146e17..23017fded 100644 --- a/src/java/org/apache/fop/area/RegionViewport.java +++ b/src/java/org/apache/fop/area/RegionViewport.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2004 The Apache Software Foundation. + * Copyright 1999-2006 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. @@ -115,7 +115,10 @@ public class RegionViewport extends Area implements Cloneable { RegionViewport rv = new RegionViewport((Rectangle2D)viewArea.clone()); rv.regionReference = (RegionReference)regionReference.clone(); if (props != null) { - rv.props = (HashMap)props.clone(); + rv.props = new HashMap(props); + } + if (foreignAttributes != null) { + rv.foreignAttributes = new HashMap(foreignAttributes); } return rv; } diff --git a/src/java/org/apache/fop/fo/ElementMappingRegistry.java b/src/java/org/apache/fop/fo/ElementMappingRegistry.java index 8ab5a3d98..9bb0ab19b 100644 --- a/src/java/org/apache/fop/fo/ElementMappingRegistry.java +++ b/src/java/org/apache/fop/fo/ElementMappingRegistry.java @@ -163,4 +163,12 @@ public class ElementMappingRegistry { return mapping.getDOMImplementation(); } + /** + * Indicates whether a namespace is known to FOP. + * @param namespaceURI the namespace URI + * @return true if the namespace is known. + */ + public boolean isKnownNamespace(String namespaceURI) { + return this.namespaces.containsKey(namespaceURI); + } } diff --git a/src/java/org/apache/fop/fo/FOTreeBuilder.java b/src/java/org/apache/fop/fo/FOTreeBuilder.java index 2711bd169..f64bae87a 100644 --- a/src/java/org/apache/fop/fo/FOTreeBuilder.java +++ b/src/java/org/apache/fop/fo/FOTreeBuilder.java @@ -304,6 +304,10 @@ public class FOTreeBuilder extends DefaultHandler { try { foNode = fobjMaker.make(currentFObj); + if (rootFObj == null) { + rootFObj = (Root) foNode; + rootFObj.setFOEventHandler(foEventHandler); + } propertyList = foNode.createPropertyList(currentPropertyList, foEventHandler); foNode.processNode(localName, getEffectiveLocator(), attlist, propertyList); foNode.startOfNode(); @@ -325,10 +329,7 @@ public class FOTreeBuilder extends DefaultHandler { delegate = subHandler; } - if (rootFObj == null) { - rootFObj = (Root) foNode; - rootFObj.setFOEventHandler(foEventHandler); - } else { + if (currentFObj != null) { currentFObj.addChildNode(foNode); } diff --git a/src/java/org/apache/fop/fo/FObj.java b/src/java/org/apache/fop/fo/FObj.java index af45735e6..44da27900 100644 --- a/src/java/org/apache/fop/fo/FObj.java +++ b/src/java/org/apache/fop/fo/FObj.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2005 The Apache Software Foundation. + * Copyright 1999-2006 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. @@ -19,6 +19,7 @@ package org.apache.fop.fo; import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.ListIterator; @@ -29,6 +30,7 @@ import org.apache.fop.apps.FOPException; import org.apache.fop.fo.extensions.ExtensionAttachment; import org.apache.fop.fo.flow.Marker; import org.apache.fop.fo.properties.PropertyMaker; +import org.apache.fop.util.QName; import org.xml.sax.Attributes; import org.xml.sax.Locator; @@ -46,6 +48,9 @@ public abstract class FObj extends FONode implements Constants { /** The list of extension attachments, null if none */ private List extensionAttachments = null; + /** The map of foreign attributes, null if none */ + private Map foreignAttributes = null; + /** Used to indicate if this FO is either an Out Of Line FO (see rec) or a descendant of one. Used during validateChildNode() FO validation. @@ -473,6 +478,40 @@ public abstract class FObj extends FONode implements Constants { return extensionAttachments; } } + + /** + * Adds a foreign attribute to this FObj. + * @param uri the namespace URI + * @param qName the fully qualified name + * @param value the attribute value + * @todo Handle this over FOP's property mechanism so we can use inheritance. + */ + public void addForeignAttribute(String uri, + String qName, String value) { + if (qName == null) { + throw new NullPointerException("Parameter qName must not be null"); + } + if (foreignAttributes == null) { + foreignAttributes = new java.util.HashMap(); + } + String localName = qName; + String prefix = null; + int p = localName.indexOf(':'); + if (p > 0) { + prefix = localName.substring(0, p); + localName = localName.substring(p + 1); + } + foreignAttributes.put(new QName(uri, prefix, localName), value); + } + + /** @return the map of foreign attributes */ + public Map getForeignAttributes() { + if (foreignAttributes == null) { + return Collections.EMPTY_MAP; + } else { + return foreignAttributes; + } + } } diff --git a/src/java/org/apache/fop/fo/PropertyList.java b/src/java/org/apache/fop/fo/PropertyList.java index 7d4b0132d..5bf00346a 100644 --- a/src/java/org/apache/fop/fo/PropertyList.java +++ b/src/java/org/apache/fop/fo/PropertyList.java @@ -24,7 +24,7 @@ import org.xml.sax.Attributes; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.fop.apps.FOPException; +import org.apache.fop.apps.FopFactory; import org.apache.fop.fo.expr.PropertyException; import org.apache.fop.fo.properties.CommonAbsolutePosition; import org.apache.fop.fo.properties.CommonAccessibility; @@ -295,13 +295,20 @@ public abstract class PropertyList { } String attributeNS; + FopFactory factory = getFObj().getUserAgent().getFactory(); for (int i = 0; i < attributes.getLength(); i++) { /* convert all attributes with the same namespace as the fo element for this fObj */ - attributeNS = attributes.getURI(i); - if (attributeNS.length() == 0 || attributeNS.equals(fobj.getNamespaceURI())) { - attributeName = attributes.getQName(i); - attributeValue = attributes.getValue(i); + attributeNS = attributes.getURI(i); + attributeName = attributes.getQName(i); + attributeValue = attributes.getValue(i); + if (attributeNS.length() == 0) { convertAttributeToProperty(attributes, attributeName, attributeValue); + } else if (!factory.isNamespaceIgnored(attributeNS)) { + if (factory.getElementMappingRegistry().isKnownNamespace(attributeNS)) { + getFObj().addForeignAttribute(attributeNS, attributeName, attributeValue); + } else { + handleInvalidProperty(attributeName); + } } } } diff --git a/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java b/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java index 310b61550..5f823d2a3 100644 --- a/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java +++ b/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2004 The Apache Software Foundation. + * Copyright 1999-2006 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. @@ -19,16 +19,17 @@ package org.apache.fop.fo.extensions; import org.apache.fop.fo.ElementMapping; +import org.apache.fop.fo.UnknownXMLObj; import java.util.HashMap; /** - * Element mapping for the pdf bookmark extension. - * This sets up the mapping for the classes that handle the - * pdf bookmark extension. + * Element mapping for FOP's proprietary extension to XSL-FO. */ public class ExtensionElementMapping extends ElementMapping { - public static String URI = "http://xml.apache.org/fop/extensions"; + + /** The FOP extension namespace URI */ + public static final String URI = "http://xmlgraphics.apache.org/fop/extensions"; /** * Constructor. @@ -43,6 +44,8 @@ public class ExtensionElementMapping extends ElementMapping { protected void initialize() { if (foObjs == null) { foObjs = new HashMap(); + foObjs.put("outline", new UnknownXMLObj.Maker(URI)); + foObjs.put("label", new UnknownXMLObj.Maker(URI)); } } } diff --git a/src/java/org/apache/fop/fo/extensions/OldExtensionElementMapping.java b/src/java/org/apache/fop/fo/extensions/OldExtensionElementMapping.java new file mode 100644 index 000000000..40ff485e5 --- /dev/null +++ b/src/java/org/apache/fop/fo/extensions/OldExtensionElementMapping.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2006 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.fo.extensions; + +/** + * Element mapping for the old FOP extension namespace. It is simply mapped to the new namespace. + */ +public class OldExtensionElementMapping extends ExtensionElementMapping { + + /** The old FOP extension namespace URI (FOP 0.20.5 and earlier) */ + public static final String URI = "http://xml.apache.org/fop/extensions"; + + /** + * Constructor. + */ + public OldExtensionElementMapping() { + namespaceURI = URI; + } + +} diff --git a/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java b/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java index 5fc39972a..d8ded23fe 100644 --- a/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2005 The Apache Software Foundation. + * Copyright 1999-2006 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. @@ -373,4 +373,12 @@ public abstract class AbstractLayoutManager extends AbstractBaseLayoutManager && isFinished()); } + /** + * Transfers foreign attributes from the formatting object to the area. + * @param targetArea the area to set the attributes on + */ + protected void transferForeignAttributes(Area targetArea) { + Map atts = getFObj().getForeignAttributes(); + targetArea.setForeignAttributes(atts); + } } diff --git a/src/java/org/apache/fop/layoutmgr/inline/AbstractGraphicsLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/AbstractGraphicsLayoutManager.java index a02e2363d..cfe74ae4f 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/AbstractGraphicsLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/AbstractGraphicsLayoutManager.java @@ -209,6 +209,7 @@ public abstract class AbstractGraphicsLayoutManager extends LeafNodeLayoutManage Area viewportArea = getChildArea(); TraitSetter.setProducerID(viewportArea, fobj.getId()); + transferForeignAttributes(viewportArea); Viewport vp = new Viewport(viewportArea); TraitSetter.setProducerID(vp, fobj.getId()); diff --git a/src/java/org/apache/fop/render/RendererContextConstants.java b/src/java/org/apache/fop/render/RendererContextConstants.java index c744a82a1..742d56f68 100644 --- a/src/java/org/apache/fop/render/RendererContextConstants.java +++ b/src/java/org/apache/fop/render/RendererContextConstants.java @@ -44,4 +44,10 @@ public interface RendererContextConstants { /** The configuration for the XMLHandler. */ String HANDLER_CONFIGURATION = "cfg"; + /** + * An optional Map (keys: QName, values: String) with attributes containing additional hints + * for rendering. + */ + String FOREIGN_ATTRIBUTES = "foreign-attributes"; + } diff --git a/src/java/org/apache/fop/render/xml/XMLRenderer.java b/src/java/org/apache/fop/render/xml/XMLRenderer.java index 00b204ee0..e97068968 100644 --- a/src/java/org/apache/fop/render/xml/XMLRenderer.java +++ b/src/java/org/apache/fop/render/xml/XMLRenderer.java @@ -45,6 +45,7 @@ import org.apache.fop.render.PrintRenderer; import org.apache.fop.render.Renderer; import org.apache.fop.render.RendererContext; import org.apache.fop.render.XMLHandler; +import org.apache.fop.util.QName; import org.apache.fop.util.XMLizable; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.apps.FOPException; @@ -274,6 +275,16 @@ public class XMLRenderer extends PrintRenderer { atts.addAttribute(NS, name, name, CDATA, value); } + /** + * Adds a new attribute to the protected member variable "atts". + * @param name name of the attribute + * @param value value of the attribute + */ + protected void addAttribute(QName name, String value) { + atts.addAttribute(name.getNamespaceURI(), name.getLocalName(), name.getQName(), + CDATA, value); + } + /** * Adds a new attribute to the protected member variable "atts". * @param name name of the attribute @@ -376,6 +387,13 @@ public class XMLRenderer extends PrintRenderer { } } } + + //Transfer foreign attributes + Iterator iter = area.getForeignAttributes().entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = (Map.Entry)iter.next(); + addAttribute((QName)entry.getKey(), (String)entry.getValue()); + } } private String createString(Rectangle2D rect) { diff --git a/src/java/org/apache/fop/util/QName.java b/src/java/org/apache/fop/util/QName.java new file mode 100644 index 000000000..ccfe05638 --- /dev/null +++ b/src/java/org/apache/fop/util/QName.java @@ -0,0 +1,113 @@ +/* + * Copyright 2006 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.util; + +import java.io.Serializable; + +/** + * Represents a qualified name of an XML element or an XML attribute. + *

+ * Note: This class allows to carry a namespace prefix but it is not used in the equals() and + * hashCode() methods. + */ +public class QName implements Serializable { + + private static final long serialVersionUID = -5225376740044770690L; + + private String namespaceURI; + private String localName; + private String prefix; + private int hashCode; + + /** + * Main constructor. + * @param namespaceURI the namespace URI + * @param prefix the namespace prefix, may be null + * @param localName the local name + */ + public QName(String namespaceURI, String prefix, String localName) { + if (localName == null) { + throw new NullPointerException("Parameter localName must not be null"); + } + if (localName.length() == 0) { + throw new IllegalArgumentException("Parameter localName must not be empty"); + } + this.namespaceURI = namespaceURI; + this.prefix = prefix; + this.localName = localName; + this.hashCode = toHashString().hashCode(); + } + + /** @return the namespace URI */ + public String getNamespaceURI() { + return this.namespaceURI; + } + + /** @return the namespace prefix */ + public String getPrefix() { + return this.prefix; + } + + /** @return the local name */ + public String getLocalName() { + return this.localName; + } + + /** @return the fully qualified name */ + public String getQName() { + return getPrefix() != null ? getPrefix() + ':' + getLocalName() : getLocalName(); + } + + /** @see java.lang.Object#hashCode() */ + public int hashCode() { + return this.hashCode; + } + + /** @see java.lang.Object#equals(java.lang.Object) */ + public boolean equals(Object obj) { + if (obj == null) { + return false; + } else if (obj == this) { + return true; + } else { + if (obj instanceof QName) { + QName other = (QName)obj; + if ((getNamespaceURI() == null && other.getNamespaceURI() == null) + || getNamespaceURI().equals(other.getNamespaceURI())) { + return getLocalName().equals(other.getLocalName()); + } + } + } + return false; + } + + /** @see java.lang.Object#toString() */ + public String toString() { + return prefix != null + ? (prefix + ":" + localName) + : toHashString(); + } + + private String toHashString() { + return (namespaceURI != null + ? ("{" + namespaceURI + "}" + localName) + : localName); + } + +} diff --git a/src/java/org/apache/fop/util/UnitConv.java b/src/java/org/apache/fop/util/UnitConv.java new file mode 100644 index 000000000..0c16cdc3f --- /dev/null +++ b/src/java/org/apache/fop/util/UnitConv.java @@ -0,0 +1,118 @@ +/* + * Copyright 2006 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: FixedLength.java 279656 2005-09-08 22:06:48Z pietsch $ */ + +package org.apache.fop.util; + +/** + * Utility class for unit conversions. + */ +public final class UnitConv { + + /** conversion factory from millimeters to inches. */ + public static final float IN2MM = 25.4f; + + /** conversion factory from centimeters to inches. */ + public static final float IN2CM = 2.54f; + + /** conversion factory from inches to points. */ + public static final int IN2PT = 72; + + /** + * Converts millimeters (mm) to points (pt) + * @param mm the value in mm + * @return the value in pt + */ + public static double mm2pt(double mm) { + return mm * IN2PT / IN2MM; + } + + /** + * Converts millimeters (mm) to millipoints (mpt) + * @param mm the value in mm + * @return the value in mpt + */ + public static double mm2mpt(double mm) { + return mm * 1000 * IN2PT / IN2MM; + } + + /** + * Converts points (pt) to millimeters (mm) + * @param pt the value in pt + * @return the value in mm + */ + public static double pt2mm(double pt) { + return pt * IN2MM / IN2PT; + } + + /** + * Converts millimeters (mm) to inches (in) + * @param mm the value in mm + * @return the value in inches + */ + public static double mm2in(double mm) { + return mm / IN2MM; + } + + /** + * Converts inches (in) to millimeters (mm) + * @param in the value in inches + * @return the value in mm + */ + public static double in2mm(double in) { + return in * IN2MM; + } + + /** + * Converts inches (in) to millipoints (mpt) + * @param in the value in inches + * @return the value in mpt + */ + public static double in2mpt(double in) { + return in * IN2PT * 1000; + } + + /** + * Converts millipoints (mpt) to inches (in) + * @param mpt the value in mpt + * @return the value in inches + */ + public static double mpt2in(double mpt) { + return mpt / IN2PT / 1000; + } + + /** + * Converts millimeters (mm) to pixels (px) + * @param mm the value in mm + * @param resolution the resolution in dpi (dots per inch) + * @return the value in pixels + */ + public static int mm2px(double mm, int resolution) { + return (int)Math.round(mm2in(mm) * resolution); + } + + /** + * Converts millipoints (mpt) to pixels (px) + * @param mpt the value in mpt + * @param resolution the resolution in dpi (dots per inch) + * @return the value in pixels + */ + public static int mpt2px(double mpt, int resolution) { + return (int)Math.round(mpt2in(mpt) * resolution); + } + +} diff --git a/src/sandbox/META-INF/services/org.apache.fop.render.XMLHandler b/src/sandbox/META-INF/services/org.apache.fop.render.XMLHandler new file mode 100644 index 000000000..cbb7eaa67 --- /dev/null +++ b/src/sandbox/META-INF/services/org.apache.fop.render.XMLHandler @@ -0,0 +1 @@ +org.apache.fop.render.pcl.PCLSVGHandler diff --git a/src/sandbox/org/apache/fop/render/pcl/DefaultMonochromeBitmapConverter.java b/src/sandbox/org/apache/fop/render/pcl/DefaultMonochromeBitmapConverter.java new file mode 100644 index 000000000..43580446f --- /dev/null +++ b/src/sandbox/org/apache/fop/render/pcl/DefaultMonochromeBitmapConverter.java @@ -0,0 +1,52 @@ +/* + * Copyright 2006 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.pcl; + +import java.awt.RenderingHints; +import java.awt.color.ColorSpace; +import java.awt.image.BufferedImage; +import java.awt.image.ColorConvertOp; +import java.awt.image.RenderedImage; + +/** + * Default implementation of the MonochromeBitmapConverter which uses the Java Class Library + * to convert grayscale bitmaps to monochrome bitmaps. + */ +public class DefaultMonochromeBitmapConverter implements + MonochromeBitmapConverter { + + /** @see MonochromeBitmapConverter#setHint(java.lang.String, java.lang.String) */ + public void setHint(String name, String value) { + //ignore, not supported + } + + /** @see MonochromeBitmapConverter#convertToMonochrome(java.awt.image.BufferedImage) */ + public RenderedImage convertToMonochrome(BufferedImage img) { + BufferedImage buf = new BufferedImage(img.getWidth(), img.getHeight(), + BufferedImage.TYPE_BYTE_BINARY); + RenderingHints hints = new RenderingHints(null); + //This hint doesn't seem to make a difference :-( + hints.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE); + ColorConvertOp op = new ColorConvertOp( + ColorSpace.getInstance(ColorSpace.CS_GRAY), hints); + op.filter(img, buf); + return buf; + } + +} diff --git a/src/sandbox/org/apache/fop/render/pcl/JAIMonochromeBitmapConverter.java b/src/sandbox/org/apache/fop/render/pcl/JAIMonochromeBitmapConverter.java new file mode 100644 index 000000000..4818b2b1d --- /dev/null +++ b/src/sandbox/org/apache/fop/render/pcl/JAIMonochromeBitmapConverter.java @@ -0,0 +1,97 @@ +/* + * Copyright 2006 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.pcl; + +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.IndexColorModel; +import java.awt.image.RenderedImage; +import java.awt.image.renderable.ParameterBlock; + +import javax.media.jai.ColorCube; +import javax.media.jai.ImageLayout; +import javax.media.jai.JAI; +import javax.media.jai.KernelJAI; +import javax.media.jai.LookupTableJAI; +import javax.media.jai.PlanarImage; + +/** + * Implementation of the MonochromeBitmapConverter which uses Java Advanced Imaging (JAI) + * to convert grayscale bitmaps to monochrome bitmaps. JAI provides better dithering options + * including error diffusion dithering. + *

+ * If you call setHint("quality", "true") on the instance you can enabled error diffusion + * dithering which produces a nicer result but is also a lot slower. + */ +public class JAIMonochromeBitmapConverter implements + MonochromeBitmapConverter { + + private boolean isErrorDiffusion = false; + + /** @see MonochromeBitmapConverter#setHint(java.lang.String, java.lang.String) */ + public void setHint(String name, String value) { + if ("quality".equalsIgnoreCase(name)) { + isErrorDiffusion = "true".equalsIgnoreCase(value); + } + } + + /** @see MonochromeBitmapConverter#convertToMonochrome(java.awt.image.BufferedImage) */ + public RenderedImage convertToMonochrome(BufferedImage img) { + if (img.getColorModel().getColorSpace().getNumComponents() != 1) { + throw new IllegalArgumentException("Source image must be a grayscale image!"); + } + + // Load the ParameterBlock for the dithering operation + // and set the operation name. + ParameterBlock pb = new ParameterBlock(); + pb.addSource(img); + String opName = null; + if (isErrorDiffusion) { + opName = "errordiffusion"; + LookupTableJAI lut = new LookupTableJAI(new byte[] {(byte)0x00, (byte)0xff}); + pb.add(lut); + pb.add(KernelJAI.ERROR_FILTER_FLOYD_STEINBERG); + } else { + opName = "ordereddither"; + //Create the color cube. + ColorCube colorMap = ColorCube.createColorCube(DataBuffer.TYPE_BYTE, + 0, new int[] {2}); + pb.add(colorMap); + pb.add(KernelJAI.DITHER_MASK_441); + } + + //Create an image layout for a monochrome b/w image + ImageLayout layout = new ImageLayout(); + byte[] map = new byte[] {(byte)0x00, (byte)0xff}; + ColorModel cm = new IndexColorModel(1, 2, map, map, map); + layout.setColorModel(cm); + + // Create a hint containing the layout. + RenderingHints hints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout); + + // Dither the image. + PlanarImage dst = JAI.create(opName, pb, hints); + + //Convert it to a BufferedImage + return dst.getAsBufferedImage(); + } + +} diff --git a/src/sandbox/org/apache/fop/render/pcl/MonochromeBitmapConverter.java b/src/sandbox/org/apache/fop/render/pcl/MonochromeBitmapConverter.java new file mode 100644 index 000000000..f2db9d798 --- /dev/null +++ b/src/sandbox/org/apache/fop/render/pcl/MonochromeBitmapConverter.java @@ -0,0 +1,43 @@ +/* + * Copyright 2006 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.pcl; + +import java.awt.image.BufferedImage; +import java.awt.image.RenderedImage; + +/** + * Interface for converters that convert grayscale images to monochrome (1-bit) bitmap images. + */ +public interface MonochromeBitmapConverter { + + /** + * Sets a hint to the implementation + * @param name the name of the hint + * @param value the value + */ + void setHint(String name, String value); + + /** + * Converts a grayscale bitmap image to a monochrome (1-bit) b/w bitmap image. + * @param img the grayscale image + * @return the converted monochrome image + */ + RenderedImage convertToMonochrome(BufferedImage img); + +} diff --git a/src/sandbox/org/apache/fop/render/pcl/PCLGenerator.java b/src/sandbox/org/apache/fop/render/pcl/PCLGenerator.java new file mode 100644 index 000000000..e0d3f9305 --- /dev/null +++ b/src/sandbox/org/apache/fop/render/pcl/PCLGenerator.java @@ -0,0 +1,502 @@ +/* + * Copyright 2006 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.pcl; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.color.ColorSpace; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.awt.image.ColorConvertOp; +import java.awt.image.ColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.io.IOException; +import java.io.OutputStream; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; + +/** + * This class provides methods for generating PCL print files. + */ +public class PCLGenerator { + + /** The ESC (escape) character */ + public static final char ESC = '\033'; + + /** A list of all supported resolutions in PCL (values in dpi) */ + public static final int[] PCL_RESOLUTIONS = new int[] {75, 100, 150, 200, 300, 600}; + + private final DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.US); + private final DecimalFormat df2 = new DecimalFormat("0.##", symbols); + private final DecimalFormat df4 = new DecimalFormat("0.####", symbols); + + private OutputStream out; + + /** + * Main constructor. + * @param out the OutputStream to write the PCL stream to + */ + public PCLGenerator(OutputStream out) { + this.out = out; + } + + /** @return the OutputStream that this generator writes to */ + public OutputStream getOutputStream() { + return this.out; + } + + /** + * Writes a PCL escape command to the output stream. + * @param cmd the command (without the ESCAPE character) + * @throws IOException In case of an I/O error + */ + public void writeCommand(String cmd) throws IOException { + out.write(27); //ESC + out.write(cmd.getBytes("US-ASCII")); + } + + /** + * Writes raw text (in ISO-8859-1 encoding) to the output stream. + * @param s the text + * @throws IOException In case of an I/O error + */ + public void writeText(String s) throws IOException { + out.write(s.getBytes("ISO-8859-1")); + } + + /** + * Formats a double value with two decimal positions for PCL output. + * + * @param value value to format + * @return the formatted value + */ + public final String formatDouble2(double value) { + return df2.format(value); + } + + /** + * Formats a double value with four decimal positions for PCL output. + * + * @param value value to format + * @return the formatted value + */ + public final String formatDouble4(double value) { + return df4.format(value); + } + + /** + * Sends the universal end of language command (UEL). + * @throws IOException In case of an I/O error + */ + public void universalEndOfLanguage() throws IOException { + writeCommand("%-12345X"); + } + + /** + * Resets the printer and restores the user default environment. + * @throws IOException In case of an I/O error + */ + public void resetPrinter() throws IOException { + writeCommand("E"); + } + + /** + * Sends the job separation command. + * @throws IOException In case of an I/O error + */ + public void separateJobs() throws IOException { + writeCommand("&l1T"); + } + + /** + * Sends the form feed character. + * @throws IOException In case of an I/O error + */ + public void formFeed() throws IOException { + out.write(12); //=OC ("FF", Form feed) + } + + /** + * Clears the horizontal margins. + * @throws IOException In case of an I/O error + */ + public void clearHorizontalMargins() throws IOException { + writeCommand("9"); + } + + /** + * The Top Margin command designates the number of lines between + * the top of the logical page and the top of the text area. + * @param numberOfLines the number of lines (See PCL specification for details) + * @throws IOException In case of an I/O error + */ + public void setTopMargin(int numberOfLines) throws IOException { + writeCommand("&l" + numberOfLines + "E"); + } + + /** + * Sets the cursor to a new absolute coordinate. + * @param x the X coordinate (in millipoints) + * @param y the Y coordinate (in millipoints) + * @throws IOException In case of an I/O error + */ + public void setCursorPos(int x, int y) throws IOException { + writeCommand("*p" + (x / 100) + "h" + (y / 100) + "V"); + } + + /** + * Generate a filled rectangle + * + * @param x the x position of left edge in millipoints + * @param y the y position of top edge in millipoints + * @param w the width in millipoints + * @param h the height in millipoints + * @param col the fill color + * @throws IOException In case of an I/O error + */ + protected void fillRect(int x, int y, int w, int h, Color col) throws IOException { + if ((w == 0) || (h == 0)) { + return; + } + if (h < 0) { + h *= -1; + } else { + //y += h; + } + + int xpos = (x / 100); + if (xpos < 0) { + //A negative x coordinate can lead to a displaced rectangle (xpos must be >= 0) + w += x; + xpos = 0; + } + writeCommand("*v1O"); + writeCommand("&a" + formatDouble4(xpos) + "h" + + formatDouble4(y / 100) + "V"); + writeCommand("*c" + formatDouble4(w / 100) + "h" + + formatDouble4(h / 100) + "V"); + int lineshade = convertToPCLShade(col); + writeCommand("*c" + lineshade + "G"); + writeCommand("*c2P"); + // Reset pattern transparency mode. + writeCommand("*v0O"); + } + + /** + * Sets the pattern transparency mode. + * @param transparent true if transparent, false for opaque + * @throws IOException In case of an I/O error + */ + public void setPatternTransparencyMode(boolean transparent) throws IOException { + if (transparent) { + writeCommand("*v0O"); + } else { + writeCommand("*v1O"); + } + } + + /** + * Convert an RGB color value to a grayscale from 0 to 100. + * @param r the red component + * @param g the green component + * @param b the blue component + * @return the gray value + */ + public final int convertToGray(int r, int g, int b) { + return (r * 30 + g * 59 + b * 11) / 100; + } + + /** + * Convert a Color value to a PCL shade value (0-100). + * @param col the color + * @return the PCL shade value (100=black) + */ + public final int convertToPCLShade(Color col) { + float gray = convertToGray(col.getRed(), col.getGreen(), col.getBlue()) / 255f; + return (int)(100 - (gray * 100f)); + } + + /** + * Select the current pattern + * @param patternID the pattern ID (*c#G command) + * @param pattern the pattern type (*v#T command) + * @throws IOException In case of an I/O error + */ + public void selectCurrentPattern(int patternID, int pattern) throws IOException { + writeCommand("*c" + patternID + "G"); + writeCommand("*v" + pattern + "T"); + } + + /** + * Indicates whether an image is a monochrome (b/w) image. + * @param img the image + * @return true if it's a monochrome image + */ + public static boolean isMonochromeImage(RenderedImage img) { + ColorModel cm = img.getColorModel(); + if (cm instanceof IndexColorModel) { + IndexColorModel icm = (IndexColorModel)cm; + return icm.getMapSize() == 2; + } else { + return false; + } + } + + /** + * Indicates whether an image is a grayscale image. + * @param img the image + * @return true if it's a grayscale image + */ + public static boolean isGrayscaleImage(RenderedImage img) { + return (img.getColorModel().getColorSpace().getNumComponents() == 1); + } + + private MonochromeBitmapConverter createMonochromeBitmapConverter() { + MonochromeBitmapConverter converter = null; + try { + String clName = "org.apache.fop.render.pcl.JAIMonochromeBitmapConverter"; + Class clazz = Class.forName(clName); + converter = (MonochromeBitmapConverter)clazz.newInstance(); + } catch (ClassNotFoundException cnfe) { + // Class was not compiled so is not available. Simply ignore. + } catch (LinkageError le) { + // This can happen if fop was build with support for a + // particular provider (e.g. a binary fop distribution) + // but the required support files (i.e. JAI) are not + // available in the current runtime environment. + // Simply continue with the backup implementation. + } catch (InstantiationException e) { + // Problem instantiating the class, simply continue with the backup implementation + } catch (IllegalAccessException e) { + // Problem instantiating the class, simply continue with the backup implementation + } + if (converter == null) { + converter = new DefaultMonochromeBitmapConverter(); + } + return converter; + } + + private int calculatePCLResolution(int resolution) { + return calculatePCLResolution(resolution, false); + } + + /** + * Calculates the ideal PCL resolution for a given resolution. + * @param resolution the input resolution + * @param increased true if you want to go to a higher resolution, for example if you + * convert grayscale or color images to monochrome images so dithering has + * a chance to generate better quality. + * @return the resulting PCL resolution (one of 75, 100, 150, 200, 300, 600) + */ + private int calculatePCLResolution(int resolution, boolean increased) { + for (int i = PCL_RESOLUTIONS.length - 2; i >= 0; i--) { + if (resolution > PCL_RESOLUTIONS[i]) { + int idx = i + 1; + if (idx < PCL_RESOLUTIONS.length - 2) { + idx += increased ? 2 : 0; + } else if (idx < PCL_RESOLUTIONS.length - 1) { + idx += increased ? 1 : 0; + } + return PCL_RESOLUTIONS[idx]; + } + } + return PCL_RESOLUTIONS[increased ? 2 : 0]; + } + + private boolean isValidPCLResolution(int resolution) { + return resolution == calculatePCLResolution(resolution); + } + + private Dimension getAdjustedDimension(Dimension orgDim, int orgResolution, + int pclResolution) { + if (orgResolution == pclResolution) { + return orgDim; + } else { + Dimension result = new Dimension(); + result.width = (int)Math.round((double)orgDim.width * pclResolution / orgResolution); + result.height = (int)Math.round((double)orgDim.height * pclResolution / orgResolution); + return result; + } + } + + /** + * Paint a bitmap at the current cursor position. The bitmap is converted to a monochrome + * (1-bit) bitmap image. + * @param img the bitmap image + * @param resolution the original resolution of the image (in dpi) + * @throws IOException In case of an I/O error + */ + public void paintBitmap(RenderedImage img, int resolution) throws IOException { + boolean monochrome = isMonochromeImage(img); + if (!monochrome) { + int effResolution = calculatePCLResolution(resolution, true); + Dimension orgDim = new Dimension(img.getWidth(), img.getHeight()); + Dimension effDim = getAdjustedDimension(orgDim, resolution, effResolution); + boolean scaled = !orgDim.equals(effDim); + BufferedImage src = null; + if (img instanceof BufferedImage && !scaled) { + if (!isGrayscaleImage(img)) { + src = new BufferedImage(effDim.width, effDim.height, + BufferedImage.TYPE_BYTE_GRAY); + ColorConvertOp op = new ColorConvertOp( + ColorSpace.getInstance(ColorSpace.CS_GRAY), null); + op.filter((BufferedImage)img, src); + } else { + src = (BufferedImage)img; + } + } + if (src == null) { + src = new BufferedImage(effDim.width, effDim.height, + BufferedImage.TYPE_BYTE_GRAY); + Graphics2D g2d = src.createGraphics(); + try { + AffineTransform at = new AffineTransform(); + double sx = effDim.getWidth() / orgDim.getWidth(); + double sy = effDim.getHeight() / orgDim.getHeight(); + at.scale(sx, sy); + g2d.drawRenderedImage(img, at); + } finally { + g2d.dispose(); + } + } + MonochromeBitmapConverter converter = createMonochromeBitmapConverter(); + converter.setHint("quality", "false"); + + long start = System.currentTimeMillis(); + BufferedImage buf = (BufferedImage)converter.convertToMonochrome(src); + long duration = System.currentTimeMillis() - start; + System.out.println(duration + " ms"); + + RenderedImage red = buf; + paintMonochromeBitmap(red, effResolution); + } else { + int effResolution = calculatePCLResolution(resolution); + paintMonochromeBitmap(img, effResolution); + } + } + + /** + * Paint a bitmap at the current cursor position. The bitmap must be a monochrome + * (1-bit) bitmap image. + * @param img the bitmap image (must be 1-bit b/w) + * @param resolution the resolution of the image (must be a PCL resolution) + * @throws IOException In case of an I/O error + */ + public void paintMonochromeBitmap(RenderedImage img, int resolution) throws IOException { + if (!isValidPCLResolution(resolution)) { + throw new IllegalArgumentException("Invalid PCL resolution: " + resolution); + } + writeCommand("*t" + resolution + "R"); + writeCommand("*r0f" + img.getHeight() + "t" + img.getWidth() + "s1A"); + Raster raster = img.getData(); + boolean monochrome = isMonochromeImage(img); + if (!monochrome) { + throw new IllegalArgumentException("img must be a monochrome image"); + } + + int x = 0; + int y = 0; + int imgw = img.getWidth(); + int imgh = img.getHeight(); + int bytewidth = (imgw / 8); + if ((imgw % 8) != 0) { + bytewidth++; + } + byte ib; + byte[] rle = new byte[bytewidth * 2]; //compressed (RLE) + byte[] uncompressed = new byte[bytewidth]; //uncompressed + int lastcount = -1; + byte lastbyte = 0; + int rlewidth = 0; + /* + int xres = (iw * 72000) / w; + int yres = (ih * 72000) / h; + int resolution = xres; + if (yres > xres) + resolution = yres; + + if (resolution > 300) + resolution = 600; + else if (resolution > 150) + resolution = 300; + else if (resolution > 100) + resolution = 150; + else if (resolution > 75) + resolution = 100; + else + resolution = 75; + */ + + // Transfer graphics data + for (y = 0; y < imgh; y++) { + ib = 0; + for (x = 0; x < imgw; x++) { + int sample = raster.getSample(x, y, 0); + //Set image bit for black + if ((sample == 0)) { + ib |= (1 << (7 - (x % 8))); + } + + //RLE encoding + if ((x % 8) == 7 || ((x + 1) == imgw)) { + if (rlewidth < bytewidth) { + if (lastcount >= 0) { + if (ib == lastbyte) { + lastcount++; + } else { + rle[rlewidth++] = (byte)(lastcount & 0xFF); + rle[rlewidth++] = lastbyte; + lastbyte = ib; + lastcount = 0; + } + } else { + lastbyte = ib; + lastcount = 0; + } + if (lastcount == 255 || ((x + 1) == imgw)) { + rle[rlewidth++] = (byte)(lastcount & 0xFF); + rle[rlewidth++] = lastbyte; + lastbyte = 0; + lastcount = -1; + } + } + uncompressed[x / 8] = ib; + ib = 0; + } + } + if (rlewidth < bytewidth) { + writeCommand("*b1m" + rlewidth + "W"); + this.out.write(rle, 0, rlewidth); + } else { + writeCommand("*b0m" + bytewidth + "W"); + this.out.write(uncompressed); + } + lastcount = -1; + rlewidth = 0; + } + + // End raster graphics + writeCommand("*rB"); + } + +} diff --git a/src/sandbox/org/apache/fop/render/pcl/PCLGraphics2D.java b/src/sandbox/org/apache/fop/render/pcl/PCLGraphics2D.java new file mode 100644 index 000000000..5e976505f --- /dev/null +++ b/src/sandbox/org/apache/fop/render/pcl/PCLGraphics2D.java @@ -0,0 +1,430 @@ +/* + * Copyright 2006 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.pcl; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsEnvironment; +import java.awt.Image; +import java.awt.Paint; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.AffineTransform; +import java.awt.geom.PathIterator; +import java.awt.geom.Point2D; +import java.awt.image.BufferedImage; +import java.awt.image.ImageObserver; +import java.awt.image.RenderedImage; +import java.awt.image.renderable.RenderableImage; +import java.io.IOException; +import java.text.AttributedCharacterIterator; + +import org.apache.fop.util.UnitConv; +import org.apache.xmlgraphics.java2d.AbstractGraphics2D; +import org.apache.xmlgraphics.java2d.GraphicContext; + +/** + * Graphics2D implementation implementing PCL and HP GL/2. + */ +public class PCLGraphics2D extends AbstractGraphics2D { + + /** The PCL generator */ + protected PCLGenerator gen; + + /** + * Create a new PCLGraphics2D. + * @param gen the PCL Generator to paint with + */ + public PCLGraphics2D(PCLGenerator gen) { + super(true); + this.gen = gen; + } + + /** + * Copy constructor + * @param g parent PCLGraphics2D + */ + public PCLGraphics2D(PCLGraphics2D g) { + super(true); + this.gen = g.gen; + } + + /** @see java.awt.Graphics#create() */ + public Graphics create() { + return new PCLGraphics2D(this); + } + + /** @see java.awt.Graphics#dispose() */ + public void dispose() { + this.gen = null; + } + + /** + * Sets the GraphicContext + * @param c GraphicContext to use + */ + public void setGraphicContext(GraphicContext c) { + this.gc = c; + } + + /** + * Central handler for IOExceptions for this class. + * @param ioe IOException to handle + */ + public void handleIOException(IOException ioe) { + //TODO Surely, there's a better way to do this. + ioe.printStackTrace(); + } + + /** @see java.awt.Graphics2D#getDeviceConfiguration() */ + public GraphicsConfiguration getDeviceConfiguration() { + return GraphicsEnvironment.getLocalGraphicsEnvironment(). + getDefaultScreenDevice().getDefaultConfiguration(); + } + + /** + * Applies a new Stroke object. + * @param stroke Stroke object to use + * @throws IOException In case of an I/O problem + */ + protected void applyStroke(Stroke stroke) throws IOException { + if (stroke instanceof BasicStroke) { + BasicStroke bs = (BasicStroke)stroke; + + float[] da = bs.getDashArray(); + if (da != null) { + + gen.writeText("UL1,"); + for (int idx = 0, len = Math.min(20, da.length); idx < len; idx++) { + gen.writeText(gen.formatDouble4(da[idx])); + if (idx < da.length - 1) { + gen.writeText(","); + } + } + gen.writeText(";"); + /* TODO Dash phase NYI + float offset = bs.getDashPhase(); + gen.writeln(gen.formatDouble4(offset) + " setdash"); + */ + gen.writeText("LT1;"); + } else { + gen.writeText("LT;"); + } + + gen.writeText("LA1"); //line cap + int ec = bs.getEndCap(); + switch (ec) { + case BasicStroke.CAP_BUTT: + gen.writeText(",1"); + break; + case BasicStroke.CAP_ROUND: + gen.writeText(",4"); + break; + case BasicStroke.CAP_SQUARE: + gen.writeText(",2"); + break; + default: System.err.println("Unsupported line cap: " + ec); + } + + gen.writeText(",2"); //line join + int lj = bs.getLineJoin(); + switch (lj) { + case BasicStroke.JOIN_MITER: + gen.writeText(",1"); + break; + case BasicStroke.JOIN_ROUND: + gen.writeText(",4"); + break; + case BasicStroke.JOIN_BEVEL: + gen.writeText(",5"); + break; + default: System.err.println("Unsupported line join: " + lj); + } + + float ml = bs.getMiterLimit(); + gen.writeText(",3" + gen.formatDouble4(ml)); + + float lw = bs.getLineWidth(); + Point2D ptSrc = new Point2D.Double(lw, 0); + //Pen widths are set as absolute metric values (WU0;) + Point2D ptDest = getTransform().transform(ptSrc, null); + double transDist = UnitConv.pt2mm(ptDest.distance(0, 0)); + //System.out.println("--" + ptDest.distance(0, 0) + " " + transDist); + gen.writeText(";PW" + gen.formatDouble4(transDist) + ";"); + + } else { + System.err.println("Unsupported Stroke: " + stroke.getClass().getName()); + } + } + + /** + * Applies a new Paint object. + * @param paint Paint object to use + * @throws IOException In case of an I/O problem + */ + protected void applyPaint(Paint paint) throws IOException { + if (paint instanceof Color) { + Color col = (Color)paint; + int shade = gen.convertToPCLShade(col); + gen.writeText("TR0;FT10," + shade + ";"); + } else { + System.err.println("Unsupported Paint: " + paint.getClass().getName()); + } + } + + /** @see java.awt.Graphics2D#draw(java.awt.Shape) */ + public void draw(Shape s) { + try { + AffineTransform trans = getTransform(); + + Shape imclip = getClip(); + //writeClip(imclip); + //establishColor(getColor()); + + applyPaint(getPaint()); + applyStroke(getStroke()); + + //gen.writeln("newpath"); + PathIterator iter = s.getPathIterator(trans); + processPathIterator(iter); + gen.writeText("EP;"); + } catch (IOException ioe) { + handleIOException(ioe); + } + } + + /** @see java.awt.Graphics2D#fill(java.awt.Shape) */ + public void fill(Shape s) { + try { + AffineTransform trans = getTransform(); + Shape imclip = getClip(); + //writeClip(imclip); + + //establishColor(getColor()); + + applyPaint(getPaint()); + + PathIterator iter = s.getPathIterator(trans); + processPathIterator(iter); + int fillMethod = (iter.getWindingRule() == PathIterator.WIND_EVEN_ODD ? 0 : 1); + gen.writeText("FP" + fillMethod + ";"); + } catch (IOException ioe) { + handleIOException(ioe); + } + } + + /** + * Processes a path iterator generating the nexessary painting operations. + * @param iter PathIterator to process + * @throws IOException In case of an I/O problem. + */ + public void processPathIterator(PathIterator iter) throws IOException { + double[] vals = new double[6]; + boolean penDown = false; + boolean hasFirst = false; + double x = 0, firstX = 0; + double y = 0, firstY = 0; + boolean pendingPM0 = true; + penUp(); + while (!iter.isDone()) { + int type = iter.currentSegment(vals); + if (type == PathIterator.SEG_CLOSE) { + hasFirst = false; + /* + if (firstX != x && firstY != y) { + plotAbsolute(firstX, firstY); + }*/ + //penUp(); + gen.writeText("PM1;"); + iter.next(); + continue; + } + if (type == PathIterator.SEG_MOVETO) { + if (penDown) { + penUp(); + penDown = false; + } + } else { + if (!penDown) { + penDown(); + penDown = true; + } + } + switch (type) { + case PathIterator.SEG_CUBICTO: + x = vals[4]; + y = vals[5]; + bezierAbsolute(vals[0], vals[1], vals[2], vals[3], x, y); + break; + case PathIterator.SEG_LINETO: + x = vals[0]; + y = vals[1]; + plotAbsolute(x, y); + break; + case PathIterator.SEG_MOVETO: + x = vals[0]; + y = vals[1]; + plotAbsolute(x, y); + break; + case PathIterator.SEG_QUADTO: + double originX = x; + double originY = y; + x = vals[2]; + y = vals[3]; + quadraticBezierAbsolute(originX, originY, vals[0], vals[1], x, y); + break; + case PathIterator.SEG_CLOSE: + break; + default: + break; + } + if (pendingPM0) { + pendingPM0 = false; + gen.writeText("PM;"); + } + if (!hasFirst) { + firstX = x; + firstY = y; + } + iter.next(); + } + gen.writeText("PM2;"); + } + + private void plotAbsolute(double x, double y) throws IOException { + gen.writeText("PA" + gen.formatDouble4(x) + "," + + gen.formatDouble4(y) + ";"); + } + + private void bezierAbsolute(double x1, double y1, double x2, double y2, double x3, double y3) + throws IOException { + gen.writeText("BZ" + gen.formatDouble4(x1) + "," + + gen.formatDouble4(y1) + "," + + gen.formatDouble4(x2) + "," + + gen.formatDouble4(y2) + "," + + gen.formatDouble4(x3) + "," + + gen.formatDouble4(y3) + ";"); + } + + private void quadraticBezierAbsolute(double originX, double originY, + double x1, double y1, double x2, double y2) + throws IOException { + //Quadratic Bezier curve can be mapped to a normal bezier curve + //See http://pfaedit.sourceforge.net/bezier.html + double nx1 = originX + (2.0 / 3.0) * (x1 - originX); + double ny1 = originY + (2.0 / 3.0) * (y1 - originY); + + double nx2 = nx1 + (1.0 / 3.0) * (x2 - originX); + double ny2 = ny1 + (1.0 / 3.0) * (y2 - originY); + + bezierAbsolute(nx1, ny1, nx2, ny2, x2, y2); + } + + private void penDown() throws IOException { + gen.writeText("PD;"); + } + + private void penUp() throws IOException { + gen.writeText("PU;"); + } + + /** @see java.awt.Graphics2D#drawString(java.lang.String, float, float) */ + public void drawString(String s, float x, float y) { + // TODO Auto-generated method stub + System.err.println("drawString NYI"); + } + + /** @see java.awt.Graphics2D#drawString(java.text.AttributedCharacterIterator, float, float) */ + public void drawString(AttributedCharacterIterator iterator, float x, + float y) { + // TODO Auto-generated method stub + System.err.println("drawString NYI"); + } + + /** + * @see java.awt.Graphics2D#drawRenderedImage(java.awt.image.RenderedImage, + * java.awt.geom.AffineTransform) + */ + public void drawRenderedImage(RenderedImage img, AffineTransform xform) { + // TODO Auto-generated method stub + System.err.println("drawRenderedImage NYI"); + } + + /** + * @see java.awt.Graphics2D#drawRenderableImage(java.awt.image.renderable.RenderableImage, + * java.awt.geom.AffineTransform) + */ + public void drawRenderableImage(RenderableImage img, AffineTransform xform) { + // TODO Auto-generated method stub + System.err.println("drawRenderedImage NYI"); + } + + /** + * @see java.awt.Graphics#drawImage(java.awt.Image, int, int, int, int, + * java.awt.image.ImageObserver) + */ + public boolean drawImage(Image img, int x, int y, int width, int height, + ImageObserver observer) { + // TODO Auto-generated method stub + System.err.println("drawImage NYI"); + return false; + } + + /** + * @see java.awt.Graphics#drawImage(java.awt.Image, int, int, java.awt.image.ImageObserver) + */ + public boolean drawImage(Image img, int x, int y, ImageObserver observer) { + // TODO Auto-generated method stub + System.err.println("drawImage NYI"); + return false; + } + + /** @see java.awt.Graphics#copyArea(int, int, int, int, int, int) */ + public void copyArea(int x, int y, int width, int height, int dx, int dy) { + // TODO Auto-generated method stub + System.err.println("copyArea NYI"); + } + + /** @see java.awt.Graphics#setXORMode(java.awt.Color) */ + public void setXORMode(Color c1) { + // TODO Auto-generated method stub + System.err.println("setXORMode NYI"); + } + + /** + * Used to create proper font metrics + */ + private Graphics2D fmg; + + { + BufferedImage bi = new BufferedImage(1, 1, + BufferedImage.TYPE_INT_ARGB); + + fmg = bi.createGraphics(); + } + + /** @see java.awt.Graphics#getFontMetrics(java.awt.Font) */ + public java.awt.FontMetrics getFontMetrics(java.awt.Font f) { + return fmg.getFontMetrics(f); + } + +} diff --git a/src/sandbox/org/apache/fop/render/pcl/PCLGraphics2DAdapter.java b/src/sandbox/org/apache/fop/render/pcl/PCLGraphics2DAdapter.java new file mode 100644 index 000000000..268cc24ec --- /dev/null +++ b/src/sandbox/org/apache/fop/render/pcl/PCLGraphics2DAdapter.java @@ -0,0 +1,137 @@ +/* + * Copyright 2006 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.pcl; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.io.IOException; + +import org.apache.fop.render.Graphics2DAdapter; +import org.apache.fop.render.Graphics2DImagePainter; +import org.apache.fop.render.RendererContext; +import org.apache.fop.util.UnitConv; +import org.apache.xmlgraphics.java2d.GraphicContext; + +/** + * Graphics2DAdapter implementation for PCL and HP GL/2. + */ +public class PCLGraphics2DAdapter implements Graphics2DAdapter { + + /** + * Main constructor + */ + public PCLGraphics2DAdapter() { + } + + /** @see org.apache.fop.render.Graphics2DAdapter */ + public void paintImage(Graphics2DImagePainter painter, + RendererContext context, + int x, int y, int width, int height) throws IOException { + PCLRendererContext pclContext = PCLRendererContext.wrapRendererContext(context); + PCLRenderer pcl = (PCLRenderer)context.getRenderer(); + PCLGenerator gen = pcl.gen; + + // get the 'width' and 'height' attributes of the SVG document + Dimension dim = painter.getImageSize(); + float imw = (float)dim.getWidth(); + float imh = (float)dim.getHeight(); + + boolean paintAsBitmap = pclContext.paintAsBitmap(); + if (paintAsBitmap) { + int resolution = 300; //TODO not hard-coded, please! + int bmw = UnitConv.mpt2px(pclContext.getWidth(), resolution); + int bmh = UnitConv.mpt2px(pclContext.getHeight(), resolution); + BufferedImage bi = new BufferedImage( + bmw, bmh, + BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = bi.createGraphics(); + try { + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_OFF); + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); + g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, + RenderingHints.VALUE_FRACTIONALMETRICS_OFF); + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, + RenderingHints.VALUE_COLOR_RENDER_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); + g2d.setRenderingHint(RenderingHints.KEY_DITHERING, + RenderingHints.VALUE_DITHER_ENABLE); + g2d.setBackground(Color.white); + g2d.setColor(Color.black); + g2d.clearRect(0, 0, bmw, bmh); + double sx = (double)bmw / pclContext.getWidth() * 1000; + double sy = (double)bmh / pclContext.getHeight() * 1000; + g2d.scale(sx, sy); + + //Paint the SVG on the BufferedImage + Rectangle2D area = new Rectangle2D.Double( + 0.0, 0.0, pclContext.getWidth(), pclContext.getHeight()); + painter.paint(g2d, area); + } finally { + g2d.dispose(); + } + + pcl.moveTo(x, y); + gen.paintBitmap(bi, resolution); + } else { + pcl.saveGraphicsState(); + GraphicContext ctx = (GraphicContext)pcl.getGraphicContext().clone(); + + // Clip to the image area. + //gen.writeln("newpath"); + //gen.defineRect(fx, fy, fwidth, fheight); + //gen.writeln("clip"); + + AffineTransform prepareHPGL2 = new AffineTransform(); + //prepareHPGL2.scale(1, 1); + ctx.setTransform(prepareHPGL2); + + pcl.moveTo(x, y); + gen.writeCommand("*c" + gen.formatDouble4(width / 100f) + "x" + + gen.formatDouble4(height / 100f) + "Y"); + gen.writeCommand("*c0T"); + gen.writeCommand("%0B"); + gen.writeText("IN;"); + gen.writeText("SP1;"); + //One Plotter unit is 0.025mm! + double scale = imw / UnitConv.mm2pt(imw * 0.025); + gen.writeText("SC0," + gen.formatDouble4(scale) + + ",0,-" + gen.formatDouble4(scale) + ",2;"); + gen.writeText("IR0,100,0,100;"); + gen.writeText("PU;PA0,0;"); + PCLGraphics2D graphics = new PCLGraphics2D(gen); + graphics.setGraphicContext(ctx); + Rectangle2D area = new Rectangle2D.Double(0.0, 0.0, imw, imh); + painter.paint(graphics, area); + + gen.writeCommand("%0A"); + pcl.restoreGraphicsState(); + } + } + +} diff --git a/src/sandbox/org/apache/fop/render/pcl/PCLPageDefinition.java b/src/sandbox/org/apache/fop/render/pcl/PCLPageDefinition.java new file mode 100644 index 000000000..40a0eddd7 --- /dev/null +++ b/src/sandbox/org/apache/fop/render/pcl/PCLPageDefinition.java @@ -0,0 +1,143 @@ +/* + * Copyright 2006 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.pcl; + +import java.util.Iterator; +import java.util.List; + +import org.apache.fop.util.UnitConv; + +/** + * This class represents a page format with PCL-specific properties. + */ +public class PCLPageDefinition { + + private static List pageDefinitions; + + private String name; + private long width; //in mpt + private long height; //in mpt + private int logicalPageXOffset; //in mpt + private boolean landscape; + + static { + createPageDefinitions(); + } + + public PCLPageDefinition(String name, long width, long height, int logicalPageXOffset) { + this(name, width, height, logicalPageXOffset, false); + } + + public PCLPageDefinition(String name, long width, long height, int logicalPageXOffset, + boolean landscape) { + this.name = name; + this.width = width; + this.height = height; + this.logicalPageXOffset = logicalPageXOffset; + this.landscape = landscape; + } + + public String getName() { + return this.name; + } + + public boolean isLandscapeFormat() { + return this.landscape; + } + + public int getLogicalPageXOffset() { + return this.logicalPageXOffset; + } + + public boolean matches(long width, long height, int errorMargin) { + return (Math.abs(this.width - width) < errorMargin) + && (Math.abs(this.height - height) < errorMargin); + } + + /** @see java.lang.Object#toString() */ + public String toString() { + return getName(); + } + + public static PCLPageDefinition getPageDefinition(long width, long height, int errorMargin) { + Iterator iter = pageDefinitions.iterator(); + while (iter.hasNext()) { + PCLPageDefinition def = (PCLPageDefinition)iter.next(); + if (def.matches(width, height, errorMargin)) { + return def; + } + } + return null; + } + + /** + * Converts an offset values for logical pages to millipoints. The values are given as pixels + * in a 300dpi environment. + * @param offset the offset as given in the PCL 5 specification (under "Printable Area") + * @return the converted value in millipoints + */ + private static int convertLogicalPageXOffset(int offset) { + return (int)Math.round(((double)offset) * 72000 / 300); + } + + private static void createPageDefinitions() { + pageDefinitions = new java.util.ArrayList(); + pageDefinitions.add(new PCLPageDefinition("Letter", + Math.round(UnitConv.in2mpt(8.5)), Math.round(UnitConv.in2mpt(11)), + convertLogicalPageXOffset(75))); + pageDefinitions.add(new PCLPageDefinition("Legal", + Math.round(UnitConv.in2mpt(8.5)), Math.round(UnitConv.in2mpt(14)), + convertLogicalPageXOffset(75))); + pageDefinitions.add(new PCLPageDefinition("Executive", + Math.round(UnitConv.in2mpt(7.25)), Math.round(UnitConv.in2mpt(10.5)), + convertLogicalPageXOffset(75))); + pageDefinitions.add(new PCLPageDefinition("Ledger", + Math.round(UnitConv.in2mpt(11)), Math.round(UnitConv.in2mpt(17)), + convertLogicalPageXOffset(75))); + pageDefinitions.add(new PCLPageDefinition("A4", + Math.round(UnitConv.mm2mpt(210)), Math.round(UnitConv.mm2mpt(297)), + convertLogicalPageXOffset(71))); + pageDefinitions.add(new PCLPageDefinition("A3", + Math.round(UnitConv.mm2mpt(297)), Math.round(UnitConv.mm2mpt(420)), + convertLogicalPageXOffset(71))); + + //TODO Add envelope definitions + + pageDefinitions.add(new PCLPageDefinition("LetterL", + Math.round(UnitConv.in2mpt(11)), Math.round(UnitConv.in2mpt(8.5)), + convertLogicalPageXOffset(60))); + pageDefinitions.add(new PCLPageDefinition("LegalL", + Math.round(UnitConv.in2mpt(14)), Math.round(UnitConv.in2mpt(8.5)), + convertLogicalPageXOffset(60))); + pageDefinitions.add(new PCLPageDefinition("ExecutiveL", + Math.round(UnitConv.in2mpt(10.5)), Math.round(UnitConv.in2mpt(7.25)), + convertLogicalPageXOffset(60))); + pageDefinitions.add(new PCLPageDefinition("LedgerL", + Math.round(UnitConv.in2mpt(17)), Math.round(UnitConv.in2mpt(11)), + convertLogicalPageXOffset(60))); + pageDefinitions.add(new PCLPageDefinition("A4L", + Math.round(UnitConv.mm2mpt(297)), Math.round(UnitConv.mm2mpt(210)), + convertLogicalPageXOffset(59), true)); + pageDefinitions.add(new PCLPageDefinition("A3L", + Math.round(UnitConv.mm2mpt(420)), Math.round(UnitConv.mm2mpt(297)), + convertLogicalPageXOffset(59))); + } + + +} diff --git a/src/sandbox/org/apache/fop/render/pcl/PCLRenderer.java b/src/sandbox/org/apache/fop/render/pcl/PCLRenderer.java index 58f4c16fb..04e619822 100644 --- a/src/sandbox/org/apache/fop/render/pcl/PCLRenderer.java +++ b/src/sandbox/org/apache/fop/render/pcl/PCLRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2005 The Apache Software Foundation. + * Copyright 1999-2006 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. @@ -19,53 +19,109 @@ package org.apache.fop.render.pcl; // FOP +import org.apache.fop.apps.FOPException; import org.apache.fop.apps.MimeConstants; +import org.apache.fop.area.Area; +import org.apache.fop.area.Block; +import org.apache.fop.area.BlockViewport; import org.apache.fop.area.CTM; +import org.apache.fop.area.PageViewport; +import org.apache.fop.area.Trait; +import org.apache.fop.area.Trait.Color; +import org.apache.fop.area.inline.AbstractTextArea; +import org.apache.fop.area.inline.ForeignObject; +import org.apache.fop.area.inline.Image; +import org.apache.fop.area.inline.SpaceArea; +import org.apache.fop.area.inline.TextArea; +import org.apache.fop.area.inline.Viewport; +import org.apache.fop.area.inline.WordArea; +import org.apache.fop.fonts.Font; +import org.apache.fop.image.EPSImage; +import org.apache.fop.image.FopImage; +import org.apache.fop.image.ImageFactory; +import org.apache.fop.image.XMLImage; +import org.apache.fop.render.Graphics2DAdapter; import org.apache.fop.render.PrintRenderer; +import org.apache.fop.render.RendererContext; +import org.apache.fop.render.RendererContextConstants; +import org.apache.fop.traits.BorderProps; +import org.apache.xmlgraphics.java2d.GraphicContext; +import org.w3c.dom.Document; // Java +import java.awt.Rectangle; +import java.awt.color.ColorSpace; +import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.PixelInterleavedSampleModel; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.awt.image.SampleModel; +import java.awt.image.WritableRaster; import java.io.IOException; import java.io.OutputStream; +import java.util.List; +import java.util.Map; +import java.util.Stack; /** - * Renderer that renders areas to PCL - * Created by Arthur E Welch III while at M&I EastPoint Technology - * Donated by EastPoint to the Apache FOP project March 2, 2001. + * Renderer for the PCL 5 printer language. It also uses HP GL/2 for certain graphic elements. */ public class PCLRenderer extends PrintRenderer { /** The MIME type for PCL */ public static final String MIME_TYPE = MimeConstants.MIME_PCL_ALT; - /** - * the current stream to add PCL commands to - */ - protected PCLStream currentStream; - - private int pageHeight = 7920; - - // These variables control the virtual paggination functionality. - private int curdiv = 0; - private int divisions = -1; - private int paperheight = -1; // Paper height in decipoints? - private int orientation = -1; // -1=default/unknown, 0=portrait, 1=landscape. - private int topmargin = -1; // Top margin in decipoints? - private int leftmargin = -1; // Left margin in decipoints? - private int fullmargin = 0; - private final boolean debug = false; - - private int xoffset = -180; // X Offset to allow for PCL implicit 1/4" left margin. - - private java.util.Hashtable options; + /** The OutputStream to write the PCL stream to */ + protected OutputStream out; + + /** The PCL generator */ + protected PCLGenerator gen; + private boolean ioTrouble = false; + private Stack graphicContextStack = new Stack(); + private GraphicContext graphicContext = new GraphicContext(); + /** * Create the PCL renderer */ public PCLRenderer() { } - public void setFont(String name, float size) { + /** + * Central exception handler for I/O exceptions. + * @param ioe IOException to handle + */ + protected void handleIOTrouble(IOException ioe) { + if (!ioTrouble) { + log.error("Error while writing to target file", ioe); + ioTrouble = true; + } + } + + /** @see org.apache.fop.render.Renderer#getGraphics2DAdapter() */ + public Graphics2DAdapter getGraphics2DAdapter() { + return new PCLGraphics2DAdapter(); + } + + /** @return the GraphicContext used to track coordinate system transformations */ + public GraphicContext getGraphicContext() { + return this.graphicContext; + } + + /** + * Sets the current font (NOTE: Hard-coded font mappings ATM!) + * @param name the font name (internal F* names for now) + * @param size the font size + * @throws IOException if an I/O problem occurs + */ + public void setFont(String name, float size) throws IOException { int fontcode = 0; if (name.length() > 1 && name.charAt(0) == 'F') { try { @@ -74,113 +130,113 @@ public class PCLRenderer extends PrintRenderer { log.error(e); } } + String formattedSize = gen.formatDouble2(size / 1000); switch (fontcode) { case 1: // F1 = Helvetica - // currentStream.add("\033(8U\033(s1p" + (size / 1000) + "v0s0b24580T"); + // gen.writeCommand("(8U"); + // gen.writeCommand("(s1p" + formattedSize + "v0s0b24580T"); // Arial is more common among PCL5 printers than Helvetica - so use Arial - currentStream.add("\033(0N\033(s1p" + (size / 1000) - + "v0s0b16602T"); + gen.writeCommand("(0N"); + gen.writeCommand("(s1p" + formattedSize + "v0s0b16602T"); break; case 2: // F2 = Helvetica Oblique - currentStream.add("\033(0N\033(s1p" + (size / 1000) - + "v1s0b16602T"); + gen.writeCommand("(0N"); + gen.writeCommand("(s1p" + formattedSize + "v1s0b16602T"); break; case 3: // F3 = Helvetica Bold - currentStream.add("\033(0N\033(s1p" + (size / 1000) - + "v0s3b16602T"); + gen.writeCommand("(0N"); + gen.writeCommand("(s1p" + formattedSize + "v0s3b16602T"); break; case 4: // F4 = Helvetica Bold Oblique - currentStream.add("\033(0N\033(s1p" + (size / 1000) - + "v1s3b16602T"); + gen.writeCommand("(0N"); + gen.writeCommand("(s1p" + formattedSize + "v1s3b16602T"); break; case 5: // F5 = Times Roman - // currentStream.add("\033(8U\033(s1p" + (size / 1000) + "v0s0b25093T"); + // gen.writeCommand("(8U"); + // gen.writeCommand("(s1p" + formattedSize + "v0s0b25093T"); // Times New is more common among PCL5 printers than Times - so use Times New - currentStream.add("\033(0N\033(s1p" + (size / 1000) - + "v0s0b16901T"); + gen.writeCommand("(0N"); + gen.writeCommand("(s1p" + formattedSize + "v0s0b16901T"); break; case 6: // F6 = Times Italic - currentStream.add("\033(0N\033(s1p" + (size / 1000) - + "v1s0b16901T"); + gen.writeCommand("(0N"); + gen.writeCommand("(s1p" + formattedSize + "v1s0b16901T"); break; case 7: // F7 = Times Bold - currentStream.add("\033(0N\033(s1p" + (size / 1000) - + "v0s3b16901T"); + gen.writeCommand("(0N"); + gen.writeCommand("(s1p" + formattedSize + "v0s3b16901T"); break; case 8: // F8 = Times Bold Italic - currentStream.add("\033(0N\033(s1p" + (size / 1000) - + "v1s3b16901T"); + gen.writeCommand("(0N"); + gen.writeCommand("(s1p" + formattedSize + "v1s3b16901T"); break; case 9: // F9 = Courier - currentStream.add("\033(0N\033(s0p" - + (120.01f / (size / 1000.00f)) + "h0s0b4099T"); + gen.writeCommand("(0N"); + gen.writeCommand("(s0p" + gen.formatDouble2(120.01f / (size / 1000.00f)) + + "h0s0b4099T"); break; case 10: // F10 = Courier Oblique - currentStream.add("\033(0N\033(s0p" - + (120.01f / (size / 1000.00f)) + "h1s0b4099T"); + gen.writeCommand("(0N"); + gen.writeCommand("(s0p" + gen.formatDouble2(120.01f / (size / 1000.00f)) + + "h1s0b4099T"); break; case 11: // F11 = Courier Bold - currentStream.add("\033(0N\033(s0p" - + (120.01f / (size / 1000.00f)) + "h0s3b4099T"); + gen.writeCommand("(0N"); + gen.writeCommand("(s0p" + gen.formatDouble2(120.01f / (size / 1000.00f)) + + "h0s3b4099T"); break; case 12: // F12 = Courier Bold Oblique - currentStream.add("\033(0N\033(s0p" - + (120.01f / (size / 1000.00f)) + "h1s3b4099T"); + gen.writeCommand("(0N"); + gen.writeCommand("(s0p" + gen.formatDouble2(120.01f / (size / 1000.00f)) + + "h1s3b4099T"); break; case 13: // F13 = Symbol - currentStream.add("\033(19M\033(s1p" + (size / 1000) - + "v0s0b16686T"); + gen.writeCommand("(19M"); + gen.writeCommand("(s1p" + formattedSize + "v0s0b16686T"); // ECMA Latin 1 Symbol Set in Times Roman??? - // currentStream.add("\033(9U\033(s1p" + (size / 1000) + "v0s0b25093T"); + // gen.writeCommand("(9U"); + // gen.writeCommand("(s1p" + formattedSize + "v0s0b25093T"); break; case 14: // F14 = Zapf Dingbats - currentStream.add("\033(14L\033(s1p" + (size / 1000) - + "v0s0b45101T"); + gen.writeCommand("(14L"); + gen.writeCommand("(s1p" + formattedSize + "v0s0b45101T"); break; default: - currentStream.add("\033(0N\033(s" + (size / 1000) + "V"); + gen.writeCommand("(0N"); + gen.writeCommand("(s" + formattedSize + "V"); break; } } /** @see org.apache.fop.render.Renderer#startRenderer(java.io.OutputStream) */ public void startRenderer(OutputStream outputStream) throws IOException { - log.info("rendering areas to PCL"); - log.fatal("The PCL Renderer is non-functional at this time. Please help resurrect it!"); - currentStream = new PCLStream(outputStream); + log.debug("Rendering areas to PCL..."); + this.out = outputStream; + this.gen = new PCLGenerator(out); - // Set orientation. - if (orientation > -1) { - currentStream.add("\033&l" + orientation + "O"); - } else { - currentStream.add("\033&l0O"); - } - if (orientation == 1 || orientation == 3) { - xoffset = -144; - } else { - xoffset = -180; - } - - // Reset the margins. - currentStream.add("\033" + "9\033&l0E"); + gen.universalEndOfLanguage(); + gen.resetPrinter(); } /** @see org.apache.fop.render.Renderer#stopRenderer() */ public void stopRenderer() throws IOException { + gen.separateJobs(); + gen.resetPrinter(); + gen.universalEndOfLanguage(); } /** @see org.apache.fop.render.AbstractRenderer */ @@ -188,18 +244,625 @@ public class PCLRenderer extends PrintRenderer { return MIME_TYPE; } + /** + * @see org.apache.fop.render.AbstractRenderer#renderPage(org.apache.fop.area.PageViewport) + */ + public void renderPage(PageViewport page) throws IOException, FOPException { + saveGraphicsState(); + final long pagewidth = Math.round(page.getViewArea().getWidth()); + final long pageheight = Math.round(page.getViewArea().getHeight()); + selectPageFormat(pagewidth, pageheight); + + if (false) { //TODO DEBUG CODE! Remove me. + //gen.fillRect(0, 0, (int)pagewidth, (int)pageheight, java.awt.Color.yellow); + //gen.fillRect(5000, 5000, (int)pagewidth - 10000, (int)pageheight - 10000, java.awt.Color.yellow); + //gen.fillRect(10000, 10000, (int)pagewidth / 4 - 20000, (int)pageheight / 4 - 20000, java.awt.Color.red); + for (int i = 0; i < 29; i++) { + if (i % 2 == 0) { + int w = (int)(10 * 2.835 * 1000); + Point2D p = transformedPoint(i * w, 0); + gen.fillRect((int)p.getX(), (int)p.getY(), w, w, java.awt.Color.yellow); + } + } + } + + super.renderPage(page); + gen.formFeed(); + restoreGraphicsState(); + } + + private void selectPageFormat(long pagewidth, long pageheight) throws IOException { + + PCLPageDefinition pageDef = PCLPageDefinition.getPageDefinition( + pagewidth, pageheight, 1000); + if (pageDef != null) { + // Adjust for the offset into the logical page + graphicContext.translate(-pageDef.getLogicalPageXOffset(), 0); + if (pageDef.isLandscapeFormat()) { + gen.writeCommand("&l1O"); //Orientation + } else { + gen.writeCommand("&l0O"); //Orientation + } + } else { + // Adjust for the offset into the logical page + // X Offset to allow for PCL implicit 1/4" left margin (= 180 decipoints) + graphicContext.translate(-18000, 18000); + gen.writeCommand("&l0O"); //Orientation + } + gen.clearHorizontalMargins(); + gen.setTopMargin(0); + } + + /** Saves the current graphics state on the stack. */ + protected void saveGraphicsState() { + graphicContextStack.push(graphicContext); + graphicContext = (GraphicContext)graphicContext.clone(); + } + + /** Restores the last graphics state from the stack. */ + protected void restoreGraphicsState() { + graphicContext = (GraphicContext)graphicContextStack.pop(); + } + + /** + * Clip an area. write a clipping operation given coordinates in the current + * transform. Coordinates are in points. + * + * @param x the x coordinate + * @param y the y coordinate + * @param width the width of the area + * @param height the height of the area + */ + protected void clipRect(float x, float y, float width, float height) { + //PCL cannot clip (only HP GL/2 can) + } + /** * @see org.apache.fop.render.AbstractRenderer#startVParea(CTM, Rectangle2D) */ protected void startVParea(CTM ctm, Rectangle2D clippingRect) { - // TODO Auto-generated method stub + saveGraphicsState(); + AffineTransform at = new AffineTransform(ctm.toArray()); + log.debug("startVPArea: " + at); + graphicContext.transform(at); } /** * @see org.apache.fop.render.AbstractRenderer#endVParea() */ protected void endVParea() { - // TODO Auto-generated method stub + restoreGraphicsState(); + } + + /** + * Handle block traits. + * The block could be any sort of block with any positioning + * so this should render the traits such as border and background + * in its position. + * + * @param block the block to render the traits + */ + protected void handleBlockTraits(Block block) { + int borderPaddingStart = block.getBorderAndPaddingWidthStart(); + int borderPaddingBefore = block.getBorderAndPaddingWidthBefore(); + + float startx = currentIPPosition / 1000f; + float starty = currentBPPosition / 1000f; + float width = block.getIPD() / 1000f; + float height = block.getBPD() / 1000f; + + startx += block.getStartIndent() / 1000f; + startx -= block.getBorderAndPaddingWidthStart() / 1000f; + + width += borderPaddingStart / 1000f; + width += block.getBorderAndPaddingWidthEnd() / 1000f; + height += borderPaddingBefore / 1000f; + height += block.getBorderAndPaddingWidthAfter() / 1000f; + + drawBackAndBorders(block, startx, starty, width, height); + } + + /** + * @see org.apache.fop.render.AbstractRenderer#renderText(TextArea) + */ + protected void renderText(TextArea area) { + //renderInlineAreaBackAndBorders(area); + String fontname = getInternalFontNameForArea(area); + int fontsize = area.getTraitAsInteger(Trait.FONT_SIZE); + + //Determine position + //int saveIP = currentIPPosition; + //int saveBP = currentBPPosition; + int rx = currentIPPosition + area.getBorderAndPaddingWidthStart(); + int bl = currentBPPosition + area.getOffset() + area.getBaselineOffset(); + + try { + setFont(fontname, fontsize); + Color col = (Color)area.getTrait(Trait.COLOR); + //this.currentFill = col; + if (col != null) { + //useColor(ct); + gen.setPatternTransparencyMode(false); + gen.selectCurrentPattern(gen.convertToPCLShade(col.getAWTColor()), 2); + } + + saveGraphicsState(); + updatePrintDirection(); + graphicContext.translate(rx, bl); + moveTo(0, 0); + + super.renderText(area); //Updates IPD + + //renderTextDecoration(tf, fontsize, area, bl, rx); + restoreGraphicsState(); + } catch (IOException ioe) { + handleIOTrouble(ioe); + } + } + + void moveTo(int x, int y) throws IOException { + Point2D transPoint = transformedPoint(x, y); + gen.writeCommand("&a" + gen.formatDouble2(transPoint.getX() / 100) + "h" + + gen.formatDouble2(transPoint.getY() / 100) + "V"); + } + + private void updatePrintDirection() throws IOException { + AffineTransform at = graphicContext.getTransform(); + if (log.isDebugEnabled()) { + log.debug(at.getScaleX() + " " + at.getScaleY() + " " + + at.getShearX() + " " + at.getShearY() ); + } + if (at.getScaleX() == 0 && at.getScaleY() == 0 + && at.getShearX() == 1 && at.getShearY() == -1) { + gen.writeCommand("&a90P"); + } else if (at.getScaleX() == -1 && at.getScaleY() == -1 + && at.getShearX() == 0 && at.getShearY() == 0) { + gen.writeCommand("&a180P"); + } else if (at.getScaleX() == 0 && at.getScaleY() == 0 + && at.getShearX() == -1 && at.getShearY() == 1) { + gen.writeCommand("&a270P"); + } else { + gen.writeCommand("&a0P"); + } + } + + private Point2D transformedPoint(float x, float y) { + return transformedPoint(Math.round(x), Math.round(y)); + } + + private Point2D transformedPoint(int x, int y) { + AffineTransform at = graphicContext.getTransform(); + if (log.isDebugEnabled()) { + log.debug("Current transform: " + at); + } + Point2D orgPoint = new Point2D.Float(x, y); + Point2D transPoint = new Point2D.Float(); + at.transform(orgPoint, transPoint); + return transPoint; + } + + /** + * @see org.apache.fop.render.AbstractRenderer#renderWord(org.apache.fop.area.inline.WordArea) + */ + protected void renderWord(WordArea word) { + //Font font = getFontFromArea(word.getParentArea()); + + String s = word.getWord(); + + try { + gen.writeText(s); + } catch (IOException ioe) { + handleIOTrouble(ioe); + } + + super.renderWord(word); + } + + /** + * @see org.apache.fop.render.AbstractRenderer#renderSpace(org.apache.fop.area.inline.SpaceArea) + */ + protected void renderSpace(SpaceArea space) { + AbstractTextArea textArea = (AbstractTextArea)space.getParentArea(); + String s = space.getSpace(); + char sp = s.charAt(0); + Font font = getFontFromArea(textArea); + + int tws = (space.isAdjustable() + ? ((TextArea) space.getParentArea()).getTextWordSpaceAdjust() + + 2 * textArea.getTextLetterSpaceAdjust() + : 0); + + double dx = (font.getCharWidth(sp) + tws) / 100f; + try { + gen.writeCommand("&a+" + gen.formatDouble2(dx) + "H"); + } catch (IOException ioe) { + handleIOTrouble(ioe); + } + super.renderSpace(space); + } + + /** + * @see org.apache.fop.render.AbstractRenderer#renderViewport(org.apache.fop.area.inline.Viewport) + */ + public void renderViewport(Viewport viewport) { + + float x = currentIPPosition / 1000f; + float y = (currentBPPosition + viewport.getOffset()) / 1000f; + float width = viewport.getIPD() / 1000f; + float height = viewport.getBPD() / 1000f; + // TODO: Calculate the border rect correctly. + float borderPaddingStart = viewport.getBorderAndPaddingWidthStart() / 1000f; + float borderPaddingBefore = viewport.getBorderAndPaddingWidthBefore() / 1000f; + float bpwidth = borderPaddingStart + + (viewport.getBorderAndPaddingWidthEnd() / 1000f); + float bpheight = borderPaddingBefore + + (viewport.getBorderAndPaddingWidthAfter() / 1000f); + + drawBackAndBorders(viewport, x, y, width + bpwidth, height + bpheight); + + if (viewport.getClip()) { + saveGraphicsState(); + + clipRect(x + borderPaddingStart, y + borderPaddingBefore, width, height); + } + super.renderViewport(viewport); + + if (viewport.getClip()) { + restoreGraphicsState(); + } + } + + /** + * @see org.apache.fop.render.AbstractRenderer#renderBlockViewport(BlockViewport, List) + */ + protected void renderBlockViewport(BlockViewport bv, List children) { + // clip and position viewport if necessary + + // save positions + int saveIP = currentIPPosition; + int saveBP = currentBPPosition; + //String saveFontName = currentFontName; + + CTM ctm = bv.getCTM(); + int borderPaddingStart = bv.getBorderAndPaddingWidthStart(); + int borderPaddingBefore = bv.getBorderAndPaddingWidthBefore(); + float x, y; + x = (float)(bv.getXOffset() + containingIPPosition) / 1000f; + y = (float)(bv.getYOffset() + containingBPPosition) / 1000f; + //This is the content-rect + float width = (float)bv.getIPD() / 1000f; + float height = (float)bv.getBPD() / 1000f; + + + if (bv.getPositioning() == Block.ABSOLUTE + || bv.getPositioning() == Block.FIXED) { + + currentIPPosition = bv.getXOffset(); + currentBPPosition = bv.getYOffset(); + + //For FIXED, we need to break out of the current viewports to the + //one established by the page. We save the state stack for restoration + //after the block-container has been painted. See below. + List breakOutList = null; + if (bv.getPositioning() == Block.FIXED) { + //breakOutList = breakOutOfStateStack(); + } + + CTM tempctm = new CTM(containingIPPosition, containingBPPosition); + ctm = tempctm.multiply(ctm); + + //Adjust for spaces (from margin or indirectly by start-indent etc. + x += bv.getSpaceStart() / 1000f; + currentIPPosition += bv.getSpaceStart(); + + y += bv.getSpaceBefore() / 1000f; + currentBPPosition += bv.getSpaceBefore(); + + float bpwidth = (borderPaddingStart + bv.getBorderAndPaddingWidthEnd()) / 1000f; + float bpheight = (borderPaddingBefore + bv.getBorderAndPaddingWidthAfter()) / 1000f; + + drawBackAndBorders(bv, x, y, width + bpwidth, height + bpheight); + + //Now adjust for border/padding + currentIPPosition += borderPaddingStart; + currentBPPosition += borderPaddingBefore; + + Rectangle2D clippingRect = null; + if (bv.getClip()) { + clippingRect = new Rectangle(currentIPPosition, currentBPPosition, + bv.getIPD(), bv.getBPD()); + } + + startVParea(ctm, clippingRect); + currentIPPosition = 0; + currentBPPosition = 0; + renderBlocks(bv, children); + endVParea(); + + if (breakOutList != null) { + //restoreStateStackAfterBreakOut(breakOutList); + } + + currentIPPosition = saveIP; + currentBPPosition = saveBP; + } else { + + currentBPPosition += bv.getSpaceBefore(); + + //borders and background in the old coordinate system + handleBlockTraits(bv); + + //Advance to start of content area + currentIPPosition += bv.getStartIndent(); + + CTM tempctm = new CTM(containingIPPosition, currentBPPosition); + ctm = tempctm.multiply(ctm); + + //Now adjust for border/padding + currentBPPosition += borderPaddingBefore; + + Rectangle2D clippingRect = null; + if (bv.getClip()) { + clippingRect = new Rectangle(currentIPPosition, currentBPPosition, + bv.getIPD(), bv.getBPD()); + } + + startVParea(ctm, clippingRect); + currentIPPosition = 0; + currentBPPosition = 0; + renderBlocks(bv, children); + endVParea(); + + currentIPPosition = saveIP; + currentBPPosition = saveBP; + + currentBPPosition += (int)(bv.getAllocBPD()); + } + //currentFontName = saveFontName; + } + + /** + * @see org.apache.fop.render.AbstractRenderer#renderImage(Image, Rectangle2D) + */ + public void renderImage(Image image, Rectangle2D pos) { + String url = ImageFactory.getURL(image.getURL()); + ImageFactory fact = userAgent.getFactory().getImageFactory(); + FopImage fopimage = fact.getImage(url, userAgent); + if (fopimage == null) { + return; + } + if (!fopimage.load(FopImage.DIMENSIONS)) { + return; + } + String mime = fopimage.getMimeType(); + if ("text/xml".equals(mime)) { + if (!fopimage.load(FopImage.ORIGINAL_DATA)) { + return; + } + Document doc = ((XMLImage) fopimage).getDocument(); + String ns = ((XMLImage) fopimage).getNameSpace(); + + renderDocument(doc, ns, pos, image.getForeignAttributes()); + } else if ("image/svg+xml".equals(mime)) { + if (!fopimage.load(FopImage.ORIGINAL_DATA)) { + return; + } + Document doc = ((XMLImage) fopimage).getDocument(); + String ns = ((XMLImage) fopimage).getNameSpace(); + + renderDocument(doc, ns, pos, image.getForeignAttributes()); + } else if (fopimage instanceof EPSImage) { + log.warn("EPS images are not supported by this renderer"); + } else { + if (!fopimage.load(FopImage.BITMAP)) { + log.error("Bitmap image could not be processed: " + fopimage); + return; + } + byte[] imgmap = fopimage.getBitmaps(); + + ColorModel cm = new ComponentColorModel( + ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB), + new int[] {8, 8, 8}, + false, false, + ColorModel.OPAQUE, DataBuffer.TYPE_BYTE); + int imgw = fopimage.getWidth(); + int imgh = fopimage.getHeight(); + SampleModel sampleModel = new PixelInterleavedSampleModel( + DataBuffer.TYPE_BYTE, imgw, imgh, 3, imgw * 3, new int[] {0, 1, 2}); + DataBuffer dbuf = new DataBufferByte(imgmap, imgw * imgh * 3); + + WritableRaster raster = Raster.createWritableRaster(sampleModel, + dbuf, null); + + // Combine the color model and raster into a buffered image + RenderedImage img = new BufferedImage(cm, raster, false, null); + + try { + moveTo(this.currentIPPosition + (int)pos.getX(), + this.currentBPPosition + (int)pos.getY()); + int resolution = (int)Math.round(Math.max(fopimage.getHorizontalResolution(), + fopimage.getVerticalResolution())); + gen.paintBitmap(img, resolution); + } catch (IOException ioe) { + handleIOTrouble(ioe); + } + } + } + + /** + * @see org.apache.fop.render.AbstractRenderer#renderForeignObject(ForeignObject, Rectangle2D) + */ + public void renderForeignObject(ForeignObject fo, Rectangle2D pos) { + Document doc = fo.getDocument(); + String ns = fo.getNameSpace(); + renderDocument(doc, ns, pos, fo.getForeignAttributes()); + } + + /** + * Renders an XML document (SVG for example). + * @param doc the DOM Document containing the XML document to be rendered + * @param ns the namespace URI for the XML document + * @param pos the position for the generated graphic/image + * @param foreignAttributes the foreign attributes containing rendering hints, or null + */ + public void renderDocument(Document doc, String ns, Rectangle2D pos, Map foreignAttributes) { + RendererContext context; + context = new RendererContext(this, MIME_TYPE); + context.setUserAgent(userAgent); + + context.setProperty(RendererContextConstants.WIDTH, + new Integer((int) pos.getWidth())); + context.setProperty(RendererContextConstants.HEIGHT, + new Integer((int) pos.getHeight())); + context.setProperty(RendererContextConstants.XPOS, + new Integer(currentIPPosition + (int) pos.getX())); + context.setProperty(RendererContextConstants.YPOS, + new Integer(currentBPPosition + (int) pos.getY())); + context.setProperty(RendererContextConstants.PAGE_VIEWPORT, + getCurrentPageViewport()); + if (foreignAttributes != null) { + context.setProperty(RendererContextConstants.FOREIGN_ATTRIBUTES, foreignAttributes); + } + + renderXML(context, doc, ns); + } + + /** + * Draw the background and borders. This draws the background and border + * traits for an area given the position. + * + * @param area the area whose traits are used + * @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 area, float startx, float starty, + float width, float height) { + try { + updatePrintDirection(); + BorderProps bpsBefore = (BorderProps) area.getTrait(Trait.BORDER_BEFORE); + BorderProps bpsAfter = (BorderProps) area.getTrait(Trait.BORDER_AFTER); + BorderProps bpsStart = (BorderProps) area.getTrait(Trait.BORDER_START); + BorderProps bpsEnd = (BorderProps) area.getTrait(Trait.BORDER_END); + + // draw background + Trait.Background back; + back = (Trait.Background) area.getTrait(Trait.BACKGROUND); + if (back != null) { + + // Calculate padding rectangle + float sx = startx; + float sy = starty; + float paddRectWidth = width; + float paddRectHeight = height; + + if (bpsStart != null) { + sx += bpsStart.width / 1000f; + paddRectWidth -= bpsStart.width / 1000f; + } + if (bpsBefore != null) { + sy += bpsBefore.width / 1000f; + paddRectHeight -= bpsBefore.width / 1000f; + } + if (bpsEnd != null) { + paddRectWidth -= bpsEnd.width / 1000f; + } + if (bpsAfter != null) { + paddRectHeight -= bpsAfter.width / 1000f; + } + + if (back.getColor() != null) { + Point2D p = transformedPoint(sx * 1000, sy * 1000); + gen.fillRect((int)p.getX(), (int)p.getY(), + (int)paddRectWidth * 1000, (int)paddRectHeight * 1000, + back.getColor().getAWTColor()); + } + + // background image + if (back.getFopImage() != null) { + FopImage fopimage = back.getFopImage(); + if (fopimage != null && fopimage.load(FopImage.DIMENSIONS)) { + saveGraphicsState(); + clipRect(sx, sy, paddRectWidth, paddRectHeight); + int horzCount = (int) ((paddRectWidth * 1000 / fopimage + .getIntrinsicWidth()) + 1.0f); + int vertCount = (int) ((paddRectHeight * 1000 / fopimage + .getIntrinsicHeight()) + 1.0f); + if (back.getRepeat() == EN_NOREPEAT) { + horzCount = 1; + vertCount = 1; + } else if (back.getRepeat() == EN_REPEATX) { + vertCount = 1; + } else if (back.getRepeat() == EN_REPEATY) { + horzCount = 1; + } + // change from points to millipoints + sx *= 1000; + sy *= 1000; + if (horzCount == 1) { + sx += back.getHoriz(); + } + if (vertCount == 1) { + sy += back.getVertical(); + } + for (int x = 0; x < horzCount; x++) { + for (int y = 0; y < vertCount; y++) { + // place once + Rectangle2D pos; + pos = new Rectangle2D.Float(sx + + (x * fopimage.getIntrinsicWidth()), sy + + (y * fopimage.getIntrinsicHeight()), + fopimage.getIntrinsicWidth(), fopimage + .getIntrinsicHeight()); + //putImage(back.getURL(), pos); // TODO test + } + } + restoreGraphicsState(); + } else { + log.warn( + "Can't find background image: " + back.getURL()); + } + } + } +/* + // draw border + // BORDER_BEFORE + if (bpsBefore != null) { + int borderWidth = (int) Math.round((bpsBefore.width / 1000f)); + state.updateColor(bpsBefore.color); + state.getGraph().fillRect((int) startx, (int) starty, (int) width, + borderWidth); + } + // BORDER_AFTER + if (bpsAfter != null) { + int borderWidth = (int) Math.round((bpsAfter.width / 1000f)); + float sy = starty + height; + state.updateColor(bpsAfter.color); + state.getGraph().fillRect((int) startx, + (int) (starty + height - borderWidth), (int) width, + borderWidth); + } + // BORDER_START + if (bpsStart != null) { + int borderWidth = (int) Math.round((bpsStart.width / 1000f)); + state.updateColor(bpsStart.color); + state.getGraph().fillRect((int) startx, (int) starty, borderWidth, + (int) height); + } + // BORDER_END + if (bpsEnd != null) { + int borderWidth = (int) Math.round((bpsEnd.width / 1000f)); + float sx = startx + width; + state.updateColor(bpsEnd.color); + state.getGraph().fillRect((int) (startx + width - borderWidth), + (int) starty, borderWidth, (int) height); + } + */ + } catch (IOException ioe) { + handleIOTrouble(ioe); + } } + } diff --git a/src/sandbox/org/apache/fop/render/pcl/PCLRendererContext.java b/src/sandbox/org/apache/fop/render/pcl/PCLRendererContext.java new file mode 100644 index 000000000..73e27bcac --- /dev/null +++ b/src/sandbox/org/apache/fop/render/pcl/PCLRendererContext.java @@ -0,0 +1,92 @@ +/* + * Copyright 2006 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.pcl; + +import java.util.Map; + +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.fop.fo.extensions.ExtensionElementMapping; +import org.apache.fop.render.RendererContext; +import org.apache.fop.util.QName; + +/** + * Wrapper on the RendererContext to access the information structure for drawing + * the XML document. + */ +public class PCLRendererContext { + + private RendererContext context; + + /** + * Wrap the render context to allow easier access to its values. + * + * @param context the renderer context + * @return the PCL-specific renderer context wrapper + */ + public static PCLRendererContext wrapRendererContext(RendererContext context) { + PCLRendererContext pcli = new PCLRendererContext(context); + return pcli; + } + + /** + * Main constructor + * @param context the RendererContent instance + */ + public PCLRendererContext(RendererContext context) { + this.context = context; + } + + /** @return the currentXPosition */ + public int getCurrentXPosition() { + return ((Integer)context.getProperty(PCLSVGHandler.XPOS)).intValue(); + } + + /** @return the currentYPosition */ + public int getCurrentYPosition() { + return ((Integer)context.getProperty(PCLSVGHandler.YPOS)).intValue(); + } + + /** @return the width of the image */ + public int getWidth() { + return ((Integer)context.getProperty(PCLSVGHandler.WIDTH)).intValue(); + } + + /** @return the height of the image */ + public int getHeight() { + return ((Integer)context.getProperty(PCLSVGHandler.HEIGHT)).intValue(); + } + + /** @return the handler configuration */ + public Configuration getHandlerConfiguration() { + return (Configuration)context.getProperty(PCLSVGHandler.HANDLER_CONFIGURATION); + } + + /** @return the foreign attributes */ + public Map getForeignAttributes() { + return (Map)context.getProperty(PCLSVGHandler.FOREIGN_ATTRIBUTES); + } + + /** @return true if the SVG image should be rendered as a bitmap */ + public boolean paintAsBitmap() { + QName qName = new QName(ExtensionElementMapping.URI, null, "conversion-mode"); + return getForeignAttributes() != null + && "bitmap".equals(getForeignAttributes().get(qName)); + } + +} \ No newline at end of file diff --git a/src/sandbox/org/apache/fop/render/pcl/PCLSVGHandler.java b/src/sandbox/org/apache/fop/render/pcl/PCLSVGHandler.java new file mode 100644 index 000000000..2e187d482 --- /dev/null +++ b/src/sandbox/org/apache/fop/render/pcl/PCLSVGHandler.java @@ -0,0 +1,144 @@ +/* + * Copyright 2006 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.pcl; + +// Java +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.io.IOException; + +// DOM +import org.w3c.dom.Document; + +// Batik +import org.apache.batik.bridge.GVTBuilder; +import org.apache.batik.bridge.BridgeContext; +import org.apache.batik.dom.svg.SVGDOMImplementation; +import org.apache.batik.gvt.GraphicsNode; + +// FOP +import org.apache.fop.render.Graphics2DAdapter; +import org.apache.fop.render.Graphics2DImagePainter; +import org.apache.fop.render.Renderer; +import org.apache.fop.render.RendererContextConstants; +import org.apache.fop.render.XMLHandler; +import org.apache.fop.render.RendererContext; +import org.apache.fop.svg.SVGUserAgent; + +// Commons-Logging +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * PCL XML handler for SVG. Uses Apache Batik for SVG processing. + * This handler handles XML for foreign objects when rendering to HP GL/2. + * It renders SVG to HP GL/2 using the PCLGraphics2D. + * + * @version $Id$ + */ +public class PCLSVGHandler implements XMLHandler, RendererContextConstants { + + /** logging instance */ + private static Log log = LogFactory.getLog(PCLSVGHandler.class); + + /** + * Create a new XML handler for use by the PCL renderer. + */ + public PCLSVGHandler() { + } + + /** @see org.apache.fop.render.XMLHandler */ + public void handleXML(RendererContext context, + Document doc, String ns) throws Exception { + PCLRendererContext pclContext = PCLRendererContext.wrapRendererContext(context); + + if (SVGDOMImplementation.SVG_NAMESPACE_URI.equals(ns)) { + renderSVGDocument(context, doc, pclContext); + } + } + + /** + * Render the SVG document. + * @param context the renderer context + * @param doc the SVG document + * @param pclContext the information of the current context + */ + protected void renderSVGDocument(final RendererContext context, + final Document doc, final PCLRendererContext pclContext) { + int x = pclContext.getCurrentXPosition(); + int y = pclContext.getCurrentYPosition(); + + Graphics2DImagePainter painter = new Graphics2DImagePainter() { + + public void paint(Graphics2D g2d, Rectangle2D area) { + SVGUserAgent ua = new SVGUserAgent( + context.getUserAgent().getSourcePixelUnitToMillimeter(), + new AffineTransform()); + GVTBuilder builder = new GVTBuilder(); + BridgeContext ctx = new BridgeContext(ua); + + GraphicsNode root; + try { + root = builder.build(ctx, doc); + + // If no viewbox is defined in the svg file, a viewbox of 100x100 is + // assumed, as defined in SVGUserAgent.getViewportSize() + float iw = (float) ctx.getDocumentSize().getWidth() * 1000f; + float ih = (float) ctx.getDocumentSize().getHeight() * 1000f; + float w = (float) area.getWidth(); + float h = (float) area.getHeight(); + g2d.scale(w / iw, h / ih); + + root.paint(g2d); + } catch (Exception e) { + log.error("SVG graphic could not be built: " + + e.getMessage(), e); + return; + } + } + + public Dimension getImageSize() { + return new Dimension(pclContext.getWidth(), pclContext.getHeight()); + } + + }; + + try { + Graphics2DAdapter adapter = context.getRenderer().getGraphics2DAdapter(); + adapter.paintImage(painter, context, + x, y, pclContext.getWidth(), pclContext.getHeight()); + } catch (IOException ioe) { + ((PCLRenderer)context.getRenderer()).handleIOTrouble(ioe); + } + } + + /** @see org.apache.fop.render.XMLHandler#supportsRenderer(org.apache.fop.render.Renderer) */ + public boolean supportsRenderer(Renderer renderer) { + return (renderer instanceof PCLRenderer); + } + + /** @see org.apache.fop.render.XMLHandler#getNamespace() */ + public String getNamespace() { + return SVGDOMImplementation.SVG_NAMESPACE_URI; + } + +} + diff --git a/src/sandbox/org/apache/fop/render/pcl/PCLStream.java b/src/sandbox/org/apache/fop/render/pcl/PCLStream.java deleted file mode 100644 index 34150f16a..000000000 --- a/src/sandbox/org/apache/fop/render/pcl/PCLStream.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.pcl; - -import java.io.IOException; -import java.io.OutputStream; - -public class PCLStream { - - private OutputStream out = null; - private boolean doOutput = true; - - public PCLStream(OutputStream os) { - out = os; - } - - public void add(String str) { - if (!doOutput) { - return; - } - - byte buff[] = new byte[str.length()]; - int countr; - int len = str.length(); - for (countr = 0; countr < len; countr++) { - buff[countr] = (byte)str.charAt(countr); - } - try { - out.write(buff); - } catch (IOException e) { - throw new RuntimeException(e.toString()); - } - } - - public void setDoOutput(boolean doout) { - doOutput = doout; - } - -} diff --git a/src/sandbox/org/apache/fop/render/pcl/package.html b/src/sandbox/org/apache/fop/render/pcl/package.html index feec0572e..ba47bd289 100644 --- a/src/sandbox/org/apache/fop/render/pcl/package.html +++ b/src/sandbox/org/apache/fop/render/pcl/package.html @@ -1,5 +1,5 @@