From 7d3dba6c3c2fc6caeeeaccbc8df159b87934e44a Mon Sep 17 00:00:00 2001 From: Luis Bernardo Date: Wed, 13 Nov 2013 15:24:26 +0000 Subject: [PATCH] FOP-2313: add support for svg gradients when generating PostScript; most code authored by Robert Meyer with a small contribution by Athanasios Giannimaras. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1541551 13f79535-47bb-0310-9956-ffa450edef68 --- build.xml | 1 + lib/xmlgraphics-commons-svn-trunk.jar | Bin 629507 -> 629955 bytes src/java/org/apache/fop/pdf/PDFFactory.java | 106 ++- src/java/org/apache/fop/pdf/PDFFunction.java | 651 ++++-------------- src/java/org/apache/fop/pdf/PDFPattern.java | 14 +- src/java/org/apache/fop/pdf/PDFShading.java | 373 +++++----- .../fop/render/ps/PSImageHandlerSVG.java | 327 +++++++-- .../apache/fop/render/ps/svg/PSFunction.java | 143 ++++ .../apache/fop/render/ps/svg/PSPattern.java | 103 +++ .../fop/render/ps/svg/PSSVGGraphics2D.java | 291 ++++++++ .../apache/fop/render/ps/svg/PSShading.java | 228 ++++++ .../apache/fop/render/shading/Function.java | 39 ++ .../fop/render/shading/FunctionDelegate.java | 451 ++++++++++++ .../fop/render/shading/FunctionPattern.java | 363 ++++++++++ .../fop/render/shading/GradientFactory.java | 162 +++++ .../fop/render/shading/GradientRegistrar.java | 45 ++ .../render/shading/PDFGradientFactory.java | 76 ++ .../fop/render/shading/PSGradientFactory.java | 70 ++ .../apache/fop/render/shading/Pattern.java | 22 + .../apache/fop/render/shading/Shading.java | 26 + .../fop/render/shading/ShadingPattern.java | 105 +++ .../org/apache/fop/svg/PDFGraphics2D.java | 62 +- .../ps/svg/PSSVGGraphics2DTestCase.java | 71 ++ .../ps/svg/PSSVGLinearGraphics2DTestCase.java | 70 ++ .../render/ps/svg/axial-shading-expected.dat | 26 + .../org/apache/fop/render/ps/svg/expected.ps | 38 + 26 files changed, 3009 insertions(+), 854 deletions(-) create mode 100644 src/java/org/apache/fop/render/ps/svg/PSFunction.java create mode 100644 src/java/org/apache/fop/render/ps/svg/PSPattern.java create mode 100644 src/java/org/apache/fop/render/ps/svg/PSSVGGraphics2D.java create mode 100644 src/java/org/apache/fop/render/ps/svg/PSShading.java create mode 100644 src/java/org/apache/fop/render/shading/Function.java create mode 100644 src/java/org/apache/fop/render/shading/FunctionDelegate.java create mode 100644 src/java/org/apache/fop/render/shading/FunctionPattern.java create mode 100644 src/java/org/apache/fop/render/shading/GradientFactory.java create mode 100644 src/java/org/apache/fop/render/shading/GradientRegistrar.java create mode 100644 src/java/org/apache/fop/render/shading/PDFGradientFactory.java create mode 100644 src/java/org/apache/fop/render/shading/PSGradientFactory.java create mode 100644 src/java/org/apache/fop/render/shading/Pattern.java create mode 100644 src/java/org/apache/fop/render/shading/Shading.java create mode 100644 src/java/org/apache/fop/render/shading/ShadingPattern.java create mode 100644 test/java/org/apache/fop/render/ps/svg/PSSVGGraphics2DTestCase.java create mode 100644 test/java/org/apache/fop/render/ps/svg/PSSVGLinearGraphics2DTestCase.java create mode 100644 test/java/org/apache/fop/render/ps/svg/axial-shading-expected.dat create mode 100644 test/java/org/apache/fop/render/ps/svg/expected.ps diff --git a/build.xml b/build.xml index 97503f208..f25239bc0 100644 --- a/build.xml +++ b/build.xml @@ -579,6 +579,7 @@ list of possible build targets. + diff --git a/lib/xmlgraphics-commons-svn-trunk.jar b/lib/xmlgraphics-commons-svn-trunk.jar index 22b0f5d52d418ceb006a6989d9856b9019d61596..fad3129404ec807e56c93b6f8a73882026299e5e 100644 GIT binary patch delta 71895 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

E&sF=oQgJOy6g_T#aCiOsax5qSEcrH? z;U9o&Tvl@pZDQLK~%LZgY`Y$T}P=Xi^&RAh%95BBNN~& zMq-;Y5opgT3s~&+KDK9@SD7zLGbN2=x+X-m3pX#yuQXX>rg*3jHI68?_idvCHdeR0sD5?vc2@O|aeQa*4JIBiEu$ z>ceFQ9@>P;mZiE1yXlMMk;j&$kWX?mqNlSH>pL0S)Tpq=OUTCiw{BO;eu+y(ft6tF zWo;7JPk}2CNV1jakH@JGv3_QZh&RhCEz{a$3+PxVU1)8Q){?R1($6}XRt`8YaGjq>t=d0MU?EInPonjiQ^zCAmk`246A zzzL4E(qgmJv*uGSDQMIW@tPUGnvV-1&U7+lRmq&u%D|bd{}wN�+F8%kuAhGYIcS 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 A_W%F@ delta 71506 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 theDomain, + List theRange, List theSize, int theBitsPerSample, + int theOrder, List theEncode, List theDecode, + StringBuffer theFunctionDataStream, List theFilter) { + delegate = new FunctionDelegate(this, theFunctionType, theDomain, theRange, + theSize, theBitsPerSample, theOrder, theEncode, theDecode, + theFunctionDataStream, theFilter); } /** @@ -260,20 +134,11 @@ public class PDFFunction extends PDFObject { * PDF Spec page 268 * @param theFunctionType The type of the function, which should be 2. */ - public PDFFunction(int theFunctionType, List theDomain, - List theRange, List theCZero, List theCOne, + public PDFFunction(int theFunctionType, List theDomain, + List theRange, List theCZero, List theCOne, double theInterpolationExponentN) { - super(); - - this.functionType = 2; // dang well better be 2; - - this.cZero = theCZero; - this.cOne = theCOne; - this.interpolationExponentN = theInterpolationExponentN; - - - this.domain = theDomain; - this.range = theRange; + delegate = new FunctionDelegate(this, theFunctionType, theDomain, theRange, + theCZero, theCOne, theInterpolationExponentN); } @@ -312,18 +177,11 @@ public class PDFFunction extends PDFObject { * @param theFunctionType This is the function type. It should be 3, * for a stitching function. */ - public PDFFunction(int theFunctionType, List theDomain, - List theRange, List theFunctions, - List theBounds, List theEncode) { - super(); - - this.functionType = 3; // dang well better be 3; - - this.functions = theFunctions; - this.bounds = theBounds; - this.encode = theEncode; - this.domain = theDomain; - this.range = theRange; + public PDFFunction(int theFunctionType, List theDomain, + List theRange, List theFunctions, + List theBounds, List theEncode) { + delegate = new FunctionDelegate(this, theFunctionType, theDomain, theRange, + theFunctions, theBounds, theEncode); } @@ -349,20 +207,12 @@ public class PDFFunction extends PDFObject { * @param theFunctionType The type of function which should be 4, as this is * a Postscript calculator function */ - public PDFFunction(int theFunctionType, List theDomain, - List theRange, StringBuffer theFunctionDataStream) { - super(); - - this.functionType = 4; // dang well better be 4; - this.functionDataStream = theFunctionDataStream; - - this.domain = theDomain; - - this.range = theRange; - + public PDFFunction(int theFunctionType, List theDomain, + List theRange, StringBuffer theFunctionDataStream) { + delegate = new FunctionDelegate(this, theFunctionType, theDomain, theRange, + theFunctionDataStream); } - /** * represent as PDF. Whatever the FunctionType is, the correct * representation spits out. The sets of required and optional @@ -375,319 +225,13 @@ public class PDFFunction extends PDFObject { * @return the PDF string. */ public byte[] toPDF() { - int vectorSize = 0; - int numberOfFunctions = 0; - int tempInt = 0; - StringBuffer p = new StringBuffer(256); - p.append("<< \n/FunctionType " + this.functionType + " \n"); - - // FunctionType 0 - if (this.functionType == 0) { - if (this.domain != null) { - // DOMAIN - p.append("/Domain [ "); - vectorSize = this.domain.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.domain.get(tempInt)) - + " "); - } - - p.append("] \n"); - } else { - p.append("/Domain [ 0 1 ] \n"); - } - - // SIZE - if (this.size != null) { - p.append("/Size [ "); - vectorSize = this.size.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.size.get(tempInt)) - + " "); - } - p.append("] \n"); - } - // ENCODE - if (this.encode != null) { - p.append("/Encode [ "); - vectorSize = this.encode.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.encode.get(tempInt)) - + " "); - } - p.append("] \n"); - } else { - p.append("/Encode [ "); - vectorSize = this.functions.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append("0 1 "); - } - p.append("] \n"); - - } - - // BITSPERSAMPLE - p.append("/BitsPerSample " + this.bitsPerSample); - - // ORDER (optional) - if (this.order == 1 || this.order == 3) { - p.append(" \n/Order " + this.order + " \n"); - } - - // RANGE - if (this.range != null) { - p.append("/Range [ "); - vectorSize = this.range.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.range.get(tempInt)) - + " "); - } - - p.append("] \n"); - } - - // DECODE - if (this.decode != null) { - p.append("/Decode [ "); - vectorSize = this.decode.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.decode.get(tempInt)) - + " "); - } - - p.append("] \n"); - } - - // LENGTH - if (this.functionDataStream != null) { - p.append("/Length " + (this.functionDataStream.length() + 1) - + " \n"); - } - - // FILTER? - if (this.filter != null) { // if there's a filter - vectorSize = this.filter.size(); - p.append("/Filter "); - if (vectorSize == 1) { - p.append("/" + ((String)this.filter.get(0)) - + " \n"); - } else { - p.append("[ "); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append("/" + ((String)this.filter.get(0)) - + " "); - } - p.append("] \n"); - } - } - p.append(">>"); - - // stream representing the function - if (this.functionDataStream != null) { - p.append("\nstream\n" + this.functionDataStream - + "\nendstream"); - } - - // end of if FunctionType 0 - - } else if (this.functionType == 2) { - // DOMAIN - if (this.domain != null) { - p.append("/Domain [ "); - vectorSize = this.domain.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.domain.get(tempInt)) - + " "); - } - - p.append("] \n"); - } else { - p.append("/Domain [ 0 1 ] \n"); - } - - - // RANGE - if (this.range != null) { - p.append("/Range [ "); - vectorSize = this.range.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.range.get(tempInt)) - + " "); - } - - p.append("] \n"); - } - - // FunctionType, C0, C1, N are required in PDF - - // C0 - if (this.cZero != null) { - p.append("/C0 [ "); - vectorSize = this.cZero.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.cZero.get(tempInt)) - + " "); - } - p.append("] \n"); - } - - // C1 - if (this.cOne != null) { - p.append("/C1 [ "); - vectorSize = this.cOne.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.cOne.get(tempInt)) - + " "); - } - p.append("] \n"); - } - - // N: The interpolation Exponent - p.append("/N " - + PDFNumber.doubleOut(new Double(this.interpolationExponentN)) - + " \n"); - - p.append(">>"); - - } else if (this.functionType - == 3) { // fix this up when my eyes uncross - // DOMAIN - if (this.domain != null) { - p.append("/Domain [ "); - vectorSize = this.domain.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.domain.get(tempInt)) - + " "); - } - p.append("] \n"); - } else { - p.append("/Domain [ 0 1 ] \n"); - } - - // RANGE - if (this.range != null) { - p.append("/Range [ "); - vectorSize = this.range.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.range.get(tempInt)) - + " "); - } - - p.append("] \n"); - } - - // FUNCTIONS - if (this.functions != null) { - p.append("/Functions [ "); - numberOfFunctions = this.functions.size(); - for (tempInt = 0; tempInt < numberOfFunctions; tempInt++) { - p.append(((PDFFunction)this.functions.get(tempInt)).referencePDF() - + " "); - - } - p.append("] \n"); - } - - - // ENCODE - if (this.encode != null) { - p.append("/Encode [ "); - vectorSize = this.encode.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.encode.get(tempInt)) - + " "); - } - - p.append("] \n"); - } else { - p.append("/Encode [ "); - vectorSize = this.functions.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append("0 1 "); - } - p.append("] \n"); - - } - - - // BOUNDS, required, but can be empty - p.append("/Bounds [ "); - if (this.bounds != null) { - - vectorSize = this.bounds.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.bounds.get(tempInt)) - + " "); - } - - } else { - if (this.functions != 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(new Double(1.0 - / ((double)numberOfFunctions))); - - for (tempInt = 0; tempInt + 1 < numberOfFunctions; - tempInt++) { - - p.append(functionsFraction + " "); - } - functionsFraction = null; // clean reference. - - } - - } - p.append("]\n>>"); - } else if (this.functionType - == 4) { // fix this up when my eyes uncross - // DOMAIN - if (this.domain != null) { - p.append("/Domain [ "); - vectorSize = this.domain.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.domain.get(tempInt)) - + " "); - } - - p.append("] \n"); - } else { - p.append("/Domain [ 0 1 ] \n"); - } - - // RANGE - if (this.range != null) { - p.append("/Range [ "); - vectorSize = this.range.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.range.get(tempInt)) - + " "); - } - - p.append("] \n"); - } - - // LENGTH - if (this.functionDataStream != null) { - p.append("/Length " + (this.functionDataStream.length() + 1) - + " \n"); - } - - p.append(">>"); - - // stream representing the function - if (this.functionDataStream != null) { - p.append("\nstream\n{ " + this.functionDataStream - + " }\nendstream"); - } - - - } + return toByteString(); + } - return encode(p.toString()); + public byte[] toByteString() { + FunctionPattern pattern = new FunctionPattern(this); + return encode(pattern.toWriteableString()); } /** {@inheritDoc} */ @@ -702,96 +246,155 @@ public class PDFFunction extends PDFObject { return false; } PDFFunction func = (PDFFunction)obj; - if (functionType != func.functionType) { + if (delegate.getFunctionType() != func.getFunctionType()) { return false; } - if (bitsPerSample != func.bitsPerSample) { + if (delegate.getBitsPerSample() != func.getBitsPerSample()) { return false; } - if (order != func.order) { + if (delegate.getOrder() != func.getOrder()) { return false; } - if (interpolationExponentN != func.interpolationExponentN) { + if (delegate.getInterpolationExponentN() != func.getInterpolationExponentN()) { return false; } - if (domain != null) { - if (!domain.equals(func.domain)) { + if (delegate.getDomain() != null) { + if (!delegate.getDomain().equals(func.getDomain())) { return false; } - } else if (func.domain != null) { + } else if (func.getDomain() != null) { return false; } - if (range != null) { - if (!range.equals(func.range)) { + if (delegate.getRange() != null) { + if (!delegate.getRange().equals(func.getRange())) { return false; } - } else if (func.range != null) { + } else if (func.getRange() != null) { return false; } - if (size != null) { - if (!size.equals(func.size)) { + if (delegate.getSize() != null) { + if (!delegate.getSize().equals(func.getSize())) { return false; } - } else if (func.size != null) { + } else if (func.getSize() != null) { return false; } - if (encode != null) { - if (!encode.equals(func.encode)) { + if (delegate.getEncode() != null) { + if (!delegate.getEncode().equals(func.getEncode())) { return false; } - } else if (func.encode != null) { + } else if (func.getEncode() != null) { return false; } - if (decode != null) { - if (!decode.equals(func.decode)) { + if (delegate.getDecode() != null) { + if (!delegate.getDecode().equals(func.getDecode())) { return false; } - } else if (func.decode != null) { + } else if (func.getDecode() != null) { return false; } - if (functionDataStream != null) { - if (!functionDataStream.equals(func.functionDataStream)) { + if (delegate.getDataStream() != null) { + if (!delegate.getDataStream().equals(func.getDataStream())) { return false; } - } else if (func.functionDataStream != null) { + } else if (func.getDataStream() != null) { return false; } - if (filter != null) { - if (!filter.equals(func.filter)) { + if (delegate.getFilter() != null) { + if (!delegate.getFilter().equals(func.getFilter())) { return false; } - } else if (func.filter != null) { + } else if (func.getFilter() != null) { return false; } - if (cZero != null) { - if (!cZero.equals(func.cZero)) { + if (delegate.getCZero() != null) { + if (!delegate.getCZero().equals(func.getCZero())) { return false; } - } else if (func.cZero != null) { + } else if (func.getCZero() != null) { return false; } - if (cOne != null) { - if (!cOne.equals(func.cOne)) { + if (delegate.getCOne() != null) { + if (!delegate.getCOne().equals(func.getCOne())) { return false; } - } else if (func.cOne != null) { + } else if (func.getCOne() != null) { return false; } - if (functions != null) { - if (!functions.equals(func.functions)) { + if (delegate.getFunctions() != null) { + if (!delegate.getFunctions().equals(func.getFunctions())) { return false; } - } else if (func.functions != null) { + } else if (func.getFunctions() != null) { return false; } - if (bounds != null) { - if (!bounds.equals(func.bounds)) { + if (delegate.getBounds() != null) { + if (!delegate.getBounds().equals(func.getBounds())) { return false; } - } else if (func.bounds != null) { + } else if (func.getBounds() != null) { return false; } return true; } + public int getFunctionType() { + return delegate.getFunctionType(); + } + + public List getBounds() { + return delegate.getBounds(); + } + + public List getDomain() { + return delegate.getDomain(); + } + + public List getSize() { + return delegate.getSize(); + } + + public List getFilter() { + return delegate.getFilter(); + } + + public List getEncode() { + return delegate.getEncode(); + } + + public List getFunctions() { + return delegate.getFunctions(); + } + + public int getBitsPerSample() { + return delegate.getBitsPerSample(); + } + + public double getInterpolationExponentN() { + return delegate.getInterpolationExponentN(); + } + + public int getOrder() { + return delegate.getOrder(); + } + + public List getRange() { + return delegate.getRange(); + } + + public List getDecode() { + return delegate.getDecode(); + } + + public StringBuffer getDataStream() { + return delegate.getDataStream(); + } + + public List getCZero() { + return delegate.getCZero(); + } + + public List getCOne() { + return delegate.getCOne(); + } } diff --git a/src/java/org/apache/fop/pdf/PDFPattern.java b/src/java/org/apache/fop/pdf/PDFPattern.java index b9e042815..df4b0233d 100644 --- a/src/java/org/apache/fop/pdf/PDFPattern.java +++ b/src/java/org/apache/fop/pdf/PDFPattern.java @@ -23,6 +23,9 @@ import java.io.IOException; import java.io.OutputStream; import java.util.List; +import org.apache.fop.render.shading.Pattern; +import org.apache.fop.render.shading.Shading; + /** * class representing a PDF Function. * @@ -33,7 +36,7 @@ import java.util.List; * * All PDF Functions have a FunctionType (0,2,3, or 4), a Domain, and a Range. */ -public class PDFPattern extends PDFPathPaint { +public class PDFPattern extends PDFPathPaint implements Pattern { /** * The resources associated with this pattern @@ -146,13 +149,14 @@ public class PDFPattern extends PDFPathPaint { * @param theExtGState optional: the extended graphics state, if used. * @param theMatrix Optional:List of Doubles that specify the matrix. */ - public PDFPattern(int thePatternType, PDFShading theShading, + public PDFPattern(int thePatternType, Shading theShading, List theXUID, StringBuffer theExtGState, List theMatrix) { super(); this.patternType = 2; // thePatternType; - this.shading = theShading; + assert theShading instanceof PDFShading; + this.shading = (PDFShading)theShading; this.xUID = theXUID; // this isn't really implemented, so it should always be null. // I just don't want to have to add a new parameter once it is implemented. @@ -259,7 +263,7 @@ public class PDFPattern extends PDFPathPaint { vectorSize = this.xUID.size(); p.append("/XUID [ "); for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(((Integer)this.xUID.get(tempInt)) + " "); + p.append((this.xUID.get(tempInt)) + " "); } p.append("] \n"); } @@ -290,7 +294,7 @@ public class PDFPattern extends PDFPathPaint { vectorSize = this.xUID.size(); p.append("/XUID [ "); for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(((Integer)this.xUID.get(tempInt)) + " "); + p.append((this.xUID.get(tempInt)) + " "); } p.append("] \n"); } diff --git a/src/java/org/apache/fop/pdf/PDFShading.java b/src/java/org/apache/fop/pdf/PDFShading.java index 62012b9b2..3f7b2b4b0 100644 --- a/src/java/org/apache/fop/pdf/PDFShading.java +++ b/src/java/org/apache/fop/pdf/PDFShading.java @@ -22,6 +22,10 @@ package org.apache.fop.pdf; // Java... import java.util.List; +import org.apache.fop.render.shading.Function; +import org.apache.fop.render.shading.Shading; +import org.apache.fop.render.shading.ShadingPattern; + /** * class representing a PDF Smooth Shading object. * @@ -32,7 +36,7 @@ import java.util.List; * * All PDF Functions have a shadingType (0,2,3, or 4), a Domain, and a Range. */ -public class PDFShading extends PDFObject { +public class PDFShading extends PDFObject implements Shading { // Guts common to all function types /** @@ -205,7 +209,7 @@ public class PDFShading extends PDFObject { public PDFShading(int theShadingType, PDFDeviceColorSpace theColorSpace, List theBackground, List theBBox, boolean theAntiAlias, List theCoords, - List theDomain, PDFFunction theFunction, + List theDomain, Function theFunction, List theExtend) { super(); this.shadingType = theShadingType; // 2 or 3 @@ -216,7 +220,8 @@ public class PDFShading extends PDFObject { this.coords = theCoords; this.domain = theDomain; - this.function = theFunction; + assert theFunction instanceof PDFFunction; + this.function = (PDFFunction)theFunction; this.extend = theExtend; } @@ -335,197 +340,8 @@ public class PDFShading extends PDFObject { * @return the PDF string. */ public String toPDFString() { - int vectorSize; - int tempInt; - StringBuffer p = new StringBuffer(128); - p.append("<<\n/ShadingType " + this.shadingType + " \n"); - if (this.colorSpace != null) { - p.append("/ColorSpace /" - + this.colorSpace.getName() + " \n"); - } - - if (this.background != null) { - p.append("/Background [ "); - vectorSize = this.background.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.background.get(tempInt)) - + " "); - } - p.append("] \n"); - } - - if (this.bBox - != null) { // I've never seen an example, so I guess this is right. - p.append("/BBox [ "); - vectorSize = this.bBox.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.bBox.get(tempInt)) - + " "); - } - p.append("] \n"); - } - - if (this.antiAlias) { - p.append("/AntiAlias " + this.antiAlias + " \n"); - } - - // Here's where we differentiate based on what type it is. - if (this.shadingType == 1) { // function based shading - if (this.domain != null) { - p.append("/Domain [ "); - vectorSize = this.domain.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.domain.get(tempInt)) - + " "); - } - p.append("] \n"); - } else { - p.append("/Domain [ 0 1 ] \n"); - } - - if (this.matrix != null) { - p.append("/Matrix [ "); - vectorSize = this.matrix.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.matrix.get(tempInt)) - + " "); - } - p.append("] \n"); - } - - if (this.function != null) { - p.append("/Function "); - p.append(this.function.referencePDF() + " \n"); - } - } else if ((this.shadingType == 2) - || (this.shadingType - == 3)) { // 2 is axial shading (linear gradient) - // 3 is radial shading (circular gradient) - if (this.coords != null) { - p.append("/Coords [ "); - vectorSize = this.coords.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.coords.get(tempInt)) - + " "); - } - p.append("] \n"); - } - - // DOMAIN - if (this.domain != null) { - p.append("/Domain [ "); - vectorSize = this.domain.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(PDFNumber.doubleOut((Double)this.domain.get(tempInt)) - + " "); - } - p.append("] \n"); - } else { - p.append("/Domain [ 0 1 ] \n"); - } - - if (this.extend != null) { - p.append("/Extend [ "); - vectorSize = this.extend.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(((Boolean)this.extend.get(tempInt)) + " "); - } - - p.append("] \n"); - } else { - p.append("/Extend [ true true ] \n"); - } - - - if (this.function != null) { - p.append("/Function "); - p.append(this.function.referencePDF() + " \n"); - } - - - } else if ((this.shadingType == 4) || (this.shadingType == 6) - || (this.shadingType - == 7)) { // 4:Free-form Gouraud-shaded triangle meshes - // 6:coons patch meshes - // 7://tensor product patch meshes (which no one ever uses) - if (this.bitsPerCoordinate > 0) { - p.append("/BitsPerCoordinate " + this.bitsPerCoordinate - + " \n"); - } else { - p.append("/BitsPerCoordinate 1 \n"); - } - - if (this.bitsPerComponent > 0) { - p.append("/BitsPerComponent " + this.bitsPerComponent - + " \n"); - } else { - p.append("/BitsPerComponent 1 \n"); - } - - if (this.bitsPerFlag > 0) { - p.append("/BitsPerFlag " + this.bitsPerFlag + " \n"); - } else { - p.append("/BitsPerFlag 2 \n"); - } - - if (this.decode != null) { - p.append("/Decode [ "); - vectorSize = this.decode.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(((Boolean)this.decode.get(tempInt)) + " "); - } - - p.append("] \n"); - } - - if (this.function != null) { - p.append("/Function "); - p.append(this.function.referencePDF() + " \n"); - } - - } else if (this.shadingType - == 5) { // Lattice Free form gouraud-shaded triangle mesh - - if (this.bitsPerCoordinate > 0) { - p.append("/BitsPerCoordinate " + this.bitsPerCoordinate - + " \n"); - } else { - p.append("/BitsPerCoordinate 1 \n"); - } - - if (this.bitsPerComponent > 0) { - p.append("/BitsPerComponent " + this.bitsPerComponent - + " \n"); - } else { - p.append("/BitsPerComponent 1 \n"); - } - - if (this.decode != null) { - p.append("/Decode [ "); - vectorSize = this.decode.size(); - for (tempInt = 0; tempInt < vectorSize; tempInt++) { - p.append(((Boolean)this.decode.get(tempInt)) + " "); - } - - p.append("] \n"); - } - - if (this.function != null) { - p.append("/Function "); - p.append(this.function.referencePDF() + " \n"); - } - - if (this.verticesPerRow > 0) { - p.append("/VerticesPerRow " + this.verticesPerRow + " \n"); - } else { - p.append("/VerticesPerRow 2 \n"); - } - - } - - p.append(">>"); - - return (p.toString()); + ShadingPattern pattern = new ShadingPattern(this); + return pattern.toString(colorSpace, shadingType, background, bBox, antiAlias); } /** {@inheritDoc} */ @@ -623,4 +439,173 @@ public class PDFShading extends PDFObject { } return true; } + + /** + * 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) { + if (this.domain != null) { + p.append("/Domain [ "); + for (int domainIndex = 0; domainIndex < domain.size(); domainIndex++) { + p.append(PDFNumber.doubleOut((Double)this.domain.get(domainIndex)) + + " "); + } + p.append("] \n"); + } else { + p.append("/Domain [ 0 1 ] \n"); + } + + if (this.matrix != null) { + p.append("/Matrix [ "); + for (int matrixIndex = 0; matrixIndex < matrix.size(); matrixIndex++) { + p.append(PDFNumber.doubleOut((Double)this.matrix.get(matrixIndex)) + + " "); + } + p.append("] \n"); + } + + if (this.function != null) { + p.append("/Function "); + p.append(this.function.referencePDF() + " \n"); + } + return p; + } + + /** + * 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) { + // 3 is radial shading (circular gradient) + if (this.coords != null) { + p.append("/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("/Domain [ "); + for (int domainIndex = 0; domainIndex < domain.size(); domainIndex++) { + p.append(PDFNumber.doubleOut((Double)this.domain.get(domainIndex)) + + " "); + } + p.append("] \n"); + } else { + p.append("/Domain [ 0 1 ] \n"); + } + + if (this.extend != null) { + p.append("/Extend [ "); + for (int extendIndex = 0; extendIndex < extend.size(); extendIndex++) { + p.append((this.extend.get(extendIndex)) + " "); + } + + p.append("] \n"); + } else { + p.append("/Extend [ true true ] \n"); + } + + + if (this.function != null) { + p.append("/Function "); + p.append(this.function.referencePDF() + " \n"); + } + + return p; + } + + /** + * 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) { + // 6:coons patch meshes + // 7://tensor product patch meshes (which no one ever uses) + if (this.bitsPerCoordinate > 0) { + p.append("/BitsPerCoordinate " + this.bitsPerCoordinate + + " \n"); + } else { + p.append("/BitsPerCoordinate 1 \n"); + } + + if (this.bitsPerComponent > 0) { + p.append("/BitsPerComponent " + this.bitsPerComponent + + " \n"); + } else { + p.append("/BitsPerComponent 1 \n"); + } + + if (this.bitsPerFlag > 0) { + p.append("/BitsPerFlag " + this.bitsPerFlag + " \n"); + } else { + p.append("/BitsPerFlag 2 \n"); + } + + if (this.decode != null) { + p.append("/Decode [ "); + for (int decodeIndex = 0; decodeIndex < decode.size(); decodeIndex++) { + p.append((this.decode.get(decodeIndex)) + " "); + } + + p.append("] \n"); + } + + if (this.function != null) { + p.append("/Function "); + p.append(this.function.referencePDF() + " \n"); + } + + return p; + } + + /** + * 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) { + if (this.bitsPerCoordinate > 0) { + p.append("/BitsPerCoordinate " + this.bitsPerCoordinate + + " \n"); + } else { + p.append("/BitsPerCoordinate 1 \n"); + } + + if (this.bitsPerComponent > 0) { + p.append("/BitsPerComponent " + this.bitsPerComponent + + " \n"); + } else { + p.append("/BitsPerComponent 1 \n"); + } + + if (this.decode != null) { + p.append("/Decode [ "); + for (int decodeIndex = 0; decodeIndex < decode.size(); decodeIndex++) { + p.append((this.decode.get(decodeIndex)) + " "); + } + + p.append("] \n"); + } + + if (this.function != null) { + p.append("/Function "); + p.append(this.function.referencePDF() + " \n"); + } + + if (this.verticesPerRow > 0) { + p.append("/VerticesPerRow " + this.verticesPerRow + " \n"); + } else { + p.append("/VerticesPerRow 2 \n"); + } + + return p; + } } 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 gradientsFound = new HashMap(); + 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 gradMatches = new ArrayList(); + 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 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/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 theDomain, + List theRange, List theFunctions, + List theBounds, List 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 theDomain, + List theRange, List theCZero, List 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 getBounds() { + return delegate.getBounds(); + } + + public List getDomain() { + return delegate.getDomain(); + } + + public List getSize() { + return delegate.getSize(); + } + + public List getFilter() { + return delegate.getFilter(); + } + + public List getEncode() { + return delegate.getEncode(); + } + + public List getFunctions() { + return delegate.getFunctions(); + } + + public int getBitsPerSample() { + return delegate.getBitsPerSample(); + } + + public double getInterpolationExponentN() { + return delegate.getInterpolationExponentN(); + } + + public int getOrder() { + return delegate.getOrder(); + } + + public List getRange() { + return delegate.getRange(); + } + + public List getDecode() { + return delegate.getDecode(); + } + + public StringBuffer getDataStream() { + return delegate.getDataStream(); + } + + public List getCZero() { + return delegate.getCZero(); + } + + public List 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..3b6b5490c --- /dev/null +++ b/src/java/org/apache/fop/render/ps/svg/PSSVGGraphics2D.java @@ -0,0 +1,291 @@ +/* + * 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 theCoords = new java.util.ArrayList(); + + + 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 someColors = new java.util.ArrayList(); + 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 theBounds = new java.util.ArrayList(); + 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 theMatrix = new java.util.ArrayList(); + double [] mat = new double[6]; + transform.getMatrix(mat); + for (int idx = 0; idx < mat.length; idx++) { + theMatrix.add(Double.valueOf(mat[idx])); + } + + List theCoords = new java.util.ArrayList(); + 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 someColors = new java.util.ArrayList(); + 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 theBounds = new java.util.ArrayList(); + 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 Graphics object that is + * a copy of this Graphics 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 theBackground, List theBBox, + boolean theAntiAlias, List theCoords, + List theDomain, Function theFunction, + List 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/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 getBounds(); + List getDomain(); + List getSize(); + List getFilter(); + List getEncode(); + List getFunctions(); + int getBitsPerSample(); + double getInterpolationExponentN(); + int getOrder(); + List getRange(); + List getDecode(); + StringBuffer getDataStream(); + List getCZero(); + List 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 domain = null; + + /** + * Required: 2 * n Array of Double numbers which are possible outputs to the function + */ + protected List 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 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 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 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 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 cZero = null; + + /** + * Required For Type 2: An Array of n Doubles defining + * the function result when x=1. Default is [1]. + */ + protected List 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 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 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 theDomain, + List theRange, List theSize, int theBitsPerSample, + int theOrder, List theEncode, List theDecode, + StringBuffer theFunctionDataStream, List 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 theDomain, + List theRange, List theCZero, List 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 theDomain, + List theRange, List theFunctions, + List theBounds, List 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 theDomain, + List 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 getBounds() { + return bounds; + } + + /** + * The function domain + */ + public List getDomain() { + return domain; + } + + /** + * The function size + */ + public List getSize() { + return size; + } + + /** + * Gets the function encoding + */ + public List getEncode() { + return encode; + } + + /** + * Gets the sub-functions + */ + public List getFunctions() { + return functions; + } + + /** + * Gets the function filter + */ + public List 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 getRange() { + return range; + } + + /** + * Gets the function decoding + */ + public List getDecode() { + return decode; + } + + /** + * Gets the function data stream + */ + public StringBuffer getDataStream() { + return functionDataStream; + } + + /** + * Gets the function C0 value (color for gradient) + */ + public List getCZero() { + return cZero; + } + + /** + * Gets the function C1 value (color for gradient) + */ + public List 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 theColors, List theBounds, + List theCoords, List theMatrix); + + protected Pattern makeGradient(boolean radial, PDFDeviceColorSpace theColorspace, + List theColors, List theBounds, + List theCoords, List theMatrix) { + Shading myShad; + Function myfunky; + Function myfunc; + List theCzero; + List theCone; + double interpolation = 1.000; + List theFunctions = new ArrayList(); + + 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 newCoords = new ArrayList(); + 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 theDomain, + List theRange, List theFunctions, + List theBounds, List theEncode); + + public abstract Function makeFunction(int functionType, List theDomain, + List theRange, List theCZero, List theCOne, + double theInterpolationExponentN); + + public abstract Shading makeShading(int theShadingType, + PDFDeviceColorSpace theColorSpace, List theBackground, List theBBox, + boolean theAntiAlias, List theCoords, List theDomain, + Function theFunction, List theExtend); + + public abstract Pattern makePattern(int thePatternType, Shading theShading, List theXUID, + StringBuffer theExtGState, List theMatrix); + + private List toColorVector(Color nextColor) { + List vector = new java.util.ArrayList(); + 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 theColors, + List theBounds, List theCoords, List theMatrix) { + return (PDFPattern)makeGradient(radial, theColorspace, theColors, theBounds, + theCoords, theMatrix); + } + + @Override + public Function makeFunction(int functionType, List theDomain, + List theRange, List theFunctions, + List theBounds, List theEncode) { + Function newFunction = new PDFFunction(functionType, theDomain, theRange, theFunctions, + theBounds, theEncode); + newFunction = registrar.registerFunction(newFunction); + return newFunction; + } + + public Function makeFunction(int functionType, List theDomain, + List theRange, List theCZero, List 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 theBackground, List theBBox, + boolean theAntiAlias, List theCoords, List theDomain, + Function theFunction, List 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 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 theColors, List theBounds, List theCoords, + List theMatrix) { + return (PSPattern)makeGradient(radial, theColorspace, theColors, theBounds, + theCoords, theMatrix); + } + + public Function makeFunction(int functionType, List theDomain, + List theRange, List theFunctions, + List theBounds, List theEncode) { + Function newFunction = new PSFunction(functionType, theDomain, theRange, theFunctions, + theBounds, theEncode); + return newFunction; + } + + @Override + public Function makeFunction(int functionType, List theDomain, + List theRange, List theCZero, List theCOne, + double theInterpolationExponentN) { + Function newFunction = new PSFunction(functionType, theDomain, theRange, theCZero, + theCOne, theInterpolationExponentN); + return newFunction; + } + + @Override + public Shading makeShading(int theShadingType, + PDFDeviceColorSpace theColorSpace, List theBackground, List theBBox, + boolean theAntiAlias, List theCoords, List theDomain, + Function theFunction, List 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 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/svg/PDFGraphics2D.java b/src/java/org/apache/fop/svg/PDFGraphics2D.java index efa71a225..1fcf9f870 100644 --- a/src/java/org/apache/fop/svg/PDFGraphics2D.java +++ b/src/java/org/apache/fop/svg/PDFGraphics2D.java @@ -80,6 +80,7 @@ import org.apache.fop.pdf.PDFColorHandler; import org.apache.fop.pdf.PDFConformanceException; import org.apache.fop.pdf.PDFDeviceColorSpace; import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFFunction; import org.apache.fop.pdf.PDFGState; import org.apache.fop.pdf.PDFImage; import org.apache.fop.pdf.PDFImageXObject; @@ -89,11 +90,18 @@ import org.apache.fop.pdf.PDFPaintingState; import org.apache.fop.pdf.PDFPattern; import org.apache.fop.pdf.PDFResourceContext; import org.apache.fop.pdf.PDFResources; +import org.apache.fop.pdf.PDFShading; import org.apache.fop.pdf.PDFText; import org.apache.fop.pdf.PDFXObject; import org.apache.fop.render.pdf.ImageRawCCITTFaxAdapter; import org.apache.fop.render.pdf.ImageRawJPEGAdapter; import org.apache.fop.render.pdf.ImageRenderedAdapter; +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.PDFGradientFactory; +import org.apache.fop.render.shading.Pattern; +import org.apache.fop.render.shading.Shading; /** *

PDF Graphics 2D. @@ -104,7 +112,7 @@ import org.apache.fop.render.pdf.ImageRenderedAdapter; * * @see org.apache.batik.ext.awt.g2d.AbstractGraphics2D */ -public class PDFGraphics2D extends AbstractGraphics2D implements NativeImageHandler { +public class PDFGraphics2D extends AbstractGraphics2D implements NativeImageHandler, GradientRegistrar { private static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform(); /** The number of decimal places. */ @@ -868,11 +876,10 @@ public class PDFGraphics2D extends AbstractGraphics2D implements NativeImageHand } //Gradients are currently restricted to sRGB - PDFDeviceColorSpace aColorSpace; - aColorSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB); - PDFPattern myPat = pdfDoc.getFactory().makeGradient( - resourceContext, false, aColorSpace, - someColors, theBounds, theCoords, theMatrix); + PDFDeviceColorSpace colSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB); + PDFGradientFactory gradientFactory = (PDFGradientFactory)GradientFactory.newInstance(this); + PDFPattern myPat = gradientFactory.createGradient(false, colSpace, someColors, theBounds, + theCoords, theMatrix); currentStream.write(myPat.getColorSpaceOut(fill)); return true; @@ -944,13 +951,10 @@ public class PDFGraphics2D extends AbstractGraphics2D implements NativeImageHand float offset = fractions[count]; theBounds.add(new Double(offset)); } - PDFDeviceColorSpace colSpace; - colSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB); - - PDFPattern myPat = pdfDoc.getFactory().makeGradient( - resourceContext, true, colSpace, - someColors, theBounds, theCoords, theMatrix); - + PDFDeviceColorSpace colSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB); + PDFGradientFactory gradientFactory = (PDFGradientFactory) GradientFactory.newInstance(this); + PDFPattern myPat = gradientFactory.createGradient(true, colSpace, someColors, theBounds, + theCoords, theMatrix); currentStream.write(myPat.getColorSpaceOut(fill)); return true; @@ -1856,4 +1860,36 @@ public class PDFGraphics2D extends AbstractGraphics2D implements NativeImageHand //NYI } + /** + * 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) { + return pdfDoc.getFactory().registerFunction((PDFFunction)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) { + assert shading instanceof PDFShading; + return pdfDoc.getFactory().registerShading(resourceContext, (PDFShading)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) { + assert pattern instanceof PDFPattern; + return pdfDoc.getFactory().registerPattern(resourceContext, (PDFPattern)pattern); + } + } diff --git a/test/java/org/apache/fop/render/ps/svg/PSSVGGraphics2DTestCase.java b/test/java/org/apache/fop/render/ps/svg/PSSVGGraphics2DTestCase.java new file mode 100644 index 000000000..b48da4186 --- /dev/null +++ b/test/java/org/apache/fop/render/ps/svg/PSSVGGraphics2DTestCase.java @@ -0,0 +1,71 @@ +/* + * 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.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.awt.geom.Rectangle2D.Float; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; + +import org.junit.Test; + +import org.apache.commons.io.FileUtils; + +import org.apache.batik.ext.awt.RadialGradientPaint; + +import org.apache.xmlgraphics.java2d.GraphicContext; +import org.apache.xmlgraphics.ps.PSGenerator; + +import static org.junit.Assert.assertEquals; + +public class PSSVGGraphics2DTestCase { + + float cx = 841.891f; + float cy = 178.583f; + float r = 16.4331f; + float[] fractions = {0.2f, 0.6012f, 0.8094f, 1.0f}; + + /** + * Tests a radial gradient generated pattern with certain inputs against + * an expected output. + * @throws IOException + */ + @Test + public void testApplyPaint() throws IOException { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + PSGenerator gen = new PSGenerator(os); + PSSVGGraphics2D svgGraphics2D = new PSSVGGraphics2D(false, gen); + svgGraphics2D.setGraphicContext(new GraphicContext()); + svgGraphics2D.setTransform(new AffineTransform()); + Color[] colors = {new Color(255, 255, 255), new Color(200, 200, 200), + new Color(170, 170, 170), new Color(140, 140, 140)}; + RadialGradientPaint paint = new RadialGradientPaint(cx, cy, r, fractions, colors); + Float s = new Rectangle2D.Float(7.0f, 3.0f, 841.891f, 178.583f); + svgGraphics2D.applyPaint(paint, true); + byte[] test = os.toByteArray(); + + byte[] expected = FileUtils.readFileToByteArray( + new File("test/java/org/apache/fop/render/ps/svg/expected.ps")); + assertEquals(new String(test), new String(expected)); + } +} diff --git a/test/java/org/apache/fop/render/ps/svg/PSSVGLinearGraphics2DTestCase.java b/test/java/org/apache/fop/render/ps/svg/PSSVGLinearGraphics2DTestCase.java new file mode 100644 index 000000000..283d3f4ad --- /dev/null +++ b/test/java/org/apache/fop/render/ps/svg/PSSVGLinearGraphics2DTestCase.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. + */ + +/* $Id$ */ + +package org.apache.fop.render.ps.svg; + +import java.awt.Color; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.awt.geom.Rectangle2D.Float; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; + +import org.junit.Test; + +import org.apache.commons.io.FileUtils; + +import org.apache.batik.ext.awt.LinearGradientPaint; + +import org.apache.xmlgraphics.java2d.GraphicContext; +import org.apache.xmlgraphics.ps.PSGenerator; + +import static org.junit.Assert.assertEquals; + +public class PSSVGLinearGraphics2DTestCase { + float startX = 115f; + float endX = 15f; + float startY = 285f; + float endY=15f; + float[] fractions = {0.0f, 1.0f}; + + /** + * Tests a linear gradient generated pattern with certain inputs against + * an expected output. + * @throws IOException + */ + @Test + public void testApplyPaint() throws IOException { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + PSGenerator gen = new PSGenerator(os); + PSSVGGraphics2D svgGraphics2D = new PSSVGGraphics2D(false, gen); + svgGraphics2D.setGraphicContext(new GraphicContext()); + svgGraphics2D.setTransform(new AffineTransform()); + Color[] colors = {new Color(255, 255, 0), new Color(255, 0, 0)}; + LinearGradientPaint paint = new LinearGradientPaint(startX, startY, endX, endY, fractions, colors); + Float s = new Rectangle2D.Float(115.0f, 15.0f, 170f, 110f); + svgGraphics2D.applyPaint(paint, true); + byte[] test = os.toByteArray(); + + byte[] expected = FileUtils.readFileToByteArray( + new File("test/java/org/apache/fop/render/ps/svg/axial-shading-expected.dat")); + assertEquals(new String(test), new String(expected)); + } +} diff --git a/test/java/org/apache/fop/render/ps/svg/axial-shading-expected.dat b/test/java/org/apache/fop/render/ps/svg/axial-shading-expected.dat new file mode 100644 index 000000000..fdf92cda5 --- /dev/null +++ b/test/java/org/apache/fop/render/ps/svg/axial-shading-expected.dat @@ -0,0 +1,26 @@ +/Pattern setcolorspace +<< +/Type /Pattern +/PatternType 2 +/Shading << +/ShadingType 2 +/ColorSpace /DeviceRGB + /Coords [ 115 285 15 15 ] + /Domain [ 0 1 ] + /Extend [ true true ] + /Function << +/FunctionType 3 +/Domain [ 0 1 ] +/Functions [ << +/FunctionType 2 +/Domain [ 0 1 ] +/C0 [ 1 1 0 ] +/C1 [ 1 0 0 ] +/N 1 +>> ] +/Encode [ 0 1 ] +/Bounds [ ] +>> +>> +>> +matrix makepattern setcolor diff --git a/test/java/org/apache/fop/render/ps/svg/expected.ps b/test/java/org/apache/fop/render/ps/svg/expected.ps new file mode 100644 index 000000000..9977070c3 --- /dev/null +++ b/test/java/org/apache/fop/render/ps/svg/expected.ps @@ -0,0 +1,38 @@ +/Pattern setcolorspace +<< +/Type /Pattern +/PatternType 2 +/Shading << +/ShadingType 3 +/ColorSpace /DeviceRGB + /Coords [ 841.890991 178.582993 3.28662 841.890991 178.582993 16.4331 ] + /Domain [ 0 1 ] + /Extend [ true true ] + /Function << +/FunctionType 3 +/Domain [ 0 1 ] +/Functions [ << +/FunctionType 2 +/Domain [ 0 1 ] +/C0 [ 1 1 1 ] +/C1 [ 0.784314 0.784314 0.784314 ] +/N 1 +>> << +/FunctionType 2 +/Domain [ 0 1 ] +/C0 [ 0.784314 0.784314 0.784314 ] +/C1 [ 0.666667 0.666667 0.666667 ] +/N 1 +>> << +/FunctionType 2 +/Domain [ 0 1 ] +/C0 [ 0.666667 0.666667 0.666667 ] +/C1 [ 0.54902 0.54902 0.54902 ] +/N 1 +>> ] +/Encode [ 0 1 0 1 0 1 ] +/Bounds [ 0.6012 0.8094 ] +>> +>> +>> +matrix makepattern setcolor -- 2.39.5