diff options
31 files changed, 2911 insertions, 156 deletions
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; @@ -279,6 +280,16 @@ public class XMLRenderer extends PrintRenderer { * @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 + * @param value value of the attribute + */ protected void addAttribute(String name, int value) { addAttribute(name, Integer.toString(value)); } @@ -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. + * <p> + * 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. + * <p> + * 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 (<ESC>*c#G command) + * @param pattern the pattern type (<ESC>*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 */ @@ -189,17 +245,624 @@ public class PCLRenderer extends PrintRenderer { } /** + * @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 @@ <!-- - Copyright 2005 The Apache Software Foundation + Copyright 2005-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. @@ -17,6 +17,6 @@ <HTML> <TITLE>org.apache.fop.render.pcl Package</TITLE> <BODY> -<P>PCL Renderer</P> +<P>PCL Renderer (Supports PCL5 and HP GL/2)</P> </BODY> </HTML>
\ No newline at end of file diff --git a/test/java/org/apache/fop/UtilityCodeTestSuite.java b/test/java/org/apache/fop/UtilityCodeTestSuite.java index cce534e92..09a2ef982 100644 --- a/test/java/org/apache/fop/UtilityCodeTestSuite.java +++ b/test/java/org/apache/fop/UtilityCodeTestSuite.java @@ -21,6 +21,7 @@ package org.apache.fop; import org.apache.fop.traits.BorderPropsTestCase; import org.apache.fop.traits.TraitColorTestCase; import org.apache.fop.util.PDFNumberTestCase; +import org.apache.fop.util.UnitConvTestCase; import junit.framework.Test; import junit.framework.TestSuite; @@ -39,6 +40,7 @@ public class UtilityCodeTestSuite { "Test suite for FOP's utility classes"); //$JUnit-BEGIN$ suite.addTest(new TestSuite(PDFNumberTestCase.class)); + suite.addTest(new TestSuite(UnitConvTestCase.class)); suite.addTest(new TestSuite(TraitColorTestCase.class)); suite.addTest(new TestSuite(BorderPropsTestCase.class)); //$JUnit-END$ diff --git a/test/java/org/apache/fop/util/UnitConvTestCase.java b/test/java/org/apache/fop/util/UnitConvTestCase.java new file mode 100644 index 000000000..32d92473d --- /dev/null +++ b/test/java/org/apache/fop/util/UnitConvTestCase.java @@ -0,0 +1,45 @@ +/* + * 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 junit.framework.TestCase; + +/** + * Test class for UnitConv. + */ +public class UnitConvTestCase extends TestCase { + + /** + * Test all kinds of unit conversions. + * @throws Exception if the test fails + */ + public void testConversions() throws Exception { + assertEquals("in2mm", 25.4, UnitConv.in2mm(1), 0.00001); + assertEquals("mm2in", 1.0, UnitConv.mm2in(25.4), 0.00001); + assertEquals("mm2pt", 841.890, UnitConv.mm2pt(297), 0.001 / 2); //height of an A4 page + assertEquals("mm2mpt", 841890, UnitConv.mm2mpt(297), 1.0 / 2); + assertEquals("pt2mm", 297, UnitConv.pt2mm(841.890), 0.0001); + assertEquals("in2mpt", 792000, UnitConv.in2mpt(11.0), 1.0 / 2); //height of a letter page + assertEquals("mpt2in", 11.0, UnitConv.mpt2in(792000), 0.01 / 2); //height of a letter page + + assertEquals("mm2px/72dpi", 842, UnitConv.mm2px(297, 72), 0.0001); + assertEquals("mm2px/300dpi", 3508, UnitConv.mm2px(297, 300), 0.0001); + } + +}
\ No newline at end of file |