diff options
author | Vincent Hennebert <vhennebert@apache.org> | 2014-01-13 20:39:34 +0000 |
---|---|---|
committer | Vincent Hennebert <vhennebert@apache.org> | 2014-01-13 20:39:34 +0000 |
commit | 37c00cdf4e9bc4b922c5961bd3f38f7ed0f0d3f0 (patch) | |
tree | a836d0edb3fc9fcc81f410799ffec6621e3e8dc6 /src/java/org/apache/fop/render | |
parent | 8fe1a6d2368b9c8fbabc447d7356079e5b61a6e7 (diff) | |
parent | e7d05ec67b2652cd6ede79067d42eae8c057c955 (diff) | |
download | xmlgraphics-fop-37c00cdf4e9bc4b922c5961bd3f38f7ed0f0d3f0.tar.gz xmlgraphics-fop-37c00cdf4e9bc4b922c5961bd3f38f7ed0f0d3f0.zip |
Brought the branch in sync with rev. r1556164 of trunk
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_InlineContainer@1557840 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java/org/apache/fop/render')
66 files changed, 4357 insertions, 452 deletions
diff --git a/src/java/org/apache/fop/render/AbstractRenderer.java b/src/java/org/apache/fop/render/AbstractRenderer.java index e274e5c4b..9a94e0cc6 100644 --- a/src/java/org/apache/fop/render/AbstractRenderer.java +++ b/src/java/org/apache/fop/render/AbstractRenderer.java @@ -28,6 +28,7 @@ import java.io.OutputStream; import java.util.List; import java.util.Locale; import java.util.Set; +import java.util.Stack; import org.w3c.dom.Document; @@ -112,8 +113,12 @@ public abstract class AbstractRenderer /** the currently active PageViewport */ protected PageViewport currentPageViewport; + /* warned XML handlers */ private Set warnedXMLHandlers; + /* layers stack */ + private Stack<String> layers; + /** {@inheritDoc} */ public abstract void setupFontInfo(FontInfo fontInfo) throws FOPException; @@ -471,6 +476,10 @@ public abstract class AbstractRenderer * @param children The children to render within the block viewport */ protected void renderBlockViewport(BlockViewport bv, List children) { + boolean inNewLayer = false; + if (maybeStartLayer(bv)) { + inNewLayer = true; + } // clip and position viewport if necessary if (bv.getPositioning() == Block.ABSOLUTE) { // save positions @@ -506,6 +515,7 @@ public abstract class AbstractRenderer currentIPPosition = saveIP; currentBPPosition = saveBP + bv.getAllocBPD(); } + maybeEndLayer(bv, inNewLayer); } /** @@ -573,6 +583,10 @@ public abstract class AbstractRenderer protected void renderBlock(Block block) { assert block != null; List children = block.getChildAreas(); + boolean inNewLayer = false; + if (maybeStartLayer(block)) { + inNewLayer = true; + } if (block instanceof BlockViewport) { if (children != null) { renderBlockViewport((BlockViewport) block, children); @@ -607,6 +621,45 @@ public abstract class AbstractRenderer currentBPPosition = saveBP + block.getAllocBPD(); } } + maybeEndLayer(block, inNewLayer); + } + + /** + * Establish new optional content group layer. + * @param layer name of layer + */ + protected abstract void startLayer(String layer); + + /** + * Finish current optional content group layer. + */ + protected abstract void endLayer(); + + protected boolean maybeStartLayer(Area area) { + String layer = (String) area.getTrait(Trait.LAYER); + if (layer != null) { + if (layers == null) { + layers = new Stack<String>(); + } + if (layers.empty() || !layers.peek().equals(layer)) { + layers.push(layer); + startLayer(layer); + return true; + } + } + return false; + } + + protected void maybeEndLayer(Area area, boolean inNewLayer) { + if (inNewLayer) { + assert layers != null; + assert !layers.empty(); + String layer = (String) area.getTrait(Trait.LAYER); + assert layer != null; + assert layers.peek().equals(layer); + endLayer(); + layers.pop(); + } } /** @@ -746,6 +799,10 @@ public abstract class AbstractRenderer * @param ip the inline parent to render */ protected void renderInlineParent(InlineParent ip) { + boolean inNewLayer = false; + if (maybeStartLayer(ip)) { + inNewLayer = true; + } int level = ip.getBidiLevel(); List children = ip.getChildAreas(); renderInlineAreaBackAndBorders(ip); @@ -782,6 +839,7 @@ public abstract class AbstractRenderer } currentIPPosition = saveIP + ip.getAllocIPD(); currentBPPosition = saveBP; + maybeEndLayer(ip, inNewLayer); } /** diff --git a/src/java/org/apache/fop/render/afp/AFPFontConfig.java b/src/java/org/apache/fop/render/afp/AFPFontConfig.java index aef0b666c..149973edd 100644 --- a/src/java/org/apache/fop/render/afp/AFPFontConfig.java +++ b/src/java/org/apache/fop/render/afp/AFPFontConfig.java @@ -316,8 +316,8 @@ public final class AFPFontConfig implements FontConfig { private final String characterset; - private CIDKeyedFontConfig(List<FontTriplet> triplets, String type, String codePage, - String encoding, String characterset, String name, CharacterSetType charsetType, boolean embeddable, String uri) { + private CIDKeyedFontConfig(List<FontTriplet> triplets, String type, String codePage, String encoding, + String characterset, String name, CharacterSetType charsetType, boolean embeddable, String uri) { super(triplets, type, codePage, encoding, name, embeddable, uri); this.characterset = characterset; this.charsetType = charsetType; diff --git a/src/java/org/apache/fop/render/afp/AFPPainter.java b/src/java/org/apache/fop/render/afp/AFPPainter.java index 12713bb24..aaa702afa 100644 --- a/src/java/org/apache/fop/render/afp/AFPPainter.java +++ b/src/java/org/apache/fop/render/afp/AFPPainter.java @@ -164,7 +164,7 @@ public class AFPPainter extends AbstractIFPainter<AFPDocumentHandler> { } /** {@inheritDoc} */ - public void startGroup(AffineTransform transform) throws IFException { + public void startGroup(AffineTransform transform, String layer) throws IFException { try { saveGraphicsState(); concatenateTransformationMatrix(transform); diff --git a/src/java/org/apache/fop/render/afp/AFPRendererConfig.java b/src/java/org/apache/fop/render/afp/AFPRendererConfig.java index 90cc6e767..a943f5aad 100644 --- a/src/java/org/apache/fop/render/afp/AFPRendererConfig.java +++ b/src/java/org/apache/fop/render/afp/AFPRendererConfig.java @@ -103,7 +103,8 @@ public final class AFPRendererConfig implements RendererConfig { } } - private final EnumMap<AFPRendererOption, Object> params = new EnumMap<AFPRendererOption, Object>(AFPRendererOption.class); + private final EnumMap<AFPRendererOption, Object> params + = new EnumMap<AFPRendererOption, Object>(AFPRendererOption.class); private final EnumMap<ImagesModeOptions, Object> imageModeParams = new EnumMap<ImagesModeOptions, Object>(ImagesModeOptions.class); diff --git a/src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java b/src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java index 2dd046fa9..f69fe2091 100644 --- a/src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java +++ b/src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java @@ -126,8 +126,8 @@ public abstract class AbstractIFPainter<T extends IFDocumentHandler> implements } /** {@inheritDoc} */ - public void startGroup(AffineTransform[] transforms) throws IFException { - startGroup(combine(transforms)); + public void startGroup(AffineTransform[] transforms, String layer) throws IFException { + startGroup(combine(transforms), layer); } /** diff --git a/src/java/org/apache/fop/render/intermediate/IFGraphicContext.java b/src/java/org/apache/fop/render/intermediate/IFGraphicContext.java index 868615360..c1742be1f 100644 --- a/src/java/org/apache/fop/render/intermediate/IFGraphicContext.java +++ b/src/java/org/apache/fop/render/intermediate/IFGraphicContext.java @@ -89,6 +89,7 @@ public class IFGraphicContext extends GraphicContext { public static class Group { private AffineTransform[] transforms; + private String layer; /** * Construct a Group. @@ -106,6 +107,16 @@ public class IFGraphicContext extends GraphicContext { this(new AffineTransform[] {transform}); } + /** + * Construct a layer Group, i.e., a Group with no transforms + * but with a optional content group layer label. + * @param layer a layer label + */ + public Group(String layer) { + this(); + this.layer = layer; + } + /** Default constructor. */ public Group() { this(EMPTY_TRANSFORM_ARRAY); @@ -116,12 +127,17 @@ public class IFGraphicContext extends GraphicContext { return this.transforms; } + /** @return layer */ + public String getLayer() { + return this.layer; + } + /** * @param painter a painter * @throws IFException in not caught */ public void start(IFPainter painter) throws IFException { - painter.startGroup(transforms); + painter.startGroup(transforms, layer); } /** @@ -136,6 +152,11 @@ public class IFGraphicContext extends GraphicContext { public String toString() { StringBuffer sb = new StringBuffer("group: "); IFUtil.toString(getTransforms(), sb); + if ((layer != null) && (layer.length() > 0)) { + sb.append(" layer("); + sb.append(layer); + sb.append(')'); + } return sb.toString(); } diff --git a/src/java/org/apache/fop/render/intermediate/IFPainter.java b/src/java/org/apache/fop/render/intermediate/IFPainter.java index 599292287..d43b5cb85 100644 --- a/src/java/org/apache/fop/render/intermediate/IFPainter.java +++ b/src/java/org/apache/fop/render/intermediate/IFPainter.java @@ -113,19 +113,21 @@ public interface IFPainter { /** * Starts a new group of graphical elements. Corresponds to SVG's g element. * @param transforms a series of transformation matrices establishing the new coordinate system + * @param layer an optional layer label (or null if none) * @throws IFException if an error occurs while handling this element */ - void startGroup(AffineTransform[] transforms) throws IFException; + void startGroup(AffineTransform[] transforms, String layer) throws IFException; /** * Starts a new group of graphical elements. Corresponds to SVG's g element. * @param transform the transformation matrix establishing the new coordinate system + * @param layer an optional layer label (or null if none) * @throws IFException if an error occurs while handling this element */ - void startGroup(AffineTransform transform) throws IFException; + void startGroup(AffineTransform transform, String layer) throws IFException; /** - * Ends the current group and restores the previous coordinate system. + * Ends the current group and restores the previous coordinate system (and layer). * @throws IFException if an error occurs while handling this element */ void endGroup() throws IFException; diff --git a/src/java/org/apache/fop/render/intermediate/IFParser.java b/src/java/org/apache/fop/render/intermediate/IFParser.java index 1af1d4a06..519726291 100644 --- a/src/java/org/apache/fop/render/intermediate/IFParser.java +++ b/src/java/org/apache/fop/render/intermediate/IFParser.java @@ -592,7 +592,8 @@ public class IFParser implements IFConstants { String transform = attributes.getValue("transform"); AffineTransform[] transforms = AffineTransformArrayParser.createAffineTransform(transform); - painter.startGroup(transforms); + String layer = attributes.getValue("layer"); + painter.startGroup(transforms, layer); } public void endElement() throws IFException { @@ -800,8 +801,8 @@ public class IFParser implements IFConstants { } painter.drawImage(uri, new Rectangle(x, y, width, height)); } - resetForeignAttributes(); resetStructureTreeElement(); + resetForeignAttributes(); inForeignObject = false; } diff --git a/src/java/org/apache/fop/render/intermediate/IFRenderer.java b/src/java/org/apache/fop/render/intermediate/IFRenderer.java index 30ceda108..e40a8f6b3 100644 --- a/src/java/org/apache/fop/render/intermediate/IFRenderer.java +++ b/src/java/org/apache/fop/render/intermediate/IFRenderer.java @@ -742,6 +742,12 @@ public class IFRenderer extends AbstractPathOrientedRenderer { protected void renderBlockViewport(BlockViewport bv, List children) { //Essentially the same code as in the super class but optimized for the IF + // Handle new layer. + boolean inNewLayer = false; + if (maybeStartLayer(bv)) { + inNewLayer = true; + } + //This is the content-rect Dimension dim = new Dimension(bv.getIPD(), bv.getBPD()); viewportDimensionStack.push(dim); @@ -842,6 +848,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { currentBPPosition += bv.getAllocBPD(); } viewportDimensionStack.pop(); + maybeEndLayer(bv, inNewLayer); } /** {@inheritDoc} */ @@ -849,7 +856,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { StructureTreeElement structElem = (StructureTreeElement) viewport.getTrait(Trait.STRUCTURE_TREE_ELEMENT); establishStructureTreeElement(structElem); - pushdID(viewport); + pushID(viewport); Dimension dim = new Dimension(viewport.getIPD(), viewport.getBPD()); viewportDimensionStack.push(dim); super.renderInlineViewport(viewport); @@ -896,9 +903,26 @@ public class IFRenderer extends AbstractPathOrientedRenderer { } /** {@inheritDoc} */ + protected void startLayer(String layer) { + if (log.isTraceEnabled()) { + log.trace("startLayer() layer=" + layer); + } + saveGraphicsState(); + pushGroup(new IFGraphicContext.Group(layer)); + } + + /** {@inheritDoc} */ + protected void endLayer() { + if (log.isTraceEnabled()) { + log.trace("endLayer()"); + } + restoreGraphicsState(); + } + + /** {@inheritDoc} */ protected void renderInlineArea(InlineArea inlineArea) { saveInlinePosIfTargetable(inlineArea); - pushdID(inlineArea); + pushID(inlineArea); super.renderInlineArea(inlineArea); popID(inlineArea); } @@ -965,7 +989,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { log.trace("renderBlock() " + block); } saveBlockPosIfTargetable(block); - pushdID(block); + pushID(block); IFContext context = documentHandler.getContext(); Locale oldLocale = context.getLanguage(); context.setLanguage(block.getLocale()); @@ -977,7 +1001,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { popID(block); } - private void pushdID(Area area) { + private void pushID(Area area) { String prodID = (String) area.getTrait(Trait.PROD_ID); if (prodID != null) { ids.push(prodID); diff --git a/src/java/org/apache/fop/render/intermediate/IFSerializer.java b/src/java/org/apache/fop/render/intermediate/IFSerializer.java index 1ffd42863..395e79719 100644 --- a/src/java/org/apache/fop/render/intermediate/IFSerializer.java +++ b/src/java/org/apache/fop/render/intermediate/IFSerializer.java @@ -428,21 +428,24 @@ implements IFConstants, IFPainter, IFDocumentNavigationHandler { } /** {@inheritDoc} */ - public void startGroup(AffineTransform[] transforms) throws IFException { - startGroup(IFUtil.toString(transforms)); + public void startGroup(AffineTransform[] transforms, String layer) throws IFException { + startGroup(IFUtil.toString(transforms), layer); } /** {@inheritDoc} */ - public void startGroup(AffineTransform transform) throws IFException { - startGroup(IFUtil.toString(transform)); + public void startGroup(AffineTransform transform, String layer) throws IFException { + startGroup(IFUtil.toString(transform), layer); } - private void startGroup(String transform) throws IFException { + private void startGroup(String transform, String layer) throws IFException { try { AttributesImpl atts = new AttributesImpl(); if (transform != null && transform.length() > 0) { addAttribute(atts, "transform", transform); } + if (layer != null && layer.length() > 0) { + addAttribute(atts, "layer", layer); + } handler.startElement(EL_GROUP, atts); } catch (SAXException e) { throw new IFException("SAX error in startGroup()", e); diff --git a/src/java/org/apache/fop/render/java2d/Java2DPainter.java b/src/java/org/apache/fop/render/java2d/Java2DPainter.java index 07440ff0b..328d1a4f8 100644 --- a/src/java/org/apache/fop/render/java2d/Java2DPainter.java +++ b/src/java/org/apache/fop/render/java2d/Java2DPainter.java @@ -143,7 +143,7 @@ public class Java2DPainter extends AbstractIFPainter<Java2DDocumentHandler> { } /** {@inheritDoc} */ - public void startGroup(AffineTransform transform) throws IFException { + public void startGroup(AffineTransform transform, String layer) throws IFException { saveGraphicsState(); try { concatenateTransformationMatrix(transform); diff --git a/src/java/org/apache/fop/render/java2d/Java2DRenderer.java b/src/java/org/apache/fop/render/java2d/Java2DRenderer.java index a98aff353..d343c55d8 100644 --- a/src/java/org/apache/fop/render/java2d/Java2DRenderer.java +++ b/src/java/org/apache/fop/render/java2d/Java2DRenderer.java @@ -476,6 +476,14 @@ public abstract class Java2DRenderer extends AbstractPathOrientedRenderer implem } /** {@inheritDoc} */ + protected void startLayer(String layer) { + } + + /** {@inheritDoc} */ + protected void endLayer() { + } + + /** {@inheritDoc} */ protected List breakOutOfStateStack() { log.debug("Block.FIXED --> break out"); List breakOutList; diff --git a/src/java/org/apache/fop/render/pcl/PCLPainter.java b/src/java/org/apache/fop/render/pcl/PCLPainter.java index c51ee834f..f934eed8c 100644 --- a/src/java/org/apache/fop/render/pcl/PCLPainter.java +++ b/src/java/org/apache/fop/render/pcl/PCLPainter.java @@ -126,7 +126,7 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements } /** {@inheritDoc} */ - public void startGroup(AffineTransform transform) throws IFException { + public void startGroup(AffineTransform transform, String layer) throws IFException { saveGraphicsState(); try { concatenateTransformationMatrix(transform); diff --git a/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java b/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java index dde6b0ef3..ac7b1d905 100644 --- a/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java +++ b/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java @@ -30,6 +30,7 @@ import org.apache.fop.pdf.PDFDocument; import org.apache.fop.pdf.PDFFilterList; import org.apache.fop.pdf.PDFNumber; import org.apache.fop.pdf.PDFPaintingState; +import org.apache.fop.pdf.PDFReference; import org.apache.fop.pdf.PDFResourceContext; import org.apache.fop.pdf.PDFStream; import org.apache.fop.pdf.PDFText; @@ -55,7 +56,7 @@ public class PDFContentGenerator { private PDFColorHandler colorHandler; /** drawing state */ - protected PDFPaintingState currentState = null; + protected PDFPaintingState currentState; /** Text generation utility holding the current font status */ protected PDFTextUtil textutil; @@ -156,15 +157,23 @@ public class PDFContentGenerator { */ protected void comment(String text) { if (WRITE_COMMENTS) { - currentStream.add("% " + text + "\n"); + getStream().add("% " + text + "\n"); } } /** Save graphics state. */ protected void saveGraphicsState() { endTextObject(); - currentState.save(); - currentStream.add("q\n"); + getState().save(); + getStream().add("q\n"); + } + + /** Save graphics state with optional layer. */ + protected void saveGraphicsState(String layer) { + endTextObject(); + getState().save(); + maybeBeginLayer(layer); + getStream().add("q\n"); } /** @@ -174,9 +183,9 @@ public class PDFContentGenerator { */ protected void saveGraphicsState(String structElemType, int sequenceNum) { endTextObject(); - currentState.save(); + getState().save(); beginMarkedContentSequence(structElemType, sequenceNum); - currentStream.add("q\n"); + getStream().add("q\n"); } /** @@ -208,18 +217,18 @@ public class PDFContentGenerator { if (structElemType != null) { String actualTextProperty = actualText == null ? "" : " /ActualText " + PDFText.escapeText(actualText); - currentStream.add(structElemType + " <</MCID " + String.valueOf(mcid) + getStream().add(structElemType + " <</MCID " + String.valueOf(mcid) + actualTextProperty + ">>\n" + "BDC\n"); } else { - currentStream.add("/Artifact\nBMC\n"); + getStream().add("/Artifact\nBMC\n"); this.inArtifactMode = true; } this.inMarkedContentSequence = true; } void endMarkedContentSequence() { - currentStream.add("EMC\n"); + getStream().add("EMC\n"); this.inMarkedContentSequence = false; this.inArtifactMode = false; } @@ -231,9 +240,10 @@ public class PDFContentGenerator { */ protected void restoreGraphicsState(boolean popState) { endTextObject(); - currentStream.add("Q\n"); + getStream().add("Q\n"); + maybeEndLayer(); if (popState) { - currentState.restore(); + getState().restore(); } } @@ -251,11 +261,42 @@ public class PDFContentGenerator { */ protected void restoreGraphicsStateAccess() { endTextObject(); - currentStream.add("Q\n"); + getStream().add("Q\n"); if (this.inMarkedContentSequence) { endMarkedContentSequence(); } - currentState.restore(); + getState().restore(); + } + + private void maybeBeginLayer(String layer) { + if ((layer != null) && (layer.length() > 0)) { + getState().setLayer(layer); + beginOptionalContent(layer); + } + } + + private void maybeEndLayer() { + if (getState().getLayerChanged()) { + endOptionalContent(); + } + } + + private int ocNameIndex = 0; + + private void beginOptionalContent(String layerId) { + String name; + PDFReference layer = document.resolveExtensionReference(layerId); + if (layer != null) { + name = "oc" + ++ocNameIndex; + document.getResources().addProperty(name, layer); + } else { + name = "unknown"; + } + getStream().add("/OC /" + name + " BDC\n"); + } + + private void endOptionalContent() { + getStream().add("EMC\n"); } /** Indicates the beginning of a text object. */ @@ -310,8 +351,8 @@ public class PDFContentGenerator { public void concatenate(AffineTransform transform) { this.transform = transform; if (!transform.isIdentity()) { - currentState.concatenate(transform); - currentStream.add(CTMHelper.toPDFString(transform, false) + " cm\n"); + getState().concatenate(transform); + getStream().add(CTMHelper.toPDFString(transform, false) + " cm\n"); } } @@ -333,7 +374,7 @@ public class PDFContentGenerator { * @param content the PDF content */ public void add(String content) { - currentStream.add(content); + getStream().add(content); } /** @@ -350,9 +391,9 @@ public class PDFContentGenerator { * @param width line width in points */ public void updateLineWidth(float width) { - if (currentState.setLineWidth(width)) { + if (getState().setLineWidth(width)) { //Only write if value has changed WRT the current line width - currentStream.add(format(width) + " w\n"); + getStream().add(format(width) + " w\n"); } } @@ -362,7 +403,7 @@ public class PDFContentGenerator { */ public void updateCharacterSpacing(float value) { if (getState().setCharacterSpacing(value)) { - currentStream.add(format(value) + " Tc\n"); + getStream().add(format(value) + " Tc\n"); } } @@ -400,7 +441,7 @@ public class PDFContentGenerator { if (pdf != null) { colorHandler.establishColor(pdf, col, fill); } else { - setColor(col, fill, this.currentStream); + setColor(col, fill, getStream()); } } diff --git a/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java index 0a3d34ef4..648cdce7a 100644 --- a/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java @@ -306,8 +306,7 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { throw new IFException("Error adding embedded file: " + embeddedFile.getSrc(), ioe); } } else if (extension instanceof PDFDictionaryAttachment) { - PDFDictionaryAttachment dictionaryExtension = (PDFDictionaryAttachment) extension; - pdfUtil.renderDictionaryExtension(dictionaryExtension, currentPage); + pdfUtil.renderDictionaryExtension((PDFDictionaryAttachment) extension, currentPage); } else if (extension != null) { log.debug("Don't know how to handle extension object. Ignoring: " + extension + " (" + extension.getClass().getName() + ")"); diff --git a/src/java/org/apache/fop/render/pdf/PDFPainter.java b/src/java/org/apache/fop/render/pdf/PDFPainter.java index d9fdd399c..f85328b8b 100644 --- a/src/java/org/apache/fop/render/pdf/PDFPainter.java +++ b/src/java/org/apache/fop/render/pdf/PDFPainter.java @@ -143,8 +143,8 @@ public class PDFPainter extends AbstractIFPainter<PDFDocumentHandler> { } /** {@inheritDoc} */ - public void startGroup(AffineTransform transform) throws IFException { - generator.saveGraphicsState(); + public void startGroup(AffineTransform transform, String layer) throws IFException { + generator.saveGraphicsState(layer); generator.concatenate(toPoints(transform)); } diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java index 962cd7847..4352dae6c 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java @@ -27,6 +27,7 @@ import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.EnumMap; +import java.util.List; import java.util.Map; import org.apache.commons.io.IOUtils; @@ -55,24 +56,33 @@ import org.apache.fop.pdf.PDFFileSpec; import org.apache.fop.pdf.PDFICCBasedColorSpace; import org.apache.fop.pdf.PDFICCStream; import org.apache.fop.pdf.PDFInfo; +import org.apache.fop.pdf.PDFLayer; import org.apache.fop.pdf.PDFMetadata; import org.apache.fop.pdf.PDFName; import org.apache.fop.pdf.PDFNames; +import org.apache.fop.pdf.PDFNavigator; +import org.apache.fop.pdf.PDFNull; import org.apache.fop.pdf.PDFNumber; import org.apache.fop.pdf.PDFOutputIntent; import org.apache.fop.pdf.PDFPage; import org.apache.fop.pdf.PDFPageLabels; import org.apache.fop.pdf.PDFReference; +import org.apache.fop.pdf.PDFSetOCGStateAction; import org.apache.fop.pdf.PDFText; +import org.apache.fop.pdf.PDFTransitionAction; import org.apache.fop.pdf.PDFXMode; import org.apache.fop.pdf.Version; import org.apache.fop.pdf.VersionController; +import org.apache.fop.render.pdf.extensions.PDFActionExtension; +import org.apache.fop.render.pdf.extensions.PDFArrayExtension; +import org.apache.fop.render.pdf.extensions.PDFCollectionEntryExtension; import org.apache.fop.render.pdf.extensions.PDFDictionaryAttachment; -import org.apache.fop.render.pdf.extensions.PDFDictionaryEntryExtension; -import org.apache.fop.render.pdf.extensions.PDFDictionaryEntryType; import org.apache.fop.render.pdf.extensions.PDFDictionaryExtension; import org.apache.fop.render.pdf.extensions.PDFDictionaryType; import org.apache.fop.render.pdf.extensions.PDFEmbeddedFileAttachment; +import org.apache.fop.render.pdf.extensions.PDFObjectType; +import org.apache.fop.render.pdf.extensions.PDFPageExtension; +import org.apache.fop.render.pdf.extensions.PDFReferenceExtension; import static org.apache.fop.render.pdf.PDFEncryptionOption.ENCRYPTION_PARAMS; import static org.apache.fop.render.pdf.PDFEncryptionOption.NO_ACCESSCONTENT; @@ -260,10 +270,189 @@ class PDFRenderingUtil { public void renderDictionaryExtension(PDFDictionaryAttachment attachment, PDFPage currentPage) { PDFDictionaryExtension extension = attachment.getExtension(); - if (extension.getDictionaryType() == PDFDictionaryType.Catalog) { + PDFDictionaryType type = extension.getDictionaryType(); + if (type == PDFDictionaryType.Action) { + addNavigatorAction(extension); + } else if (type == PDFDictionaryType.Layer) { + addLayer(extension); + } else if (type == PDFDictionaryType.Navigator) { + addNavigator(extension); + } else { + renderDictionaryExtension(extension, currentPage); + } + } + + public void addLayer(PDFDictionaryExtension extension) { + assert extension.getDictionaryType() == PDFDictionaryType.Layer; + String id = extension.getProperty(PDFDictionaryExtension.PROPERTY_ID); + if ((id != null) && (id.length() > 0)) { + PDFLayer layer = pdfDoc.getFactory().makeLayer(id); + layer.setResolver(new PDFLayer.Resolver(layer, extension) { + public void performResolution() { + PDFDictionaryExtension extension = (PDFDictionaryExtension) getExtension(); + Object name = extension.findEntryValue("Name"); + Object intent = extension.findEntryValue("Intent"); + Object usage = makeDictionary(extension.findEntryValue("Usage")); + getLayer().populate(name, intent, usage); + } + }); + } + } + + public void addNavigatorAction(PDFDictionaryExtension extension) { + assert extension.getDictionaryType() == PDFDictionaryType.Action; + String id = extension.getProperty(PDFDictionaryExtension.PROPERTY_ID); + if ((id != null) && (id.length() > 0)) { + String type = extension.getProperty(PDFActionExtension.PROPERTY_TYPE); + if (type != null) { + if (type.equals("SetOCGState")) { + PDFSetOCGStateAction action = pdfDoc.getFactory().makeSetOCGStateAction(id); + action.setResolver(new PDFSetOCGStateAction.Resolver(action, extension) { + public void performResolution() { + PDFDictionaryExtension extension = (PDFDictionaryExtension) getExtension(); + Object state = makeArray(extension.findEntryValue("State")); + Object preserveRB = extension.findEntryValue("PreserveRB"); + Object nextAction = makeDictionaryOrArray(extension.findEntryValue("Next")); + getAction().populate(state, preserveRB, nextAction); + } + }); + } else if (type.equals("Trans")) { + PDFTransitionAction action = pdfDoc.getFactory().makeTransitionAction(id); + action.setResolver(new PDFTransitionAction.Resolver(action, extension) { + public void performResolution() { + PDFDictionaryExtension extension = (PDFDictionaryExtension) getExtension(); + Object transition = makeDictionary(extension.findEntryValue("Trans")); + Object nextAction = makeDictionaryOrArray(extension.findEntryValue("Next")); + getAction().populate(transition, nextAction); + } + }); + } else { + throw new UnsupportedOperationException(); + } + } + } + } + + public void addNavigator(PDFDictionaryExtension extension) { + assert extension.getDictionaryType() == PDFDictionaryType.Navigator; + String id = extension.getProperty(PDFDictionaryExtension.PROPERTY_ID); + if ((id != null) && (id.length() > 0)) { + PDFNavigator navigator = pdfDoc.getFactory().makeNavigator(id); + navigator.setResolver(new PDFNavigator.Resolver(navigator, extension) { + public void performResolution() { + PDFDictionaryExtension extension = (PDFDictionaryExtension) getExtension(); + Object nextAction = makeDictionary(extension.findEntryValue("NA")); + Object next = makeDictionary(extension.findEntryValue("Next")); + Object prevAction = makeDictionary(extension.findEntryValue("PA")); + Object prev = makeDictionary(extension.findEntryValue("Prev")); + Object duration = extension.findEntryValue("Dur"); + getNavigator().populate(nextAction, next, prevAction, prev, duration); + } + }); + } + } + + private Object makeArray(Object value) { + if (value == null) { + return null; + } else if (value instanceof PDFReferenceExtension) { + return resolveReference((PDFReferenceExtension) value); + } else if (value instanceof List<?>) { + return populateArray(new PDFArray(), (List<?>) value); + } else { + throw new IllegalArgumentException(); + } + } + + private Object populateArray(PDFArray array, List<?> entries) { + for (PDFCollectionEntryExtension entry : (List<PDFCollectionEntryExtension>) entries) { + PDFObjectType type = entry.getType(); + if (type == PDFObjectType.Array) { + array.add(makeArray(entry.getValue())); + } else if (type == PDFObjectType.Boolean) { + array.add(entry.getValueAsBoolean()); + } else if (type == PDFObjectType.Dictionary) { + array.add(makeDictionary(entry.getValue())); + } else if (type == PDFObjectType.Name) { + array.add(new PDFName(entry.getValueAsString())); + } else if (type == PDFObjectType.Number) { + array.add(new PDFNumber(entry.getValueAsNumber())); + } else if (type == PDFObjectType.Reference) { + array.add(resolveReference((PDFReferenceExtension) entry)); + } else if (type == PDFObjectType.String) { + array.add(entry.getValue()); + } + } + return array; + } + + private Object makeDictionary(Object value) { + if (value == null) { + return null; + } else if (value instanceof PDFReferenceExtension) { + return resolveReference((PDFReferenceExtension) value); + } else if (value instanceof List<?>) { + return populateDictionary(new PDFDictionary(), (List<?>) value); + } else { + throw new IllegalArgumentException(); + } + } + + private Object populateDictionary(PDFDictionary dictionary, List<?> entries) { + for (PDFCollectionEntryExtension entry : (List<PDFCollectionEntryExtension>) entries) { + PDFObjectType type = entry.getType(); + String key = entry.getKey(); + if (type == PDFObjectType.Array) { + dictionary.put(key, makeArray(entry.getValue())); + } else if (type == PDFObjectType.Boolean) { + dictionary.put(key, entry.getValueAsBoolean()); + } else if (type == PDFObjectType.Dictionary) { + dictionary.put(key, makeDictionary(entry.getValue())); + } else if (type == PDFObjectType.Name) { + dictionary.put(key, new PDFName(entry.getValueAsString())); + } else if (type == PDFObjectType.Number) { + dictionary.put(key, new PDFNumber(entry.getValueAsNumber())); + } else if (type == PDFObjectType.Reference) { + dictionary.put(key, resolveReference((PDFReferenceExtension) entry)); + } else if (type == PDFObjectType.String) { + dictionary.put(key, entry.getValue()); + } + } + return dictionary; + } + + private Object makeDictionaryOrArray(Object value) { + if (value == null) { + return null; + } else if (value instanceof PDFReferenceExtension) { + return resolveReference((PDFReferenceExtension) value); + } else if (value instanceof List<?>) { + if (hasKeyedEntry((List<?>) value)) { + return populateDictionary(new PDFDictionary(), (List<?>) value); + } else { + return populateArray(new PDFArray(), (List<?>) value); + } + } else { + throw new IllegalArgumentException(); + } + } + + private boolean hasKeyedEntry(List<?> entries) { + for (PDFCollectionEntryExtension entry : (List<PDFCollectionEntryExtension>) entries) { + if (entry.getKey() != null) { + return true; + } + } + return false; + } + + public void renderDictionaryExtension(PDFDictionaryExtension extension, PDFPage currentPage) { + PDFDictionaryType type = extension.getDictionaryType(); + if (type == PDFDictionaryType.Catalog) { augmentDictionary(pdfDoc.getRoot(), extension); - } else if (extension.getDictionaryType() == PDFDictionaryType.Page) { - if (extension.matchesPageNumber(currentPage.getPageIndex() + 1)) { + } else if (type == PDFDictionaryType.Page) { + assert extension instanceof PDFPageExtension; + if (((PDFPageExtension) extension).matchesPageNumber(currentPage.getPageIndex() + 1)) { augmentDictionary(currentPage, extension); } } else { @@ -272,9 +461,12 @@ class PDFRenderingUtil { } private PDFDictionary augmentDictionary(PDFDictionary dictionary, PDFDictionaryExtension extension) { - for (PDFDictionaryEntryExtension entry : extension.getEntries()) { + for (PDFCollectionEntryExtension entry : extension.getEntries()) { if (entry instanceof PDFDictionaryExtension) { - dictionary.put(entry.getKey(), augmentDictionary(new PDFDictionary(dictionary), (PDFDictionaryExtension) entry)); + dictionary.put(entry.getKey(), + augmentDictionary(new PDFDictionary(dictionary), (PDFDictionaryExtension) entry)); + } else if (entry instanceof PDFArrayExtension) { + dictionary.put(entry.getKey(), augmentArray(new PDFArray(dictionary), (PDFArrayExtension) entry)); } else { augmentDictionary(dictionary, entry); } @@ -282,22 +474,68 @@ class PDFRenderingUtil { return dictionary; } - private void augmentDictionary(PDFDictionary dictionary, PDFDictionaryEntryExtension entry) { - PDFDictionaryEntryType type = entry.getType(); + private void augmentDictionary(PDFDictionary dictionary, PDFCollectionEntryExtension entry) { + PDFObjectType type = entry.getType(); String key = entry.getKey(); - if (type == PDFDictionaryEntryType.Boolean) { + if (type == PDFObjectType.Boolean) { dictionary.put(key, entry.getValueAsBoolean()); - } else if (type == PDFDictionaryEntryType.Name) { + } else if (type == PDFObjectType.Name) { dictionary.put(key, new PDFName(entry.getValueAsString())); - } else if (type == PDFDictionaryEntryType.Number) { + } else if (type == PDFObjectType.Number) { dictionary.put(key, new PDFNumber(entry.getValueAsNumber())); - } else if (type == PDFDictionaryEntryType.String) { + } else if (type == PDFObjectType.Reference) { + assert entry instanceof PDFReferenceExtension; + dictionary.put(key, resolveReference((PDFReferenceExtension) entry)); + } else if (type == PDFObjectType.String) { dictionary.put(key, entry.getValueAsString()); } else { throw new IllegalStateException(); } } + private Object resolveReference(PDFReferenceExtension entry) { + PDFReference reference = (PDFReference) entry.getResolvedReference(); + if (reference == null) { + reference = pdfDoc.resolveExtensionReference(entry.getReferenceId()); + if (reference != null) { + entry.setResolvedReference(reference); + } + return reference; + } + return PDFNull.INSTANCE; + } + + private PDFArray augmentArray(PDFArray array, PDFArrayExtension extension) { + for (PDFCollectionEntryExtension entry : extension.getEntries()) { + if (entry instanceof PDFDictionaryExtension) { + array.add(augmentDictionary(new PDFDictionary(array), (PDFDictionaryExtension) entry)); + } else if (entry instanceof PDFArrayExtension) { + array.add(augmentArray(new PDFArray(array), (PDFArrayExtension) entry)); + } else { + augmentArray(array, entry); + } + } + return array; + } + + private void augmentArray(PDFArray array, PDFCollectionEntryExtension entry) { + PDFObjectType type = entry.getType(); + if (type == PDFObjectType.Boolean) { + array.add(entry.getValueAsBoolean()); + } else if (type == PDFObjectType.Name) { + array.add(new PDFName(entry.getValueAsString())); + } else if (type == PDFObjectType.Number) { + array.add(new PDFNumber(entry.getValueAsNumber())); + } else if (type == PDFObjectType.Reference) { + assert entry instanceof PDFReferenceExtension; + array.add(resolveReference((PDFReferenceExtension) entry)); + } else if (type == PDFObjectType.String) { + array.add(entry.getValueAsString()); + } else { + throw new IllegalStateException(); + } + } + public PDFDocument setupPDFDocument(OutputStream out) throws IOException { if (this.pdfDoc != null) { throw new IllegalStateException("PDFDocument already set up"); diff --git a/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java b/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java index 4d34b8be4..985974b80 100644 --- a/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java +++ b/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java @@ -358,6 +358,7 @@ class PDFStructureTreeBuilder implements StructureTreeEventHandler { } public StructureTreeElement startNode(String name, Attributes attributes, StructureTreeElement parent) { + assert parent == null || parent instanceof PDFStructElem; PDFStructElem parentElem = parent == null ? ancestors.getFirst() : (PDFStructElem) parent; PDFStructElem structElem = createStructureElement(name, parentElem, attributes, pdfFactory, eventBroadcaster); diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFActionElement.java b/src/java/org/apache/fop/render/pdf/extensions/PDFActionElement.java new file mode 100644 index 000000000..e4a5747e6 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFActionElement.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf.extensions; + +import org.xml.sax.Attributes; +import org.xml.sax.Locator; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.fo.Constants; +import org.apache.fop.fo.FONode; +import org.apache.fop.fo.PropertyList; + +// CSOFF: LineLengthCheck + +/** + * Extension element for pdf:action. + */ +public class PDFActionElement extends PDFDictionaryElement { + + public static final String ATT_TYPE = PDFActionExtension.PROPERTY_TYPE; + + /** + * Main constructor + * @param parent parent FO node + */ + PDFActionElement(FONode parent) { + super(parent, PDFDictionaryType.Action); + } + + @Override + public void processNode(String elementName, Locator locator, Attributes attlist, PropertyList propertyList) throws FOPException { + super.processNode(elementName, locator, attlist, propertyList); + String type = attlist.getValue(ATT_TYPE); + if (type != null) { + getDictionaryExtension().setProperty(PDFActionExtension.PROPERTY_TYPE, type); + } + } + + @Override + public void startOfNode() throws FOPException { + super.startOfNode(); + if (parent.getNameId() != Constants.FO_DECLARATIONS) { + invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), "rule.childOfDeclarations"); + } + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFActionExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFActionExtension.java new file mode 100644 index 000000000..778b8a203 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFActionExtension.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf.extensions; + +// CSOFF: LineLengthCheck + +public class PDFActionExtension extends PDFDictionaryExtension { + + public static final String PROPERTY_TYPE = "type"; + + PDFActionExtension() { + super(PDFDictionaryType.Action); + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFArrayElement.java b/src/java/org/apache/fop/render/pdf/extensions/PDFArrayElement.java new file mode 100644 index 000000000..1f3ba22b2 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFArrayElement.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf.extensions; + +import org.xml.sax.Attributes; +import org.xml.sax.Locator; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.fo.FONode; +import org.apache.fop.fo.PropertyList; + +// CSOFF: LineLengthCheck + +/** + * Extension element for pdf:array. + */ +public class PDFArrayElement extends PDFCollectionEntryElement { + + private PDFArrayExtension extension; + + /** + * Main constructor + * @param parent parent FO node + */ + PDFArrayElement(FONode parent) { + super(parent, PDFObjectType.Array, new PDFArrayExtension()); + } + + public PDFArrayExtension getArrayExtension() { + assert getExtension() instanceof PDFArrayExtension; + return (PDFArrayExtension) getExtension(); + } + + @Override + public void processNode(String elementName, Locator locator, Attributes attlist, PropertyList propertyList) throws FOPException { + super.processNode(elementName, locator, attlist, propertyList); + } + + @Override + public void startOfNode() throws FOPException { + super.startOfNode(); + } + + @Override + protected void addChildNode(FONode child) throws FOPException { + PDFArrayExtension extension = getArrayExtension(); + if (child instanceof PDFCollectionEntryElement) { + PDFCollectionEntryExtension entry = ((PDFCollectionEntryElement) child).getExtension(); + if (entry.getKey() == null) { + extension.addEntry(entry); + } + } + } + + @Override + public void endOfNode() throws FOPException { + super.endOfNode(); + } + + @Override + public String getLocalName() { + return PDFObjectType.Array.elementName(); + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFArrayExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFArrayExtension.java new file mode 100644 index 000000000..80c6c94e4 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFArrayExtension.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf.extensions; + +import java.util.List; +import java.util.Map; + +// CSOFF: LineLengthCheck + +public class PDFArrayExtension extends PDFCollectionExtension { + + private static final long serialVersionUID = -1L; + + private Map<String, String> properties; + private List<PDFCollectionEntryExtension> entries; + + PDFArrayExtension() { + super(PDFObjectType.Array); + this.properties = new java.util.HashMap<String, String>(); + this.entries = new java.util.ArrayList<PDFCollectionEntryExtension>(); + } + + @Override + public void setValue(Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Object getValue() { + return getEntries(); + } + + public void setProperty(String name, String value) { + properties.put(name, value); + } + + public String getProperty(String name) { + return properties.get(name); + } + + @Override + public void addEntry(PDFCollectionEntryExtension entry) { + if (entry.getKey() != null) { + throw new IllegalArgumentException(); + } else { + entries.add(entry); + } + } + + public List<PDFCollectionEntryExtension> getEntries() { + return entries; + } + + public PDFCollectionEntryExtension getLastEntry() { + if (entries.size() > 0) { + return entries.get(entries.size() - 1); + } else { + return null; + } + } + + @Override + public String getElementName() { + return PDFObjectType.Array.elementName(); + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFCatalogElement.java b/src/java/org/apache/fop/render/pdf/extensions/PDFCatalogElement.java new file mode 100644 index 000000000..029357d22 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFCatalogElement.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf.extensions; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.fo.Constants; +import org.apache.fop.fo.FONode; + +// CSOFF: LineLengthCheck + +/** + * Extension element for pdf:catalog. + */ +public class PDFCatalogElement extends PDFDictionaryElement { + + PDFCatalogElement(FONode parent) { + super(parent, PDFDictionaryType.Catalog); + } + + @Override + public void startOfNode() throws FOPException { + super.startOfNode(); + if (parent.getNameId() != Constants.FO_DECLARATIONS) { + invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), "rule.childOfDeclarations"); + } + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFCatalogExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFCatalogExtension.java new file mode 100644 index 000000000..381f6fbe8 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFCatalogExtension.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf.extensions; + +// CSOFF: LineLengthCheck + +public class PDFCatalogExtension extends PDFDictionaryExtension { + + PDFCatalogExtension() { + super(PDFDictionaryType.Catalog); + } +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFCollectionEntryElement.java b/src/java/org/apache/fop/render/pdf/extensions/PDFCollectionEntryElement.java new file mode 100644 index 000000000..4185ceef0 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFCollectionEntryElement.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf.extensions; + +import org.xml.sax.Attributes; +import org.xml.sax.Locator; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.fo.FONode; +import org.apache.fop.fo.PropertyList; + +// CSOFF: LineLengthCheck + +/** + * Extension element for collection entries: pdf:{array,boolean,dictionary,name,number,reference,string}. The specific type + * of entry is established at construction type. + */ +public class PDFCollectionEntryElement extends AbstractPDFExtensionElement { + + public static final String ATT_KEY = PDFCollectionEntryExtension.PROPERTY_KEY; + + private PDFCollectionEntryExtension extension; + private StringBuffer characters; + + PDFCollectionEntryElement(FONode parent, PDFObjectType type, PDFCollectionEntryExtension extension) { + super(parent); + this.extension = extension; + } + + PDFCollectionEntryElement(FONode parent, PDFObjectType type) { + this(parent, type, createExtension(type)); + } + + private static PDFCollectionEntryExtension createExtension(PDFObjectType type) { + if (type == PDFObjectType.Reference) { + return new PDFReferenceExtension(); + } else { + return new PDFCollectionEntryExtension(type); + } + } + + public PDFCollectionEntryExtension getExtension() { + return extension; + } + + @Override + public void processNode(String elementName, Locator locator, Attributes attlist, PropertyList propertyList) throws FOPException { + if (parent instanceof PDFDictionaryElement) { + String key = attlist.getValue(ATT_KEY); + if (key == null) { + missingPropertyError(ATT_KEY); + } else if (key.length() == 0) { + invalidPropertyValueError(ATT_KEY, key, null); + } else { + extension.setKey(key); + } + } + } + + @Override + public void startOfNode() throws FOPException { + super.startOfNode(); + if (parent instanceof PDFDictionaryElement) { + if (!PDFDictionaryType.hasValueOfElementName(parent.getLocalName())) { + invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), null); + } + } + } + + @Override + protected void characters(char[] data, int start, int length, PropertyList pList, Locator locator) throws FOPException { + if (capturePCData(extension.getType())) { + if (characters == null) { + characters = new StringBuffer((length < 16) ? 16 : length); + } + characters.append(data, start, length); + } + } + + private boolean capturePCData(PDFObjectType type) { + if (type == PDFObjectType.Array) { + return false; + } else if (type == PDFObjectType.Dictionary) { + return false; + } else { + return (type != PDFObjectType.Reference); + } + } + + @Override + public void endOfNode() throws FOPException { + if (capturePCData(extension.getType())) { + if (extension.getType() == PDFObjectType.Boolean) { + String value = (characters != null) ? characters.toString() : ""; + if (!value.equals("true") && !value.equals("false")) { + invalidPropertyValueError("<value>", value, null); + } + extension.setValue(Boolean.valueOf(value)); + } else if (extension.getType() == PDFObjectType.Name) { + String value = (characters != null) ? characters.toString() : ""; + if (value.length() == 0) { + invalidPropertyValueError("<value>", value, null); + } + extension.setValue(value); + } else if (extension.getType() == PDFObjectType.Number) { + String value = (characters != null) ? characters.toString() : ""; + try { + double d = Double.parseDouble(value); + if (Math.abs(Math.floor(d) - d) < 1E-10) { + extension.setValue(Long.valueOf((long) d)); + } else { + extension.setValue(Double.valueOf(d)); + } + } catch (NumberFormatException e) { + invalidPropertyValueError("<value>", value, null); + } + } else if (extension.getType() == PDFObjectType.String) { + String value = (characters != null) ? characters.toString() : ""; + extension.setValue(value); + } + } + super.endOfNode(); + } + + @Override + public String getLocalName() { + return extension.getType().elementName(); + } +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/AbstractPDFDictionaryElement.java b/src/java/org/apache/fop/render/pdf/extensions/PDFCollectionEntryExtension.java index 9de7e95da..d28f1602f 100644 --- a/src/java/org/apache/fop/render/pdf/extensions/AbstractPDFDictionaryElement.java +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFCollectionEntryExtension.java @@ -19,25 +19,25 @@ package org.apache.fop.render.pdf.extensions; -import org.apache.fop.fo.FONode; - // CSOFF: LineLengthCheck -/** - * Base class for the PDF dictionary related extension elements. - */ -public abstract class AbstractPDFDictionaryElement extends AbstractPDFExtensionElement { - - public static final String ATT_KEY = PDFDictionaryEntryExtension.PROPERTY_KEY; - - /** - * Default constructor - * - * @param parent parent of this node - * @see org.apache.fop.fo.FONode#FONode(FONode) - */ - public AbstractPDFDictionaryElement(FONode parent) { - super(parent); +public class PDFCollectionEntryExtension extends PDFObjectExtension { + + public static final String PROPERTY_KEY = "key"; + + /* Non-empty key if used as dictionary entry, otherwise must be null. */ + private String key; + + PDFCollectionEntryExtension(PDFObjectType type) { + super(type); } -} + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFCollectionExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFCollectionExtension.java new file mode 100644 index 000000000..228d69c79 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFCollectionExtension.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf.extensions; + +// CSOFF: LineLengthCheck + +public abstract class PDFCollectionExtension extends PDFCollectionEntryExtension { + + protected PDFCollectionExtension(PDFObjectType type) { + super(type); + } + + public abstract void addEntry(PDFCollectionEntryExtension entry); + + public abstract PDFCollectionEntryExtension getLastEntry(); + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryAttachment.java b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryAttachment.java index 19e5ce07a..3832619ba 100644 --- a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryAttachment.java +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryAttachment.java @@ -43,14 +43,13 @@ public class PDFDictionaryAttachment extends PDFExtensionAttachment { } public void toSAX(ContentHandler handler) throws SAXException { - PDFDictionaryType dictionaryType = extension.getDictionaryType(); int pageNumber = 0; - if (dictionaryType == PDFDictionaryType.Page) { + if (extension instanceof PDFPageExtension) { if (handler instanceof GenerationHelperContentHandler) { Object context = ((GenerationHelperContentHandler) handler).getContentHandlerContext(); if (context instanceof IFContext) { int pageIndex = ((IFContext) context).getPageIndex(); - if ((pageIndex >= 0) && extension.matchesPageNumber(pageIndex + 1)) { + if ((pageIndex >= 0) && ((PDFPageExtension) extension).matchesPageNumber(pageIndex + 1)) { pageNumber = pageIndex + 1; } else { pageNumber = -1; @@ -69,24 +68,40 @@ public class PDFDictionaryAttachment extends PDFExtensionAttachment { String qn = PREFIX + ":" + ln; attributes = extractIFAttributes(attributes, dictionary); handler.startElement(CATEGORY, ln, qn, attributes); - for (PDFDictionaryEntryExtension entry : dictionary.getEntries()) { + for (PDFCollectionEntryExtension entry : dictionary.getEntries()) { toSAX(handler, entry); } handler.endElement(CATEGORY, ln, qn); } - private void toSAX(ContentHandler handler, PDFDictionaryEntryExtension entry) throws SAXException { + private void toSAX(ContentHandler handler, PDFArrayExtension array) throws SAXException { + AttributesImpl attributes = new AttributesImpl(); + String ln = array.getElementName(); + String qn = PREFIX + ":" + ln; + attributes = extractIFAttributes(attributes, array); + handler.startElement(CATEGORY, ln, qn, attributes); + for (PDFCollectionEntryExtension entry : array.getEntries()) { + toSAX(handler, entry); + } + handler.endElement(CATEGORY, ln, qn); + } + + private void toSAX(ContentHandler handler, PDFCollectionEntryExtension entry) throws SAXException { if (entry instanceof PDFDictionaryExtension) { toSAX(handler, (PDFDictionaryExtension) entry); + } else if (entry instanceof PDFArrayExtension) { + toSAX(handler, (PDFArrayExtension) entry); } else { AttributesImpl attributes = new AttributesImpl(); String ln = entry.getElementName(); String qn = PREFIX + ":" + ln; attributes = extractIFAttributes(attributes, entry); handler.startElement(CATEGORY, ln, qn, attributes); - char[] characters = entry.getValueAsXMLEscapedString().toCharArray(); - if (characters.length > 0) { - handler.characters(characters, 0, characters.length); + if (!(entry instanceof PDFReferenceExtension)) { + char[] characters = entry.getValueAsXMLEscapedString().toCharArray(); + if (characters.length > 0) { + handler.characters(characters, 0, characters.length); + } } handler.endElement(CATEGORY, ln, qn); } @@ -94,16 +109,27 @@ public class PDFDictionaryAttachment extends PDFExtensionAttachment { private static AttributesImpl extractIFAttributes(AttributesImpl attributes, PDFDictionaryExtension dictionary) { PDFDictionaryType type = dictionary.getDictionaryType(); - if (type == PDFDictionaryType.Catalog) { - // no specific attriburtes + if (dictionary.usesIDAttribute()) { + String idName = PDFDictionaryElement.ATT_ID; + String id = dictionary.getProperty(PDFDictionaryExtension.PROPERTY_ID); + if (id != null) { + attributes.addAttribute(null, idName, idName, "ID", id); + } + } + if (type == PDFDictionaryType.Action) { + String actionTypeName = PDFActionElement.ATT_TYPE; + String actionType = dictionary.getProperty(PDFActionExtension.PROPERTY_TYPE); + if (actionType != null) { + attributes.addAttribute(null, actionTypeName, actionTypeName, "CDATA", actionType); + } } else if (type == PDFDictionaryType.Page) { - String pageNumbersName = PDFDictionaryExtension.PROPERTY_PAGE_NUMBERS; + String pageNumbersName = PDFPageExtension.PROPERTY_PAGE_NUMBERS; String pageNumbers = dictionary.getProperty(pageNumbersName); if (pageNumbers != null) { attributes.addAttribute(null, pageNumbersName, pageNumbersName, "CDATA", pageNumbers); } } else if (type == PDFDictionaryType.Dictionary) { - String keyName = PDFDictionaryEntryExtension.PROPERTY_KEY; + String keyName = PDFCollectionEntryElement.ATT_KEY; String key = dictionary.getKey(); if (key != null) { attributes.addAttribute(null, keyName, keyName, "CDATA", key); @@ -112,12 +138,28 @@ public class PDFDictionaryAttachment extends PDFExtensionAttachment { return attributes; } - private static AttributesImpl extractIFAttributes(AttributesImpl attributes, PDFDictionaryEntryExtension entry) { - String keyName = PDFDictionaryEntryExtension.PROPERTY_KEY; + private static AttributesImpl extractIFAttributes(AttributesImpl attributes, PDFArrayExtension array) { + String keyName = PDFCollectionEntryExtension.PROPERTY_KEY; + String key = array.getKey(); + if (key != null) { + attributes.addAttribute(null, keyName, keyName, "CDATA", key); + } + return attributes; + } + + private static AttributesImpl extractIFAttributes(AttributesImpl attributes, PDFCollectionEntryExtension entry) { + String keyName = PDFCollectionEntryElement.ATT_KEY; String key = entry.getKey(); if (key != null) { attributes.addAttribute(null, keyName, keyName, "CDATA", key); } + if (entry instanceof PDFReferenceExtension) { + String refid = ((PDFReferenceExtension) entry).getReferenceId(); + if (refid != null) { + String refidName = PDFReferenceElement.ATT_REFID; + attributes.addAttribute(null, refidName, refidName, "IDREF", refid); + } + } return attributes; } diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryElement.java b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryElement.java index 0920f3a78..9dc127da6 100644 --- a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryElement.java +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryElement.java @@ -23,7 +23,6 @@ import org.xml.sax.Attributes; import org.xml.sax.Locator; import org.apache.fop.apps.FOPException; -import org.apache.fop.fo.Constants; import org.apache.fop.fo.FONode; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.extensions.ExtensionAttachment; @@ -34,38 +33,54 @@ import org.apache.fop.fo.extensions.ExtensionAttachment; * Extension element for dictionaries: pdf:{catalog,page,dictionary}. The specific type * of dictionary is established at construction type. */ -public class PDFDictionaryElement extends AbstractPDFDictionaryElement { +public class PDFDictionaryElement extends PDFCollectionEntryElement { - public static final String ATT_PAGE_NUMBERS = PDFDictionaryExtension.PROPERTY_PAGE_NUMBERS; - - private PDFDictionaryExtension extension; + public static final String ATT_ID = PDFDictionaryExtension.PROPERTY_ID; /** * Main constructor * @param parent parent FO node */ PDFDictionaryElement(FONode parent, PDFDictionaryType type) { - super(parent); - this.extension = new PDFDictionaryExtension(type); + super(parent, PDFObjectType.Dictionary, createExtension(type)); + } + + private static PDFDictionaryExtension createExtension(PDFDictionaryType type) { + if (type == PDFDictionaryType.Action) { + return new PDFActionExtension(); + } else if (type == PDFDictionaryType.Catalog) { + return new PDFCatalogExtension(); + } else if (type == PDFDictionaryType.Layer) { + return new PDFLayerExtension(); + } else if (type == PDFDictionaryType.Navigator) { + return new PDFNavigatorExtension(); + } else if (type == PDFDictionaryType.Page) { + return new PDFPageExtension(); + } else { + return new PDFDictionaryExtension(type); + } } - public PDFDictionaryExtension getExtension() { - return extension; + public PDFDictionaryExtension getDictionaryExtension() { + assert getExtension() instanceof PDFDictionaryExtension; + return (PDFDictionaryExtension) getExtension(); } @Override public void processNode(String elementName, Locator locator, Attributes attlist, PropertyList propertyList) throws FOPException { - if (extension.getDictionaryType() == PDFDictionaryType.Catalog) { - // no specific properties - } else if (extension.getDictionaryType() == PDFDictionaryType.Page) { - String pageNumbers = attlist.getValue(ATT_PAGE_NUMBERS); - if (pageNumbers != null) { - extension.setProperty(ATT_PAGE_NUMBERS, pageNumbers); + PDFDictionaryExtension extension = getDictionaryExtension(); + if (extension.usesIDAttribute()) { + String id = attlist.getValue(ATT_ID); + if (id != null) { + extension.setProperty(PDFDictionaryExtension.PROPERTY_ID, id); } - } else if (extension.getDictionaryType() == PDFDictionaryType.Dictionary) { + } + if (extension.getDictionaryType() == PDFDictionaryType.Dictionary) { String key = attlist.getValue(ATT_KEY); if (key == null) { - missingPropertyError(ATT_KEY); + if (parent instanceof PDFDictionaryElement) { + missingPropertyError(ATT_KEY); + } } else if (key.length() == 0) { invalidPropertyValueError(ATT_KEY, key, null); } else { @@ -78,16 +93,18 @@ public class PDFDictionaryElement extends AbstractPDFDictionaryElement { public void startOfNode() throws FOPException { super.startOfNode(); String localName = getLocalName(); - if (localName.equals("catalog")) { - if (parent.getNameId() != Constants.FO_DECLARATIONS) { - invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), "rule.childOfDeclarations"); - } + if (localName.equals("action")) { + // handled in PDFActionElement subclass + } else if (localName.equals("catalog")) { + // handled in PDFCatalogElement subclass + } else if (localName.equals("layer")) { + // handled in PDFLayerElement subclass + } else if (localName.equals("navigator")) { + // handled in PDFNavigattorElement subclass } else if (localName.equals("page")) { - if (parent.getNameId() != Constants.FO_SIMPLE_PAGE_MASTER) { - invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), "rule.childOfSPM"); - } + // handled in PDFPageElement subclass } else if (localName.equals("dictionary")) { - if (!PDFDictionaryType.hasValueOfElementName(parent.getLocalName())) { + if (!PDFDictionaryType.hasValueOfElementName(parent.getLocalName()) && !PDFObjectType.Array.elementName().equals(parent.getLocalName())) { invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), null); } } else { @@ -97,14 +114,15 @@ public class PDFDictionaryElement extends AbstractPDFDictionaryElement { @Override protected void addChildNode(FONode child) throws FOPException { + PDFDictionaryExtension extension = getDictionaryExtension(); if (child instanceof PDFDictionaryElement) { - PDFDictionaryExtension extension = ((PDFDictionaryElement) child).getExtension(); - if (extension.getDictionaryType() == PDFDictionaryType.Dictionary) { - this.extension.addEntry(extension); + PDFDictionaryExtension entry = ((PDFDictionaryElement) child).getDictionaryExtension(); + if (entry.getDictionaryType() == PDFDictionaryType.Dictionary) { + extension.addEntry(entry); } - } else if (child instanceof PDFDictionaryEntryElement) { - PDFDictionaryEntryExtension extension = ((PDFDictionaryEntryElement) child).getExtension(); - this.extension.addEntry(extension); + } else if (child instanceof PDFCollectionEntryElement) { + PDFCollectionEntryExtension entry = ((PDFCollectionEntryElement) child).getExtension(); + extension.addEntry(entry); } } @@ -115,12 +133,13 @@ public class PDFDictionaryElement extends AbstractPDFDictionaryElement { @Override public String getLocalName() { + PDFDictionaryExtension extension = getDictionaryExtension(); return extension.getDictionaryType().elementName(); } @Override protected ExtensionAttachment instantiateExtensionAttachment() { - return new PDFDictionaryAttachment(extension); + return new PDFDictionaryAttachment(getDictionaryExtension()); } } diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryElement.java b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryElement.java deleted file mode 100644 index bcdb90c2c..000000000 --- a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryElement.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* $Id$ */ - -package org.apache.fop.render.pdf.extensions; - -import org.xml.sax.Attributes; -import org.xml.sax.Locator; - -import org.apache.fop.apps.FOPException; -import org.apache.fop.fo.FONode; -import org.apache.fop.fo.PropertyList; - -// CSOFF: LineLengthCheck - -/** - * Extension element for dictionary entries: pdf:{boolean,name,number,string}. The specific type - * of entry is established at construction type. - */ -public class PDFDictionaryEntryElement extends AbstractPDFDictionaryElement { - - private PDFDictionaryEntryExtension extension; - private StringBuffer characters; - - /** - * Main constructor - * @param parent parent FO node - */ - PDFDictionaryEntryElement(FONode parent, PDFDictionaryEntryType type) { - super(parent); - this.extension = new PDFDictionaryEntryExtension(type); - } - - public PDFDictionaryEntryExtension getExtension() { - return extension; - } - - @Override - public void processNode(String elementName, Locator locator, Attributes attlist, PropertyList propertyList) throws FOPException { - String key = attlist.getValue("key"); - if (key == null) { - missingPropertyError("key"); - } else if (key.length() == 0) { - invalidPropertyValueError("key", key, null); - } else { - extension.setKey(key); - } - } - - @Override - public void startOfNode() throws FOPException { - super.startOfNode(); - if (!PDFDictionaryType.hasValueOfElementName(parent.getLocalName())) { - invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), null); - } - } - - @Override - protected void characters(char[] data, int start, int length, PropertyList pList, Locator locator) throws FOPException { - if (characters == null) { - characters = new StringBuffer((length < 16) ? 16 : length); - } - characters.append(data, start, length); - } - - @Override - public void endOfNode() throws FOPException { - String value = (characters != null) ? characters.toString() : ""; - if (extension.getType() == PDFDictionaryEntryType.Boolean) { - if (!value.equals("true") && !value.equals("false")) { - invalidPropertyValueError("<value>", value, null); - } - } else if (extension.getType() == PDFDictionaryEntryType.Name) { - if (value.length() == 0) { - invalidPropertyValueError("<value>", value, null); - } - } else if (extension.getType() == PDFDictionaryEntryType.Number) { - try { - Double.valueOf(value); - } catch (NumberFormatException e) { - invalidPropertyValueError("<value>", value, null); - } - } else if (extension.getType() != PDFDictionaryEntryType.String) { - throw new IllegalStateException(); - } - extension.setValue(value); - super.endOfNode(); - } - - @Override - public String getLocalName() { - return extension.getType().elementName(); - } -} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryExtension.java index d4b11cdb4..50b6f3a83 100644 --- a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryExtension.java +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryExtension.java @@ -24,24 +24,36 @@ import java.util.Map; // CSOFF: LineLengthCheck -public class PDFDictionaryExtension extends PDFDictionaryEntryExtension { +public class PDFDictionaryExtension extends PDFCollectionExtension { + public static final String PROPERTY_ID = "id"; public static final String PROPERTY_PAGE_NUMBERS = "page-numbers"; private static final long serialVersionUID = -1L; private PDFDictionaryType dictionaryType; private Map<String, String> properties; - private List<PDFDictionaryEntryExtension> entries; + private List<PDFCollectionEntryExtension> entries; PDFDictionaryExtension() { + this(PDFDictionaryType.Dictionary); } PDFDictionaryExtension(PDFDictionaryType dictionaryType) { - super(PDFDictionaryEntryType.Dictionary); + super(PDFObjectType.Dictionary); this.dictionaryType = dictionaryType; this.properties = new java.util.HashMap<String, String>(); - this.entries = new java.util.ArrayList<PDFDictionaryEntryExtension>(); + this.entries = new java.util.ArrayList<PDFCollectionEntryExtension>(); + } + + @Override + public void setValue(Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Object getValue() { + return getEntries(); } public PDFDictionaryType getDictionaryType() { @@ -56,15 +68,40 @@ public class PDFDictionaryExtension extends PDFDictionaryEntryExtension { return properties.get(name); } - public void addEntry(PDFDictionaryEntryExtension entry) { - entries.add(entry); + @Override + public void addEntry(PDFCollectionEntryExtension entry) { + if ((entry.getKey() == null) || (entry.getKey().length() == 0)) { + throw new IllegalArgumentException(); + } else { + entries.add(entry); + } } - public List<PDFDictionaryEntryExtension> getEntries() { + public List<PDFCollectionEntryExtension> getEntries() { return entries; } - public PDFDictionaryEntryExtension getLastEntry() { + public PDFCollectionEntryExtension findEntry(String key) { + for (PDFCollectionEntryExtension entry : entries) { + String entryKey = entry.getKey(); + if ((entryKey != null) && entryKey.equals(key)) { + return entry; + } + } + return null; + } + + public Object findEntryValue(String key) { + for (PDFCollectionEntryExtension entry : entries) { + String entryKey = entry.getKey(); + if ((entryKey != null) && entryKey.equals(key)) { + return entry.getValue(); + } + } + return null; + } + + public PDFCollectionEntryExtension getLastEntry() { if (entries.size() > 0) { return entries.get(entries.size() - 1); } else { @@ -72,48 +109,8 @@ public class PDFDictionaryExtension extends PDFDictionaryEntryExtension { } } - /** - * Determine if page dictionary and page number matches. - * @param pageNumber page number, where first page number is 1 - * @return true if this dictionary is a page dictionary and specified page number matches specified page-number property - */ - public boolean matchesPageNumber(int pageNumber) { - if (dictionaryType != PDFDictionaryType.Page) { - return false; - } - String pageNumbers = getProperty(PROPERTY_PAGE_NUMBERS); - if ((pageNumbers == null) || (pageNumbers.length() == 0)) { - return false; - } else if (pageNumbers.equals("*")) { - return true; - } else { - for (String interval : pageNumbers.split("\\s*,\\s*")) { - String[] components = interval.split("\\s*-\\s*"); - if (components.length < 1) { - continue; - } else { - try { - int start = Integer.parseInt(components[0]); - int end = 0; - if (components.length > 1) { - if (!components[1].equals("LAST")) { - end = Integer.parseInt(components[1]); - } - } - if ((end == 0) && (pageNumber == start)) { - return true; - } else if ((end > start) && (pageNumber >= start) && (pageNumber < end)) { - return true; - } else { - continue; - } - } catch (NumberFormatException e) { - continue; - } - } - } - } - return false; + public boolean usesIDAttribute() { + return dictionaryType.usesIDAttribute(); } @Override diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryType.java b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryType.java index edd95160a..a49a5fc8c 100644 --- a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryType.java +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryType.java @@ -25,17 +25,28 @@ package org.apache.fop.render.pdf.extensions; * Enumeration type for PDF dictionary extension elements. */ public enum PDFDictionaryType { - Dictionary("dictionary"), // generic (nested) dictionary element - Catalog("catalog"), // catalog dictionary element - Page("page"); // page dictionary element + Action("action", true), // action dictionary element + Catalog("catalog"), // catalog dictionary element + Dictionary("dictionary"), // generic (nested) dictionary element + Layer("layer", true), // optional content group dictionary element + Navigator("navigator", true), // navigation node dictionary element + Page("page"); // page dictionary element private String elementName; - PDFDictionaryType(String elementName) { + private boolean usesIDAttribute; + PDFDictionaryType(String elementName, boolean usesIDAttribute) { this.elementName = elementName; + this.usesIDAttribute = usesIDAttribute; + } + PDFDictionaryType(String elementName) { + this(elementName, false); } public String elementName() { return elementName; } + public boolean usesIDAttribute() { + return usesIDAttribute; + } static PDFDictionaryType valueOfElementName(String elementName) { for (PDFDictionaryType type : values()) { if (type.elementName.equals(elementName)) { diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFElementMapping.java b/src/java/org/apache/fop/render/pdf/extensions/PDFElementMapping.java index 3e063e24b..1fba80796 100644 --- a/src/java/org/apache/fop/render/pdf/extensions/PDFElementMapping.java +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFElementMapping.java @@ -41,17 +41,56 @@ public class PDFElementMapping extends ElementMapping { protected void initialize() { if (foObjs == null) { foObjs = new java.util.HashMap<String, Maker>(); + // pdf:action + foObjs.put(PDFDictionaryType.Action.elementName(), new PDFActionElementMaker()); + // pdf:array + foObjs.put(PDFObjectType.Array.elementName(), new PDFArrayElementMaker()); + // pdf:boolean + foObjs.put(PDFObjectType.Boolean.elementName(), new PDFCollectionEntryElementMaker(PDFObjectType.Boolean)); + // pdf:catalog + foObjs.put(PDFDictionaryType.Catalog.elementName(), new PDFCatalogElementMaker()); + // pdf:dictionary + foObjs.put(PDFDictionaryType.Dictionary.elementName(), new PDFDictionaryElementMaker()); // pdf:embedded-file foObjs.put(PDFEmbeddedFileElement.ELEMENT, new PDFEmbeddedFileElementMaker()); - // pdf:{catalog,page} et al. - for (PDFDictionaryType type : PDFDictionaryType.values()) { - foObjs.put(type.elementName(), new PDFDictionaryElementMaker(type)); - } - for (PDFDictionaryEntryType type : PDFDictionaryEntryType.values()) { - if (type != PDFDictionaryEntryType.Dictionary) { - foObjs.put(type.elementName(), new PDFDictionaryEntryElementMaker(type)); - } - } + // pdf:name + foObjs.put(PDFObjectType.Name.elementName(), new PDFCollectionEntryElementMaker(PDFObjectType.Name)); + // pdf:number + foObjs.put(PDFObjectType.Number.elementName(), new PDFCollectionEntryElementMaker(PDFObjectType.Number)); + // pdf:navigator + foObjs.put(PDFDictionaryType.Navigator.elementName(), new PDFNavigatorElementMaker()); + // pdf:layer + foObjs.put(PDFDictionaryType.Layer.elementName(), new PDFLayerElementMaker()); + // pdf:page + foObjs.put(PDFDictionaryType.Page.elementName(), new PDFPageElementMaker()); + // pdf:reference + foObjs.put(PDFObjectType.Reference.elementName(), new PDFReferenceElementMaker()); + // pdf:string + foObjs.put(PDFObjectType.String.elementName(), new PDFCollectionEntryElementMaker(PDFObjectType.String)); + } + } + + static class PDFActionElementMaker extends ElementMapping.Maker { + public FONode make(FONode parent) { + return new PDFActionElement(parent); + } + } + + static class PDFArrayElementMaker extends ElementMapping.Maker { + public FONode make(FONode parent) { + return new PDFArrayElement(parent); + } + } + + static class PDFCatalogElementMaker extends ElementMapping.Maker { + public FONode make(FONode parent) { + return new PDFCatalogElement(parent); + } + } + + static class PDFDictionaryElementMaker extends ElementMapping.Maker { + public FONode make(FONode parent) { + return new PDFDictionaryElement(parent, PDFDictionaryType.Dictionary); } } @@ -61,23 +100,38 @@ public class PDFElementMapping extends ElementMapping { } } - static class PDFDictionaryElementMaker extends ElementMapping.Maker { - private PDFDictionaryType dictionaryType; - PDFDictionaryElementMaker(PDFDictionaryType dictionaryType) { - this.dictionaryType = dictionaryType; + static class PDFLayerElementMaker extends ElementMapping.Maker { + public FONode make(FONode parent) { + return new PDFLayerElement(parent); } + } + + static class PDFNavigatorElementMaker extends ElementMapping.Maker { public FONode make(FONode parent) { - return new PDFDictionaryElement(parent, dictionaryType); + return new PDFNavigatorElement(parent); } } - static class PDFDictionaryEntryElementMaker extends ElementMapping.Maker { - private PDFDictionaryEntryType entryType; - PDFDictionaryEntryElementMaker(PDFDictionaryEntryType entryType) { + static class PDFPageElementMaker extends ElementMapping.Maker { + public FONode make(FONode parent) { + return new PDFPageElement(parent); + } + } + + static class PDFCollectionEntryElementMaker extends ElementMapping.Maker { + private PDFObjectType entryType; + PDFCollectionEntryElementMaker(PDFObjectType entryType) { this.entryType = entryType; } public FONode make(FONode parent) { - return new PDFDictionaryEntryElement(parent, entryType); + return new PDFCollectionEntryElement(parent, entryType); } } + + static class PDFReferenceElementMaker extends ElementMapping.Maker { + public FONode make(FONode parent) { + return new PDFReferenceElement(parent); + } + } + } diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFExtensionHandler.java b/src/java/org/apache/fop/render/pdf/extensions/PDFExtensionHandler.java index f14f1e7d6..2fd14058e 100644 --- a/src/java/org/apache/fop/render/pdf/extensions/PDFExtensionHandler.java +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFExtensionHandler.java @@ -49,7 +49,7 @@ public class PDFExtensionHandler extends DefaultHandler implements ContentHandle private Attributes lastAttributes; // PDFDictionaryAttachment related - private Stack<PDFDictionaryExtension> dictionaries = new Stack<PDFDictionaryExtension>(); + private Stack<PDFCollectionExtension> collections = new Stack<PDFCollectionExtension>(); private boolean captureContent; private StringBuffer characters; @@ -58,29 +58,78 @@ public class PDFExtensionHandler extends DefaultHandler implements ContentHandle if (PDFExtensionAttachment.CATEGORY.equals(uri)) { if (localName.equals(PDFEmbeddedFileAttachment.ELEMENT)) { lastAttributes = new AttributesImpl(attributes); - } else if (PDFDictionaryType.hasValueOfElementName(localName)) { - PDFDictionaryExtension dictionary = new PDFDictionaryExtension(PDFDictionaryType.valueOfElementName(localName)); - String key = attributes.getValue(PDFDictionaryEntryExtension.PROPERTY_KEY); + } else if (PDFDictionaryType.Action.elementName().equals(localName)) { + PDFActionExtension action = new PDFActionExtension(); + String id = attributes.getValue(PDFDictionaryElement.ATT_ID); + if (id != null) { + action.setProperty(PDFDictionaryExtension.PROPERTY_ID, id); + } + String type = attributes.getValue(PDFActionElement.ATT_TYPE); + if (type != null) { + action.setProperty(PDFActionExtension.PROPERTY_TYPE, type); + } + collections.push(action); + } else if (PDFObjectType.Array.elementName().equals(localName)) { + PDFArrayExtension array = new PDFArrayExtension(); + String key = attributes.getValue(PDFCollectionEntryElement.ATT_KEY); + if (key != null) { + array.setKey(key); + } + collections.push(array); + } else if (PDFDictionaryType.Catalog.elementName().equals(localName)) { + PDFCatalogExtension catalog = new PDFCatalogExtension(); + collections.push(catalog); + } else if (PDFDictionaryType.Dictionary.elementName().equals(localName)) { + PDFDictionaryExtension dictionary = new PDFDictionaryExtension(); + String key = attributes.getValue(PDFCollectionEntryElement.ATT_KEY); if (key != null) { dictionary.setKey(key); } - if (dictionary.getDictionaryType() == PDFDictionaryType.Page) { - String pageNumbers = attributes.getValue(PDFDictionaryElement.ATT_PAGE_NUMBERS); - if (pageNumbers != null) { - dictionary.setProperty(PDFDictionaryElement.ATT_PAGE_NUMBERS, pageNumbers); - } + collections.push(dictionary); + } else if (PDFDictionaryType.Layer.elementName().equals(localName)) { + PDFLayerExtension layer = new PDFLayerExtension(); + String id = attributes.getValue(PDFDictionaryElement.ATT_ID); + if (id != null) { + layer.setProperty(PDFDictionaryExtension.PROPERTY_ID, id); + } + collections.push(layer); + } else if (PDFDictionaryType.Navigator.elementName().equals(localName)) { + PDFNavigatorExtension navigator = new PDFNavigatorExtension(); + String id = attributes.getValue(PDFDictionaryElement.ATT_ID); + if (id != null) { + navigator.setProperty(PDFDictionaryExtension.PROPERTY_ID, id); + } + collections.push(navigator); + } else if (PDFDictionaryType.Page.elementName().equals(localName)) { + PDFPageExtension page = new PDFPageExtension(); + String pageNumbers = attributes.getValue(PDFPageElement.ATT_PAGE_NUMBERS); + if (pageNumbers != null) { + page.setProperty(PDFPageExtension.PROPERTY_PAGE_NUMBERS, pageNumbers); + } + collections.push(page); + } else if (PDFObjectType.hasValueOfElementName(localName)) { + PDFCollectionEntryExtension entry; + if (PDFObjectType.Reference.elementName().equals(localName)) { + entry = new PDFReferenceExtension(); + } else { + entry = new PDFCollectionEntryExtension(PDFObjectType.valueOfElementName(localName)); } - dictionaries.push(dictionary); - } else if (PDFDictionaryEntryType.hasValueOfElementName(localName)) { - PDFDictionaryEntryExtension entry = new PDFDictionaryEntryExtension(PDFDictionaryEntryType.valueOfElementName(localName)); - String key = attributes.getValue(PDFDictionaryEntryElement.ATT_KEY); + String key = attributes.getValue(PDFCollectionEntryElement.ATT_KEY); if (key != null) { entry.setKey(key); } - if (!dictionaries.empty()) { - PDFDictionaryExtension dictionary = dictionaries.peek(); - dictionary.addEntry(entry); - captureContent = true; + if (entry instanceof PDFReferenceExtension) { + String refid = attributes.getValue(PDFReferenceElement.ATT_REFID); + if (refid != null) { + ((PDFReferenceExtension) entry).setReferenceId(refid); + } + } + if (!collections.empty()) { + PDFCollectionExtension collection = collections.peek(); + collection.addEntry(entry); + if (!(entry instanceof PDFReferenceExtension)) { + captureContent = true; + } } } else { throw new SAXException("Unhandled element " + localName + " in namespace: " + uri); @@ -107,33 +156,48 @@ public class PDFExtensionHandler extends DefaultHandler implements ContentHandle String name = lastAttributes.getValue("name"); String src = lastAttributes.getValue("src"); String desc = lastAttributes.getValue("description"); + this.lastAttributes = null; this.returnedObject = new PDFEmbeddedFileAttachment(name, src, desc); } else if (PDFDictionaryType.hasValueOfElementName(localName)) { - if (!dictionaries.empty()) { - PDFDictionaryExtension dictionary = dictionaries.pop(); - if ((dictionary.getDictionaryType() == PDFDictionaryType.Catalog) || (dictionary.getDictionaryType() == PDFDictionaryType.Page)) { + if (!collections.empty() && (collections.peek() instanceof PDFDictionaryExtension)) { + PDFDictionaryExtension dictionary = (PDFDictionaryExtension) collections.pop(); + if (!collections.empty()) { + PDFCollectionExtension collectionOuter = collections.peek(); + collectionOuter.addEntry(dictionary); + } else if (dictionary.getDictionaryType() != PDFDictionaryType.Dictionary) { this.returnedObject = new PDFDictionaryAttachment(dictionary); - } else if (!dictionaries.empty()) { - PDFDictionaryExtension dictionaryOuter = dictionaries.peek(); - dictionaryOuter.addEntry(dictionary); + } else { + throw new SAXException(new IllegalStateException("generic dictionary not permitted at outer level")); + } + } else { + throw new SAXException(new IllegalStateException("collections stack is empty or not a dictionary")); + } + } else if (PDFObjectType.Array.elementName().equals(localName)) { + if (!collections.empty() && (collections.peek() instanceof PDFArrayExtension)) { + PDFArrayExtension array = (PDFArrayExtension) collections.pop(); + if (!collections.empty()) { + PDFCollectionExtension collectionOuter = collections.peek(); + collectionOuter.addEntry(array); + } else { + throw new SAXException(new IllegalStateException("array not permitted at outer level")); } } else { - throw new SAXException(new IllegalStateException("no active dictionary")); + throw new SAXException(new IllegalStateException("collections stack is empty or not an array")); } - } else if (PDFDictionaryEntryType.hasValueOfElementName(localName)) { - if (!dictionaries.empty()) { - PDFDictionaryExtension dictionary = dictionaries.peek(); - PDFDictionaryEntryExtension entry = dictionary.getLastEntry(); + } else if (PDFObjectType.hasValueOfElementName(localName)) { + if (!collections.empty()) { + PDFCollectionExtension collection = collections.peek(); + PDFCollectionEntryExtension entry = collection.getLastEntry(); if (entry != null) { if (characters != null) { entry.setValue(characters.toString()); characters = null; } } else { - throw new SAXException(new IllegalStateException("no active entry")); + throw new SAXException(new IllegalStateException("no current entry")); } } else { - throw new SAXException(new IllegalStateException("no active dictionary")); + throw new SAXException(new IllegalStateException("entry not permitted at outer level")); } } } diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFLayerElement.java b/src/java/org/apache/fop/render/pdf/extensions/PDFLayerElement.java new file mode 100644 index 000000000..dab0ecf78 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFLayerElement.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf.extensions; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.fo.Constants; +import org.apache.fop.fo.FONode; + +// CSOFF: LineLengthCheck + +/** + * Extension element for pdf:layer. + */ +public class PDFLayerElement extends PDFDictionaryElement { + + PDFLayerElement(FONode parent) { + super(parent, PDFDictionaryType.Layer); + } + + @Override + public void startOfNode() throws FOPException { + super.startOfNode(); + if (parent.getNameId() != Constants.FO_DECLARATIONS) { + invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), "rule.childOfDeclarations"); + } + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFLayerExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFLayerExtension.java new file mode 100644 index 000000000..d6cc27ab8 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFLayerExtension.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf.extensions; + +// CSOFF: LineLengthCheck + +public class PDFLayerExtension extends PDFDictionaryExtension { + + PDFLayerExtension() { + super(PDFDictionaryType.Layer); + } +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFNavigatorElement.java b/src/java/org/apache/fop/render/pdf/extensions/PDFNavigatorElement.java new file mode 100644 index 000000000..5c5b779b5 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFNavigatorElement.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf.extensions; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.fo.Constants; +import org.apache.fop.fo.FONode; + +// CSOFF: LineLengthCheck + +/** + * Extension element for pdf:navigator. + */ +public class PDFNavigatorElement extends PDFDictionaryElement { + + PDFNavigatorElement(FONode parent) { + super(parent, PDFDictionaryType.Navigator); + } + + @Override + public void startOfNode() throws FOPException { + super.startOfNode(); + if (parent.getNameId() != Constants.FO_DECLARATIONS) { + invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), "rule.childOfDeclarations"); + } + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFNavigatorExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFNavigatorExtension.java new file mode 100644 index 000000000..ee9dfa7c8 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFNavigatorExtension.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf.extensions; + +// CSOFF: LineLengthCheck + +public class PDFNavigatorExtension extends PDFDictionaryExtension { + + PDFNavigatorExtension() { + super(PDFDictionaryType.Navigator); + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFObjectExtension.java index 94d6b5dbf..5447d87f2 100644 --- a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryExtension.java +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFObjectExtension.java @@ -23,34 +23,20 @@ import org.apache.fop.util.XMLUtil; // CSOFF: LineLengthCheck -public class PDFDictionaryEntryExtension { +public class PDFObjectExtension { - public static final String PROPERTY_KEY = "key"; - - private PDFDictionaryEntryType type; - private String key = ""; + private PDFObjectType type; private Object value; - PDFDictionaryEntryExtension() { - } - - PDFDictionaryEntryExtension(PDFDictionaryEntryType type) { + PDFObjectExtension(PDFObjectType type) { this.type = type; } - public PDFDictionaryEntryType getType() { + public PDFObjectType getType() { return type; } - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - - public void setValue(String value) { + public void setValue(Object value) { this.value = value; } @@ -63,7 +49,10 @@ public class PDFDictionaryEntryExtension { * @return entry value */ public Boolean getValueAsBoolean() { - if (value instanceof String) { + Object value = getValue(); + if (value instanceof Boolean) { + return (Boolean) value; + } else if (value instanceof String) { return Boolean.valueOf((String)value); } else { return false; @@ -75,9 +64,12 @@ public class PDFDictionaryEntryExtension { * @return entry value */ public Number getValueAsNumber() { - if (value instanceof String) { + Object value = getValue(); + if (value instanceof Number) { + return (Number) value; + } else if (value instanceof String) { double d = Double.parseDouble((String) value); - if (Math.floor(d) == d) { + if (Math.abs(Math.floor(d) - d) < 1E-10) { return Long.valueOf((long) d); } else { return Double.valueOf(d); @@ -88,10 +80,13 @@ public class PDFDictionaryEntryExtension { } public String getValueAsString() { - if (value instanceof String) { + Object value = getValue(); + if (value == null) { + return null; + } else if (value instanceof String) { return (String) value; } else { - return ""; + return value.toString(); } } @@ -99,10 +94,6 @@ public class PDFDictionaryEntryExtension { return XMLUtil.escape(getValueAsString()); } - public void setValue(Object value) { - this.value = value; - } - public String getElementName() { return type.elementName(); } diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryType.java b/src/java/org/apache/fop/render/pdf/extensions/PDFObjectType.java index e4b25819a..c193a3b7b 100644 --- a/src/java/org/apache/fop/render/pdf/extensions/PDFDictionaryEntryType.java +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFObjectType.java @@ -22,24 +22,27 @@ package org.apache.fop.render.pdf.extensions; // CSOFF: LineLengthCheck /** - * Enumeration type for leaf PDF dictionary entry extension elements. + * Enumeration type for leaf PDF object extension types used as singletons, + * dictionary entries, or array entries. */ -public enum PDFDictionaryEntryType { - Boolean("boolean"), // boolean valued entry - Dictionary("dictionary"), // dictionary valued entry - Name("name"), // name valued entry - Number("number"), // number valued entry - String("string"); // string valued entry +public enum PDFObjectType { + Array("array"), // array valued entry + Boolean("boolean"), // boolean valued entry + Dictionary("dictionary"), // dictionary valued entry + Name("name"), // name valued entry + Number("number"), // number valued entry + Reference("reference"), // indirect object reference entry + String("string"); // string valued entry private String elementName; - PDFDictionaryEntryType(String elementName) { + PDFObjectType(String elementName) { this.elementName = elementName; } public String elementName() { return elementName; } - static PDFDictionaryEntryType valueOfElementName(String elementName) { - for (PDFDictionaryEntryType type : values()) { + static PDFObjectType valueOfElementName(String elementName) { + for (PDFObjectType type : values()) { if (type.elementName.equals(elementName)) { return type; } @@ -48,7 +51,8 @@ public enum PDFDictionaryEntryType { } static boolean hasValueOfElementName(String elementName) { try { - return valueOfElementName(elementName) != null; + valueOfElementName(elementName); + return true; } catch (Exception e) { return false; } diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFPageElement.java b/src/java/org/apache/fop/render/pdf/extensions/PDFPageElement.java new file mode 100644 index 000000000..28a966ead --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFPageElement.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf.extensions; + +import org.xml.sax.Attributes; +import org.xml.sax.Locator; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.fo.Constants; +import org.apache.fop.fo.FONode; +import org.apache.fop.fo.PropertyList; + +// CSOFF: LineLengthCheck + +/** + * Extension element for pdf:page. + */ +public class PDFPageElement extends PDFDictionaryElement { + + public static final String ATT_PAGE_NUMBERS = PDFDictionaryExtension.PROPERTY_PAGE_NUMBERS; + + /** + * Main constructor + * @param parent parent FO node + */ + PDFPageElement(FONode parent) { + super(parent, PDFDictionaryType.Page); + } + + @Override + public void processNode(String elementName, Locator locator, Attributes attlist, PropertyList propertyList) throws FOPException { + super.processNode(elementName, locator, attlist, propertyList); + String pageNumbers = attlist.getValue(ATT_PAGE_NUMBERS); + if (pageNumbers != null) { + getDictionaryExtension().setProperty(PDFDictionaryExtension.PROPERTY_PAGE_NUMBERS, pageNumbers); + } + } + + @Override + public void startOfNode() throws FOPException { + super.startOfNode(); + if (parent.getNameId() != Constants.FO_SIMPLE_PAGE_MASTER) { + invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), "rule.childOfSPM"); + } + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFPageExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFPageExtension.java new file mode 100644 index 000000000..0726a4e55 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFPageExtension.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf.extensions; + +// CSOFF: LineLengthCheck + +public class PDFPageExtension extends PDFDictionaryExtension { + + public static final String PROPERTY_PAGE_NUMBERS = "page-numbers"; + + PDFPageExtension() { + super(PDFDictionaryType.Page); + } + + /** + * Determine if page dictionary and page number matches. + * @param pageNumber page number, where first page number is 1 + * @return true if this dictionary is a page dictionary and specified page number matches specified page-number property + */ + public boolean matchesPageNumber(int pageNumber) { + String pageNumbers = getProperty(PROPERTY_PAGE_NUMBERS); + if ((pageNumbers == null) || (pageNumbers.length() == 0)) { + return false; + } else if (pageNumbers.equals("*")) { + return true; + } else { + for (String interval : pageNumbers.split("\\s*,\\s*")) { + String[] components = interval.split("\\s*-\\s*"); + if (components.length < 1) { + continue; + } else { + try { + int start = Integer.parseInt(components[0]); + int end = 0; + if (components.length > 1) { + if (!components[1].equals("LAST")) { + end = Integer.parseInt(components[1]); + } + } + if ((end == 0) && (pageNumber == start)) { + return true; + } else if ((end > start) && (pageNumber >= start) && (pageNumber < end)) { + return true; + } else { + continue; + } + } catch (NumberFormatException e) { + continue; + } + } + } + } + return false; + } + +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFReferenceElement.java b/src/java/org/apache/fop/render/pdf/extensions/PDFReferenceElement.java new file mode 100644 index 000000000..37aeeb890 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFReferenceElement.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf.extensions; + +import org.xml.sax.Attributes; +import org.xml.sax.Locator; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.fo.FONode; +import org.apache.fop.fo.PropertyList; + +// CSOFF: LineLengthCheck + +/** + * Extension element for pdf:reference. + */ +public class PDFReferenceElement extends PDFCollectionEntryElement { + + public static final String ATT_REFID = PDFReferenceExtension.PROPERTY_REFID; + + /** + * Main constructor + * @param parent parent FO node + */ + PDFReferenceElement(FONode parent) { + super(parent, PDFObjectType.Reference); + } + + @Override + public void processNode(String elementName, Locator locator, Attributes attlist, PropertyList propertyList) throws FOPException { + super.processNode(elementName, locator, attlist, propertyList); + String refid = attlist.getValue(ATT_REFID); + if (refid == null) { + missingPropertyError(ATT_REFID); + } else if (refid.length() == 0) { + invalidPropertyValueError(ATT_REFID, refid, null); + } else { + ((PDFReferenceExtension) getExtension()).setReferenceId(refid); + } + } +} diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFReferenceExtension.java b/src/java/org/apache/fop/render/pdf/extensions/PDFReferenceExtension.java new file mode 100644 index 000000000..09621c01c --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFReferenceExtension.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf.extensions; + +// CSOFF: LineLengthCheck + +public class PDFReferenceExtension extends PDFCollectionEntryExtension { + + public static final String PROPERTY_REFID = "refid"; + + private String refid; + private Object resolvedReference; + + PDFReferenceExtension() { + super(PDFObjectType.Reference); + } + + @Override + public void setValue(Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Object getValue() { + return this; + } + + public String getReferenceId() { + return refid; + } + + public void setReferenceId(String refid) { + this.refid = refid; + } + + public Object getResolvedReference() { + return resolvedReference; + } + + public void setResolvedReference(Object resolvedReference) { + this.resolvedReference = resolvedReference; + } + +} diff --git a/src/java/org/apache/fop/render/ps/PSFontUtils.java b/src/java/org/apache/fop/render/ps/PSFontUtils.java index c22a3ba28..06191f84d 100644 --- a/src/java/org/apache/fop/render/ps/PSFontUtils.java +++ b/src/java/org/apache/fop/render/ps/PSFontUtils.java @@ -175,6 +175,15 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils { if (!tracker.isResourceSupplied(WINANSI_ENCODING_RESOURCE)) { //Only out Base 14 fonts still use that + for (Typeface tf : fonts.values()) { + if (tf instanceof LazyFont) { + tf = ((LazyFont)tf).getRealFont(); + if (tf instanceof SingleByteFont + && ((SingleByteFont) tf).getEncoding().getName().equals("custom")) { + defineEncoding(gen, ((SingleByteFont) tf).getEncoding()); + } + } + } defineWinAnsiEncoding(gen); } gen.commentln("%FOPBeginFontReencode"); diff --git a/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java b/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java index 3ade34522..cadc28267 100644 --- a/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java +++ b/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java @@ -19,26 +19,50 @@ package org.apache.fop.render.ps; +import java.awt.Color; +import java.awt.Dimension; import java.awt.Rectangle; import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.imageio.ImageIO; import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import org.apache.batik.bridge.BridgeContext; import org.apache.batik.bridge.GVTBuilder; import org.apache.batik.gvt.GraphicsNode; +import org.apache.batik.transcoder.SVGAbstractTranscoder; +import org.apache.batik.transcoder.TranscoderException; +import org.apache.batik.transcoder.TranscoderInput; +import org.apache.batik.transcoder.TranscoderOutput; +import org.apache.batik.transcoder.image.PNGTranscoder; import org.apache.xmlgraphics.image.loader.Image; import org.apache.xmlgraphics.image.loader.ImageFlavor; import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM; -import org.apache.xmlgraphics.java2d.ps.PSGraphics2D; +import org.apache.xmlgraphics.ps.ImageEncoder; +import org.apache.xmlgraphics.ps.ImageEncodingHelper; import org.apache.xmlgraphics.ps.PSGenerator; import org.apache.fop.image.loader.batik.BatikImageFlavors; import org.apache.fop.image.loader.batik.BatikUtil; import org.apache.fop.render.ImageHandler; import org.apache.fop.render.RenderingContext; +import org.apache.fop.render.ps.svg.PSSVGGraphics2D; import org.apache.fop.svg.SVGEventProducer; import org.apache.fop.svg.SVGUserAgent; @@ -47,6 +71,9 @@ import org.apache.fop.svg.SVGUserAgent; */ public class PSImageHandlerSVG implements ImageHandler { + private static final Color FALLBACK_COLOR = new Color(255, 33, 117); + private HashMap<String, String> gradientsFound = new HashMap<String, String>(); + private static final ImageFlavor[] FLAVORS = new ImageFlavor[] { BatikImageFlavors.SVG_DOM }; @@ -58,78 +85,262 @@ public class PSImageHandlerSVG implements ImageHandler { PSGenerator gen = psContext.getGenerator(); ImageXMLDOM imageSVG = (ImageXMLDOM)image; - //Controls whether text painted by Batik is generated using text or path operations - boolean strokeText = false; - //TODO Configure text stroking + if (shouldRaster(imageSVG)) { + InputStream is = renderSVGToInputStream(context, imageSVG); + + float x = (float) pos.getX() / 1000f; + float y = (float) pos.getY() / 1000f; + float w = (float) pos.getWidth() / 1000f; + float h = (float) pos.getHeight() / 1000f; + Rectangle2D targetRect = new Rectangle2D.Double(x, y, w, h); + + MaskedImage mi = convertToRGB(ImageIO.read(is)); + BufferedImage ri = mi.getImage(); + ImageEncoder encoder = ImageEncodingHelper.createRenderedImageEncoder(ri); + Dimension imgDim = new Dimension(ri.getWidth(), ri.getHeight()); + String imgDescription = ri.getClass().getName(); + ImageEncodingHelper helper = new ImageEncodingHelper(ri); + ColorModel cm = helper.getEncodedColorModel(); + PSImageUtils.writeImage(encoder, imgDim, imgDescription, targetRect, cm, gen, ri, mi.getMaskColor()); + } else { + //Controls whether text painted by Batik is generated using text or path operations + boolean strokeText = false; + //TODO Configure text stroking + + SVGUserAgent ua + = new SVGUserAgent(context.getUserAgent(), new AffineTransform()); + + PSSVGGraphics2D graphics = new PSSVGGraphics2D(strokeText, gen); + graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext()); + + BridgeContext ctx = new PSBridgeContext(ua, + (strokeText ? null : psContext.getFontInfo()), + context.getUserAgent().getImageManager(), + context.getUserAgent().getImageSessionContext()); + + //Cloning SVG DOM as Batik attaches non-thread-safe facilities (like the CSS engine) + //to it. + Document clonedDoc = BatikUtil.cloneSVGDocument(imageSVG.getDocument()); + + GraphicsNode root; + try { + GVTBuilder builder = new GVTBuilder(); + root = builder.build(ctx, clonedDoc); + } catch (Exception e) { + SVGEventProducer eventProducer = SVGEventProducer.Provider.get( + context.getUserAgent().getEventBroadcaster()); + eventProducer.svgNotBuilt(this, e, image.getInfo().getOriginalURI()); + return; + } + // get the 'width' and 'height' attributes of the SVG document + float w = (float)ctx.getDocumentSize().getWidth() * 1000f; + float h = (float)ctx.getDocumentSize().getHeight() * 1000f; - SVGUserAgent ua - = new SVGUserAgent(context.getUserAgent(), new AffineTransform()); + float sx = pos.width / w; + float sy = pos.height / h; - PSGraphics2D graphics = new PSGraphics2D(strokeText, gen); - graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext()); + ctx = null; - BridgeContext ctx = new PSBridgeContext(ua, - (strokeText ? null : psContext.getFontInfo()), - context.getUserAgent().getImageManager(), - context.getUserAgent().getImageSessionContext()); + gen.commentln("%FOPBeginSVG"); + gen.saveGraphicsState(); + final boolean clip = false; + if (clip) { + /* + * Clip to the svg area. + * Note: To have the svg overlay (under) a text area then use + * an fo:block-container + */ + gen.writeln("newpath"); + gen.defineRect(pos.getMinX() / 1000f, pos.getMinY() / 1000f, + pos.width / 1000f, pos.height / 1000f); + gen.writeln("clip"); + } - //Cloning SVG DOM as Batik attaches non-thread-safe facilities (like the CSS engine) - //to it. - Document clonedDoc = BatikUtil.cloneSVGDocument(imageSVG.getDocument()); + // transform so that the coordinates (0,0) is from the top left + // and positive is down and to the right. (0,0) is where the + // viewBox puts it. + gen.concatMatrix(sx, 0, 0, sy, pos.getMinX() / 1000f, pos.getMinY() / 1000f); - GraphicsNode root; + AffineTransform transform = new AffineTransform(); + // scale to viewbox + transform.translate(pos.getMinX(), pos.getMinY()); + gen.getCurrentState().concatMatrix(transform); + try { + root.paint(graphics); + } catch (Exception e) { + SVGEventProducer eventProducer = SVGEventProducer.Provider.get( + context.getUserAgent().getEventBroadcaster()); + eventProducer.svgRenderingError(this, e, image.getInfo().getOriginalURI()); + } + + gen.restoreGraphicsState(); + gen.commentln("%FOPEndSVG"); + } + } + + private InputStream renderSVGToInputStream(RenderingContext context, ImageXMLDOM imageSVG) throws IOException { + PNGTranscoder png = new PNGTranscoder(); + Float width = getDimension(imageSVG.getDocument(), "width") * 8; + png.addTranscodingHint(SVGAbstractTranscoder.KEY_WIDTH, width); + Float height = getDimension(imageSVG.getDocument(), "height") * 8; + png.addTranscodingHint(SVGAbstractTranscoder.KEY_HEIGHT, height); + TranscoderInput input = new TranscoderInput(imageSVG.getDocument()); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + TranscoderOutput output = new TranscoderOutput(os); try { - GVTBuilder builder = new GVTBuilder(); - root = builder.build(ctx, clonedDoc); - } catch (Exception e) { + png.transcode(input, output); + } catch (TranscoderException ex) { SVGEventProducer eventProducer = SVGEventProducer.Provider.get( context.getUserAgent().getEventBroadcaster()); - eventProducer.svgNotBuilt(this, e, image.getInfo().getOriginalURI()); - return; + eventProducer.svgRenderingError(this, ex, imageSVG.getInfo().getOriginalURI()); + } finally { + os.flush(); + os.close(); + } + return new ByteArrayInputStream(os.toByteArray()); + } + + private MaskedImage convertToRGB(BufferedImage alphaImage) { + int[] red = new int[256]; + int[] green = new int[256]; + int[] blue = new int[256]; + BufferedImage rgbImage = new BufferedImage(alphaImage.getWidth(), + alphaImage.getHeight(), BufferedImage.TYPE_INT_RGB); + //Count occurances of each colour in image + for (int cx = 0; cx < alphaImage.getWidth(); cx++) { + for (int cy = 0; cy < alphaImage.getHeight(); cy++) { + int pixelValue = alphaImage.getRGB(cx, cy); + Color pixelColor = new Color(pixelValue); + red[pixelColor.getRed()]++; + green[pixelColor.getGreen()]++; + blue[pixelColor.getBlue()]++; + } + } + //Find colour not in image + Color alphaSwap = null; + for (int i = 0; i < 256; i++) { + if (red[i] == 0) { + alphaSwap = new Color(i, 0, 0); + break; + } else if (green[i] == 0) { + alphaSwap = new Color(0, i, 0); + break; + } else if (blue[i] == 0) { + alphaSwap = new Color(0, 0, i); + break; + } } - // get the 'width' and 'height' attributes of the SVG document - float w = (float)ctx.getDocumentSize().getWidth() * 1000f; - float h = (float)ctx.getDocumentSize().getHeight() * 1000f; - - float sx = pos.width / w; - float sy = pos.height / h; - - ctx = null; - - gen.commentln("%FOPBeginSVG"); - gen.saveGraphicsState(); - final boolean clip = false; - if (clip) { - /* - * Clip to the svg area. - * Note: To have the svg overlay (under) a text area then use - * an fo:block-container - */ - gen.writeln("newpath"); - gen.defineRect(pos.getMinX() / 1000f, pos.getMinY() / 1000f, - pos.width / 1000f, pos.height / 1000f); - gen.writeln("clip"); + //Check if all variations are used in all three colours + if (alphaSwap == null) { + //Fallback colour is no unique colour channel can be found + alphaSwap = FALLBACK_COLOR; } + //Replace alpha channel with the new mask colour + for (int cx = 0; cx < alphaImage.getWidth(); cx++) { + for (int cy = 0; cy < alphaImage.getHeight(); cy++) { + int pixelValue = alphaImage.getRGB(cx, cy); + if (pixelValue == 0) { + rgbImage.setRGB(cx, cy, alphaSwap.getRGB()); + } else { + rgbImage.setRGB(cx, cy, alphaImage.getRGB(cx, cy)); + } + } + } + return new MaskedImage(rgbImage, alphaSwap); + } + + private static class MaskedImage { + private Color maskColor = new Color(0, 0, 0); + private BufferedImage image; + + public MaskedImage(BufferedImage image, Color maskColor) { + this.image = image; + this.maskColor = maskColor; + } + + public Color getMaskColor() { + return maskColor; + } + + public BufferedImage getImage() { + return image; + } + } - // transform so that the coordinates (0,0) is from the top left - // and positive is down and to the right. (0,0) is where the - // viewBox puts it. - gen.concatMatrix(sx, 0, 0, sy, pos.getMinX() / 1000f, pos.getMinY() / 1000f); + private Float getDimension(Document document, String dimension) { + if (document.getFirstChild().getAttributes().getNamedItem(dimension) != null) { + String width = document.getFirstChild().getAttributes().getNamedItem(dimension).getNodeValue(); + width = width.replaceAll("[^\\d.]", ""); + return Float.parseFloat(width); + } + return null; + } - AffineTransform transform = new AffineTransform(); - // scale to viewbox - transform.translate(pos.getMinX(), pos.getMinY()); - gen.getCurrentState().concatMatrix(transform); + private boolean shouldRaster(ImageXMLDOM image) { + //A list of objects on which to check opacity try { - root.paint(graphics); - } catch (Exception e) { - SVGEventProducer eventProducer = SVGEventProducer.Provider.get( - context.getUserAgent().getEventBroadcaster()); - eventProducer.svgRenderingError(this, e, image.getInfo().getOriginalURI()); + List<String> gradMatches = new ArrayList<String>(); + gradMatches.add("radialGradient"); + gradMatches.add("linearGradient"); + return recurseSVGElements(image.getDocument().getChildNodes(), gradMatches, false); + } finally { + gradientsFound.clear(); } + } - gen.restoreGraphicsState(); - gen.commentln("%FOPEndSVG"); + private boolean recurseSVGElements(NodeList childNodes, List<String> gradMatches, boolean isMatched) { + boolean opacityFound = false; + for (int i = 0; i < childNodes.getLength(); i++) { + Node curNode = childNodes.item(i); + if (isMatched && curNode.getLocalName() != null && curNode.getLocalName().equals("stop")) { + if (curNode.getAttributes().getNamedItem("style") != null) { + String[] stylePairs = curNode.getAttributes().getNamedItem("style").getNodeValue() + .split(";"); + for (int styleAtt = 0; styleAtt < stylePairs.length; styleAtt++) { + String[] style = stylePairs[styleAtt].split(":"); + if (style[0].equalsIgnoreCase("stop-opacity")) { + if (Double.parseDouble(style[1]) < 1) { + return true; + } + } + } + } + if (curNode.getAttributes().getNamedItem("stop-opacity") != null) { + String opacityValue = curNode.getAttributes().getNamedItem("stop-opacity").getNodeValue(); + if (Double.parseDouble(opacityValue) < 1) { + return true; + } + } + } + String nodeName = curNode.getLocalName(); + //Special case where rasterization needed for radial gradients + if (nodeName != null && nodeName.equals("ellipse")) { + String found = ""; + String ellipseFill = curNode.getAttributes().getNamedItem("fill").getNodeValue(); + Pattern pattern = Pattern.compile("#(.*?)\\)"); + Matcher matcher = pattern.matcher(ellipseFill); + if (matcher.find()) { + found = matcher.group(1); + } + if (gradientsFound.get(found) != null) { + return true; + } + } + boolean inMatch = false; + if (!isMatched) { + inMatch = nodeName != null && gradMatches.contains(nodeName); + if (inMatch) { + gradientsFound.put(curNode.getAttributes().getNamedItem("id").getNodeValue(), nodeName); + } + } else { + inMatch = true; + } + opacityFound = recurseSVGElements(curNode.getChildNodes(), gradMatches, inMatch); + if (opacityFound) { + return true; + } + } + return opacityFound; } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/render/ps/PSPainter.java b/src/java/org/apache/fop/render/ps/PSPainter.java index 3d1887f2d..547662c47 100644 --- a/src/java/org/apache/fop/render/ps/PSPainter.java +++ b/src/java/org/apache/fop/render/ps/PSPainter.java @@ -116,7 +116,7 @@ public class PSPainter extends AbstractIFPainter<PSDocumentHandler> { } /** {@inheritDoc} */ - public void startGroup(AffineTransform transform) throws IFException { + public void startGroup(AffineTransform transform, String layer) throws IFException { try { PSGenerator generator = getGenerator(); saveGraphicsState(); diff --git a/src/java/org/apache/fop/render/ps/svg/PSFunction.java b/src/java/org/apache/fop/render/ps/svg/PSFunction.java new file mode 100644 index 000000000..b03e0b590 --- /dev/null +++ b/src/java/org/apache/fop/render/ps/svg/PSFunction.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.ps.svg; + +import java.io.UnsupportedEncodingException; +import java.util.List; + +import org.apache.fop.render.shading.Function; +import org.apache.fop.render.shading.FunctionDelegate; +import org.apache.fop.render.shading.FunctionPattern; + +public class PSFunction implements Function { + + private FunctionDelegate delegate; + + /** + * Creates a Postscript function dictionary + * @param theFunctionType The function type (0 = Sampled, 2 = Exponential + * Interpolation, 3 = Stitching) + * @param theDomain The function domain + * @param theRange Range used for clipping + * @param theFunctions An array of sub-functions such as determining the + * colour values used in a gradient. + * @param theBounds Bounds determines where each boundary exists for whatever + * the function is mean't. In a gradient case, it would be the point between + * colours. + * @param theEncode The function encoding + */ + public PSFunction(int theFunctionType, List<Double> theDomain, + List<Double> theRange, List<Function> theFunctions, + List<Double> theBounds, List<Double> theEncode) { + delegate = new FunctionDelegate(this, theFunctionType, theDomain, theRange, theFunctions, + theBounds, theEncode); + } + + /** + * Creates a Postscript function dictionary + * @param theFunctionType The function type (0 = Sampled, 2 = Exponential + * Interpolation, 3 = Stitching) + * @param theDomain The function domain + * @param theRange Range used for clipping + * @param theCZero In a gradient, this would be the first colour + * @param theCOne In a gradient, this would be the second colour + * @param theInterpolationExponentN Determines the number of values + * the function returns. + */ + public PSFunction(int theFunctionType, List<Double> theDomain, + List<Double> theRange, List<Double> theCZero, List<Double> theCOne, + double theInterpolationExponentN) { + delegate = new FunctionDelegate(this, theFunctionType, theDomain, theRange, theCZero, + theCOne, theInterpolationExponentN); + } + + /** + * Outputs the function to a byte array + */ + public byte[] toByteString() { + FunctionPattern pattern = new FunctionPattern(this); + try { + return pattern.toWriteableString().getBytes("UTF-8"); + } catch (UnsupportedEncodingException ex) { + //This should have been made an enum type to avoid throwing exceptions. + return new byte[0]; + } + } + + public int getFunctionType() { + return delegate.getFunctionType(); + } + + public List<Double> getBounds() { + return delegate.getBounds(); + } + + public List<Double> getDomain() { + return delegate.getDomain(); + } + + public List<Double> getSize() { + return delegate.getSize(); + } + + public List<String> getFilter() { + return delegate.getFilter(); + } + + public List<Double> getEncode() { + return delegate.getEncode(); + } + + public List<Function> getFunctions() { + return delegate.getFunctions(); + } + + public int getBitsPerSample() { + return delegate.getBitsPerSample(); + } + + public double getInterpolationExponentN() { + return delegate.getInterpolationExponentN(); + } + + public int getOrder() { + return delegate.getOrder(); + } + + public List<Double> getRange() { + return delegate.getRange(); + } + + public List<Double> getDecode() { + return delegate.getDecode(); + } + + public StringBuffer getDataStream() { + return delegate.getDataStream(); + } + + public List<Double> getCZero() { + return delegate.getCZero(); + } + + public List<Double> getCOne() { + return delegate.getCOne(); + } +} diff --git a/src/java/org/apache/fop/render/ps/svg/PSPattern.java b/src/java/org/apache/fop/render/ps/svg/PSPattern.java new file mode 100644 index 000000000..46f976672 --- /dev/null +++ b/src/java/org/apache/fop/render/ps/svg/PSPattern.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.ps.svg; + +import java.util.List; + +import org.apache.fop.render.shading.Pattern; +import org.apache.fop.render.shading.Shading; + +public class PSPattern implements Pattern { + + /** + * Either one (1) for tiling, or two (2) for shading. + */ + protected int patternType = 2; // Default + + /** + * The Shading object comprising the Type 2 pattern + */ + protected PSShading shading = null; + + /** + * List of Integers represetning the Extended unique Identifier + */ + protected List xUID = null; + + /** + * TODO use PDFGState + * String representing the extended Graphics state. + * Probably will never be used like this. + */ + protected StringBuffer extGState = null; + + /** + * Creates a radial or axial shading pattern + * @param thePatternType The pattern type which will be 3 for radial and 2 for axial + * @param theShading The shading object to determine how the gradient + * is drawn + * @param theXUID The XUID + * @param theExtGState The exit state + */ + public PSPattern(int thePatternType, Shading theShading, List theXUID, + StringBuffer theExtGState) { + this.patternType = 2; // thePatternType; + assert theShading instanceof PSShading; + this.shading = (PSShading)theShading; + this.xUID = theXUID; + this.extGState = theExtGState; // always null + } + + /** + * Outputs the radial or axial pattern as a string dictionary to insert + * into a postscript document. + */ + public String toString() { + int vectorSize = 0; + int tempInt = 0; + StringBuffer p = new StringBuffer(64); + p.append("/Pattern setcolorspace\n"); + p.append("<< \n/Type /Pattern \n"); + + p.append("/PatternType " + this.patternType + " \n"); + + if (this.shading != null) { + p.append("/Shading " + this.shading.toString() + " \n"); + } + + if (this.xUID != null) { + vectorSize = this.xUID.size(); + p.append("/XUID [ "); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append((this.xUID.get(tempInt)) + " "); + } + p.append("] \n"); + } + + if (this.extGState != null) { + p.append("/ExtGState " + this.extGState + " \n"); + } + + p.append(">> \n"); + p.append("matrix makepattern setcolor\n"); + + return p.toString(); + } +} diff --git a/src/java/org/apache/fop/render/ps/svg/PSSVGGraphics2D.java b/src/java/org/apache/fop/render/ps/svg/PSSVGGraphics2D.java new file mode 100644 index 000000000..1c15e569b --- /dev/null +++ b/src/java/org/apache/fop/render/ps/svg/PSSVGGraphics2D.java @@ -0,0 +1,294 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.ps.svg; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Paint; +import java.awt.geom.AffineTransform; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.batik.ext.awt.LinearGradientPaint; +import org.apache.batik.ext.awt.MultipleGradientPaint; +import org.apache.batik.ext.awt.RadialGradientPaint; + +import org.apache.xmlgraphics.java2d.ps.PSGraphics2D; +import org.apache.xmlgraphics.ps.PSGenerator; + +import org.apache.fop.pdf.PDFDeviceColorSpace; +import org.apache.fop.render.shading.Function; +import org.apache.fop.render.shading.GradientFactory; +import org.apache.fop.render.shading.GradientRegistrar; +import org.apache.fop.render.shading.PSGradientFactory; +import org.apache.fop.render.shading.Pattern; +import org.apache.fop.render.shading.Shading; + + +public class PSSVGGraphics2D extends PSGraphics2D implements GradientRegistrar { + + private static final Log LOG = LogFactory.getLog(PSSVGGraphics2D.class); + + /** + * Create a new Graphics2D that generates PostScript code. + * @param textAsShapes True if text should be rendered as graphics + * @see org.apache.xmlgraphics.java2d.AbstractGraphics2D#AbstractGraphics2D(boolean) + */ + public PSSVGGraphics2D(boolean textAsShapes) { + super(textAsShapes); + } + + /** + * Create a new Graphics2D that generates PostScript code. + * @param textAsShapes True if text should be rendered as graphics + * @param gen PostScript generator to use for output + * @see org.apache.xmlgraphics.java2d.AbstractGraphics2D#AbstractGraphics2D(boolean) + */ + public PSSVGGraphics2D(boolean textAsShapes, PSGenerator gen) { + super(textAsShapes, gen); + } + + /** + * Constructor for creating copies + * @param g parent PostScript Graphics2D + */ + public PSSVGGraphics2D(PSGraphics2D g) { + super(g); + } + + protected void applyPaint(Paint paint, boolean fill) { + super.applyPaint(paint, fill); + if (paint instanceof RadialGradientPaint) { + RadialGradientPaint rgp = (RadialGradientPaint)paint; + try { + handleRadialGradient(rgp, gen); + } catch (IOException ioe) { + handleIOException(ioe); + } + } else if (paint instanceof LinearGradientPaint) { + LinearGradientPaint lgp = (LinearGradientPaint)paint; + try { + handleLinearGradient(lgp, gen); + } catch (IOException ioe) { + handleIOException(ioe); + } + } + } + + private void handleLinearGradient(LinearGradientPaint lgp, PSGenerator gen) throws IOException { + MultipleGradientPaint.CycleMethodEnum cycle = lgp.getCycleMethod(); + if (cycle != MultipleGradientPaint.NO_CYCLE) { + return; + } + float[] fractions = lgp.getFractions(); + Color[] cols = lgp.getColors(); + + AffineTransform transform = new AffineTransform(getBaseTransform()); + transform.concatenate(getTransform()); + transform.concatenate(lgp.getTransform()); + + List theMatrix = new ArrayList(); + double [] mat = new double[6]; + transform.getMatrix(mat); + for (int idx = 0; idx < mat.length; idx++) { + theMatrix.add(Double.valueOf(mat[idx])); + } + + + List<Double> theCoords = new java.util.ArrayList<Double>(); + + + AffineTransform start = applyTransform(lgp.getTransform(), + lgp.getStartPoint().getX(), lgp.getStartPoint().getY()); + AffineTransform end = applyTransform(lgp.getTransform(), lgp.getEndPoint().getX(), lgp.getEndPoint().getY()); + double startX = start.getTranslateX(); + double startY = start.getTranslateY(); + double endX = end.getTranslateX(); + double endY = end.getTranslateY(); + + double width = endX - startX; + double height = endY - startY; + + startX = startX + width * fractions[0]; + endX = endX - width * (1 - fractions[fractions.length - 1]); + startY = startY + (height * fractions[0]); + endY = endY - height * (1 - fractions[fractions.length - 1]); + + theCoords.add(startX); + theCoords.add(startY); + theCoords.add(endX); + theCoords.add(endY); + + + List<Color> someColors = new java.util.ArrayList<Color>(); + for (int count = 0; count < cols.length; count++) { + Color c1 = cols[count]; + if (c1.getAlpha() != 255) { + LOG.warn("Opacity is not currently supported for Postscript output"); + } + someColors.add(c1); + } + List<Double> theBounds = new java.util.ArrayList<Double>(); + for (int count = 1; count < fractions.length - 1; count++) { + float offset = fractions[count]; + theBounds.add(Double.valueOf(offset)); + } + PDFDeviceColorSpace colSpace; + colSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB); + + PSGradientFactory gradientFactory = (PSGradientFactory)GradientFactory.newInstance(this); + PSPattern myPattern = gradientFactory.createGradient(false, colSpace, + someColors, theBounds, theCoords, theMatrix); + + gen.write(myPattern.toString()); + + } + + + + private void handleRadialGradient(RadialGradientPaint rgp, PSGenerator gen) throws IOException { + MultipleGradientPaint.CycleMethodEnum cycle = rgp.getCycleMethod(); + if (cycle != MultipleGradientPaint.NO_CYCLE) { + return; + } + + AffineTransform transform; + transform = new AffineTransform(getBaseTransform()); + transform.concatenate(getTransform()); + transform.concatenate(rgp.getTransform()); + + AffineTransform resultCentre = applyTransform(rgp.getTransform(), + rgp.getCenterPoint().getX(), rgp.getCenterPoint().getY()); + AffineTransform resultFocus = applyTransform(rgp.getTransform(), + rgp.getFocusPoint().getX(), rgp.getFocusPoint().getY()); + double scale = Math.sqrt(rgp.getTransform().getDeterminant()); + double radius = rgp.getRadius() * scale; + double centreX = resultCentre.getTranslateX(); + double centreY = resultCentre.getTranslateY(); + double focusX = resultFocus.getTranslateX(); + double focusY = resultFocus.getTranslateY(); + + List<Double> theMatrix = new java.util.ArrayList<Double>(); + double [] mat = new double[6]; + transform.getMatrix(mat); + for (int idx = 0; idx < mat.length; idx++) { + theMatrix.add(Double.valueOf(mat[idx])); + } + + List<Double> theCoords = new java.util.ArrayList<Double>(); + float[] fractions = rgp.getFractions(); + + theCoords.add(centreX); + theCoords.add(centreY); + theCoords.add(radius * rgp.getFractions()[0]); + theCoords.add(focusX); + theCoords.add(focusY); + theCoords.add(radius * fractions[fractions.length - 1]); + + Color[] cols = rgp.getColors(); + List<Color> someColors = new java.util.ArrayList<Color>(); + for (int count = 0; count < cols.length; count++) { + Color cc = cols[count]; + if (cc.getAlpha() != 255) { + /* This should never happen because radial gradients with opacity should now + * be rasterized in the PSImageHandlerSVG class. Please see the shouldRaster() + * method for more information. */ + LOG.warn("Opacity is not currently supported for Postscript output"); + } + + someColors.add(cc); + } + + List<Double> theBounds = new java.util.ArrayList<Double>(); + for (int count = 1; count < fractions.length - 1; count++) { + float offset = fractions[count]; + theBounds.add(Double.valueOf(offset)); + } + PDFDeviceColorSpace colSpace; + colSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB); + + PSGradientFactory gradientFactory = (PSGradientFactory)GradientFactory.newInstance(this); + PSPattern myPattern = gradientFactory.createGradient(true, colSpace, + someColors, theBounds, theCoords, theMatrix); + + gen.write(myPattern.toString()); + } + + private AffineTransform applyTransform(AffineTransform base, double posX, double posY) { + AffineTransform result = AffineTransform.getTranslateInstance(posX, posY); + AffineTransform orig = base; + orig.concatenate(result); + return orig; + } + + protected AffineTransform getBaseTransform() { + AffineTransform at = new AffineTransform(this.getTransform()); + return at; + } + + /** + * Creates a new <code>Graphics</code> object that is + * a copy of this <code>Graphics</code> object. + * @return a new graphics context that is a copy of + * this graphics context. + */ + @Override + public Graphics create() { + preparePainting(); + return new PSSVGGraphics2D(this); + } + + /** + * Registers a function object against the output format document + * @param function The function object to register + * @return Returns either the function which has already been registered + * or the current new registered object. + */ + public Function registerFunction(Function function) { + //Objects aren't needed to be registered in Postscript + return function; + } + + /** + * Registers a shading object against the otuput format document + * @param shading The shading object to register + * @return Returs either the shading which has already been registered + * or the current new registered object + */ + public Shading registerShading(Shading shading) { + //Objects aren't needed to be registered in Postscript + return shading; + } + + /** + * Registers a pattern object against the output format document + * @param pattern The pattern object to register + * @return Returns either the pattern which has already been registered + * or the current new registered object + */ + public Pattern registerPattern(Pattern pattern) { + // TODO Auto-generated method stub + return pattern; + } +} diff --git a/src/java/org/apache/fop/render/ps/svg/PSShading.java b/src/java/org/apache/fop/render/ps/svg/PSShading.java new file mode 100644 index 000000000..7465fcadb --- /dev/null +++ b/src/java/org/apache/fop/render/ps/svg/PSShading.java @@ -0,0 +1,228 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.ps.svg; + +import java.io.UnsupportedEncodingException; +import java.util.List; + +import org.apache.fop.pdf.PDFDeviceColorSpace; +import org.apache.fop.pdf.PDFNumber; +import org.apache.fop.render.shading.Function; +import org.apache.fop.render.shading.Shading; +import org.apache.fop.render.shading.ShadingPattern; + +public class PSShading implements Shading { + + /** + * Required: The Type of shading (1,2,3,4,5,6,7) + */ + protected int shadingType = 3; // Default + + /** + * A ColorSpace representing the colorspace. "DeviceRGB" is an example. + */ + protected PDFDeviceColorSpace colorSpace = null; + + /** + * The background color. Since shading is opaque, + * this is very rarely used. + */ + protected List background = null; + + /** + * Optional: A List specifying the clipping rectangle + */ + protected List bBox = null; + + /** + * Optional: A flag whether or not to filter the shading function + * to prevent aliasing artifacts. Default is false. + */ + protected boolean antiAlias = false; + + /** + * Optional for Type 1: Array of four numbers, xmin, xmax, ymin, ymax. + * Default is [0 1 0 1] + * Optional for Type 2: An array of two numbers between which the blend + * varies between start and end points. Default is 0, 1. + * Optional for Type 3: An array of two numbers between which the blend + * varies between start and end points. Default is 0, 1. + */ + protected List domain = null; + + /** + * Required for Type 1, 2, and 3: + * The object of the color mapping function (usually type 2 or 3). + * Optional for Type 4,5,6, and 7: When it's nearly the same thing. + */ + protected PSFunction function = null; + + /** + * Required for Type 2: An Array of four numbers specifying + * the starting and ending coordinate pairs + * Required for Type 3: An Array of six numbers [x0,y0,r0,x1,y1,r1] + * specifying the centers and radii of + * the starting and ending circles. + */ + protected List coords = null; + + /** + * Required for Type 2+3: An Array of two boolean values specifying + * whether to extend the start and end colors past the start + * and end points, respectively. + * Default is false, false. + */ + protected List extend = null; + + /** + * Constructor for Type 2 and 3 + * + * @param theShadingType 2 or 3 for axial or radial shading + * @param theColorSpace "DeviceRGB" or similar. + * @param theBackground theBackground An array of color components appropriate to the + * colorspace key specifying a single color value. + * This key is used by the f operator buy ignored by the sh operator. + * @param theBBox List of double's representing a rectangle + * in the coordinate space that is current at the + * time of shading is imaged. Temporary clipping + * boundary. + * @param theAntiAlias Default is false + * @param theCoords List of four (type 2) or 6 (type 3) Double + * @param theDomain List of Doubles specifying the domain + * @param theFunction the Stitching (PDFfunction type 3) function, + * even if it's stitching a single function + * @param theExtend List of Booleans of whether to extend the start + * and end colors past the start and end points + * The default is [false, false] + */ + public PSShading(int theShadingType, PDFDeviceColorSpace theColorSpace, + List<Double> theBackground, List<Double> theBBox, + boolean theAntiAlias, List<Double> theCoords, + List<Double> theDomain, Function theFunction, + List<Integer> theExtend) { + this.shadingType = theShadingType; // 2 or 3 + this.colorSpace = theColorSpace; + this.background = theBackground; + this.bBox = theBBox; + this.antiAlias = theAntiAlias; + + this.coords = theCoords; + this.domain = theDomain; + assert theFunction instanceof PSFunction; + this.function = (PSFunction)theFunction; + this.extend = theExtend; + } + + /** + * represent as PS. Whatever the shadingType is, the correct + * representation spits out. The sets of required and optional + * attributes are different for each type, but if a required + * attribute's object was constructed as null, then no error + * is raised. Instead, the malformed PS that was requested + * by the construction is dutifully output. + * This policy should be reviewed. + * + * @return the PDF string. + */ + public String toString() { + ShadingPattern pattern = new ShadingPattern(this); + return pattern.toString(colorSpace, shadingType, background, bBox, antiAlias); + } + + /** + * A method to write a type 2 or 3 shading object + * @param p The StringBuffer to write the shading object + * @return Returns the StringBuffer to which the shading object was written + */ + public StringBuffer handleShadingType2or3(StringBuffer p) { + if (this.coords != null) { + p.append("\t/Coords [ "); + for (int coordIndex = 0; coordIndex < coords.size(); coordIndex++) { + p.append(PDFNumber.doubleOut((Double)this.coords.get(coordIndex)) + + " "); + } + p.append("] \n"); + } + + // DOMAIN + if (this.domain != null) { + p.append("\t/Domain [ "); + for (int domainIndex = 0; domainIndex < domain.size(); domainIndex++) { + p.append(PDFNumber.doubleOut((Double)this.domain.get(domainIndex)) + + " "); + } + p.append("] \n"); + } else { + p.append("\t/Domain [ 0 1 ] \n"); + } + + if (this.extend != null) { + p.append("\t/Extend [ "); + for (int extendIndex = 0; extendIndex < extend.size(); extendIndex++) { + p.append((this.extend.get(extendIndex)) + " "); + } + + p.append("] \n"); + } else { + p.append("\t/Extend [ true true ] \n"); + } + + + if (this.function != null) { + p.append("\t/Function "); + try { + p.append(new String(this.function.toByteString(), "UTF-8") + " \n"); + } catch (UnsupportedEncodingException ex) { + //This should have been made an enum type to avoid throwing exceptions. + } + } + return p; + } + + /** + * A method to write a type 1 shading object + * @param p The StringBuffer to write the shading object + * @return Returns the StringBuffer to which the shading object was written + */ + public StringBuffer handleShadingType1(StringBuffer p) { + // TODO Auto-generated method stub + return null; + } + + /** + * A method to write a type 4, 6 or 7 shading object + * @param p The StringBuffer to write the shading object + * @return Returns the StringBuffer to which the shading object was written + */ + public StringBuffer handleShadingType4or6or7(StringBuffer p) { + // TODO Auto-generated method stub + return null; + } + + /** + * A method to write a type 5 shading object + * @param p The StringBuffer to write the shading object + * @return Returns the StringBuffer to which the shading object was written + */ + public StringBuffer handleShadingType5(StringBuffer p) { + // TODO Auto-generated method stub + return null; + } +} diff --git a/src/java/org/apache/fop/render/rtf/RTFHandler.java b/src/java/org/apache/fop/render/rtf/RTFHandler.java index dfeff65f1..73fa3504c 100644 --- a/src/java/org/apache/fop/render/rtf/RTFHandler.java +++ b/src/java/org/apache/fop/render/rtf/RTFHandler.java @@ -157,6 +157,7 @@ public class RTFHandler extends FOEventHandler { private PercentContext percentManager = new PercentContext(); + /** * Creates a new RTF structure handler. * @param userAgent the FOUserAgent for this process @@ -270,7 +271,7 @@ public class RTFHandler extends FOEventHandler { return; } else { - builderContext.popContainer(); + builderContext.popContainer(RtfSection.class, this); this.pagemaster = null; } } @@ -377,10 +378,10 @@ public class RTFHandler extends FOEventHandler { //just do nothing } else if (regionBefore != null && fl.getFlowName().equals(regionBefore.getRegionName())) { - builderContext.popContainer(); + builderContext.popContainer(RtfBefore.class, this); } else if (regionAfter != null && fl.getFlowName().equals(regionAfter.getRegionName())) { - builderContext.popContainer(); + builderContext.popContainer(RtfAfter.class, this); } } catch (Exception e) { log.error("endFlow: " + e.getMessage()); @@ -571,7 +572,7 @@ public class RTFHandler extends FOEventHandler { nestedTableDepth--; builderContext.popTableContext(); - builderContext.popContainer(); + builderContext.popContainer(RtfTable.class, this); } /** {@inheritDoc} */ @@ -605,21 +606,25 @@ public class RTFHandler extends FOEventHandler { /** {@inheritDoc} */ public void startHeader(TableHeader header) { + builderContext.pushPart(header); startPart(header); } /** {@inheritDoc} */ public void endHeader(TableHeader header) { + builderContext.popPart(header.getClass(), this); endPart(header); } /** {@inheritDoc} */ public void startFooter(TableFooter footer) { + builderContext.pushPart(footer); startPart(footer); } /** {@inheritDoc} */ public void endFooter(TableFooter footer) { + builderContext.popPart(footer.getClass(), this); endPart(footer); } @@ -711,11 +716,13 @@ public class RTFHandler extends FOEventHandler { * {@inheritDoc} */ public void startBody(TableBody body) { + builderContext.pushPart(body); startPart(body); } /** {@inheritDoc} */ public void endBody(TableBody body) { + builderContext.popPart(TableBody.class, this); endPart(body); } @@ -784,7 +791,7 @@ public class RTFHandler extends FOEventHandler { } - builderContext.popContainer(); + builderContext.popContainer(RtfTableRow.class, this); builderContext.getTableContext().decreaseRowSpannings(); } @@ -893,7 +900,7 @@ public class RTFHandler extends FOEventHandler { throw new RuntimeException(e.getMessage()); } - builderContext.popContainer(); + builderContext.popContainer(RtfTableCell.class, this); builderContext.getTableContext().selectNextColumn(); } @@ -929,7 +936,7 @@ public class RTFHandler extends FOEventHandler { return; } - builderContext.popContainer(); + builderContext.popContainer(RtfList.class, this); } /** {@inheritDoc} */ @@ -976,7 +983,7 @@ public class RTFHandler extends FOEventHandler { return; } - builderContext.popContainer(); + builderContext.popContainer(RtfListItem.class, this); } /** {@inheritDoc} */ @@ -1005,7 +1012,7 @@ public class RTFHandler extends FOEventHandler { return; } - builderContext.popContainer(); + builderContext.popContainer(RtfListItemLabel.class, this); } /** {@inheritDoc} */ @@ -1070,7 +1077,7 @@ public class RTFHandler extends FOEventHandler { return; } - builderContext.popContainer(); + builderContext.popContainer(RtfHyperLink.class, this); } /** {@inheritDoc} */ @@ -1306,7 +1313,7 @@ public class RTFHandler extends FOEventHandler { return; } - builderContext.popContainer(); + builderContext.popContainer(RtfFootnote.class, this); } /** {@inheritDoc} */ @@ -1688,6 +1695,19 @@ public class RTFHandler extends FOEventHandler { } /** + * Closes any mismatched tags that are detected in the RTF structure. + * @param containerClass The class representing the tag to close. + * @return Determines whether the tag mismatch has been handled. + */ + public boolean endContainer(Class containerClass) { + if (containerClass == RtfTableRow.class) { + endRow(null); + return true; + } + return false; + } + + /** * Calls the event handlers for the passed FONode and all its elements. * * @param foNode FONode object which shall be recursed diff --git a/src/java/org/apache/fop/render/rtf/RTFPlaceHolderHelper.java b/src/java/org/apache/fop/render/rtf/RTFPlaceHolderHelper.java new file mode 100644 index 000000000..e3278ad95 --- /dev/null +++ b/src/java/org/apache/fop/render/rtf/RTFPlaceHolderHelper.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.rtf; + +import org.apache.fop.render.rtf.rtflib.exceptions.RtfException; +import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfAttributes; +import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfContainer; +import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfTable; +import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfTableRow; +import org.apache.fop.render.rtf.rtflib.tools.BuilderContext; + +/** + * This class creates objects which are missing from the XSL:FO but are required + * by the RTF format. + */ +public class RTFPlaceHolderHelper { + /** The context object for building the RTF */ + private BuilderContext builderContext; + + /** + * Creates a new instance for the RTF place holder which attempts to resolve + * mismatches in structure between XSL:FO and RTF. + * @param builderContext The builder context + */ + public RTFPlaceHolderHelper(BuilderContext builderContext) { + this.builderContext = builderContext; + } + + /** + * A method to create an object which is missing and required from the + * RTF structure. + * @param containerClass The class which is missing + * @throws Exception + */ + public void createRTFPlaceholder(Class containerClass) throws RtfException { + if (containerClass == RtfTableRow.class) { + createRtfTableRow(); + } + } + + private void createRtfTableRow() throws RtfException { + try { + RtfContainer element = builderContext.getContainer(RtfTable.class, true, null); + if (element != null && element instanceof RtfTable) { + RtfTable table = (RtfTable)element; + RtfAttributes attribs = new RtfAttributes(); + RtfTableRow newRow = table.newTableRow(attribs); + builderContext.pushContainer(newRow); + builderContext.getTableContext().selectFirstColumn(); + } + } catch (org.apache.fop.apps.FOPException e) { + throw new RtfException(e.getMessage()); + } catch (java.io.IOException e) { + throw new RtfException(e.getMessage()); + } + } +} diff --git a/src/java/org/apache/fop/render/rtf/rtflib/tools/BuilderContext.java b/src/java/org/apache/fop/render/rtf/rtflib/tools/BuilderContext.java index f0a29a0ab..4b3f8bd4a 100644 --- a/src/java/org/apache/fop/render/rtf/rtflib/tools/BuilderContext.java +++ b/src/java/org/apache/fop/render/rtf/rtflib/tools/BuilderContext.java @@ -21,6 +21,12 @@ package org.apache.fop.render.rtf.rtflib.tools; import java.util.Stack; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fop.fo.FObj; +import org.apache.fop.render.rtf.RTFHandler; +import org.apache.fop.render.rtf.RTFPlaceHolderHelper; import org.apache.fop.render.rtf.rtflib.exceptions.RtfException; import org.apache.fop.render.rtf.rtflib.rtfdoc.IRtfOptions; import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfContainer; @@ -38,6 +44,10 @@ import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfContainer; */ public class BuilderContext { + + /** Static logging instance */ + protected static final Log LOG = LogFactory.getLog(BuilderContext.class.getName()); + /** stack of RtfContainers */ private final Stack containers = new Stack(); @@ -96,17 +106,22 @@ public class BuilderContext { * @throws RtfException if not caught */ public RtfContainer getContainer(Class containerClass, boolean required, - Object /*IBuilder*/ forWhichBuilder) throws RtfException { + Object forWhichBuilder) throws RtfException { // TODO what to do if the desired container is not at the top of the stack? // close top-of-stack container? - final RtfContainer result = (RtfContainer)getObjectFromStack(containers, + RtfContainer result = (RtfContainer)getObjectFromStack(containers, containerClass); if (result == null && required) { - throw new RtfException( - "No RtfContainer of class '" + containerClass.getName() - + "' available for '" + forWhichBuilder.getClass().getName() + "' builder" - ); + RTFPlaceHolderHelper placeHolderHelper = new RTFPlaceHolderHelper(this); + placeHolderHelper.createRTFPlaceholder(containerClass); + result = getContainer(containerClass, required, forWhichBuilder); + if (result == null) { + throw new RtfException( + "No RtfContainer of class '" + containerClass.getName() + + "' available for '" + forWhichBuilder.getClass().getName() + "' builder" + ); + } } return result; @@ -121,6 +136,14 @@ public class BuilderContext { } /** + * Push a Class representing a non-writeable section of the FO tree + * @param part the part + */ + public void pushPart(FObj part) { + containers.push(part); + } + + /** * In some cases an RtfContainer must be replaced by another one on the * stack. This happens when handling nested fo:blocks for example: after * handling a nested block the enclosing block must switch to a new @@ -142,8 +165,40 @@ public class BuilderContext { } /** pop the topmost RtfContainer from our stack */ - public void popContainer() { - containers.pop(); + public void popContainer(Class containerClass, RTFHandler handler) { + handlePop(containerClass, handler); + } + + /** pop the topmost part class from our stack */ + public void popPart(Class part, RTFHandler handler) { + handlePop(part, handler); + } + + /** + * This method checks for any tag mismatches between what is being closed + * and what exists on the stack. If a mismatch is found, then it will push + * the object back onto the stack and attempt to close the given section + * before retrying with the current pop task. + * @param aClass The class to be popped from the stack + * @param handler The RTF handler class to fix any mismatches + */ + private void handlePop(Class aClass, RTFHandler handler) { + Object object = containers.pop(); + if (object.getClass() != aClass) { + pushAndClose(aClass, object, handler); + } + } + + private void pushAndClose(Class aClass, Object object, RTFHandler handler) { + containers.push(object); + if (handler.endContainer(object.getClass())) { + popContainer(aClass, handler); + } else { + /* This should never happen unless a placeholder is not catered for + * in the RTFHandler.endContainer method. */ + LOG.warn("Unhandled RTF structure tag mismatch detected between " + + aClass.getSimpleName() + " and " + object.getClass().getSimpleName()); + } } /* push an IBuilder to our stack / diff --git a/src/java/org/apache/fop/render/shading/Function.java b/src/java/org/apache/fop/render/shading/Function.java new file mode 100644 index 000000000..5bd44087e --- /dev/null +++ b/src/java/org/apache/fop/render/shading/Function.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.fop.render.shading; + +import java.util.List; + +public interface Function { + int getFunctionType(); + List<Double> getBounds(); + List<Double> getDomain(); + List<Double> getSize(); + List<String> getFilter(); + List<Double> getEncode(); + List<Function> getFunctions(); + int getBitsPerSample(); + double getInterpolationExponentN(); + int getOrder(); + List<Double> getRange(); + List<Double> getDecode(); + StringBuffer getDataStream(); + List<Double> getCZero(); + List<Double> getCOne(); + byte[] toByteString(); +} diff --git a/src/java/org/apache/fop/render/shading/FunctionDelegate.java b/src/java/org/apache/fop/render/shading/FunctionDelegate.java new file mode 100644 index 000000000..eea24e0db --- /dev/null +++ b/src/java/org/apache/fop/render/shading/FunctionDelegate.java @@ -0,0 +1,451 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.fop.render.shading; + +import java.util.List; + +public class FunctionDelegate implements Function { + + private Function parentFunction; + + /** + * Required: The Type of function (0,2,3,4) default is 0. + */ + protected int functionType = 0; // Default + + /** + * Required: 2 * m Array of Double numbers which are possible inputs to the function + */ + protected List<Double> domain = null; + + /** + * Required: 2 * n Array of Double numbers which are possible outputs to the function + */ + protected List<Double> range = null; + + /* ********************TYPE 0***************************** */ + // FunctionType 0 specific function guts + + /** + * Required: Array containing the Integer size of the Domain and Range, respectively. + * Note: This is really more like two seperate integers, sizeDomain, and sizeRange, + * but since they're expressed as an array in PDF, my implementation reflects that. + */ + protected List<Double> size = null; + + /** + * Required for Type 0: Number of Bits used to represent each sample value. + * Limited to 1,2,4,8,12,16,24, or 32 + */ + protected int bitsPerSample = 1; + + /** + * Optional for Type 0: order of interpolation between samples. + * Limited to linear (1) or cubic (3). Default is 1 + */ + protected int order = 1; + + /** + * Optional for Type 0: A 2 * m array of Doubles which provides a + * linear mapping of input values to the domain. + * + * Required for Type 3: A 2 * k array of Doubles that, taken + * in pairs, map each subset of the domain defined by Domain + * and the Bounds array to the domain of the corresponding function. + * Should be two values per function, usually (0,1), + * as in [0 1 0 1] for 2 functions. + */ + protected List<Double> encode = null; + + /** + * Optional for Type 0: A 2 * n array of Doubles which provides + * a linear mapping of sample values to the range. Defaults to Range. + */ + protected List<Double> decode = null; + + /** + * Optional For Type 0: A stream of sample values + */ + + /** + * Required For Type 4: Postscript Calculator function + * composed of arithmetic, boolean, and stack operators + boolean constants + */ + protected StringBuffer functionDataStream = null; + + /** + * Required (possibly) For Type 0: A vector of Strings for the + * various filters to be used to decode the stream. + * These are how the string is compressed. Flate, LZW, etc. + */ + protected List<String> filter = null; + /* *************************TYPE 2************************** */ + + /** + * Required For Type 2: An Array of n Doubles defining + * the function result when x=0. Default is [0]. + */ + protected List<Double> cZero = null; + + /** + * Required For Type 2: An Array of n Doubles defining + * the function result when x=1. Default is [1]. + */ + protected List<Double> cOne = null; + + /** + * Required for Type 2: The interpolation exponent. + * Each value x will return n results. + * Must be greater than 0. + */ + protected double interpolationExponentN = 1; + + /* *************************TYPE 3************************** */ + + /** + * Required for Type 3: An vector of PDFFunctions which + * form an array of k single input functions making up + * the stitching function. + */ + protected List<Function> functions = null; + + /** + * Optional for Type 3: An array of (k-1) Doubles that, + * in combination with Domain, define the intervals to which + * each function from the Functions array apply. Bounds + * elements must be in order of increasing magnitude, + * and each value must be within the value of Domain. + * k is the number of functions. + * If you pass null, it will output (1/k) in an array of k-1 elements. + * This makes each function responsible for an equal amount of the stitching function. + * It makes the gradient even. + */ + protected List<Double> bounds = null; + + /** + * create an complete Function object of Type 0, A Sampled function. + * + * Use null for an optional object parameter if you choose not to use it. + * For optional int parameters, pass the default. + * + * @param theDomain List objects of Double objects. + * This is the domain of the function. + * See page 264 of the PDF 1.3 Spec. + * @param theRange List objects of Double objects. + * This is the Range of the function. + * See page 264 of the PDF 1.3 Spec. + * @param theSize A List object of Integer objects. + * This is the number of samples in each input dimension. + * I can't imagine there being more or less than two input dimensions, + * so maybe this should be an array of length 2. + * + * See page 265 of the PDF 1.3 Spec. + * @param theBitsPerSample An int specifying the number of bits + used to represent each sample value. + * Limited to 1,2,4,8,12,16,24 or 32. + * See page 265 of the 1.3 PDF Spec. + * @param theOrder The order of interpolation between samples. Default is 1 (one). Limited + * to 1 (one) or 3, which means linear or cubic-spline interpolation. + * + * This attribute is optional. + * + * See page 265 in the PDF 1.3 spec. + * @param theEncode List objects of Double objects. + * This is the linear mapping of input values intop the domain + * of the function's sample table. Default is hard to represent in + * ascii, but basically [0 (Size0 1) 0 (Size1 1)...]. + * This attribute is optional. + * + * See page 265 in the PDF 1.3 spec. + * @param theDecode List objects of Double objects. + * This is a linear mapping of sample values into the range. + * The default is just the range. + * + * This attribute is optional. + * Read about it on page 265 of the PDF 1.3 spec. + * @param theFunctionDataStream The sample values that specify + * the function are provided in a stream. + * + * This is optional, but is almost always used. + * + * Page 265 of the PDF 1.3 spec has more. + * @param theFilter This is a vector of String objects which are the various filters that + * have are to be applied to the stream to make sense of it. Order matters, + * so watch out. + * + * This is not documented in the Function section of the PDF 1.3 spec, + * it was deduced from samples that this is sometimes used, even if we may never + * use it in FOP. It is added for completeness sake. + * @param theFunctionType This is the type of function (0,2,3, or 4). + * It should be 0 as this is the constructor for sampled functions. + */ + public FunctionDelegate(Function parentFunction, int theFunctionType, List<Double> theDomain, + List<Double> theRange, List<Double> theSize, int theBitsPerSample, + int theOrder, List<Double> theEncode, List<Double> theDecode, + StringBuffer theFunctionDataStream, List<String> theFilter) { + this.parentFunction = parentFunction; + this.functionType = 0; // dang well better be 0; + this.size = theSize; + this.bitsPerSample = theBitsPerSample; + this.order = theOrder; // int + this.encode = theEncode; // vector of int + this.decode = theDecode; // vector of int + this.functionDataStream = theFunctionDataStream; + this.filter = theFilter; // vector of Strings + + // the domain and range are actually two dimensional arrays. + // so if there's not an even number of items, bad stuff + // happens. + this.domain = theDomain; + this.range = theRange; + } + + /** + * create an complete Function object of Type 2, an Exponential Interpolation function. + * + * Use null for an optional object parameter if you choose not to use it. + * For optional int parameters, pass the default. + * + * @param theDomain List objects of Double objects. + * This is the domain of the function. + * See page 264 of the PDF 1.3 Spec. + * @param theRange List of Doubles that is the Range of the function. + * See page 264 of the PDF 1.3 Spec. + * @param theCZero This is a vector of Double objects which defines the function result + * when x=0. + * + * This attribute is optional. + * It's described on page 268 of the PDF 1.3 spec. + * @param theCOne This is a vector of Double objects which defines the function result + * when x=1. + * + * This attribute is optional. + * It's described on page 268 of the PDF 1.3 spec. + * @param theInterpolationExponentN This is the inerpolation exponent. + * + * This attribute is required. + * PDF Spec page 268 + * @param theFunctionType The type of the function, which should be 2. + */ + public FunctionDelegate(Function parentFunction, int theFunctionType, List<Double> theDomain, + List<Double> theRange, List<Double> theCZero, List<Double> theCOne, + double theInterpolationExponentN) { + this.parentFunction = parentFunction; + this.functionType = 2; // dang well better be 2; + + this.cZero = theCZero; + this.cOne = theCOne; + this.interpolationExponentN = theInterpolationExponentN; + + this.domain = theDomain; + this.range = theRange; + + } + + /** + * create an complete Function object of Type 3, a Stitching function. + * + * Use null for an optional object parameter if you choose not to use it. + * For optional int parameters, pass the default. + * + * @param theDomain List objects of Double objects. + * This is the domain of the function. + * See page 264 of the PDF 1.3 Spec. + * @param theRange List objects of Double objects. + * This is the Range of the function. + * See page 264 of the PDF 1.3 Spec. + * @param theFunctions A List of the PDFFunction objects that the stitching function stitches. + * + * This attributed is required. + * It is described on page 269 of the PDF spec. + * @param theBounds This is a vector of Doubles representing the numbers that, + * in conjunction with Domain define the intervals to which each function from + * the 'functions' object applies. It must be in order of increasing magnitude, + * and each must be within Domain. + * + * It basically sets how much of the gradient each function handles. + * + * This attributed is required. + * It's described on page 269 of the PDF 1.3 spec. + * @param theEncode List objects of Double objects. + * This is the linear mapping of input values intop the domain + * of the function's sample table. Default is hard to represent in + * ascii, but basically [0 (Size0 1) 0 (Size1 1)...]. + * This attribute is required. + * + * See page 270 in the PDF 1.3 spec. + * @param theFunctionType This is the function type. It should be 3, + * for a stitching function. + */ + public FunctionDelegate(Function parentFunction, int theFunctionType, List<Double> theDomain, + List<Double> theRange, List<Function> theFunctions, + List<Double> theBounds, List<Double> theEncode) { + this.parentFunction = parentFunction; + this.functionType = 3; // dang well better be 3; + + this.functions = theFunctions; + this.bounds = theBounds; + this.encode = theEncode; + this.domain = theDomain; + this.range = theRange; + + } + + /** + * create an complete Function object of Type 4, a postscript calculator function. + * + * Use null for an optional object parameter if you choose not to use it. + * For optional int parameters, pass the default. + * + * @param theDomain List object of Double objects. + * This is the domain of the function. + * See page 264 of the PDF 1.3 Spec. + * @param theRange List object of Double objects. + * This is the Range of the function. + * See page 264 of the PDF 1.3 Spec. + * @param theFunctionDataStream This is a stream of arithmetic, + * boolean, and stack operators and boolean constants. + * I end up enclosing it in the '{' and '}' braces for you, so don't do it + * yourself. + * + * This attribute is required. + * It's described on page 269 of the PDF 1.3 spec. + * @param theFunctionType The type of function which should be 4, as this is + * a Postscript calculator function + */ + public FunctionDelegate(Function parentFunction, int theFunctionType, List<Double> theDomain, + List<Double> theRange, StringBuffer theFunctionDataStream) { + this.parentFunction = parentFunction; + this.functionType = 4; // dang well better be 4; + this.functionDataStream = theFunctionDataStream; + + this.domain = theDomain; + + this.range = theRange; + + } + + /** + * Gets the function type + */ + public int getFunctionType() { + return functionType; + } + + /** + * Gets the function bounds + */ + public List<Double> getBounds() { + return bounds; + } + + /** + * The function domain + */ + public List<Double> getDomain() { + return domain; + } + + /** + * The function size + */ + public List<Double> getSize() { + return size; + } + + /** + * Gets the function encoding + */ + public List<Double> getEncode() { + return encode; + } + + /** + * Gets the sub-functions + */ + public List<Function> getFunctions() { + return functions; + } + + /** + * Gets the function filter + */ + public List<String> getFilter() { + return filter; + } + + /** + * Gets the bits per sample of the function + */ + public int getBitsPerSample() { + return bitsPerSample; + } + + /** + * Gets the interpolation exponent of the function + */ + public double getInterpolationExponentN() { + return interpolationExponentN; + } + + /** + * Gets the function order + */ + public int getOrder() { + return order; + } + + /** + * Gets the function range + */ + public List<Double> getRange() { + return range; + } + + /** + * Gets the function decoding + */ + public List<Double> getDecode() { + return decode; + } + + /** + * Gets the function data stream + */ + public StringBuffer getDataStream() { + return functionDataStream; + } + + /** + * Gets the function C0 value (color for gradient) + */ + public List<Double> getCZero() { + return cZero; + } + + /** + * Gets the function C1 value (color for gradient) + */ + public List<Double> getCOne() { + return cOne; + } + + public byte[] toByteString() { + return parentFunction.toByteString(); + } +} diff --git a/src/java/org/apache/fop/render/shading/FunctionPattern.java b/src/java/org/apache/fop/render/shading/FunctionPattern.java new file mode 100644 index 000000000..044053a8b --- /dev/null +++ b/src/java/org/apache/fop/render/shading/FunctionPattern.java @@ -0,0 +1,363 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.shading; + +import java.io.UnsupportedEncodingException; + +import org.apache.fop.pdf.PDFFunction; +import org.apache.fop.pdf.PDFNumber; +import org.apache.fop.render.ps.svg.PSFunction; + +/** + * A class for writing function objects for different output formats + */ +public class FunctionPattern { + + private Function function; + + /** + * Constructor + * @param function The function from which to write the output + */ + public FunctionPattern(Function function) { + this.function = function; + } + + /** + * Outputs the function to a byte array + */ + public String toWriteableString() { + int vectorSize = 0; + int numberOfFunctions = 0; + int tempInt = 0; + StringBuffer p = new StringBuffer(256); + p.append("<< \n/FunctionType " + function.getFunctionType() + " \n"); + + // FunctionType 0 + if (this.function.getFunctionType() == 0) { + if (function.getDomain() != null) { + // DOMAIN + p.append("/Domain [ "); + vectorSize = function.getDomain().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getDomain().get(tempInt)) + + " "); + } + + p.append("] \n"); + } else { + p.append("/Domain [ 0 1 ] \n"); + } + + // SIZE + if (function.getSize() != null) { + p.append("/Size [ "); + vectorSize = function.getSize().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getSize().get(tempInt)) + + " "); + } + p.append("] \n"); + } + // ENCODE + if (function.getEncode() != null) { + p.append("/Encode [ "); + vectorSize = function.getEncode().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getEncode().get(tempInt)) + + " "); + } + p.append("] \n"); + } else { + p.append("/Encode [ "); + vectorSize = function.getFunctions().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append("0 1 "); + } + p.append("] \n"); + + } + + // BITSPERSAMPLE + p.append("/BitsPerSample " + function.getBitsPerSample()); + + // ORDER (optional) + if (function.getOrder() == 1 || function.getOrder() == 3) { + p.append(" \n/Order " + function.getOrder() + " \n"); + } + + // RANGE + if (function.getRange() != null) { + p.append("/Range [ "); + vectorSize = function.getRange().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getRange().get(tempInt)) + + " "); + } + + p.append("] \n"); + } + + // DECODE + if (function.getDecode() != null) { + p.append("/Decode [ "); + vectorSize = function.getDecode().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getDecode().get(tempInt)) + + " "); + } + + p.append("] \n"); + } + + // LENGTH + if (function.getDataStream() != null) { + p.append("/Length " + (function.getDataStream().length() + 1) + + " \n"); + } + + // FILTER? + if (function.getFilter() != null) { // if there's a filter + vectorSize = function.getFilter().size(); + p.append("/Filter "); + if (vectorSize == 1) { + p.append("/" + (function.getFilter().get(0)) + + " \n"); + } else { + p.append("[ "); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append("/" + (function.getFilter().get(0)) + + " "); + } + p.append("] \n"); + } + } + p.append(">>"); + + // stream representing the function + if (function.getDataStream() != null) { + p.append("\nstream\n" + function.getDataStream() + + "\nendstream"); + } + + // end of if FunctionType 0 + + } else if (function.getFunctionType() == 2) { + // DOMAIN + if (function.getDomain() != null) { + p.append("/Domain [ "); + vectorSize = function.getDomain().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getDomain().get(tempInt)) + + " "); + } + + p.append("] \n"); + } else { + p.append("/Domain [ 0 1 ] \n"); + } + + + // RANGE + if (function.getRange() != null) { + p.append("/Range [ "); + vectorSize = function.getRange().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getRange().get(tempInt)) + + " "); + } + + p.append("] \n"); + } + + // FunctionType, C0, C1, N are required in PDF + + // C0 + if (function.getCZero() != null) { + p.append("/C0 [ "); + vectorSize = function.getCZero().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getCZero().get(tempInt)) + + " "); + } + p.append("] \n"); + } + + // C1 + if (function.getCOne() != null) { + p.append("/C1 [ "); + vectorSize = function.getCOne().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getCOne().get(tempInt)) + + " "); + } + p.append("] \n"); + } + + // N: The interpolation Exponent + p.append("/N " + + PDFNumber.doubleOut(Double.valueOf(function.getInterpolationExponentN())) + + " \n"); + + p.append(">>"); + + } else if (function.getFunctionType() + == 3) { // fix this up when my eyes uncross + // DOMAIN + if (function.getDomain() != null) { + p.append("/Domain [ "); + vectorSize = function.getDomain().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getDomain().get(tempInt)) + + " "); + } + p.append("] \n"); + } else { + p.append("/Domain [ 0 1 ] \n"); + } + + // RANGE + if (function.getRange() != null) { + p.append("/Range [ "); + vectorSize = function.getRange().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getRange().get(tempInt)) + + " "); + } + + p.append("] \n"); + } + + // FUNCTIONS + if (function.getFunctions() != null) { + p.append("/Functions [ "); + numberOfFunctions = function.getFunctions().size(); + for (tempInt = 0; tempInt < numberOfFunctions; tempInt++) { + try { + if (function instanceof PSFunction) { + p.append(new String(function.getFunctions().get(tempInt).toByteString(), "UTF-8") + + " "); + } else { + p.append(((PDFFunction)function.getFunctions().get(tempInt)).referencePDF() + + " "); + } + } catch (UnsupportedEncodingException ex) { + //This should have been made an enum type to avoid throwing exceptions. + } + } + p.append("] \n"); + } + + + // ENCODE + if (function.getEncode() != null) { + p.append("/Encode [ "); + vectorSize = function.getEncode().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getEncode().get(tempInt)) + + " "); + } + + p.append("] \n"); + } else { + p.append("/Encode [ "); + vectorSize = function.getFunctions().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append("0 1 "); + } + p.append("] \n"); + + } + + + // BOUNDS, required, but can be empty + p.append("/Bounds [ "); + if (function.getBounds() != null) { + + vectorSize = function.getBounds().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getBounds().get(tempInt)) + + " "); + } + + } else { + if (function.getFunctions() != null) { + // if there are n functions, + // there must be n-1 bounds. + // so let each function handle an equal portion + // of the whole. e.g. if there are 4, then [ 0.25 0.25 0.25 ] + + String functionsFraction = PDFNumber.doubleOut(Double.valueOf(1.0 + / (numberOfFunctions))); + + for (tempInt = 0; tempInt + 1 < numberOfFunctions; + tempInt++) { + + p.append(functionsFraction + " "); + } + } + + } + p.append("]\n>>"); + } else if (function.getFunctionType() + == 4) { // fix this up when my eyes uncross + // DOMAIN + if (function.getDomain() != null) { + p.append("/Domain [ "); + vectorSize = function.getDomain().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getDomain().get(tempInt)) + + " "); + } + + p.append("] \n"); + } else { + p.append("/Domain [ 0 1 ] \n"); + } + + // RANGE + if (function.getRange() != null) { + p.append("/Range [ "); + vectorSize = function.getRange().size(); + for (tempInt = 0; tempInt < vectorSize; tempInt++) { + p.append(PDFNumber.doubleOut(function.getRange().get(tempInt)) + + " "); + } + + p.append("] \n"); + } + + // LENGTH + if (function.getDataStream() != null) { + p.append("/Length " + (function.getDataStream().length() + 1) + + " \n"); + } + + p.append(">>"); + + // stream representing the function + if (function.getDataStream() != null) { + p.append("\nstream\n{ " + function.getDataStream() + + " }\nendstream"); + } + } + return p.toString(); + } +} diff --git a/src/java/org/apache/fop/render/shading/GradientFactory.java b/src/java/org/apache/fop/render/shading/GradientFactory.java new file mode 100644 index 000000000..87ac11c83 --- /dev/null +++ b/src/java/org/apache/fop/render/shading/GradientFactory.java @@ -0,0 +1,162 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.shading; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.List; + +import org.apache.xmlgraphics.java2d.color.ColorUtil; + +import org.apache.fop.pdf.PDFDeviceColorSpace; +import org.apache.fop.render.ps.svg.PSSVGGraphics2D; + +public abstract class GradientFactory { + + static GradientRegistrar registrar; + + /** + * Constructor + * @param registrar The object used to register new embedded objects in the + * output format. + */ + public static GradientFactory newInstance(GradientRegistrar theRegistrar) { + registrar = theRegistrar; + if (registrar instanceof PSSVGGraphics2D) { + return new PSGradientFactory(); + } else { + return new PDFGradientFactory(); + } + } + + /** + * Creates a new gradient + * @param radial Determines whether the gradient is radial + * @param theColorspace The colorspace used in PDF and Postscript + * @param theColors The colors to be used in the gradient + * @param theBounds The bounds of each color + * @param theCoords The co-ordinates of the gradient + * @param theMatrix The matrix for any transformations + * @return Returns the Pattern object of the gradient + */ + public abstract Pattern createGradient(boolean radial, + PDFDeviceColorSpace theColorspace, List<Color> theColors, List<Double> theBounds, + List<Double> theCoords, List<Double> theMatrix); + + protected Pattern makeGradient(boolean radial, PDFDeviceColorSpace theColorspace, + List<Color> theColors, List<Double> theBounds, + List<Double> theCoords, List<Double> theMatrix) { + Shading myShad; + Function myfunky; + Function myfunc; + List<Double> theCzero; + List<Double> theCone; + double interpolation = 1.000; + List<Function> theFunctions = new ArrayList<Function>(); + + int currentPosition; + int lastPosition = theColors.size() - 1; + + + // if 5 elements, the penultimate element is 3. + // do not go beyond that, because you always need + // to have a next color when creating the function. + + for (currentPosition = 0; currentPosition < lastPosition; + currentPosition++) { // for every consecutive color pair + Color currentColor = theColors.get(currentPosition); + Color nextColor = theColors.get(currentPosition + 1); + + // colorspace must be consistent, so we simply convert to sRGB where necessary + if (!currentColor.getColorSpace().isCS_sRGB()) { + //Convert to sRGB + currentColor = ColorUtil.toSRGBColor(currentColor); + theColors.set(currentPosition, currentColor); + } + if (!nextColor.getColorSpace().isCS_sRGB()) { + //Convert to sRGB + nextColor = ColorUtil.toSRGBColor(nextColor); + theColors.set(currentPosition + 1, nextColor); + } + + theCzero = toColorVector(currentColor); + theCone = toColorVector(nextColor); + + myfunc = makeFunction(2, null, null, theCzero, theCone, + interpolation); + + theFunctions.add(myfunc); + + } // end of for every consecutive color pair + + myfunky = makeFunction(3, null, null, theFunctions, theBounds, + null); + + if (radial) { + if (theCoords.size() == 6) { + // make Shading of Type 2 or 3 + myShad = makeShading(3, theColorspace, null, null, false, theCoords, + null, myfunky, null); + } else { // if the center x, center y, and radius specifiy + // the gradient, then assume the same center x, center y, + // and radius of zero for the other necessary component + List<Double> newCoords = new ArrayList<Double>(); + newCoords.add(theCoords.get(0)); + newCoords.add(theCoords.get(1)); + newCoords.add(theCoords.get(2)); + newCoords.add(theCoords.get(0)); + newCoords.add(theCoords.get(1)); + newCoords.add(Double.valueOf(0.0)); + + myShad = makeShading(3, theColorspace, null, null, false, newCoords, + null, myfunky, null); + } + } else { + myShad = makeShading(2, theColorspace, null, null, false, theCoords, + null, myfunky, null); + } + return makePattern(2, myShad, null, null, theMatrix); + } + + public abstract Function makeFunction(int functionType, List<Double> theDomain, + List<Double> theRange, List<Function> theFunctions, + List<Double> theBounds, List<Double> theEncode); + + public abstract Function makeFunction(int functionType, List<Double> theDomain, + List<Double> theRange, List<Double> theCZero, List<Double> theCOne, + double theInterpolationExponentN); + + public abstract Shading makeShading(int theShadingType, + PDFDeviceColorSpace theColorSpace, List<Double> theBackground, List<Double> theBBox, + boolean theAntiAlias, List<Double> theCoords, List<Double> theDomain, + Function theFunction, List<Integer> theExtend); + + public abstract Pattern makePattern(int thePatternType, Shading theShading, List theXUID, + StringBuffer theExtGState, List<Double> theMatrix); + + private List<Double> toColorVector(Color nextColor) { + List<Double> vector = new java.util.ArrayList<Double>(); + float[] comps = nextColor.getColorComponents(null); + for (int i = 0, c = comps.length; i < c; i++) { + vector.add(Double.valueOf(comps[i])); + } + return vector; + } +} diff --git a/src/java/org/apache/fop/render/shading/GradientRegistrar.java b/src/java/org/apache/fop/render/shading/GradientRegistrar.java new file mode 100644 index 000000000..617fcd4fb --- /dev/null +++ b/src/java/org/apache/fop/render/shading/GradientRegistrar.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.fop.render.shading; + +public interface GradientRegistrar { + + /** + * Registers a function object against the output format document + * @param function The function object to register + * @return Returns either the function which has already been registered + * or the current new registered object. + */ + Function registerFunction(Function function); + + /** + * Registers a shading object against the output format document + * @param shading The shading object to register + * @return Returns either the shading which has already been registered + * or the current new registered object + */ + Shading registerShading(Shading shading); + + /** + * Registers a pattern object against the output format document + * @param pattern The pattern object to register + * @return Returns either the pattern which has already been registered + * or the current new registered object + */ + Pattern registerPattern(Pattern pattern); +} diff --git a/src/java/org/apache/fop/render/shading/PDFGradientFactory.java b/src/java/org/apache/fop/render/shading/PDFGradientFactory.java new file mode 100644 index 000000000..3b3dcab75 --- /dev/null +++ b/src/java/org/apache/fop/render/shading/PDFGradientFactory.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.fop.render.shading; + +import java.awt.Color; +import java.util.List; + +import org.apache.fop.pdf.PDFDeviceColorSpace; +import org.apache.fop.pdf.PDFFunction; +import org.apache.fop.pdf.PDFPattern; +import org.apache.fop.pdf.PDFShading; + +public class PDFGradientFactory extends GradientFactory { + + @Override + public PDFPattern createGradient(boolean radial, PDFDeviceColorSpace theColorspace, List<Color> theColors, + List<Double> theBounds, List<Double> theCoords, List<Double> theMatrix) { + return (PDFPattern)makeGradient(radial, theColorspace, theColors, theBounds, + theCoords, theMatrix); + } + + @Override + public Function makeFunction(int functionType, List<Double> theDomain, + List<Double> theRange, List<Function> theFunctions, + List<Double> theBounds, List<Double> theEncode) { + Function newFunction = new PDFFunction(functionType, theDomain, theRange, theFunctions, + theBounds, theEncode); + newFunction = registrar.registerFunction(newFunction); + return newFunction; + } + + public Function makeFunction(int functionType, List<Double> theDomain, + List<Double> theRange, List<Double> theCZero, List<Double> theCOne, + double theInterpolationExponentN) { + Function newFunction = new PDFFunction(functionType, theDomain, theRange, theCZero, + theCOne, theInterpolationExponentN); + newFunction = registrar.registerFunction(newFunction); + return newFunction; + } + + @Override + public Shading makeShading(int theShadingType, + PDFDeviceColorSpace theColorSpace, List<Double> theBackground, List<Double> theBBox, + boolean theAntiAlias, List<Double> theCoords, List<Double> theDomain, + Function theFunction, List<Integer> theExtend) { + Shading newShading = new PDFShading(theShadingType, theColorSpace, theBackground, + theBBox, theAntiAlias, theCoords, theDomain, theFunction, theExtend); + newShading = registrar.registerShading(newShading); + return newShading; + } + + @Override + public Pattern makePattern(int thePatternType, Shading theShading, List theXUID, + StringBuffer theExtGState, List<Double> theMatrix) { + Pattern newPattern = new PDFPattern(thePatternType, theShading, theXUID, theExtGState, + theMatrix); + newPattern = registrar.registerPattern(newPattern); + return newPattern; + } + +} diff --git a/src/java/org/apache/fop/render/shading/PSGradientFactory.java b/src/java/org/apache/fop/render/shading/PSGradientFactory.java new file mode 100644 index 000000000..cd47de93a --- /dev/null +++ b/src/java/org/apache/fop/render/shading/PSGradientFactory.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.fop.render.shading; + +import java.awt.Color; +import java.util.List; + +import org.apache.fop.pdf.PDFDeviceColorSpace; +import org.apache.fop.render.ps.svg.PSFunction; +import org.apache.fop.render.ps.svg.PSPattern; +import org.apache.fop.render.ps.svg.PSShading; + +public class PSGradientFactory extends GradientFactory { + + @Override + public PSPattern createGradient(boolean radial, PDFDeviceColorSpace theColorspace, + List<Color> theColors, List<Double> theBounds, List<Double> theCoords, + List<Double> theMatrix) { + return (PSPattern)makeGradient(radial, theColorspace, theColors, theBounds, + theCoords, theMatrix); + } + + public Function makeFunction(int functionType, List<Double> theDomain, + List<Double> theRange, List<Function> theFunctions, + List<Double> theBounds, List<Double> theEncode) { + Function newFunction = new PSFunction(functionType, theDomain, theRange, theFunctions, + theBounds, theEncode); + return newFunction; + } + + @Override + public Function makeFunction(int functionType, List<Double> theDomain, + List<Double> theRange, List<Double> theCZero, List<Double> theCOne, + double theInterpolationExponentN) { + Function newFunction = new PSFunction(functionType, theDomain, theRange, theCZero, + theCOne, theInterpolationExponentN); + return newFunction; + } + + @Override + public Shading makeShading(int theShadingType, + PDFDeviceColorSpace theColorSpace, List<Double> theBackground, List<Double> theBBox, + boolean theAntiAlias, List<Double> theCoords, List<Double> theDomain, + Function theFunction, List<Integer> theExtend) { + Shading newShading = new PSShading(theShadingType, theColorSpace, theBackground, theBBox, + theAntiAlias, theCoords, theDomain, theFunction, theExtend); + return newShading; + } + + @Override + public Pattern makePattern(int thePatternType, Shading theShading, List theXUID, + StringBuffer theExtGState, List<Double> theMatrix) { + return new PSPattern(thePatternType, theShading, theXUID, theExtGState); + } +} diff --git a/src/java/org/apache/fop/render/shading/Pattern.java b/src/java/org/apache/fop/render/shading/Pattern.java new file mode 100644 index 000000000..b66926e53 --- /dev/null +++ b/src/java/org/apache/fop/render/shading/Pattern.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.fop.render.shading; + +public interface Pattern { + +} diff --git a/src/java/org/apache/fop/render/shading/Shading.java b/src/java/org/apache/fop/render/shading/Shading.java new file mode 100644 index 000000000..385cb112b --- /dev/null +++ b/src/java/org/apache/fop/render/shading/Shading.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.fop.render.shading; + + +public interface Shading { + StringBuffer handleShadingType1(StringBuffer p); + StringBuffer handleShadingType2or3(StringBuffer p); + StringBuffer handleShadingType4or6or7(StringBuffer p); + StringBuffer handleShadingType5(StringBuffer p); +} diff --git a/src/java/org/apache/fop/render/shading/ShadingPattern.java b/src/java/org/apache/fop/render/shading/ShadingPattern.java new file mode 100644 index 000000000..6dac65f55 --- /dev/null +++ b/src/java/org/apache/fop/render/shading/ShadingPattern.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.fop.render.shading; + +import java.util.List; + +import org.apache.fop.pdf.PDFDeviceColorSpace; +import org.apache.fop.pdf.PDFNumber; + +/** + * A class for writing shading objects for different output formats + */ +public class ShadingPattern { + + private Shading shading; + + /** + * Constructor + * @param shading The shading object from which to write the output + */ + public ShadingPattern(Shading shading) { + this.shading = shading; + } + + /** + * Outputs the given shading object to a String + * @param colorSpace The Colospace (PDF and Postscript) + * @param shadingType The shading type + * @param background The background + * @param bBox The bounding box + * @param antiAlias Anti-aliasing + * @return Returns the output string + */ + public String toString(PDFDeviceColorSpace colorSpace, int shadingType, List background, + List bBox, boolean antiAlias) { + StringBuffer p = new StringBuffer(128); + p.append("<<\n/ShadingType " + shadingType + " \n"); + if (colorSpace != null) { + p.append("/ColorSpace /" + + colorSpace.getName() + " \n"); + } + + if (background != null) { + p.append("/Background [ "); + for (int bgIndex = 0; bgIndex < background.size(); bgIndex++) { + p.append(PDFNumber.doubleOut((Double)background.get(bgIndex)) + + " "); + } + p.append("] \n"); + } + + if (bBox + != null) { // I've never seen an example, so I guess this is right. + p.append("/BBox [ "); + for (int bboxIndex = 0; bboxIndex < bBox.size(); bboxIndex++) { + p.append(PDFNumber.doubleOut((Double)bBox.get(bboxIndex)) + + " "); + } + p.append("] \n"); + } + + if (antiAlias) { + p.append("/AntiAlias " + antiAlias + " \n"); + } + + // Here's where we differentiate based on what type it is. + switch (shadingType) { + //Function based shading + case 1: p = shading.handleShadingType1(p); break; + //Axial shading + case 2: + //Radial shading + case 3: p = shading.handleShadingType2or3(p); break; + //Free-form Gouraud-shaded triangle meshes + case 4: + //Coons patch meshes + case 6: + //Tensor product patch meshes + case 7: p = shading.handleShadingType4or6or7(p); break; + //Lattice Free form gouraud-shaded triangle mesh + case 5: p = shading.handleShadingType5(p); break; + default: //Shading pattern outside expecting values + break; + } + + p.append(">>"); + + return (p.toString()); + } +} diff --git a/src/java/org/apache/fop/render/txt/TXTRenderer.java b/src/java/org/apache/fop/render/txt/TXTRenderer.java index bf4a46e19..5b398711f 100644 --- a/src/java/org/apache/fop/render/txt/TXTRenderer.java +++ b/src/java/org/apache/fop/render/txt/TXTRenderer.java @@ -583,6 +583,14 @@ public class TXTRenderer extends AbstractPathOrientedRenderer { } /** {@inheritDoc} */ + protected void startLayer(String layer) { + } + + /** {@inheritDoc} */ + protected void endLayer() { + } + + /** {@inheritDoc} */ protected void concatenateTransformationMatrix(AffineTransform at) { currentState.push(new CTM(UnitConv.ptToMpt(at))); } diff --git a/src/java/org/apache/fop/render/xml/XMLRenderer.java b/src/java/org/apache/fop/render/xml/XMLRenderer.java index 2212da434..68e2e3c62 100644 --- a/src/java/org/apache/fop/render/xml/XMLRenderer.java +++ b/src/java/org/apache/fop/render/xml/XMLRenderer.java @@ -545,6 +545,16 @@ public class XMLRenderer extends AbstractXMLRenderer { //only necessary for graphical output } + /** {@inheritDoc} */ + protected void startLayer(String layer) { + //only necessary for graphical output + } + + /** {@inheritDoc} */ + protected void endLayer() { + //only necessary for graphical output + } + /** * {@inheritDoc} * org.apache.fop.area.inline.InlineArea) |