]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
FOP-2313: add support for svg gradients when generating PostScript; most code authore...
authorLuis Bernardo <lbernardo@apache.org>
Wed, 13 Nov 2013 15:24:26 +0000 (15:24 +0000)
committerLuis Bernardo <lbernardo@apache.org>
Wed, 13 Nov 2013 15:24:26 +0000 (15:24 +0000)
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1541551 13f79535-47bb-0310-9956-ffa450edef68

26 files changed:
build.xml
lib/xmlgraphics-commons-svn-trunk.jar
src/java/org/apache/fop/pdf/PDFFactory.java
src/java/org/apache/fop/pdf/PDFFunction.java
src/java/org/apache/fop/pdf/PDFPattern.java
src/java/org/apache/fop/pdf/PDFShading.java
src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java
src/java/org/apache/fop/render/ps/svg/PSFunction.java [new file with mode: 0644]
src/java/org/apache/fop/render/ps/svg/PSPattern.java [new file with mode: 0644]
src/java/org/apache/fop/render/ps/svg/PSSVGGraphics2D.java [new file with mode: 0644]
src/java/org/apache/fop/render/ps/svg/PSShading.java [new file with mode: 0644]
src/java/org/apache/fop/render/shading/Function.java [new file with mode: 0644]
src/java/org/apache/fop/render/shading/FunctionDelegate.java [new file with mode: 0644]
src/java/org/apache/fop/render/shading/FunctionPattern.java [new file with mode: 0644]
src/java/org/apache/fop/render/shading/GradientFactory.java [new file with mode: 0644]
src/java/org/apache/fop/render/shading/GradientRegistrar.java [new file with mode: 0644]
src/java/org/apache/fop/render/shading/PDFGradientFactory.java [new file with mode: 0644]
src/java/org/apache/fop/render/shading/PSGradientFactory.java [new file with mode: 0644]
src/java/org/apache/fop/render/shading/Pattern.java [new file with mode: 0644]
src/java/org/apache/fop/render/shading/Shading.java [new file with mode: 0644]
src/java/org/apache/fop/render/shading/ShadingPattern.java [new file with mode: 0644]
src/java/org/apache/fop/svg/PDFGraphics2D.java
test/java/org/apache/fop/render/ps/svg/PSSVGGraphics2DTestCase.java [new file with mode: 0644]
test/java/org/apache/fop/render/ps/svg/PSSVGLinearGraphics2DTestCase.java [new file with mode: 0644]
test/java/org/apache/fop/render/ps/svg/axial-shading-expected.dat [new file with mode: 0644]
test/java/org/apache/fop/render/ps/svg/expected.ps [new file with mode: 0644]

index 97503f208b480ea7b20d70a46f35b9a86652db4d..f25239bc02ef66bd268bd3bd0ba26970f2897fb2 100644 (file)
--- a/build.xml
+++ b/build.xml
@@ -579,6 +579,7 @@ list of possible build targets.
       <include name="org/apache/fop/util/Finalizable.class"/>
       <include name="org/apache/fop/util/CharUtilities.class"/>
       <include name="org/apache/fop/util/DecimalFormatCache*.class"/>
+      <include name="org/apache/fop/render/shading/**"/>
     </patternset>
 <!-- PDF transcoder -->
     <patternset>
index 22b0f5d52d418ceb006a6989d9856b9019d61596..fad3129404ec807e56c93b6f8a73882026299e5e 100644 (file)
Binary files a/lib/xmlgraphics-commons-svn-trunk.jar and b/lib/xmlgraphics-commons-svn-trunk.jar differ
index 5af33866e4e9f2cce628aa1656e873a7a77fcf05..07063027443abf30350c33ec242679f358f57d30 100644 (file)
@@ -310,12 +310,7 @@ public class PDFFactory {
                                                theFunctionDataStream,
                                                theFilter);
 
-        PDFFunction oldfunc = getDocument().findFunction(function);
-        if (oldfunc == null) {
-            getDocument().registerObject(function);
-        } else {
-            function = oldfunc;
-        }
+        function = registerFunction(function);
         return (function);
     }
 
@@ -352,12 +347,7 @@ public class PDFFactory {
         PDFFunction function = new PDFFunction(theFunctionType, theDomain,
                                                theRange, theCZero, theCOne,
                                                theInterpolationExponentN);
-        PDFFunction oldfunc = getDocument().findFunction(function);
-        if (oldfunc == null) {
-            getDocument().registerObject(function);
-        } else {
-            function = oldfunc;
-        }
+        function = registerFunction(function);
         return (function);
     }
 
@@ -407,12 +397,7 @@ public class PDFFactory {
                                                theRange, theFunctions,
                                                theBounds, theEncode);
 
-        PDFFunction oldfunc = getDocument().findFunction(function);
-        if (oldfunc == null) {
-            getDocument().registerObject(function);
-        } else {
-            function = oldfunc;
-        }
+        function = registerFunction(function);
         return (function);
     }
 
@@ -434,14 +419,23 @@ public class PDFFactory {
                                                theRange,
                                                theFunctionDataStream);
 
+        function = registerFunction(function);
+        return (function);
+
+    }
+
+    /**
+     * Registers a function against the document
+     * @param function The function to register
+     */
+    public PDFFunction registerFunction(PDFFunction function) {
         PDFFunction oldfunc = getDocument().findFunction(function);
         if (oldfunc == null) {
             getDocument().registerObject(function);
         } else {
             function = oldfunc;
         }
-        return (function);
-
+        return function;
     }
 
     /* ========================= shadings ================================== */
@@ -481,20 +475,7 @@ public class PDFFactory {
                                             theBBox, theAntiAlias, theDomain,
                                             theMatrix, theFunction);
 
-        PDFShading oldshad = getDocument().findShading(shading);
-        if (oldshad == null) {
-            getDocument().registerObject(shading);
-        } else {
-            shading = oldshad;
-        }
-
-        // add this shading to resources
-        if (res != null) {
-            res.getPDFResources().addShading(shading);
-        } else {
-            getDocument().getResources().addShading(shading);
-        }
-
+        shading = registerShading(res, shading);
         return (shading);
     }
 
@@ -534,18 +515,7 @@ public class PDFFactory {
                                             theDomain, theFunction,
                                             theExtend);
 
-        PDFShading oldshad = getDocument().findShading(shading);
-        if (oldshad == null) {
-            getDocument().registerObject(shading);
-        } else {
-            shading = oldshad;
-        }
-
-        if (res != null) {
-            res.getPDFResources().addShading(shading);
-        } else {
-            getDocument().getResources().addShading(shading);
-        }
+        shading = registerShading(res, shading);
 
         return (shading);
     }
@@ -591,18 +561,7 @@ public class PDFFactory {
                                             theBitsPerFlag, theDecode,
                                             theFunction);
 
-        PDFShading oldshad = getDocument().findShading(shading);
-        if (oldshad == null) {
-            getDocument().registerObject(shading);
-        } else {
-            shading = oldshad;
-        }
-
-        if (res != null) {
-            res.getPDFResources().addShading(shading);
-        } else {
-            getDocument().getResources().addShading(shading);
-        }
+        shading = registerShading(res, shading);
 
         return (shading);
     }
@@ -645,6 +604,17 @@ public class PDFFactory {
                                             theBitsPerComponent, theDecode,
                                             theVerticesPerRow, theFunction);
 
+        shading = registerShading(res, shading);
+
+        return (shading);
+    }
+
+    /**
+     * Registers a shading object against the document
+     * @param res The PDF resource context
+     * @param shading The shading object to be registered
+     */
+    public PDFShading registerShading(PDFResourceContext res, PDFShading shading) {
         PDFShading oldshad = getDocument().findShading(shading);
         if (oldshad == null) {
             getDocument().registerObject(shading);
@@ -652,13 +622,13 @@ public class PDFFactory {
             shading = oldshad;
         }
 
+        // add this shading to resources
         if (res != null) {
             res.getPDFResources().addShading(shading);
         } else {
             getDocument().getResources().addShading(shading);
         }
-
-        return (shading);
+        return shading;
     }
 
     /* ========================= patterns ================================== */
@@ -707,6 +677,22 @@ public class PDFFactory {
         return (pattern);
     }
 
+    public PDFPattern registerPattern(PDFResourceContext res, PDFPattern pattern) {
+        PDFPattern oldpatt = getDocument().findPattern(pattern);
+        if (oldpatt == null) {
+            getDocument().registerObject(pattern);
+        } else {
+            pattern = oldpatt;
+        }
+
+        if (res != null) {
+            res.getPDFResources().addPattern(pattern);
+        } else {
+            getDocument().getResources().addPattern(pattern);
+        }
+        return pattern;
+    }
+
     /**
      * Make a smooth shading pattern
      *
index f424569b95224ebae3b3dd8d7e23e52ab500059c..09cbd97085c03ac5db938088e0c6f226e73f3f35 100644 (file)
@@ -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.FunctionDelegate;
+import org.apache.fop.render.shading.FunctionPattern;
+
 /**
  * class representing a PDF Function.
  *
@@ -33,126 +37,9 @@ import java.util.List;
  *
  * All PDF Functions have a FunctionType (0,2,3, or 4), a Domain, and a Range.
  */
-public class PDFFunction extends PDFObject {
-    // Guts common to all function types
-
-    /**
-     * 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;
+public class PDFFunction extends PDFObject implements Function {
 
-    /**
-     * 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;
-    // See encode above, as it's also part of Type 3 Functions.
-
-    /* *************************TYPE 4************************** */
-    // See 'data' above.
+    private FunctionDelegate delegate;
 
     /**
      * create an complete Function object of Type 0, A Sampled function.
@@ -211,26 +98,13 @@ public class PDFFunction extends PDFObject {
      * @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 PDFFunction(int theFunctionType, List theDomain,
-            List theRange, List theSize, int theBitsPerSample,
-            int theOrder, List theEncode, List theDecode,
-            StringBuffer theFunctionDataStream, List theFilter) {
-        super();
-
-        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;
+    public PDFFunction(int theFunctionType, List<Double> theDomain,
+                       List<Double> theRange, List<Double> theSize, int theBitsPerSample,
+                       int theOrder, List<Double> theEncode, List<Double> theDecode,
+                       StringBuffer theFunctionDataStream, List<String> theFilter) {
+        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<Double> theDomain,
+                       List<Double> theRange, List<Double> theCZero, List<Double> 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<Double> theDomain,
+                       List<Double> theRange, List<Function> theFunctions,
+                       List<Double> theBounds, List<Double> 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<Double> theDomain,
+                       List<Double> 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<Double> getBounds() {
+        return delegate.getBounds();
+    }
+
+    public List<Double> getDomain() {
+        return delegate.getDomain();
+    }
+
+    public List<Double> getSize() {
+        return delegate.getSize();
+    }
+
+    public List<String> getFilter() {
+        return delegate.getFilter();
+    }
+
+    public List<Double> getEncode() {
+        return delegate.getEncode();
+    }
+
+    public List<Function> getFunctions() {
+        return delegate.getFunctions();
+    }
+
+    public int getBitsPerSample() {
+        return delegate.getBitsPerSample();
+    }
+
+    public double getInterpolationExponentN() {
+        return delegate.getInterpolationExponentN();
+    }
+
+    public int getOrder() {
+        return delegate.getOrder();
+    }
+
+    public List<Double> getRange() {
+        return delegate.getRange();
+    }
+
+    public List<Double> getDecode() {
+        return delegate.getDecode();
+    }
+
+    public StringBuffer getDataStream() {
+        return delegate.getDataStream();
+    }
+
+    public List<Double> getCZero() {
+        return delegate.getCZero();
+    }
+
+    public List<Double> getCOne() {
+        return delegate.getCOne();
+    }
 }
index b9e042815c1c7df519ec072bec98735be3c4ec60..df4b0233d96f327aca84bf4423bb702545335b05 100644 (file)
@@ -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");
             }
index 62012b9b29587f6841d80d1a42a46336f4a67517..3f7b2b4b0a5413b4ac264e4a7723acc6d38829f2 100644 (file)
@@ -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;
+    }
 }
index 3ade3452212abadbeee2b517c1bbcc4a8e6a5dc9..cadc282679f6875c51bda385e6e37d2a12ecd9ae 100644 (file)
 
 package org.apache.fop.render.ps;
 
+import java.awt.Color;
+import java.awt.Dimension;
 import java.awt.Rectangle;
 import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.imageio.ImageIO;
 
 import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
 
 import org.apache.batik.bridge.BridgeContext;
 import org.apache.batik.bridge.GVTBuilder;
 import org.apache.batik.gvt.GraphicsNode;
+import org.apache.batik.transcoder.SVGAbstractTranscoder;
+import org.apache.batik.transcoder.TranscoderException;
+import org.apache.batik.transcoder.TranscoderInput;
+import org.apache.batik.transcoder.TranscoderOutput;
+import org.apache.batik.transcoder.image.PNGTranscoder;
 
 import org.apache.xmlgraphics.image.loader.Image;
 import org.apache.xmlgraphics.image.loader.ImageFlavor;
 import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM;
-import org.apache.xmlgraphics.java2d.ps.PSGraphics2D;
+import org.apache.xmlgraphics.ps.ImageEncoder;
+import org.apache.xmlgraphics.ps.ImageEncodingHelper;
 import org.apache.xmlgraphics.ps.PSGenerator;
 
 import org.apache.fop.image.loader.batik.BatikImageFlavors;
 import org.apache.fop.image.loader.batik.BatikUtil;
 import org.apache.fop.render.ImageHandler;
 import org.apache.fop.render.RenderingContext;
+import org.apache.fop.render.ps.svg.PSSVGGraphics2D;
 import org.apache.fop.svg.SVGEventProducer;
 import org.apache.fop.svg.SVGUserAgent;
 
@@ -47,6 +71,9 @@ import org.apache.fop.svg.SVGUserAgent;
  */
 public class PSImageHandlerSVG implements ImageHandler {
 
+    private static final Color FALLBACK_COLOR = new Color(255, 33, 117);
+    private HashMap<String, String> gradientsFound = new HashMap<String, String>();
+
     private static final ImageFlavor[] FLAVORS = new ImageFlavor[] {
         BatikImageFlavors.SVG_DOM
     };
@@ -58,78 +85,262 @@ public class PSImageHandlerSVG implements ImageHandler {
         PSGenerator gen = psContext.getGenerator();
         ImageXMLDOM imageSVG = (ImageXMLDOM)image;
 
-        //Controls whether text painted by Batik is generated using text or path operations
-        boolean strokeText = false;
-        //TODO Configure text stroking
+        if (shouldRaster(imageSVG)) {
+            InputStream is = renderSVGToInputStream(context, imageSVG);
+
+            float x = (float) pos.getX() / 1000f;
+            float y = (float) pos.getY() / 1000f;
+            float w = (float) pos.getWidth() / 1000f;
+            float h = (float) pos.getHeight() / 1000f;
+            Rectangle2D targetRect = new Rectangle2D.Double(x, y, w, h);
+
+            MaskedImage mi = convertToRGB(ImageIO.read(is));
+            BufferedImage ri = mi.getImage();
+            ImageEncoder encoder = ImageEncodingHelper.createRenderedImageEncoder(ri);
+            Dimension imgDim = new Dimension(ri.getWidth(), ri.getHeight());
+            String imgDescription = ri.getClass().getName();
+            ImageEncodingHelper helper = new ImageEncodingHelper(ri);
+            ColorModel cm = helper.getEncodedColorModel();
+            PSImageUtils.writeImage(encoder, imgDim, imgDescription, targetRect, cm, gen, ri, mi.getMaskColor());
+        } else {
+            //Controls whether text painted by Batik is generated using text or path operations
+            boolean strokeText = false;
+            //TODO Configure text stroking
+
+            SVGUserAgent ua
+                 = new SVGUserAgent(context.getUserAgent(), new AffineTransform());
+
+            PSSVGGraphics2D graphics = new PSSVGGraphics2D(strokeText, gen);
+            graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext());
+
+            BridgeContext ctx = new PSBridgeContext(ua,
+                    (strokeText ? null : psContext.getFontInfo()),
+                    context.getUserAgent().getImageManager(),
+                    context.getUserAgent().getImageSessionContext());
+
+            //Cloning SVG DOM as Batik attaches non-thread-safe facilities (like the CSS engine)
+            //to it.
+            Document clonedDoc = BatikUtil.cloneSVGDocument(imageSVG.getDocument());
+
+            GraphicsNode root;
+            try {
+                GVTBuilder builder = new GVTBuilder();
+                root = builder.build(ctx, clonedDoc);
+            } catch (Exception e) {
+                SVGEventProducer eventProducer = SVGEventProducer.Provider.get(
+                        context.getUserAgent().getEventBroadcaster());
+                eventProducer.svgNotBuilt(this, e, image.getInfo().getOriginalURI());
+                return;
+            }
+            // get the 'width' and 'height' attributes of the SVG document
+            float w = (float)ctx.getDocumentSize().getWidth() * 1000f;
+            float h = (float)ctx.getDocumentSize().getHeight() * 1000f;
 
-        SVGUserAgent ua
-             = new SVGUserAgent(context.getUserAgent(), new AffineTransform());
+            float sx = pos.width / w;
+            float sy = pos.height / h;
 
-        PSGraphics2D graphics = new PSGraphics2D(strokeText, gen);
-        graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext());
+            ctx = null;
 
-        BridgeContext ctx = new PSBridgeContext(ua,
-                (strokeText ? null : psContext.getFontInfo()),
-                context.getUserAgent().getImageManager(),
-                context.getUserAgent().getImageSessionContext());
+            gen.commentln("%FOPBeginSVG");
+            gen.saveGraphicsState();
+            final boolean clip = false;
+            if (clip) {
+                /*
+                 * Clip to the svg area.
+                 * Note: To have the svg overlay (under) a text area then use
+                 * an fo:block-container
+                 */
+                gen.writeln("newpath");
+                gen.defineRect(pos.getMinX() / 1000f, pos.getMinY() / 1000f,
+                        pos.width / 1000f, pos.height / 1000f);
+                gen.writeln("clip");
+            }
 
-        //Cloning SVG DOM as Batik attaches non-thread-safe facilities (like the CSS engine)
-        //to it.
-        Document clonedDoc = BatikUtil.cloneSVGDocument(imageSVG.getDocument());
+            // transform so that the coordinates (0,0) is from the top left
+            // and positive is down and to the right. (0,0) is where the
+            // viewBox puts it.
+            gen.concatMatrix(sx, 0, 0, sy, pos.getMinX() / 1000f, pos.getMinY() / 1000f);
 
-        GraphicsNode root;
+            AffineTransform transform = new AffineTransform();
+            // scale to viewbox
+            transform.translate(pos.getMinX(), pos.getMinY());
+            gen.getCurrentState().concatMatrix(transform);
+            try {
+                root.paint(graphics);
+            } catch (Exception e) {
+                SVGEventProducer eventProducer = SVGEventProducer.Provider.get(
+                        context.getUserAgent().getEventBroadcaster());
+                eventProducer.svgRenderingError(this, e, image.getInfo().getOriginalURI());
+            }
+
+            gen.restoreGraphicsState();
+            gen.commentln("%FOPEndSVG");
+        }
+    }
+
+    private InputStream renderSVGToInputStream(RenderingContext context, ImageXMLDOM imageSVG) throws IOException {
+        PNGTranscoder png = new PNGTranscoder();
+        Float width = getDimension(imageSVG.getDocument(), "width") * 8;
+        png.addTranscodingHint(SVGAbstractTranscoder.KEY_WIDTH, width);
+        Float height = getDimension(imageSVG.getDocument(), "height") * 8;
+        png.addTranscodingHint(SVGAbstractTranscoder.KEY_HEIGHT, height);
+        TranscoderInput input = new TranscoderInput(imageSVG.getDocument());
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        TranscoderOutput output = new TranscoderOutput(os);
         try {
-            GVTBuilder builder = new GVTBuilder();
-            root = builder.build(ctx, clonedDoc);
-        } catch (Exception e) {
+            png.transcode(input, output);
+        } catch (TranscoderException ex) {
             SVGEventProducer eventProducer = SVGEventProducer.Provider.get(
                     context.getUserAgent().getEventBroadcaster());
-            eventProducer.svgNotBuilt(this, e, image.getInfo().getOriginalURI());
-            return;
+            eventProducer.svgRenderingError(this, ex, imageSVG.getInfo().getOriginalURI());
+        } finally {
+            os.flush();
+            os.close();
+        }
+        return new ByteArrayInputStream(os.toByteArray());
+    }
+
+    private MaskedImage convertToRGB(BufferedImage alphaImage) {
+        int[] red = new int[256];
+        int[] green = new int[256];
+        int[] blue = new int[256];
+        BufferedImage rgbImage = new BufferedImage(alphaImage.getWidth(),
+                alphaImage.getHeight(), BufferedImage.TYPE_INT_RGB);
+        //Count occurances of each colour in image
+        for (int cx = 0; cx < alphaImage.getWidth(); cx++) {
+            for (int cy = 0; cy < alphaImage.getHeight(); cy++) {
+                int pixelValue = alphaImage.getRGB(cx, cy);
+                Color pixelColor = new Color(pixelValue);
+                red[pixelColor.getRed()]++;
+                green[pixelColor.getGreen()]++;
+                blue[pixelColor.getBlue()]++;
+            }
+        }
+        //Find colour not in image
+        Color alphaSwap = null;
+        for (int i = 0; i < 256; i++) {
+            if (red[i] == 0) {
+                alphaSwap = new Color(i, 0, 0);
+                break;
+            } else if (green[i] == 0) {
+                alphaSwap = new Color(0, i, 0);
+                break;
+            } else if (blue[i] == 0) {
+                alphaSwap = new Color(0, 0, i);
+                break;
+            }
         }
-        // get the 'width' and 'height' attributes of the SVG document
-        float w = (float)ctx.getDocumentSize().getWidth() * 1000f;
-        float h = (float)ctx.getDocumentSize().getHeight() * 1000f;
-
-        float sx = pos.width / w;
-        float sy = pos.height / h;
-
-        ctx = null;
-
-        gen.commentln("%FOPBeginSVG");
-        gen.saveGraphicsState();
-        final boolean clip = false;
-        if (clip) {
-            /*
-             * Clip to the svg area.
-             * Note: To have the svg overlay (under) a text area then use
-             * an fo:block-container
-             */
-            gen.writeln("newpath");
-            gen.defineRect(pos.getMinX() / 1000f, pos.getMinY() / 1000f,
-                    pos.width / 1000f, pos.height / 1000f);
-            gen.writeln("clip");
+        //Check if all variations are used in all three colours
+        if (alphaSwap == null) {
+            //Fallback colour is no unique colour channel can be found
+            alphaSwap = FALLBACK_COLOR;
         }
+        //Replace alpha channel with the new mask colour
+        for (int cx = 0; cx < alphaImage.getWidth(); cx++) {
+            for (int cy = 0; cy < alphaImage.getHeight(); cy++) {
+                int pixelValue = alphaImage.getRGB(cx, cy);
+                if (pixelValue == 0) {
+                    rgbImage.setRGB(cx, cy, alphaSwap.getRGB());
+                } else {
+                    rgbImage.setRGB(cx, cy, alphaImage.getRGB(cx, cy));
+                }
+            }
+        }
+        return new MaskedImage(rgbImage, alphaSwap);
+    }
+
+    private static class MaskedImage {
+        private Color maskColor = new Color(0, 0, 0);
+        private BufferedImage image;
+
+        public MaskedImage(BufferedImage image, Color maskColor) {
+            this.image = image;
+            this.maskColor = maskColor;
+        }
+
+        public Color getMaskColor() {
+            return maskColor;
+        }
+
+        public BufferedImage getImage() {
+            return image;
+        }
+    }
 
-        // transform so that the coordinates (0,0) is from the top left
-        // and positive is down and to the right. (0,0) is where the
-        // viewBox puts it.
-        gen.concatMatrix(sx, 0, 0, sy, pos.getMinX() / 1000f, pos.getMinY() / 1000f);
+    private Float getDimension(Document document, String dimension) {
+        if (document.getFirstChild().getAttributes().getNamedItem(dimension) != null) {
+            String width = document.getFirstChild().getAttributes().getNamedItem(dimension).getNodeValue();
+            width = width.replaceAll("[^\\d.]", "");
+            return Float.parseFloat(width);
+        }
+        return null;
+    }
 
-        AffineTransform transform = new AffineTransform();
-        // scale to viewbox
-        transform.translate(pos.getMinX(), pos.getMinY());
-        gen.getCurrentState().concatMatrix(transform);
+    private boolean shouldRaster(ImageXMLDOM image) {
+        //A list of objects on which to check opacity
         try {
-            root.paint(graphics);
-        } catch (Exception e) {
-            SVGEventProducer eventProducer = SVGEventProducer.Provider.get(
-                    context.getUserAgent().getEventBroadcaster());
-            eventProducer.svgRenderingError(this, e, image.getInfo().getOriginalURI());
+        List<String> gradMatches = new ArrayList<String>();
+        gradMatches.add("radialGradient");
+        gradMatches.add("linearGradient");
+        return recurseSVGElements(image.getDocument().getChildNodes(), gradMatches, false);
+        } finally {
+            gradientsFound.clear();
         }
+    }
 
-        gen.restoreGraphicsState();
-        gen.commentln("%FOPEndSVG");
+    private boolean recurseSVGElements(NodeList childNodes, List<String> gradMatches, boolean isMatched) {
+        boolean opacityFound = false;
+        for (int i = 0; i < childNodes.getLength(); i++) {
+            Node curNode = childNodes.item(i);
+            if (isMatched && curNode.getLocalName() != null && curNode.getLocalName().equals("stop")) {
+                if (curNode.getAttributes().getNamedItem("style") != null) {
+                    String[] stylePairs = curNode.getAttributes().getNamedItem("style").getNodeValue()
+                            .split(";");
+                    for (int styleAtt = 0; styleAtt < stylePairs.length; styleAtt++) {
+                        String[] style = stylePairs[styleAtt].split(":");
+                        if (style[0].equalsIgnoreCase("stop-opacity")) {
+                            if (Double.parseDouble(style[1]) < 1) {
+                                return true;
+                            }
+                        }
+                    }
+                }
+                if (curNode.getAttributes().getNamedItem("stop-opacity") != null) {
+                    String opacityValue = curNode.getAttributes().getNamedItem("stop-opacity").getNodeValue();
+                    if (Double.parseDouble(opacityValue) < 1) {
+                        return true;
+                    }
+                }
+            }
+            String nodeName = curNode.getLocalName();
+            //Special case where rasterization needed for radial gradients
+            if (nodeName != null && nodeName.equals("ellipse")) {
+                String found = "";
+                String ellipseFill = curNode.getAttributes().getNamedItem("fill").getNodeValue();
+                Pattern pattern = Pattern.compile("#(.*?)\\)");
+                Matcher matcher = pattern.matcher(ellipseFill);
+                if (matcher.find()) {
+                    found = matcher.group(1);
+                }
+                if (gradientsFound.get(found) != null) {
+                    return true;
+                }
+            }
+            boolean inMatch = false;
+            if (!isMatched) {
+                inMatch = nodeName != null && gradMatches.contains(nodeName);
+                if (inMatch) {
+                    gradientsFound.put(curNode.getAttributes().getNamedItem("id").getNodeValue(), nodeName);
+                }
+            } else {
+                inMatch = true;
+            }
+            opacityFound = recurseSVGElements(curNode.getChildNodes(), gradMatches, inMatch);
+            if (opacityFound) {
+                return true;
+            }
+        }
+        return opacityFound;
     }
 
     /** {@inheritDoc} */
diff --git a/src/java/org/apache/fop/render/ps/svg/PSFunction.java b/src/java/org/apache/fop/render/ps/svg/PSFunction.java
new file mode 100644 (file)
index 0000000..b03e0b5
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.ps.svg;
+
+import java.io.UnsupportedEncodingException;
+import java.util.List;
+
+import org.apache.fop.render.shading.Function;
+import org.apache.fop.render.shading.FunctionDelegate;
+import org.apache.fop.render.shading.FunctionPattern;
+
+public class PSFunction implements Function {
+
+    private FunctionDelegate delegate;
+
+    /**
+     * Creates a Postscript function dictionary
+     * @param theFunctionType The function type (0 = Sampled, 2 = Exponential
+     * Interpolation, 3 = Stitching)
+     * @param theDomain The function domain
+     * @param theRange Range used for clipping
+     * @param theFunctions An array of sub-functions such as determining the
+     * colour values used in a gradient.
+     * @param theBounds Bounds determines where each boundary exists for whatever
+     * the function is mean't. In a gradient case, it would be the point between
+     * colours.
+     * @param theEncode The function encoding
+     */
+    public PSFunction(int theFunctionType, List<Double> theDomain,
+            List<Double> theRange, List<Function> theFunctions,
+            List<Double> theBounds, List<Double> theEncode) {
+        delegate = new FunctionDelegate(this, theFunctionType, theDomain, theRange, theFunctions,
+                theBounds, theEncode);
+    }
+
+    /**
+     * Creates a Postscript function dictionary
+     * @param theFunctionType The function type (0 = Sampled, 2 = Exponential
+     * Interpolation, 3 = Stitching)
+     * @param theDomain The function domain
+     * @param theRange Range used for clipping
+     * @param theCZero In a gradient, this would be the first colour
+     * @param theCOne In a gradient, this would be the second colour
+     * @param theInterpolationExponentN Determines the number of values
+     * the function returns.
+     */
+    public PSFunction(int theFunctionType, List<Double> theDomain,
+            List<Double> theRange, List<Double> theCZero, List<Double> theCOne,
+            double theInterpolationExponentN) {
+        delegate = new FunctionDelegate(this, theFunctionType, theDomain, theRange, theCZero,
+                theCOne, theInterpolationExponentN);
+    }
+
+    /**
+     * Outputs the function to a byte array
+     */
+    public byte[] toByteString() {
+        FunctionPattern pattern = new FunctionPattern(this);
+        try {
+            return pattern.toWriteableString().getBytes("UTF-8");
+        } catch (UnsupportedEncodingException ex) {
+            //This should have been made an enum type to avoid throwing exceptions.
+            return new byte[0];
+        }
+    }
+
+    public int getFunctionType() {
+        return delegate.getFunctionType();
+    }
+
+    public List<Double> getBounds() {
+        return delegate.getBounds();
+    }
+
+    public List<Double> getDomain() {
+        return delegate.getDomain();
+    }
+
+    public List<Double> getSize() {
+        return delegate.getSize();
+    }
+
+    public List<String> getFilter() {
+        return delegate.getFilter();
+    }
+
+    public List<Double> getEncode() {
+        return delegate.getEncode();
+    }
+
+    public List<Function> getFunctions() {
+        return delegate.getFunctions();
+    }
+
+    public int getBitsPerSample() {
+        return delegate.getBitsPerSample();
+    }
+
+    public double getInterpolationExponentN() {
+        return delegate.getInterpolationExponentN();
+    }
+
+    public int getOrder() {
+        return delegate.getOrder();
+    }
+
+    public List<Double> getRange() {
+        return delegate.getRange();
+    }
+
+    public List<Double> getDecode() {
+        return delegate.getDecode();
+    }
+
+    public StringBuffer getDataStream() {
+        return delegate.getDataStream();
+    }
+
+    public List<Double> getCZero() {
+        return delegate.getCZero();
+    }
+
+    public List<Double> getCOne() {
+        return delegate.getCOne();
+    }
+}
diff --git a/src/java/org/apache/fop/render/ps/svg/PSPattern.java b/src/java/org/apache/fop/render/ps/svg/PSPattern.java
new file mode 100644 (file)
index 0000000..46f9766
--- /dev/null
@@ -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 (file)
index 0000000..3b6b549
--- /dev/null
@@ -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<Double> theCoords = new java.util.ArrayList<Double>();
+
+
+        AffineTransform start = applyTransform(lgp.getTransform(), lgp.getStartPoint().getX(), lgp.getStartPoint().getY());
+        AffineTransform end = applyTransform(lgp.getTransform(), lgp.getEndPoint().getX(), lgp.getEndPoint().getY());
+        double startX = start.getTranslateX();
+        double startY = start.getTranslateY();
+        double endX = end.getTranslateX();
+        double endY = end.getTranslateY();
+
+        double width = endX - startX;
+        double height = endY - startY;
+
+        startX = startX + width * fractions[0];
+        endX = endX - width * (1 - fractions[fractions.length - 1]);
+        startY = startY + (height * fractions[0]);
+        endY =  endY - height * (1 - fractions[fractions.length - 1]);
+
+        theCoords.add(startX);
+        theCoords.add(startY);
+        theCoords.add(endX);
+        theCoords.add(endY);
+
+
+        List<Color> someColors = new java.util.ArrayList<Color>();
+        for (int count = 0; count < cols.length; count++) {
+            Color c1 = cols[count];
+            if (c1.getAlpha() != 255) {
+                LOG.warn("Opacity is not currently supported for Postscript output");
+            }
+            someColors.add(c1);
+        }
+        List<Double> theBounds = new java.util.ArrayList<Double>();
+        for (int count = 1; count < fractions.length - 1; count++) {
+            float offset = fractions[count];
+            theBounds.add(Double.valueOf(offset));
+        }
+        PDFDeviceColorSpace colSpace;
+        colSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB);
+
+        PSGradientFactory gradientFactory = (PSGradientFactory)GradientFactory.newInstance(this);
+        PSPattern myPattern = gradientFactory.createGradient(false, colSpace,
+                someColors, theBounds, theCoords, theMatrix);
+
+        gen.write(myPattern.toString());
+
+    }
+
+
+
+    private void handleRadialGradient(RadialGradientPaint rgp, PSGenerator gen) throws IOException {
+        MultipleGradientPaint.CycleMethodEnum cycle = rgp.getCycleMethod();
+        if (cycle != MultipleGradientPaint.NO_CYCLE) {
+            return;
+        }
+
+        AffineTransform transform;
+        transform = new AffineTransform(getBaseTransform());
+        transform.concatenate(getTransform());
+        transform.concatenate(rgp.getTransform());
+
+        AffineTransform resultCentre = applyTransform(rgp.getTransform(), rgp.getCenterPoint().getX(), rgp.getCenterPoint().getY());
+        AffineTransform resultFocus = applyTransform(rgp.getTransform(), rgp.getFocusPoint().getX(), rgp.getFocusPoint().getY());
+        double scale = Math.sqrt(rgp.getTransform().getDeterminant());
+        double radius = rgp.getRadius() * scale;
+        double centreX = resultCentre.getTranslateX();
+        double centreY = resultCentre.getTranslateY();
+        double focusX = resultFocus.getTranslateX();
+        double focusY = resultFocus.getTranslateY();
+
+        List<Double> theMatrix = new java.util.ArrayList<Double>();
+        double [] mat = new double[6];
+        transform.getMatrix(mat);
+        for (int idx = 0; idx < mat.length; idx++) {
+            theMatrix.add(Double.valueOf(mat[idx]));
+        }
+
+        List<Double> theCoords = new java.util.ArrayList<Double>();
+        float[] fractions = rgp.getFractions();
+
+        theCoords.add(centreX);
+        theCoords.add(centreY);
+        theCoords.add(radius * rgp.getFractions()[0]);
+        theCoords.add(focusX);
+        theCoords.add(focusY);
+        theCoords.add(radius * fractions[fractions.length - 1]);
+
+        Color[] cols = rgp.getColors();
+        List<Color> someColors = new java.util.ArrayList<Color>();
+        for (int count = 0; count < cols.length; count++) {
+            Color cc = cols[count];
+            if (cc.getAlpha() != 255) {
+                /* This should never happen because radial gradients with opacity should now
+                 * be rasterized in the PSImageHandlerSVG class. Please see the shouldRaster()
+                 * method for more information. */
+                LOG.warn("Opacity is not currently supported for Postscript output");
+            }
+
+            someColors.add(cc);
+        }
+
+        List<Double> theBounds = new java.util.ArrayList<Double>();
+        for (int count = 1; count < fractions.length - 1; count++) {
+            float offset = fractions[count];
+            theBounds.add(Double.valueOf(offset));
+        }
+        PDFDeviceColorSpace colSpace;
+        colSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB);
+
+        PSGradientFactory gradientFactory = (PSGradientFactory)GradientFactory.newInstance(this);
+        PSPattern myPattern = gradientFactory.createGradient(true, colSpace,
+                someColors, theBounds, theCoords, theMatrix);
+
+        gen.write(myPattern.toString());
+    }
+
+    private AffineTransform applyTransform(AffineTransform base, double posX, double posY) {
+        AffineTransform result = AffineTransform.getTranslateInstance(posX, posY);
+        AffineTransform orig = base;
+        orig.concatenate(result);
+        return orig;
+    }
+
+    protected AffineTransform getBaseTransform() {
+        AffineTransform at = new AffineTransform(this.getTransform());
+        return at;
+    }
+
+    /**
+     * Creates a new <code>Graphics</code> object that is
+     * a copy of this <code>Graphics</code> object.
+     * @return     a new graphics context that is a copy of
+     * this graphics context.
+     */
+    @Override
+    public Graphics create() {
+        preparePainting();
+        return new PSSVGGraphics2D(this);
+    }
+
+    /**
+     * Registers a function object against the output format document
+     * @param function The function object to register
+     * @return Returns either the function which has already been registered
+     * or the current new registered object.
+     */
+    public Function registerFunction(Function function) {
+        //Objects aren't needed to be registered in Postscript
+        return function;
+    }
+
+    /**
+     * Registers a shading object against the otuput format document
+     * @param shading The shading object to register
+     * @return Returs either the shading which has already been registered
+     * or the current new registered object
+     */
+    public Shading registerShading(Shading shading) {
+        //Objects aren't needed to be registered in Postscript
+        return shading;
+    }
+
+    /**
+     * Registers a pattern object against the output format document
+     * @param pattern The pattern object to register
+     * @return Returns either the pattern which has already been registered
+     * or the current new registered object
+     */
+    public Pattern registerPattern(Pattern pattern) {
+        // TODO Auto-generated method stub
+        return pattern;
+    }
+}
diff --git a/src/java/org/apache/fop/render/ps/svg/PSShading.java b/src/java/org/apache/fop/render/ps/svg/PSShading.java
new file mode 100644 (file)
index 0000000..7465fca
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.ps.svg;
+
+import java.io.UnsupportedEncodingException;
+import java.util.List;
+
+import org.apache.fop.pdf.PDFDeviceColorSpace;
+import org.apache.fop.pdf.PDFNumber;
+import org.apache.fop.render.shading.Function;
+import org.apache.fop.render.shading.Shading;
+import org.apache.fop.render.shading.ShadingPattern;
+
+public class PSShading implements Shading {
+
+    /**
+     * Required: The Type of shading (1,2,3,4,5,6,7)
+     */
+    protected int shadingType = 3;    // Default
+
+    /**
+     * A ColorSpace representing the colorspace. "DeviceRGB" is an example.
+     */
+    protected PDFDeviceColorSpace colorSpace = null;
+
+    /**
+     * The background color. Since shading is opaque,
+     * this is very rarely used.
+     */
+    protected List background = null;
+
+    /**
+     * Optional: A List specifying the clipping rectangle
+     */
+    protected List bBox = null;
+
+    /**
+     * Optional: A flag whether or not to filter the shading function
+     * to prevent aliasing artifacts. Default is false.
+     */
+    protected boolean antiAlias = false;
+
+    /**
+     * Optional for Type 1: Array of four numbers, xmin, xmax, ymin, ymax.
+     *                      Default is [0 1 0 1]
+     * Optional for Type 2: An array of two numbers between which the blend
+     *                      varies between start and end points. Default is 0, 1.
+     * Optional for Type 3: An array of two numbers between which the blend
+     *                      varies between start and end points. Default is 0, 1.
+     */
+    protected List domain = null;
+
+    /**
+     * Required for Type 1, 2, and 3:
+     * The object of the color mapping function (usually type 2 or 3).
+     * Optional for Type 4,5,6, and 7: When it's nearly the same thing.
+     */
+    protected PSFunction function = null;
+
+    /**
+     * Required for Type 2: An Array of four numbers specifying
+     *                      the starting and ending coordinate pairs
+     * Required for Type 3: An Array of six numbers [x0,y0,r0,x1,y1,r1]
+     *                      specifying the centers and radii of
+     *                      the starting and ending circles.
+     */
+    protected List coords = null;
+
+    /**
+     * Required for Type 2+3: An Array of two boolean values specifying
+     * whether to extend the start and end colors past the start
+     * and end points, respectively.
+     * Default is false, false.
+     */
+    protected List extend = null;
+
+    /**
+     * Constructor for Type 2 and 3
+     *
+     * @param theShadingType 2 or 3 for axial or radial shading
+     * @param theColorSpace "DeviceRGB" or similar.
+     * @param theBackground theBackground An array of color components appropriate to the
+     * colorspace key specifying a single color value.
+     * This key is used by the f operator buy ignored by the sh operator.
+     * @param theBBox List of double's representing a rectangle
+     * in the coordinate space that is current at the
+     * time of shading is imaged. Temporary clipping
+     * boundary.
+     * @param theAntiAlias Default is false
+     * @param theCoords List of four (type 2) or 6 (type 3) Double
+     * @param theDomain List of Doubles specifying the domain
+     * @param theFunction the Stitching (PDFfunction type 3) function,
+     *                    even if it's stitching a single function
+     * @param theExtend List of Booleans of whether to extend the start
+     *                  and end colors past the start and end points
+     * The default is [false, false]
+     */
+    public PSShading(int theShadingType, PDFDeviceColorSpace theColorSpace,
+                      List<Double> theBackground, List<Double> theBBox,
+                      boolean theAntiAlias, List<Double> theCoords,
+                      List<Double> theDomain, Function theFunction,
+                      List<Integer> theExtend) {
+        this.shadingType = theShadingType;    // 2 or 3
+        this.colorSpace = theColorSpace;
+        this.background = theBackground;
+        this.bBox = theBBox;
+        this.antiAlias = theAntiAlias;
+
+        this.coords = theCoords;
+        this.domain = theDomain;
+        assert theFunction instanceof PSFunction;
+        this.function = (PSFunction)theFunction;
+        this.extend = theExtend;
+    }
+
+    /**
+     * represent as PS. Whatever the shadingType is, the correct
+     * representation spits out. The sets of required and optional
+     * attributes are different for each type, but if a required
+     * attribute's object was constructed as null, then no error
+     * is raised. Instead, the malformed PS that was requested
+     * by the construction is dutifully output.
+     * This policy should be reviewed.
+     *
+     * @return the PDF string.
+     */
+    public String toString() {
+        ShadingPattern pattern = new ShadingPattern(this);
+        return pattern.toString(colorSpace, shadingType, background, bBox, antiAlias);
+    }
+
+    /**
+     * A method to write a type 2 or 3 shading object
+     * @param p The StringBuffer to write the shading object
+     * @return Returns the StringBuffer to which the shading object was written
+     */
+    public StringBuffer handleShadingType2or3(StringBuffer p) {
+        if (this.coords != null) {
+            p.append("\t/Coords [ ");
+            for (int coordIndex = 0; coordIndex < coords.size(); coordIndex++) {
+                p.append(PDFNumber.doubleOut((Double)this.coords.get(coordIndex))
+                         + " ");
+            }
+            p.append("] \n");
+        }
+
+        // DOMAIN
+        if (this.domain != null) {
+            p.append("\t/Domain [ ");
+            for (int domainIndex = 0; domainIndex < domain.size(); domainIndex++) {
+                p.append(PDFNumber.doubleOut((Double)this.domain.get(domainIndex))
+                         + " ");
+            }
+            p.append("] \n");
+        } else {
+            p.append("\t/Domain [ 0 1 ] \n");
+        }
+
+        if (this.extend != null) {
+            p.append("\t/Extend [ ");
+            for (int extendIndex = 0; extendIndex < extend.size(); extendIndex++) {
+                p.append((this.extend.get(extendIndex)) + " ");
+            }
+
+            p.append("] \n");
+        } else {
+            p.append("\t/Extend [ true true ] \n");
+        }
+
+
+        if (this.function != null) {
+            p.append("\t/Function ");
+            try {
+                p.append(new String(this.function.toByteString(), "UTF-8") + " \n");
+            } catch (UnsupportedEncodingException ex) {
+                //This should have been made an enum type to avoid throwing exceptions.
+            }
+        }
+        return p;
+    }
+
+    /**
+     * A method to write a type 1 shading object
+     * @param p The StringBuffer to write the shading object
+     * @return Returns the StringBuffer to which the shading object was written
+     */
+    public StringBuffer handleShadingType1(StringBuffer p) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    /**
+     * A method to write a type 4, 6 or 7 shading object
+     * @param p The StringBuffer to write the shading object
+     * @return Returns the StringBuffer to which the shading object was written
+     */
+    public StringBuffer handleShadingType4or6or7(StringBuffer p) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    /**
+     * A method to write a type 5 shading object
+     * @param p The StringBuffer to write the shading object
+     * @return Returns the StringBuffer to which the shading object was written
+     */
+    public StringBuffer handleShadingType5(StringBuffer p) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+}
diff --git a/src/java/org/apache/fop/render/shading/Function.java b/src/java/org/apache/fop/render/shading/Function.java
new file mode 100644 (file)
index 0000000..5bd4408
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.fop.render.shading;
+
+import java.util.List;
+
+public interface Function {
+    int getFunctionType();
+    List<Double> getBounds();
+    List<Double> getDomain();
+    List<Double> getSize();
+    List<String> getFilter();
+    List<Double> getEncode();
+    List<Function> getFunctions();
+    int getBitsPerSample();
+    double getInterpolationExponentN();
+    int getOrder();
+    List<Double> getRange();
+    List<Double> getDecode();
+    StringBuffer getDataStream();
+    List<Double> getCZero();
+    List<Double> getCOne();
+    byte[] toByteString();
+}
diff --git a/src/java/org/apache/fop/render/shading/FunctionDelegate.java b/src/java/org/apache/fop/render/shading/FunctionDelegate.java
new file mode 100644 (file)
index 0000000..eea24e0
--- /dev/null
@@ -0,0 +1,451 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.fop.render.shading;
+
+import java.util.List;
+
+public class FunctionDelegate implements Function {
+
+    private Function parentFunction;
+
+    /**
+     * Required: The Type of function (0,2,3,4) default is 0.
+     */
+    protected int functionType = 0;    // Default
+
+    /**
+     * Required: 2 * m Array of Double numbers which are possible inputs to the function
+     */
+    protected List<Double> domain = null;
+
+    /**
+     * Required: 2 * n Array of Double numbers which are possible outputs to the function
+     */
+    protected List<Double> range = null;
+
+    /* ********************TYPE 0***************************** */
+    // FunctionType 0 specific function guts
+
+    /**
+     * Required: Array containing the Integer size of the Domain and Range, respectively.
+     * Note: This is really more like two seperate integers, sizeDomain, and sizeRange,
+     * but since they're expressed as an array in PDF, my implementation reflects that.
+     */
+    protected List<Double> size = null;
+
+    /**
+     * Required for Type 0: Number of Bits used to represent each sample value.
+     * Limited to 1,2,4,8,12,16,24, or 32
+     */
+    protected int bitsPerSample = 1;
+
+    /**
+     * Optional for Type 0: order of interpolation between samples.
+     * Limited to linear (1) or cubic (3). Default is 1
+     */
+    protected int order = 1;
+
+    /**
+     * Optional for Type 0: A 2 * m array of Doubles which provides a
+     * linear mapping of input values to the domain.
+     *
+     * Required for Type 3: A 2 * k array of Doubles that, taken
+     * in pairs, map each subset of the domain defined by Domain
+     * and the Bounds array to the domain of the corresponding function.
+     * Should be two values per function, usually (0,1),
+     * as in [0 1 0 1] for 2 functions.
+     */
+    protected List<Double> encode = null;
+
+    /**
+     * Optional for Type 0: A 2 * n array of Doubles which provides
+     * a linear mapping of sample values to the range. Defaults to Range.
+     */
+    protected List<Double> decode = null;
+
+    /**
+     * Optional For Type 0: A stream of sample values
+     */
+
+    /**
+     * Required For Type 4: Postscript Calculator function
+     * composed of arithmetic, boolean, and stack operators + boolean constants
+     */
+    protected StringBuffer functionDataStream = null;
+
+    /**
+     * Required (possibly) For Type 0: A vector of Strings for the
+     * various filters to be used to decode the stream.
+     * These are how the string is compressed. Flate, LZW, etc.
+     */
+    protected List<String> filter = null;
+    /* *************************TYPE 2************************** */
+
+    /**
+     * Required For Type 2: An Array of n Doubles defining
+     * the function result when x=0. Default is [0].
+     */
+    protected List<Double> cZero = null;
+
+    /**
+     * Required For Type 2: An Array of n Doubles defining
+     * the function result when x=1. Default is [1].
+     */
+    protected List<Double> cOne = null;
+
+    /**
+     * Required for Type 2: The interpolation exponent.
+     * Each value x will return n results.
+     * Must be greater than 0.
+     */
+    protected double interpolationExponentN = 1;
+
+    /* *************************TYPE 3************************** */
+
+    /**
+     * Required for Type 3: An vector of PDFFunctions which
+     * form an array of k single input functions making up
+     * the stitching function.
+     */
+    protected List<Function> functions = null;
+
+    /**
+     * Optional for Type 3: An array of (k-1) Doubles that,
+     * in combination with Domain, define the intervals to which
+     * each function from the Functions array apply. Bounds
+     * elements must be in order of increasing magnitude,
+     * and each value must be within the value of Domain.
+     * k is the number of functions.
+     * If you pass null, it will output (1/k) in an array of k-1 elements.
+     * This makes each function responsible for an equal amount of the stitching function.
+     * It makes the gradient even.
+     */
+    protected List<Double> bounds = null;
+
+    /**
+     * create an complete Function object of Type 0, A Sampled function.
+     *
+     * Use null for an optional object parameter if you choose not to use it.
+     * For optional int parameters, pass the default.
+     *
+     * @param theDomain List objects of Double objects.
+     * This is the domain of the function.
+     * See page 264 of the PDF 1.3 Spec.
+     * @param theRange List objects of Double objects.
+     * This is the Range of the function.
+     * See page 264 of the PDF 1.3 Spec.
+     * @param theSize A List object of Integer objects.
+     * This is the number of samples in each input dimension.
+     * I can't imagine there being more or less than two input dimensions,
+     * so maybe this should be an array of length 2.
+     *
+     * See page 265 of the PDF 1.3 Spec.
+     * @param theBitsPerSample An int specifying the number of bits
+                               used to represent each sample value.
+     * Limited to 1,2,4,8,12,16,24 or 32.
+     * See page 265 of the 1.3 PDF Spec.
+     * @param theOrder The order of interpolation between samples. Default is 1 (one). Limited
+     * to 1 (one) or 3, which means linear or cubic-spline interpolation.
+     *
+     * This attribute is optional.
+     *
+     * See page 265 in the PDF 1.3 spec.
+     * @param theEncode List objects of Double objects.
+     * This is the linear mapping of input values intop the domain
+     * of the function's sample table. Default is hard to represent in
+     * ascii, but basically [0 (Size0 1) 0 (Size1 1)...].
+     * This attribute is optional.
+     *
+     * See page 265 in the PDF 1.3 spec.
+     * @param theDecode List objects of Double objects.
+     * This is a linear mapping of sample values into the range.
+     * The default is just the range.
+     *
+     * This attribute is optional.
+     * Read about it on page 265 of the PDF 1.3 spec.
+     * @param theFunctionDataStream The sample values that specify
+     *                     the function are provided in a stream.
+     *
+     * This is optional, but is almost always used.
+     *
+     * Page 265 of the PDF 1.3 spec has more.
+     * @param theFilter This is a vector of String objects which are the various filters that
+     * have are to be applied to the stream to make sense of it. Order matters,
+     * so watch out.
+     *
+     * This is not documented in the Function section of the PDF 1.3 spec,
+     * it was deduced from samples that this is sometimes used, even if we may never
+     * use it in FOP. It is added for completeness sake.
+     * @param theFunctionType This is the type of function (0,2,3, or 4).
+     * It should be 0 as this is the constructor for sampled functions.
+     */
+    public FunctionDelegate(Function parentFunction, int theFunctionType, List<Double> theDomain,
+                       List<Double> theRange, List<Double> theSize, int theBitsPerSample,
+                       int theOrder, List<Double> theEncode, List<Double> theDecode,
+                       StringBuffer theFunctionDataStream, List<String> theFilter) {
+        this.parentFunction = parentFunction;
+        this.functionType = 0;      // dang well better be 0;
+        this.size = theSize;
+        this.bitsPerSample = theBitsPerSample;
+        this.order = theOrder;      // int
+        this.encode = theEncode;    // vector of int
+        this.decode = theDecode;    // vector of int
+        this.functionDataStream = theFunctionDataStream;
+        this.filter = theFilter;    // vector of Strings
+
+        // the domain and range are actually two dimensional arrays.
+        // so if there's not an even number of items, bad stuff
+        // happens.
+        this.domain = theDomain;
+        this.range = theRange;
+    }
+
+    /**
+     * create an complete Function object of Type 2, an Exponential Interpolation function.
+     *
+     * Use null for an optional object parameter if you choose not to use it.
+     * For optional int parameters, pass the default.
+     *
+     * @param theDomain List objects of Double objects.
+     * This is the domain of the function.
+     * See page 264 of the PDF 1.3 Spec.
+     * @param theRange List of Doubles that is the Range of the function.
+     * See page 264 of the PDF 1.3 Spec.
+     * @param theCZero This is a vector of Double objects which defines the function result
+     * when x=0.
+     *
+     * This attribute is optional.
+     * It's described on page 268 of the PDF 1.3 spec.
+     * @param theCOne This is a vector of Double objects which defines the function result
+     * when x=1.
+     *
+     * This attribute is optional.
+     * It's described on page 268 of the PDF 1.3 spec.
+     * @param theInterpolationExponentN This is the inerpolation exponent.
+     *
+     * This attribute is required.
+     * PDF Spec page 268
+     * @param theFunctionType The type of the function, which should be 2.
+     */
+    public FunctionDelegate(Function parentFunction, int theFunctionType, List<Double> theDomain,
+                       List<Double> theRange, List<Double> theCZero, List<Double> theCOne,
+                       double theInterpolationExponentN) {
+        this.parentFunction = parentFunction;
+        this.functionType = 2;    // dang well better be 2;
+
+        this.cZero = theCZero;
+        this.cOne = theCOne;
+        this.interpolationExponentN = theInterpolationExponentN;
+
+        this.domain = theDomain;
+        this.range = theRange;
+
+    }
+
+    /**
+     * create an complete Function object of Type 3, a Stitching function.
+     *
+     * Use null for an optional object parameter if you choose not to use it.
+     * For optional int parameters, pass the default.
+     *
+     * @param theDomain List objects of Double objects.
+     * This is the domain of the function.
+     * See page 264 of the PDF 1.3 Spec.
+     * @param theRange List objects of Double objects.
+     * This is the Range of the function.
+     * See page 264 of the PDF 1.3 Spec.
+     * @param theFunctions A List of the PDFFunction objects that the stitching function stitches.
+     *
+     * This attributed is required.
+     * It is described on page 269 of the PDF spec.
+     * @param theBounds This is a vector of Doubles representing the numbers that,
+     * in conjunction with Domain define the intervals to which each function from
+     * the 'functions' object applies. It must be in order of increasing magnitude,
+     * and each must be within Domain.
+     *
+     * It basically sets how much of the gradient each function handles.
+     *
+     * This attributed is required.
+     * It's described on page 269 of the PDF 1.3 spec.
+     * @param theEncode List objects of Double objects.
+     * This is the linear mapping of input values intop the domain
+     * of the function's sample table. Default is hard to represent in
+     * ascii, but basically [0 (Size0 1) 0 (Size1 1)...].
+     * This attribute is required.
+     *
+     * See page 270 in the PDF 1.3 spec.
+     * @param theFunctionType This is the function type. It should be 3,
+     * for a stitching function.
+     */
+    public FunctionDelegate(Function parentFunction, int theFunctionType, List<Double> theDomain,
+                       List<Double> theRange, List<Function> theFunctions,
+                       List<Double> theBounds, List<Double> theEncode) {
+        this.parentFunction = parentFunction;
+        this.functionType = 3;    // dang well better be 3;
+
+        this.functions = theFunctions;
+        this.bounds = theBounds;
+        this.encode = theEncode;
+        this.domain = theDomain;
+        this.range = theRange;
+
+    }
+
+    /**
+     * create an complete Function object of Type 4, a postscript calculator function.
+     *
+     * Use null for an optional object parameter if you choose not to use it.
+     * For optional int parameters, pass the default.
+     *
+     * @param theDomain List object of Double objects.
+     * This is the domain of the function.
+     * See page 264 of the PDF 1.3 Spec.
+     * @param theRange List object of Double objects.
+     * This is the Range of the function.
+     * See page 264 of the PDF 1.3 Spec.
+     * @param theFunctionDataStream This is a stream of arithmetic,
+     *            boolean, and stack operators and boolean constants.
+     * I end up enclosing it in the '{' and '}' braces for you, so don't do it
+     * yourself.
+     *
+     * This attribute is required.
+     * It's described on page 269 of the PDF 1.3 spec.
+     * @param theFunctionType The type of function which should be 4, as this is
+     * a Postscript calculator function
+     */
+    public FunctionDelegate(Function parentFunction, int theFunctionType, List<Double> theDomain,
+                       List<Double> theRange, StringBuffer theFunctionDataStream) {
+        this.parentFunction = parentFunction;
+        this.functionType = 4;    // dang well better be 4;
+        this.functionDataStream = theFunctionDataStream;
+
+        this.domain = theDomain;
+
+        this.range = theRange;
+
+    }
+
+    /**
+     * Gets the function type
+     */
+    public int getFunctionType() {
+        return functionType;
+    }
+
+    /**
+     * Gets the function bounds
+     */
+    public List<Double> getBounds() {
+        return bounds;
+    }
+
+    /**
+     * The function domain
+     */
+    public List<Double> getDomain() {
+        return domain;
+    }
+
+    /**
+     * The function size
+     */
+    public List<Double> getSize() {
+        return size;
+    }
+
+    /**
+     * Gets the function encoding
+     */
+    public List<Double> getEncode() {
+        return encode;
+    }
+
+    /**
+     * Gets the sub-functions
+     */
+    public List<Function> getFunctions() {
+        return functions;
+    }
+
+    /**
+     * Gets the function filter
+     */
+    public List<String> getFilter() {
+        return filter;
+    }
+
+    /**
+     * Gets the bits per sample of the function
+     */
+    public int getBitsPerSample() {
+        return bitsPerSample;
+    }
+
+    /**
+     * Gets the interpolation exponent of the function
+     */
+    public double getInterpolationExponentN() {
+        return interpolationExponentN;
+    }
+
+    /**
+     * Gets the function order
+     */
+    public int getOrder() {
+        return order;
+    }
+
+    /**
+     * Gets the function range
+     */
+    public List<Double> getRange() {
+        return range;
+    }
+
+    /**
+     * Gets the function decoding
+     */
+    public List<Double> getDecode() {
+        return decode;
+    }
+
+    /**
+     * Gets the function data stream
+     */
+    public StringBuffer getDataStream() {
+        return functionDataStream;
+    }
+
+    /**
+     * Gets the function C0 value (color for gradient)
+     */
+    public List<Double> getCZero() {
+        return cZero;
+    }
+
+    /**
+     * Gets the function C1 value (color for gradient)
+     */
+    public List<Double> getCOne() {
+        return cOne;
+    }
+
+    public byte[] toByteString() {
+        return parentFunction.toByteString();
+    }
+}
diff --git a/src/java/org/apache/fop/render/shading/FunctionPattern.java b/src/java/org/apache/fop/render/shading/FunctionPattern.java
new file mode 100644 (file)
index 0000000..044053a
--- /dev/null
@@ -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 (file)
index 0000000..87ac11c
--- /dev/null
@@ -0,0 +1,162 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.shading;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.xmlgraphics.java2d.color.ColorUtil;
+
+import org.apache.fop.pdf.PDFDeviceColorSpace;
+import org.apache.fop.render.ps.svg.PSSVGGraphics2D;
+
+public abstract class GradientFactory {
+
+    static GradientRegistrar registrar;
+
+    /**
+     * Constructor
+     * @param registrar The object used to register new embedded objects in the
+     * output format.
+     */
+    public static GradientFactory newInstance(GradientRegistrar theRegistrar) {
+        registrar = theRegistrar;
+        if (registrar instanceof PSSVGGraphics2D) {
+            return new PSGradientFactory();
+        } else {
+            return new PDFGradientFactory();
+        }
+    }
+
+    /**
+     * Creates a new gradient
+     * @param radial Determines whether the gradient is radial
+     * @param theColorspace The colorspace used in PDF and Postscript
+     * @param theColors The colors to be used in the gradient
+     * @param theBounds The bounds of each color
+     * @param theCoords The co-ordinates of the gradient
+     * @param theMatrix The matrix for any transformations
+     * @return Returns the Pattern object of the gradient
+     */
+    public abstract Pattern createGradient(boolean radial,
+            PDFDeviceColorSpace theColorspace, List<Color> theColors, List<Double> theBounds,
+            List<Double> theCoords, List<Double> theMatrix);
+
+    protected Pattern makeGradient(boolean radial, PDFDeviceColorSpace theColorspace,
+                                   List<Color> theColors, List<Double> theBounds,
+                                   List<Double> theCoords, List<Double> theMatrix) {
+        Shading myShad;
+        Function myfunky;
+        Function myfunc;
+        List<Double> theCzero;
+        List<Double> theCone;
+        double interpolation = 1.000;
+        List<Function> theFunctions = new ArrayList<Function>();
+
+        int currentPosition;
+        int lastPosition = theColors.size() - 1;
+
+
+        // if 5 elements, the penultimate element is 3.
+        // do not go beyond that, because you always need
+        // to have a next color when creating the function.
+
+        for (currentPosition = 0; currentPosition < lastPosition;
+                currentPosition++) {    // for every consecutive color pair
+            Color currentColor = theColors.get(currentPosition);
+            Color nextColor = theColors.get(currentPosition + 1);
+
+            // colorspace must be consistent, so we simply convert to sRGB where necessary
+            if (!currentColor.getColorSpace().isCS_sRGB()) {
+                //Convert to sRGB
+                currentColor = ColorUtil.toSRGBColor(currentColor);
+                theColors.set(currentPosition, currentColor);
+            }
+            if (!nextColor.getColorSpace().isCS_sRGB()) {
+                //Convert to sRGB
+                nextColor = ColorUtil.toSRGBColor(nextColor);
+                theColors.set(currentPosition + 1, nextColor);
+            }
+
+            theCzero = toColorVector(currentColor);
+            theCone = toColorVector(nextColor);
+
+            myfunc = makeFunction(2, null, null, theCzero, theCone,
+                    interpolation);
+
+            theFunctions.add(myfunc);
+
+        }                               // end of for every consecutive color pair
+
+        myfunky = makeFunction(3, null, null, theFunctions, theBounds,
+                null);
+
+        if (radial) {
+            if (theCoords.size() == 6) {
+                // make Shading of Type 2 or 3
+                myShad = makeShading(3, theColorspace, null, null, false, theCoords,
+                                    null, myfunky, null);
+            } else {    // if the center x, center y, and radius specifiy
+                // the gradient, then assume the same center x, center y,
+                // and radius of zero for the other necessary component
+                List<Double> newCoords = new ArrayList<Double>();
+                newCoords.add(theCoords.get(0));
+                newCoords.add(theCoords.get(1));
+                newCoords.add(theCoords.get(2));
+                newCoords.add(theCoords.get(0));
+                newCoords.add(theCoords.get(1));
+                newCoords.add(Double.valueOf(0.0));
+
+                myShad = makeShading(3, theColorspace, null, null, false, newCoords,
+                        null, myfunky, null);
+            }
+        } else {
+            myShad = makeShading(2, theColorspace, null, null, false, theCoords,
+                    null, myfunky, null);
+        }
+        return makePattern(2, myShad, null, null, theMatrix);
+    }
+
+    public abstract Function makeFunction(int functionType, List<Double> theDomain,
+            List<Double> theRange, List<Function> theFunctions,
+            List<Double> theBounds, List<Double> theEncode);
+
+    public abstract Function makeFunction(int functionType, List<Double> theDomain,
+            List<Double> theRange, List<Double> theCZero, List<Double> theCOne,
+            double theInterpolationExponentN);
+
+    public abstract Shading makeShading(int theShadingType,
+            PDFDeviceColorSpace theColorSpace, List<Double> theBackground, List<Double> theBBox,
+            boolean theAntiAlias, List<Double> theCoords, List<Double> theDomain,
+            Function theFunction, List<Integer> theExtend);
+
+    public abstract Pattern makePattern(int thePatternType, Shading theShading, List theXUID,
+            StringBuffer theExtGState, List<Double> theMatrix);
+
+    private List<Double> toColorVector(Color nextColor) {
+        List<Double> vector = new java.util.ArrayList<Double>();
+        float[] comps = nextColor.getColorComponents(null);
+        for (int i = 0, c = comps.length; i < c; i++) {
+            vector.add(Double.valueOf(comps[i]));
+        }
+        return vector;
+    }
+}
diff --git a/src/java/org/apache/fop/render/shading/GradientRegistrar.java b/src/java/org/apache/fop/render/shading/GradientRegistrar.java
new file mode 100644 (file)
index 0000000..617fcd4
--- /dev/null
@@ -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 (file)
index 0000000..3b3dcab
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.fop.render.shading;
+
+import java.awt.Color;
+import java.util.List;
+
+import org.apache.fop.pdf.PDFDeviceColorSpace;
+import org.apache.fop.pdf.PDFFunction;
+import org.apache.fop.pdf.PDFPattern;
+import org.apache.fop.pdf.PDFShading;
+
+public class PDFGradientFactory extends GradientFactory {
+
+    @Override
+    public PDFPattern createGradient(boolean radial, PDFDeviceColorSpace theColorspace, List<Color> theColors,
+            List<Double> theBounds, List<Double> theCoords, List<Double> theMatrix) {
+        return (PDFPattern)makeGradient(radial, theColorspace, theColors, theBounds,
+                theCoords, theMatrix);
+    }
+
+    @Override
+    public Function makeFunction(int functionType, List<Double> theDomain,
+            List<Double> theRange, List<Function> theFunctions,
+            List<Double> theBounds, List<Double> theEncode) {
+        Function newFunction = new PDFFunction(functionType, theDomain, theRange, theFunctions,
+                    theBounds, theEncode);
+        newFunction = registrar.registerFunction(newFunction);
+        return newFunction;
+    }
+
+    public Function makeFunction(int functionType, List<Double> theDomain,
+            List<Double> theRange, List<Double> theCZero, List<Double> theCOne,
+            double theInterpolationExponentN) {
+        Function newFunction = new PDFFunction(functionType, theDomain, theRange, theCZero,
+                    theCOne, theInterpolationExponentN);
+        newFunction = registrar.registerFunction(newFunction);
+        return newFunction;
+    }
+
+    @Override
+    public Shading makeShading(int theShadingType,
+            PDFDeviceColorSpace theColorSpace, List<Double> theBackground, List<Double> theBBox,
+            boolean theAntiAlias, List<Double> theCoords, List<Double> theDomain,
+            Function theFunction, List<Integer> theExtend) {
+        Shading newShading = new PDFShading(theShadingType, theColorSpace, theBackground,
+                    theBBox, theAntiAlias, theCoords, theDomain, theFunction, theExtend);
+        newShading = registrar.registerShading(newShading);
+        return newShading;
+    }
+
+    @Override
+    public Pattern makePattern(int thePatternType, Shading theShading, List theXUID,
+            StringBuffer theExtGState, List<Double> theMatrix) {
+        Pattern newPattern = new PDFPattern(thePatternType, theShading, theXUID, theExtGState,
+                    theMatrix);
+        newPattern = registrar.registerPattern(newPattern);
+        return newPattern;
+    }
+
+}
diff --git a/src/java/org/apache/fop/render/shading/PSGradientFactory.java b/src/java/org/apache/fop/render/shading/PSGradientFactory.java
new file mode 100644 (file)
index 0000000..cd47de9
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.fop.render.shading;
+
+import java.awt.Color;
+import java.util.List;
+
+import org.apache.fop.pdf.PDFDeviceColorSpace;
+import org.apache.fop.render.ps.svg.PSFunction;
+import org.apache.fop.render.ps.svg.PSPattern;
+import org.apache.fop.render.ps.svg.PSShading;
+
+public class PSGradientFactory extends GradientFactory {
+
+    @Override
+    public PSPattern createGradient(boolean radial, PDFDeviceColorSpace theColorspace,
+            List<Color> theColors, List<Double> theBounds, List<Double> theCoords,
+            List<Double> theMatrix) {
+        return (PSPattern)makeGradient(radial, theColorspace, theColors, theBounds,
+                theCoords, theMatrix);
+    }
+
+    public Function makeFunction(int functionType, List<Double> theDomain,
+            List<Double> theRange, List<Function> theFunctions,
+            List<Double> theBounds, List<Double> theEncode) {
+        Function newFunction = new PSFunction(functionType, theDomain, theRange, theFunctions,
+                    theBounds, theEncode);
+        return newFunction;
+    }
+
+    @Override
+    public Function makeFunction(int functionType, List<Double> theDomain,
+            List<Double> theRange, List<Double> theCZero, List<Double> theCOne,
+            double theInterpolationExponentN) {
+        Function newFunction = new PSFunction(functionType, theDomain, theRange, theCZero,
+                    theCOne, theInterpolationExponentN);
+        return newFunction;
+    }
+
+    @Override
+    public Shading makeShading(int theShadingType,
+            PDFDeviceColorSpace theColorSpace, List<Double> theBackground, List<Double> theBBox,
+            boolean theAntiAlias, List<Double> theCoords, List<Double> theDomain,
+            Function theFunction, List<Integer> theExtend) {
+        Shading newShading = new PSShading(theShadingType, theColorSpace, theBackground, theBBox,
+                    theAntiAlias, theCoords, theDomain, theFunction, theExtend);
+        return newShading;
+    }
+
+    @Override
+    public Pattern makePattern(int thePatternType, Shading theShading, List theXUID,
+            StringBuffer theExtGState, List<Double> theMatrix) {
+        return new PSPattern(thePatternType, theShading, theXUID, theExtGState);
+    }
+}
diff --git a/src/java/org/apache/fop/render/shading/Pattern.java b/src/java/org/apache/fop/render/shading/Pattern.java
new file mode 100644 (file)
index 0000000..b66926e
--- /dev/null
@@ -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 (file)
index 0000000..385cb11
--- /dev/null
@@ -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 (file)
index 0000000..6dac65f
--- /dev/null
@@ -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());
+    }
+}
index efa71a22551b81c458a33f3f55d498081dfe49be..1fcf9f8700db1f44f96f22904ae0d4bc9e56f0e7 100644 (file)
@@ -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;
 
 /**
  * <p>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 (file)
index 0000000..b48da41
--- /dev/null
@@ -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 (file)
index 0000000..283d3f4
--- /dev/null
@@ -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 (file)
index 0000000..fdf92cd
--- /dev/null
@@ -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 (file)
index 0000000..9977070
--- /dev/null
@@ -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