001/*
002 * $Id: PdfGraphics2D.java 4784 2011-03-15 08:33:00Z blowagie $
003 *
004 * This file is part of the iText (R) project.
005 * Copyright (c) 1998-2011 1T3XT BVBA
006 * Authors: Bruno Lowagie, Paulo Soares, et al.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU Affero General Public License version 3
010 * as published by the Free Software Foundation with the addition of the
011 * following permission added to Section 15 as permitted in Section 7(a):
012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT,
013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
014 *
015 * This program is distributed in the hope that it will be useful, but
016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
017 * or FITNESS FOR A PARTICULAR PURPOSE.
018 * See the GNU Affero General Public License for more details.
019 * You should have received a copy of the GNU Affero General Public License
020 * along with this program; if not, see http://www.gnu.org/licenses or write to
021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
022 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
023 * http://itextpdf.com/terms-of-use/
024 *
025 * The interactive user interfaces in modified source and object code versions
026 * of this program must display Appropriate Legal Notices, as required under
027 * Section 5 of the GNU Affero General Public License.
028 *
029 * In accordance with Section 7(b) of the GNU Affero General Public License,
030 * a covered work must retain the producer line in every PDF that is created
031 * or manipulated using iText.
032 *
033 * You can be released from the requirements of the license by purchasing
034 * a commercial license. Buying such a license is mandatory as soon as you
035 * develop commercial activities involving the iText software without
036 * disclosing the source code of your own applications.
037 * These activities include: offering paid services to customers as an ASP,
038 * serving PDFs on the fly in a web application, shipping iText with a closed
039 * source product.
040 *
041 * For more information, please contact iText Software Corp. at this
042 * address: sales@itextpdf.com
043 */
044package com.itextpdf.text.pdf;
045
046import java.awt.AlphaComposite;
047import java.awt.BasicStroke;
048import java.awt.Color;
049import java.awt.Component;
050import java.awt.Composite;
051import java.awt.Font;
052import java.awt.FontMetrics;
053import java.awt.GradientPaint;
054import java.awt.Graphics;
055import java.awt.Graphics2D;
056import java.awt.GraphicsConfiguration;
057import java.awt.Image;
058import java.awt.MediaTracker;
059import java.awt.Paint;
060import java.awt.Polygon;
061import java.awt.Rectangle;
062import java.awt.RenderingHints;
063import java.awt.Shape;
064import java.awt.Stroke;
065import java.awt.TexturePaint;
066import java.awt.Transparency;
067import java.awt.RenderingHints.Key;
068import java.awt.font.FontRenderContext;
069import java.awt.font.GlyphVector;
070import java.awt.font.TextAttribute;
071import java.awt.geom.AffineTransform;
072import java.awt.geom.Arc2D;
073import java.awt.geom.Area;
074import java.awt.geom.Ellipse2D;
075import java.awt.geom.Line2D;
076import java.awt.geom.NoninvertibleTransformException;
077import java.awt.geom.PathIterator;
078import java.awt.geom.Point2D;
079import java.awt.geom.Rectangle2D;
080import java.awt.geom.RoundRectangle2D;
081import java.awt.image.BufferedImage;
082import java.awt.image.BufferedImageOp;
083import java.awt.image.ColorModel;
084import java.awt.image.ImageObserver;
085import java.awt.image.RenderedImage;
086import java.awt.image.WritableRaster;
087import java.awt.image.renderable.RenderableImage;
088import java.io.ByteArrayOutputStream;
089import java.text.AttributedCharacterIterator;
090import java.util.ArrayList;
091import java.util.HashMap;
092import java.util.Hashtable;
093import java.util.Locale;
094import java.util.Map;
095
096import javax.imageio.IIOImage;
097import javax.imageio.ImageIO;
098import javax.imageio.ImageWriteParam;
099import javax.imageio.ImageWriter;
100import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
101import javax.imageio.stream.ImageOutputStream;
102
103import com.itextpdf.text.BaseColor;
104import com.itextpdf.text.pdf.internal.PolylineShape;
105
106public class PdfGraphics2D extends Graphics2D {
107
108    private static final int FILL = 1;
109    private static final int STROKE = 2;
110    private static final int CLIP = 3;
111    private BasicStroke strokeOne = new BasicStroke(1);
112
113    private static final AffineTransform IDENTITY = new AffineTransform();
114
115    private Font font;
116    private BaseFont baseFont;
117    private float fontSize;
118    private AffineTransform transform;
119    private Paint paint;
120    private Color background;
121    private float width;
122    private float height;
123
124    private Area clip;
125
126    private RenderingHints rhints = new RenderingHints(null);
127
128    private Stroke stroke;
129    private Stroke originalStroke;
130
131    private PdfContentByte cb;
132
133    /** Storage for BaseFont objects created. */
134    private HashMap<String, BaseFont> baseFonts;
135
136    private boolean disposeCalled = false;
137
138    private FontMapper fontMapper;
139
140    private static final class Kid {
141        final int pos;
142        final PdfGraphics2D graphics;
143        Kid(int pos, PdfGraphics2D graphics) {
144            this.pos = pos;
145            this.graphics = graphics;
146        }
147    }
148    private ArrayList<Kid> kids;
149
150    private boolean kid = false;
151
152    private Graphics2D dg2;
153
154    private boolean onlyShapes = false;
155
156    private Stroke oldStroke;
157    private Paint paintFill;
158    private Paint paintStroke;
159
160    private MediaTracker mediaTracker;
161
162    // Added by Jurij Bilas
163    protected boolean underline;          // indicates if the font style is underlined
164    // Added by Peter Severin
165    /** @since 5.0.3 */
166    protected boolean strikethrough;
167
168    protected PdfGState fillGState[];
169    protected PdfGState strokeGState[];
170    protected int currentFillGState = 255;
171    protected int currentStrokeGState = 255;
172
173    public static final int AFM_DIVISOR = 1000; // used to calculate coordinates
174
175    private boolean convertImagesToJPEG = false;
176    private float jpegQuality = .95f;
177
178        // Added by Alexej Suchov
179        private float alpha;
180
181        // Added by Alexej Suchov
182        private Composite composite;
183
184        // Added by Alexej Suchov
185        private Paint realPaint;
186        
187        /**
188         * Method that creates a Graphics2D object.
189         * Contributed by Peter Harvey: he moved code from the constructor to a separate method
190         * @since 5.0.2
191         */
192        private Graphics2D getDG2() {
193                if (dg2 == null) {
194                        dg2 = new BufferedImage(2, 2, BufferedImage.TYPE_INT_RGB).createGraphics();             
195                        dg2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
196                        setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
197                        setRenderingHint(HyperLinkKey.KEY_INSTANCE, HyperLinkKey.VALUE_HYPERLINKKEY_OFF);
198                }
199                return dg2;
200        }
201        
202    private PdfGraphics2D() {
203    }
204
205    /**
206     * Constructor for PDFGraphics2D.
207     *
208     */
209    PdfGraphics2D(PdfContentByte cb, float width, float height, FontMapper fontMapper, boolean onlyShapes, boolean convertImagesToJPEG, float quality) {
210        super();
211        this.fillGState = new PdfGState[256];
212        this.strokeGState = new PdfGState[256];
213        this.convertImagesToJPEG = convertImagesToJPEG;
214        this.jpegQuality = quality;
215        this.onlyShapes = onlyShapes;
216        this.transform = new AffineTransform();
217        this.baseFonts = new HashMap<String, BaseFont>();
218        if (!onlyShapes) {
219            this.fontMapper = fontMapper;
220            if (this.fontMapper == null)
221                this.fontMapper = new DefaultFontMapper();
222        }
223        paint = Color.black;
224        background = Color.white;
225        setFont(new Font("sanserif", Font.PLAIN, 12));
226        this.cb = cb;
227        cb.saveState();
228        this.width = width;
229        this.height = height;
230        clip = new Area(new Rectangle2D.Float(0, 0, width, height));
231        clip(clip);
232        originalStroke = stroke = oldStroke = strokeOne;
233        setStrokeDiff(stroke, null);
234        cb.saveState();
235    }
236
237    /**
238     * @see Graphics2D#draw(Shape)
239     */
240    @Override
241    public void draw(Shape s) {
242        followPath(s, STROKE);
243    }
244
245    /**
246     * @see Graphics2D#drawImage(Image, AffineTransform, ImageObserver)
247     */
248    @Override
249    public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) {
250        return drawImage(img, null, xform, null, obs);
251    }
252
253    /**
254     * @see Graphics2D#drawImage(BufferedImage, BufferedImageOp, int, int)
255     */
256    @Override
257    public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) {
258        BufferedImage result = img;
259        if (op != null) {
260            result = op.createCompatibleDestImage(img, img.getColorModel());
261            result = op.filter(img, result);
262        }
263        drawImage(result, x, y, null);
264    }
265
266    /**
267     * @see Graphics2D#drawRenderedImage(RenderedImage, AffineTransform)
268     */
269    @Override
270    public void drawRenderedImage(RenderedImage img, AffineTransform xform) {
271        BufferedImage image = null;
272        if (img instanceof BufferedImage) {
273            image = (BufferedImage)img;
274        } else {
275            ColorModel cm = img.getColorModel();
276            int width = img.getWidth();
277            int height = img.getHeight();
278            WritableRaster raster = cm.createCompatibleWritableRaster(width, height);
279            boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
280            Hashtable<String, Object> properties = new Hashtable<String, Object>();
281            String[] keys = img.getPropertyNames();
282            if (keys!=null) {
283                for (String key : keys) {
284                    properties.put(key, img.getProperty(key));
285                }
286            }
287            BufferedImage result = new BufferedImage(cm, raster, isAlphaPremultiplied, properties);
288            img.copyData(raster);
289            image=result;
290        }
291        drawImage(image, xform, null);
292    }
293
294    /**
295     * @see Graphics2D#drawRenderableImage(RenderableImage, AffineTransform)
296     */
297    @Override
298    public void drawRenderableImage(RenderableImage img, AffineTransform xform) {
299        drawRenderedImage(img.createDefaultRendering(), xform);
300    }
301
302    /**
303     * @see Graphics#drawString(String, int, int)
304     */
305    @Override
306    public void drawString(String s, int x, int y) {
307        drawString(s, (float)x, (float)y);
308    }
309
310    /**
311     * Calculates position and/or stroke thickness depending on the font size
312     * @param d value to be converted
313     * @param i font size
314     * @return position and/or stroke thickness depending on the font size
315     */
316    public static double asPoints(double d, int i) {
317        return d * i / AFM_DIVISOR;
318    }
319    /**
320     * This routine goes through the attributes and sets the font
321     * before calling the actual string drawing routine
322     * @param iter
323     */
324    @SuppressWarnings("unchecked")
325    protected void doAttributes(AttributedCharacterIterator iter) {
326        underline = false;
327        strikethrough = false;
328        for (AttributedCharacterIterator.Attribute attribute: iter.getAttributes().keySet()) {
329            if (!(attribute instanceof TextAttribute))
330                continue;
331            TextAttribute textattribute = (TextAttribute)attribute;
332            if(textattribute.equals(TextAttribute.FONT)) {
333                Font font = (Font)iter.getAttributes().get(textattribute);
334                setFont(font);
335            }
336            else if(textattribute.equals(TextAttribute.UNDERLINE)) {
337                if(iter.getAttributes().get(textattribute) == TextAttribute.UNDERLINE_ON)
338                    underline = true;
339            }
340            else if(textattribute.equals(TextAttribute.STRIKETHROUGH)) {
341                if(iter.getAttributes().get(textattribute) == TextAttribute.STRIKETHROUGH_ON)
342                        strikethrough = true;
343            }
344            else if(textattribute.equals(TextAttribute.SIZE)) {
345                Object obj = iter.getAttributes().get(textattribute);
346                if(obj instanceof Integer) {
347                    int i = ((Integer)obj).intValue();
348                    setFont(getFont().deriveFont(getFont().getStyle(), i));
349                }
350                else if(obj instanceof Float) {
351                    float f = ((Float)obj).floatValue();
352                    setFont(getFont().deriveFont(getFont().getStyle(), f));
353                }
354            }
355            else if(textattribute.equals(TextAttribute.FOREGROUND)) {
356                setColor((Color) iter.getAttributes().get(textattribute));
357            }
358            else if(textattribute.equals(TextAttribute.FAMILY)) {
359              Font font = getFont();
360              Map fontAttributes = font.getAttributes();
361              fontAttributes.put(TextAttribute.FAMILY, iter.getAttributes().get(textattribute));
362              setFont(font.deriveFont(fontAttributes));
363            }
364            else if(textattribute.equals(TextAttribute.POSTURE)) {
365              Font font = getFont();
366              Map fontAttributes = font.getAttributes();
367              fontAttributes.put(TextAttribute.POSTURE, iter.getAttributes().get(textattribute));
368              setFont(font.deriveFont(fontAttributes));
369            }
370            else if(textattribute.equals(TextAttribute.WEIGHT)) {
371              Font font = getFont();
372              Map fontAttributes = font.getAttributes();
373              fontAttributes.put(TextAttribute.WEIGHT, iter.getAttributes().get(textattribute));
374              setFont(font.deriveFont(fontAttributes));
375            }
376        }
377    }
378
379    /**
380     * @see Graphics2D#drawString(String, float, float)
381     */
382    @Override
383    public void drawString(String s, float x, float y) {
384        if (s.length() == 0)
385            return;
386        setFillPaint();
387        if (onlyShapes) {
388            drawGlyphVector(this.font.layoutGlyphVector(getFontRenderContext(), s.toCharArray(), 0, s.length(), java.awt.Font.LAYOUT_LEFT_TO_RIGHT), x, y);
389//            Use the following line to compile in JDK 1.3
390//            drawGlyphVector(this.font.createGlyphVector(getFontRenderContext(), s), x, y);
391        }
392        else {
393                boolean restoreTextRenderingMode = false;
394            AffineTransform at = getTransform();
395            AffineTransform at2 = getTransform();
396            at2.translate(x, y);
397            at2.concatenate(font.getTransform());
398            setTransform(at2);
399            AffineTransform inverse = this.normalizeMatrix();
400            AffineTransform flipper = AffineTransform.getScaleInstance(1,-1);
401            inverse.concatenate(flipper);
402            double[] mx = new double[6];
403            inverse.getMatrix(mx);
404            cb.beginText();
405            cb.setFontAndSize(baseFont, fontSize);
406            // Check if we need to simulate an italic font.
407            // When there are different fonts for italic, bold, italic bold
408            // the font.getName() will be different from the font.getFontName()
409            // value. When they are the same value then we are normally dealing
410            // with a single font that has been made into an italic or bold
411            // font.
412            if (font.isItalic() && font.getFontName().equals(font.getName())) {
413                float angle = baseFont.getFontDescriptor(BaseFont.ITALICANGLE, 1000);
414                float angle2 = font.getItalicAngle();
415                // We don't have an italic version of this font so we need
416                // to set the font angle ourselves to produce an italic font.
417                if (angle2 == 0) {
418                    // The JavaVM didn't have an angle setting for making
419                    // the font an italic font so use a default of
420                    // italic angle of 15 degrees.
421                    angle2 = 15.0f;
422                } else {
423                    // This sign of the angle for Java and PDF seams
424                    // seams to be reversed.
425                    angle2 = -angle2;
426                }
427                if (angle == 0) {
428                    mx[2] = angle2 / 100.0f;
429                }
430            }
431            cb.setTextMatrix((float)mx[0], (float)mx[1], (float)mx[2], (float)mx[3], (float)mx[4], (float)mx[5]);
432            Float fontTextAttributeWidth = (Float)font.getAttributes().get(TextAttribute.WIDTH);
433            fontTextAttributeWidth = fontTextAttributeWidth == null
434                                     ? TextAttribute.WIDTH_REGULAR
435                                     : fontTextAttributeWidth;
436            if (!TextAttribute.WIDTH_REGULAR.equals(fontTextAttributeWidth))
437                cb.setHorizontalScaling(100.0f / fontTextAttributeWidth.floatValue());
438
439            // Check if we need to simulate a bold font.
440            // Do nothing if the BaseFont is already bold. This test is not foolproof but it will work most of the times.
441            if (baseFont.getPostscriptFontName().toLowerCase().indexOf("bold") < 0) {
442                // Get the weight of the font so we can detect fonts with a weight
443                // that makes them bold, but the Font.isBold() value is false.
444                Float weight = (Float) font.getAttributes().get(TextAttribute.WEIGHT);
445                if (weight == null) {
446                    weight = font.isBold() ? TextAttribute.WEIGHT_BOLD
447                                             : TextAttribute.WEIGHT_REGULAR;
448                }
449                if ((font.isBold() || weight.floatValue() >= TextAttribute.WEIGHT_SEMIBOLD.floatValue())
450                    && font.getFontName().equals(font.getName())) {
451                    // Simulate a bold font.
452                    float strokeWidth = font.getSize2D() * (weight.floatValue() - TextAttribute.WEIGHT_REGULAR.floatValue()) / 30f;
453                    if (strokeWidth != 1) {
454                        if(realPaint instanceof Color){
455                            cb.setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE);
456                            cb.setLineWidth(strokeWidth);
457                            Color color = (Color)realPaint;
458                            int alpha = color.getAlpha();
459                            if (alpha != currentStrokeGState) {
460                                currentStrokeGState = alpha;
461                                PdfGState gs = strokeGState[alpha];
462                                if (gs == null) {
463                                    gs = new PdfGState();
464                                    gs.setStrokeOpacity(alpha / 255f);
465                                    strokeGState[alpha] = gs;
466                                }
467                                cb.setGState(gs);
468                            }
469                            cb.setColorStroke(new BaseColor(color));
470                            restoreTextRenderingMode = true;
471                        }
472                    }
473                }
474            }
475
476            double width = 0;
477            if (font.getSize2D() > 0) {
478                float scale = 1000 / font.getSize2D();
479                Font derivedFont = font.deriveFont(AffineTransform.getScaleInstance(scale, scale));
480                width = derivedFont.getStringBounds(s, getFontRenderContext()).getWidth();
481                if (derivedFont.isTransformed())
482                    width /= scale;
483            }
484            // if the hyperlink flag is set add an action to the text
485            Object url = getRenderingHint(HyperLinkKey.KEY_INSTANCE);
486            if (url != null && !url.equals(HyperLinkKey.VALUE_HYPERLINKKEY_OFF))
487            {
488                float scale = 1000 / font.getSize2D();
489                Font derivedFont = font.deriveFont(AffineTransform.getScaleInstance(scale, scale));
490                double height = derivedFont.getStringBounds(s, getFontRenderContext()).getHeight();
491                if (derivedFont.isTransformed())
492                    height /= scale;
493                double leftX = cb.getXTLM();
494                double leftY = cb.getYTLM();
495                PdfAction action = new  PdfAction(url.toString());
496                cb.setAction(action, (float)leftX, (float)leftY, (float)(leftX+width), (float)(leftY+height));
497            }
498            if (s.length() > 1) {
499                float adv = ((float)width - baseFont.getWidthPoint(s, fontSize)) / (s.length() - 1);
500                cb.setCharacterSpacing(adv);
501            }
502            cb.showText(s);
503            if (s.length() > 1) {
504                cb.setCharacterSpacing(0);
505            }
506            if (!TextAttribute.WIDTH_REGULAR.equals(fontTextAttributeWidth))
507                cb.setHorizontalScaling(100);
508
509            // Restore the original TextRenderingMode if needed.
510            if (restoreTextRenderingMode) {
511                cb.setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_FILL);
512            }
513
514            cb.endText();
515            setTransform(at);
516            if(underline) {
517                // These two are supposed to be taken from the .AFM file
518                //int UnderlinePosition = -100;
519                int UnderlineThickness = 50;
520                //
521                double d = asPoints(UnderlineThickness, (int)fontSize);
522                Stroke savedStroke = originalStroke;
523                setStroke(new BasicStroke((float)d));
524                y = (float)(y + asPoints(UnderlineThickness, (int)fontSize));
525                Line2D line = new Line2D.Double(x, y, width+x, y);
526                draw(line);
527                setStroke(savedStroke);
528            }
529            if(strikethrough) {
530                // These two are supposed to be taken from the .AFM file
531                int StrikethroughThickness = 50;
532                int StrikethroughPosition = 350;
533                //
534                double d = asPoints(StrikethroughThickness, (int)fontSize);
535                double p = asPoints(StrikethroughPosition, (int)fontSize);
536                Stroke savedStroke = originalStroke;
537                setStroke(new BasicStroke((float)d));
538                y = (float)(y + asPoints(StrikethroughThickness, (int)fontSize));
539                Line2D line = new Line2D.Double(x, y-p, width+x, y-p);
540                draw(line);
541                setStroke(savedStroke);
542            }
543        }
544    }
545
546    /**
547     * @see Graphics#drawString(AttributedCharacterIterator, int, int)
548     */
549    @Override
550    public void drawString(AttributedCharacterIterator iterator, int x, int y) {
551        drawString(iterator, (float)x, (float)y);
552    }
553
554    /**
555     * @see Graphics2D#drawString(AttributedCharacterIterator, float, float)
556     */
557    @Override
558    public void drawString(AttributedCharacterIterator iter, float x, float y) {
559/*
560        StringBuffer sb = new StringBuffer();
561        for(char c = iter.first(); c != AttributedCharacterIterator.DONE; c = iter.next()) {
562            sb.append(c);
563        }
564        drawString(sb.toString(),x,y);
565*/
566        StringBuffer stringbuffer = new StringBuffer(iter.getEndIndex());
567        for(char c = iter.first(); c != '\uFFFF'; c = iter.next())
568        {
569            if(iter.getIndex() == iter.getRunStart())
570            {
571                if(stringbuffer.length() > 0)
572                {
573                    drawString(stringbuffer.toString(), x, y);
574                    FontMetrics fontmetrics = getFontMetrics();
575                    x = (float)(x + fontmetrics.getStringBounds(stringbuffer.toString(), this).getWidth());
576                    stringbuffer.delete(0, stringbuffer.length());
577                }
578                doAttributes(iter);
579            }
580            stringbuffer.append(c);
581        }
582
583        drawString(stringbuffer.toString(), x, y);
584        underline = false;
585        strikethrough = false;
586    }
587
588    /**
589     * @see Graphics2D#drawGlyphVector(GlyphVector, float, float)
590     */
591    @Override
592    public void drawGlyphVector(GlyphVector g, float x, float y) {
593        Shape s = g.getOutline(x, y);
594        fill(s);
595    }
596
597    /**
598     * @see Graphics2D#fill(Shape)
599     */
600    @Override
601    public void fill(Shape s) {
602        followPath(s, FILL);
603    }
604
605    /**
606     * @see Graphics2D#hit(Rectangle, Shape, boolean)
607     */
608    @Override
609    public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
610        if (onStroke) {
611            s = stroke.createStrokedShape(s);
612        }
613        s = transform.createTransformedShape(s);
614        Area area = new Area(s);
615        if (clip != null)
616            area.intersect(clip);
617        return area.intersects(rect.x, rect.y, rect.width, rect.height);
618    }
619
620    /**
621     * @see Graphics2D#getDeviceConfiguration()
622     */
623    @Override
624    public GraphicsConfiguration getDeviceConfiguration() {
625        return getDG2().getDeviceConfiguration();
626    }
627
628    /**
629         * Method contributed by Alexej Suchov
630     * @see Graphics2D#setComposite(Composite)
631     */
632    @Override
633    public void setComposite(Composite comp) {
634
635                if (comp instanceof AlphaComposite) {
636
637                        AlphaComposite composite = (AlphaComposite) comp;
638
639                        if (composite.getRule() == 3) {
640
641                                alpha = composite.getAlpha();
642                                this.composite = composite;
643
644                                if (realPaint != null && realPaint instanceof Color) {
645
646                                        Color c = (Color) realPaint;
647                                        paint = new Color(c.getRed(), c.getGreen(), c.getBlue(),
648                                                        (int) (c.getAlpha() * alpha));
649                                }
650                                return;
651                        }
652                }
653
654                this.composite = comp;
655                alpha = 1.0F;
656
657    }
658
659    /**
660         * Method contributed by Alexej Suchov
661     * @see Graphics2D#setPaint(Paint)
662     */
663    @Override
664    public void setPaint(Paint paint) {
665        if (paint == null)
666            return;
667        this.paint = paint;
668                realPaint = paint;
669
670                if (composite instanceof AlphaComposite && paint instanceof Color) {
671
672                        AlphaComposite co = (AlphaComposite) composite;
673
674                        if (co.getRule() == 3) {
675                                Color c = (Color) paint;
676                                this.paint = new Color(c.getRed(), c.getGreen(), c.getBlue(), (int) (c.getAlpha() * alpha));
677                                realPaint = paint;
678                        }
679                }
680
681    }
682
683    private Stroke transformStroke(Stroke stroke) {
684        if (!(stroke instanceof BasicStroke))
685            return stroke;
686        BasicStroke st = (BasicStroke)stroke;
687        float scale = (float)Math.sqrt(Math.abs(transform.getDeterminant()));
688        float dash[] = st.getDashArray();
689        if (dash != null) {
690            for (int k = 0; k < dash.length; ++k)
691                dash[k] *= scale;
692        }
693        return new BasicStroke(st.getLineWidth() * scale, st.getEndCap(), st.getLineJoin(), st.getMiterLimit(), dash, st.getDashPhase() * scale);
694    }
695
696    private void setStrokeDiff(Stroke newStroke, Stroke oldStroke) {
697        if (newStroke == oldStroke)
698            return;
699        if (!(newStroke instanceof BasicStroke))
700            return;
701        BasicStroke nStroke = (BasicStroke)newStroke;
702        boolean oldOk = oldStroke instanceof BasicStroke;
703        BasicStroke oStroke = null;
704        if (oldOk)
705            oStroke = (BasicStroke)oldStroke;
706        if (!oldOk || nStroke.getLineWidth() != oStroke.getLineWidth())
707            cb.setLineWidth(nStroke.getLineWidth());
708        if (!oldOk || nStroke.getEndCap() != oStroke.getEndCap()) {
709            switch (nStroke.getEndCap()) {
710            case BasicStroke.CAP_BUTT:
711                cb.setLineCap(0);
712                break;
713            case BasicStroke.CAP_SQUARE:
714                cb.setLineCap(2);
715                break;
716            default:
717                cb.setLineCap(1);
718            }
719        }
720        if (!oldOk || nStroke.getLineJoin() != oStroke.getLineJoin()) {
721            switch (nStroke.getLineJoin()) {
722            case BasicStroke.JOIN_MITER:
723                cb.setLineJoin(0);
724                break;
725            case BasicStroke.JOIN_BEVEL:
726                cb.setLineJoin(2);
727                break;
728            default:
729                cb.setLineJoin(1);
730            }
731        }
732        if (!oldOk || nStroke.getMiterLimit() != oStroke.getMiterLimit())
733            cb.setMiterLimit(nStroke.getMiterLimit());
734        boolean makeDash;
735        if (oldOk) {
736            if (nStroke.getDashArray() != null) {
737                if (nStroke.getDashPhase() != oStroke.getDashPhase()) {
738                    makeDash = true;
739                }
740                else if (!java.util.Arrays.equals(nStroke.getDashArray(), oStroke.getDashArray())) {
741                    makeDash = true;
742                }
743                else
744                    makeDash = false;
745            }
746            else if (oStroke.getDashArray() != null) {
747                makeDash = true;
748            }
749            else
750                makeDash = false;
751        }
752        else {
753            makeDash = true;
754        }
755        if (makeDash) {
756            float dash[] = nStroke.getDashArray();
757            if (dash == null)
758                cb.setLiteral("[]0 d\n");
759            else {
760                cb.setLiteral('[');
761                int lim = dash.length;
762                for (int k = 0; k < lim; ++k) {
763                    cb.setLiteral(dash[k]);
764                    cb.setLiteral(' ');
765                }
766                cb.setLiteral(']');
767                cb.setLiteral(nStroke.getDashPhase());
768                cb.setLiteral(" d\n");
769            }
770        }
771    }
772
773    /**
774     * @see Graphics2D#setStroke(Stroke)
775     */
776    @Override
777    public void setStroke(Stroke s) {
778        originalStroke = s;
779        this.stroke = transformStroke(s);
780    }
781
782
783    /**
784     * Sets a rendering hint
785     * @param arg0
786     * @param arg1
787     */
788    @Override
789    public void setRenderingHint(Key arg0, Object arg1) {
790         if (arg1 != null) {
791                rhints.put(arg0, arg1);
792         } else {
793                 if (arg0 instanceof HyperLinkKey)
794                 {
795                         rhints.put(arg0, HyperLinkKey.VALUE_HYPERLINKKEY_OFF);
796                 }
797                 else
798                 {
799                         rhints.remove(arg0);
800                 }
801         }
802    }
803
804    /**
805     * @param arg0 a key
806     * @return the rendering hint
807     */
808    @Override
809    public Object getRenderingHint(Key arg0) {
810        return rhints.get(arg0);
811    }
812
813    /**
814     * @see Graphics2D#setRenderingHints(Map)
815     */
816    @Override
817    public void setRenderingHints(Map<?,?> hints) {
818        rhints.clear();
819        rhints.putAll(hints);
820    }
821
822    /**
823     * @see Graphics2D#addRenderingHints(Map)
824     */
825    @Override
826    public void addRenderingHints(Map<?,?> hints) {
827        rhints.putAll(hints);
828    }
829
830    /**
831     * @see Graphics2D#getRenderingHints()
832     */
833    @Override
834    public RenderingHints getRenderingHints() {
835        return rhints;
836    }
837
838    /**
839     * @see Graphics#translate(int, int)
840     */
841    @Override
842    public void translate(int x, int y) {
843        translate((double)x, (double)y);
844    }
845
846    /**
847     * @see Graphics2D#translate(double, double)
848     */
849    @Override
850    public void translate(double tx, double ty) {
851        transform.translate(tx,ty);
852    }
853
854    /**
855     * @see Graphics2D#rotate(double)
856     */
857    @Override
858    public void rotate(double theta) {
859        transform.rotate(theta);
860    }
861
862    /**
863     * @see Graphics2D#rotate(double, double, double)
864     */
865    @Override
866    public void rotate(double theta, double x, double y) {
867        transform.rotate(theta, x, y);
868    }
869
870    /**
871     * @see Graphics2D#scale(double, double)
872     */
873    @Override
874    public void scale(double sx, double sy) {
875        transform.scale(sx, sy);
876        this.stroke = transformStroke(originalStroke);
877    }
878
879    /**
880     * @see Graphics2D#shear(double, double)
881     */
882    @Override
883    public void shear(double shx, double shy) {
884        transform.shear(shx, shy);
885    }
886
887    /**
888     * @see Graphics2D#transform(AffineTransform)
889     */
890    @Override
891    public void transform(AffineTransform tx) {
892        transform.concatenate(tx);
893        this.stroke = transformStroke(originalStroke);
894    }
895
896    /**
897     * @see Graphics2D#setTransform(AffineTransform)
898     */
899    @Override
900    public void setTransform(AffineTransform t) {
901        transform = new AffineTransform(t);
902        this.stroke = transformStroke(originalStroke);
903    }
904
905    /**
906     * @see Graphics2D#getTransform()
907     */
908    @Override
909    public AffineTransform getTransform() {
910        return new AffineTransform(transform);
911    }
912
913    /**
914         * Method contributed by Alexej Suchov
915     * @see Graphics2D#getPaint()
916     */
917    @Override
918    public Paint getPaint() {
919        if (realPaint != null) {
920            return realPaint;
921        } else {
922            return paint;
923        }
924        }
925
926    /**
927     * @see Graphics2D#getComposite()
928     */
929    @Override
930    public Composite getComposite() {
931        return composite;
932    }
933
934    /**
935     * @see Graphics2D#setBackground(Color)
936     */
937    @Override
938    public void setBackground(Color color) {
939        background = color;
940    }
941
942    /**
943     * @see Graphics2D#getBackground()
944     */
945    @Override
946    public Color getBackground() {
947        return background;
948    }
949
950    /**
951     * @see Graphics2D#getStroke()
952     */
953    @Override
954    public Stroke getStroke() {
955        return originalStroke;
956    }
957
958
959    /**
960     * @see Graphics2D#getFontRenderContext()
961     */
962    @Override
963    public FontRenderContext getFontRenderContext() {
964        boolean antialias = RenderingHints.VALUE_TEXT_ANTIALIAS_ON.equals(getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING));
965        boolean fractions = RenderingHints.VALUE_FRACTIONALMETRICS_ON.equals(getRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS));
966        return new FontRenderContext(new AffineTransform(), antialias, fractions);
967    }
968
969    /**
970     * @see Graphics#create()
971     */
972    @Override
973    public Graphics create() {
974        PdfGraphics2D g2 = new PdfGraphics2D();
975        g2.rhints.putAll( this.rhints );
976        g2.onlyShapes = this.onlyShapes;
977        g2.transform = new AffineTransform(this.transform);
978        g2.baseFonts = this.baseFonts;
979        g2.fontMapper = this.fontMapper;
980        g2.paint = this.paint;
981        g2.fillGState = this.fillGState;
982        g2.currentFillGState = this.currentFillGState;
983        g2.strokeGState = this.strokeGState;
984        g2.background = this.background;
985        g2.mediaTracker = this.mediaTracker;
986        g2.convertImagesToJPEG = this.convertImagesToJPEG;
987        g2.jpegQuality = this.jpegQuality;
988        g2.setFont(this.font);
989        g2.cb = this.cb.getDuplicate();
990        g2.cb.saveState();
991        g2.width = this.width;
992        g2.height = this.height;
993        g2.followPath(new Area(new Rectangle2D.Float(0, 0, width, height)), CLIP);
994        if (this.clip != null)
995            g2.clip = new Area(this.clip);
996        g2.composite = composite;
997        g2.stroke = stroke;
998        g2.originalStroke = originalStroke;
999        g2.strokeOne = (BasicStroke)g2.transformStroke(g2.strokeOne);
1000        g2.oldStroke = g2.strokeOne;
1001        g2.setStrokeDiff(g2.oldStroke, null);
1002        g2.cb.saveState();
1003        if (g2.clip != null)
1004            g2.followPath(g2.clip, CLIP);
1005        g2.kid = true;
1006        if (this.kids == null)
1007            this.kids = new ArrayList<Kid>();
1008        this.kids.add(new Kid(cb.getInternalBuffer().size(), g2));
1009        return g2;
1010    }
1011
1012    public PdfContentByte getContent() {
1013        return this.cb;
1014    }
1015    /**
1016     * @see Graphics#getColor()
1017     */
1018    @Override
1019    public Color getColor() {
1020        if (paint instanceof Color) {
1021            return (Color)paint;
1022        } else {
1023            return Color.black;
1024        }
1025    }
1026
1027    /**
1028     * @see Graphics#setColor(Color)
1029     */
1030    @Override
1031    public void setColor(Color color) {
1032        setPaint(color);
1033    }
1034
1035    /**
1036     * @see Graphics#setPaintMode()
1037     */
1038    @Override
1039    public void setPaintMode() {}
1040
1041    /**
1042     * @see Graphics#setXORMode(Color)
1043     */
1044    @Override
1045    public void setXORMode(Color c1) {
1046
1047    }
1048
1049    /**
1050     * @see Graphics#getFont()
1051     */
1052    @Override
1053    public Font getFont() {
1054        return font;
1055    }
1056
1057    /**
1058     * @see Graphics#setFont(Font)
1059     */
1060    /**
1061     * Sets the current font.
1062     */
1063    @Override
1064    public void setFont(Font f) {
1065        if (f == null)
1066            return;
1067        if (onlyShapes) {
1068            font = f;
1069            return;
1070        }
1071        if (f == font)
1072            return;
1073        font = f;
1074        fontSize = f.getSize2D();
1075        baseFont = getCachedBaseFont(f);
1076    }
1077
1078    private BaseFont getCachedBaseFont(Font f) {
1079        synchronized (baseFonts) {
1080            BaseFont bf = baseFonts.get(f.getFontName());
1081            if (bf == null) {
1082                bf = fontMapper.awtToPdf(f);
1083                baseFonts.put(f.getFontName(), bf);
1084            }
1085            return bf;
1086        }
1087    }
1088
1089    /**
1090     * @see Graphics#getFontMetrics(Font)
1091     */
1092    @Override
1093    public FontMetrics getFontMetrics(Font f) {
1094        return getDG2().getFontMetrics(f);
1095    }
1096
1097    /**
1098     * @see Graphics#getClipBounds()
1099     */
1100    @Override
1101    public Rectangle getClipBounds() {
1102        if (clip == null)
1103            return null;
1104        return getClip().getBounds();
1105    }
1106
1107    /**
1108     * @see Graphics#clipRect(int, int, int, int)
1109     */
1110    @Override
1111    public void clipRect(int x, int y, int width, int height) {
1112        Rectangle2D rect = new Rectangle2D.Double(x,y,width,height);
1113        clip(rect);
1114    }
1115
1116    /**
1117     * @see Graphics#setClip(int, int, int, int)
1118     */
1119    @Override
1120    public void setClip(int x, int y, int width, int height) {
1121        Rectangle2D rect = new Rectangle2D.Double(x,y,width,height);
1122        setClip(rect);
1123    }
1124
1125    /**
1126     * @see Graphics2D#clip(Shape)
1127     */
1128    @Override
1129    public void clip(Shape s) {
1130        if (s == null) {
1131            setClip(null);
1132            return;
1133        }
1134        s = transform.createTransformedShape(s);
1135        if (clip == null)
1136            clip = new Area(s);
1137        else
1138            clip.intersect(new Area(s));
1139        followPath(s, CLIP);
1140    }
1141
1142    /**
1143     * @see Graphics#getClip()
1144     */
1145    @Override
1146    public Shape getClip() {
1147        try {
1148            return transform.createInverse().createTransformedShape(clip);
1149        }
1150        catch (NoninvertibleTransformException e) {
1151            return null;
1152        }
1153    }
1154
1155    /**
1156     * @see Graphics#setClip(Shape)
1157     */
1158    @Override
1159    public void setClip(Shape s) {
1160        cb.restoreState();
1161        cb.saveState();
1162        if (s != null)
1163            s = transform.createTransformedShape(s);
1164        if (s == null) {
1165            clip = null;
1166        }
1167        else {
1168            clip = new Area(s);
1169            followPath(s, CLIP);
1170        }
1171        paintFill = paintStroke = null;
1172        currentFillGState = currentStrokeGState = 255;
1173        oldStroke = strokeOne;
1174    }
1175
1176    /**
1177     * @see Graphics#copyArea(int, int, int, int, int, int)
1178     */
1179    @Override
1180    public void copyArea(int x, int y, int width, int height, int dx, int dy) {
1181
1182    }
1183
1184    /**
1185     * @see Graphics#drawLine(int, int, int, int)
1186     */
1187    @Override
1188    public void drawLine(int x1, int y1, int x2, int y2) {
1189        Line2D line = new Line2D.Double(x1, y1, x2, y2);
1190        draw(line);
1191    }
1192
1193    /**
1194     * @see Graphics#fillRect(int, int, int, int)
1195     */
1196    @Override
1197    public void drawRect(int x, int y, int width, int height) {
1198        draw(new Rectangle(x, y, width, height));
1199    }
1200
1201    /**
1202     * @see Graphics#fillRect(int, int, int, int)
1203     */
1204    @Override
1205    public void fillRect(int x, int y, int width, int height) {
1206        fill(new Rectangle(x,y,width,height));
1207    }
1208
1209    /**
1210     * @see Graphics#clearRect(int, int, int, int)
1211     */
1212    @Override
1213    public void clearRect(int x, int y, int width, int height) {
1214        Paint temp = paint;
1215        setPaint(background);
1216        fillRect(x,y,width,height);
1217        setPaint(temp);
1218    }
1219
1220    /**
1221     * @see Graphics#drawRoundRect(int, int, int, int, int, int)
1222     */
1223    @Override
1224    public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) {
1225        RoundRectangle2D rect = new RoundRectangle2D.Double(x,y,width,height,arcWidth, arcHeight);
1226        draw(rect);
1227    }
1228
1229    /**
1230     * @see Graphics#fillRoundRect(int, int, int, int, int, int)
1231     */
1232    @Override
1233    public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) {
1234        RoundRectangle2D rect = new RoundRectangle2D.Double(x,y,width,height,arcWidth, arcHeight);
1235        fill(rect);
1236    }
1237
1238    /**
1239     * @see Graphics#drawOval(int, int, int, int)
1240     */
1241    @Override
1242    public void drawOval(int x, int y, int width, int height) {
1243        Ellipse2D oval = new Ellipse2D.Float(x, y, width, height);
1244        draw(oval);
1245    }
1246
1247    /**
1248     * @see Graphics#fillOval(int, int, int, int)
1249     */
1250    @Override
1251    public void fillOval(int x, int y, int width, int height) {
1252        Ellipse2D oval = new Ellipse2D.Float(x, y, width, height);
1253        fill(oval);
1254    }
1255
1256    /**
1257     * @see Graphics#drawArc(int, int, int, int, int, int)
1258     */
1259    @Override
1260    public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) {
1261        Arc2D arc = new Arc2D.Double(x,y,width,height,startAngle, arcAngle, Arc2D.OPEN);
1262        draw(arc);
1263
1264    }
1265
1266    /**
1267     * @see Graphics#fillArc(int, int, int, int, int, int)
1268     */
1269    @Override
1270    public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) {
1271        Arc2D arc = new Arc2D.Double(x,y,width,height,startAngle, arcAngle, Arc2D.PIE);
1272        fill(arc);
1273    }
1274
1275    /**
1276     * @see Graphics#drawPolyline(int[], int[], int)
1277     */
1278    @Override
1279    public void drawPolyline(int[] x, int[] y, int nPoints) {
1280        PolylineShape polyline = new PolylineShape(x, y, nPoints);
1281        draw(polyline);
1282    }
1283
1284    /**
1285     * @see Graphics#drawPolygon(int[], int[], int)
1286     */
1287    @Override
1288    public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) {
1289        Polygon poly = new Polygon(xPoints, yPoints, nPoints);
1290        draw(poly);
1291    }
1292
1293    /**
1294     * @see Graphics#fillPolygon(int[], int[], int)
1295     */
1296    @Override
1297    public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) {
1298        Polygon poly = new Polygon();
1299        for (int i = 0; i < nPoints; i++) {
1300            poly.addPoint(xPoints[i], yPoints[i]);
1301        }
1302        fill(poly);
1303    }
1304
1305    /**
1306     * @see Graphics#drawImage(Image, int, int, ImageObserver)
1307     */
1308    @Override
1309    public boolean drawImage(Image img, int x, int y, ImageObserver observer) {
1310        return drawImage(img, x, y, null, observer);
1311    }
1312
1313    /**
1314     * @see Graphics#drawImage(Image, int, int, int, int, ImageObserver)
1315     */
1316    @Override
1317    public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) {
1318        return drawImage(img, x, y, width, height, null, observer);
1319    }
1320
1321    /**
1322     * @see Graphics#drawImage(Image, int, int, Color, ImageObserver)
1323     */
1324    @Override
1325    public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) {
1326        waitForImage(img);
1327        return drawImage(img, x, y, img.getWidth(observer), img.getHeight(observer), bgcolor, observer);
1328    }
1329
1330    /**
1331     * @see Graphics#drawImage(Image, int, int, int, int, Color, ImageObserver)
1332     */
1333    @Override
1334    public boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer) {
1335        waitForImage(img);
1336        double scalex = width/(double)img.getWidth(observer);
1337        double scaley = height/(double)img.getHeight(observer);
1338        AffineTransform tx = AffineTransform.getTranslateInstance(x,y);
1339        tx.scale(scalex,scaley);
1340        return drawImage(img, null, tx, bgcolor, observer);
1341    }
1342
1343    /**
1344     * @see Graphics#drawImage(Image, int, int, int, int, int, int, int, int, ImageObserver)
1345     */
1346    @Override
1347    public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer) {
1348        return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null, observer);
1349    }
1350
1351    /**
1352     * @see Graphics#drawImage(Image, int, int, int, int, int, int, int, int, Color, ImageObserver)
1353     */
1354    @Override
1355    public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer) {
1356        waitForImage(img);
1357        double dwidth = (double)dx2-dx1;
1358        double dheight = (double)dy2-dy1;
1359        double swidth = (double)sx2-sx1;
1360        double sheight = (double)sy2-sy1;
1361
1362        //if either width or height is 0, then there is nothing to draw
1363        if (dwidth == 0 || dheight == 0 || swidth == 0 || sheight == 0) return true;
1364
1365        double scalex = dwidth/swidth;
1366        double scaley = dheight/sheight;
1367
1368        double transx = sx1*scalex;
1369        double transy = sy1*scaley;
1370        AffineTransform tx = AffineTransform.getTranslateInstance(dx1-transx,dy1-transy);
1371        tx.scale(scalex,scaley);
1372
1373        BufferedImage mask = new BufferedImage(img.getWidth(observer), img.getHeight(observer), BufferedImage.TYPE_BYTE_BINARY);
1374        Graphics g = mask.getGraphics();
1375        g.fillRect(sx1,sy1, (int)swidth, (int)sheight);
1376        drawImage(img, mask, tx, null, observer);
1377        g.dispose();
1378        return true;
1379    }
1380
1381    /**
1382     * @see Graphics#dispose()
1383     */
1384    @Override
1385    public void dispose() {
1386        if (kid)
1387            return;
1388        if (!disposeCalled) {
1389            disposeCalled = true;
1390            cb.restoreState();
1391            cb.restoreState();
1392            if (dg2 != null) {
1393                dg2.dispose();
1394                dg2 = null;
1395            }
1396            if (kids != null) {
1397                ByteBuffer buf = new ByteBuffer();
1398                internalDispose(buf);
1399                ByteBuffer buf2 = cb.getInternalBuffer();
1400                buf2.reset();
1401                buf2.append(buf);
1402            }
1403        }
1404    }
1405
1406    private void internalDispose(ByteBuffer buf) {
1407        int last = 0;
1408        int pos = 0;
1409        ByteBuffer buf2 = cb.getInternalBuffer();
1410        if (kids != null) {
1411            for (Kid kid: kids) {
1412                pos = kid.pos;
1413                PdfGraphics2D g2 = kid.graphics;
1414                g2.cb.restoreState();
1415                g2.cb.restoreState();
1416                buf.append(buf2.getBuffer(), last, pos - last);
1417                if (g2.dg2 != null) {
1418                        g2.dg2.dispose();
1419                        g2.dg2 = null;
1420                }
1421                g2.internalDispose(buf);
1422                last = pos;
1423            }
1424        }
1425        buf.append(buf2.getBuffer(), last, buf2.size() - last);
1426    }
1427
1428    ///////////////////////////////////////////////
1429    //
1430    //
1431    //          implementation specific methods
1432    //
1433    //
1434
1435
1436    private void followPath(Shape s, int drawType) {
1437        if (s==null) return;
1438        if (drawType==STROKE) {
1439            if (!(stroke instanceof BasicStroke)) {
1440                s = stroke.createStrokedShape(s);
1441                followPath(s, FILL);
1442                return;
1443            }
1444        }
1445        if (drawType==STROKE) {
1446            setStrokeDiff(stroke, oldStroke);
1447            oldStroke = stroke;
1448            setStrokePaint();
1449        }
1450        else if (drawType==FILL)
1451            setFillPaint();
1452        PathIterator points;
1453        int traces = 0;
1454        if (drawType == CLIP)
1455            points = s.getPathIterator(IDENTITY);
1456        else
1457            points = s.getPathIterator(transform);
1458        float[] coords = new float[6];
1459        double[] dcoords = new double[6];
1460        while(!points.isDone()) {
1461            ++traces;
1462            // Added by Peter Harvey (start)
1463            int segtype = points.currentSegment(dcoords);
1464            int numpoints = (segtype == PathIterator.SEG_CLOSE ? 0
1465                        : (segtype == PathIterator.SEG_QUADTO ? 2
1466                                        : (segtype == PathIterator.SEG_CUBICTO ? 3
1467                                                        : 1)));
1468            for (int i = 0; i < numpoints * 2; i++) {
1469                coords[i] = (float) dcoords[i];
1470            }
1471            // Added by Peter Harvey (end)
1472            normalizeY(coords);
1473            switch(segtype) {
1474                case PathIterator.SEG_CLOSE:
1475                    cb.closePath();
1476                    break;
1477
1478                case PathIterator.SEG_CUBICTO:
1479                    cb.curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]);
1480                    break;
1481
1482                case PathIterator.SEG_LINETO:
1483                    cb.lineTo(coords[0], coords[1]);
1484                    break;
1485
1486                case PathIterator.SEG_MOVETO:
1487                    cb.moveTo(coords[0], coords[1]);
1488                    break;
1489
1490                case PathIterator.SEG_QUADTO:
1491                    cb.curveTo(coords[0], coords[1], coords[2], coords[3]);
1492                    break;
1493            }
1494            points.next();
1495        }
1496        switch (drawType) {
1497        case FILL:
1498            if (traces > 0) {
1499                if (points.getWindingRule() == PathIterator.WIND_EVEN_ODD)
1500                    cb.eoFill();
1501                else
1502                    cb.fill();
1503            }
1504            break;
1505        case STROKE:
1506            if (traces > 0)
1507                cb.stroke();
1508            break;
1509        default: //drawType==CLIP
1510            if (traces == 0)
1511                cb.rectangle(0, 0, 0, 0);
1512            if (points.getWindingRule() == PathIterator.WIND_EVEN_ODD)
1513                cb.eoClip();
1514            else
1515                cb.clip();
1516            cb.newPath();
1517        }
1518    }
1519
1520    private float normalizeY(float y) {
1521        return this.height - y;
1522    }
1523
1524    private void normalizeY(float[] coords) {
1525        coords[1] = normalizeY(coords[1]);
1526        coords[3] = normalizeY(coords[3]);
1527        coords[5] = normalizeY(coords[5]);
1528    }
1529
1530    private AffineTransform normalizeMatrix() {
1531        double[] mx = new double[6];
1532        AffineTransform result = AffineTransform.getTranslateInstance(0,0);
1533        result.getMatrix(mx);
1534        mx[3]=-1;
1535        mx[5]=height;
1536        result = new AffineTransform(mx);
1537        result.concatenate(transform);
1538        return result;
1539    }
1540
1541    private boolean drawImage(Image img, Image mask, AffineTransform xform, Color bgColor, ImageObserver obs) {
1542        if (xform==null)
1543            xform = new AffineTransform();
1544        else
1545            xform = new AffineTransform(xform);
1546        xform.translate(0, img.getHeight(obs));
1547        xform.scale(img.getWidth(obs), img.getHeight(obs));
1548
1549        AffineTransform inverse = this.normalizeMatrix();
1550        AffineTransform flipper = AffineTransform.getScaleInstance(1,-1);
1551        inverse.concatenate(xform);
1552        inverse.concatenate(flipper);
1553
1554        double[] mx = new double[6];
1555        inverse.getMatrix(mx);
1556        if (currentFillGState != 255) {
1557            PdfGState gs = fillGState[255];
1558            if (gs == null) {
1559                gs = new PdfGState();
1560                gs.setFillOpacity(1);
1561                fillGState[255] = gs;
1562            }
1563            cb.setGState(gs);
1564        }
1565
1566        try {
1567            com.itextpdf.text.Image image = null;
1568            if(!convertImagesToJPEG){
1569                image = com.itextpdf.text.Image.getInstance(img, bgColor);
1570            }
1571            else{
1572                BufferedImage scaled = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_RGB);
1573                Graphics2D g3 = scaled.createGraphics();
1574                g3.drawImage(img, 0, 0, img.getWidth(null), img.getHeight(null), null);
1575                g3.dispose();
1576
1577                ByteArrayOutputStream baos = new ByteArrayOutputStream();
1578                ImageWriteParam iwparam = new JPEGImageWriteParam(Locale.getDefault());
1579                iwparam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
1580                iwparam.setCompressionQuality(jpegQuality);//Set here your compression rate
1581                ImageWriter iw = ImageIO.getImageWritersByFormatName("jpg").next();
1582                ImageOutputStream ios = ImageIO.createImageOutputStream(baos);
1583                iw.setOutput(ios);
1584                iw.write(null, new IIOImage(scaled, null, null), iwparam);
1585                iw.dispose();
1586                ios.close();
1587
1588                scaled.flush();
1589                scaled = null;
1590                image = com.itextpdf.text.Image.getInstance(baos.toByteArray());
1591
1592            }
1593            if (mask!=null) {
1594                com.itextpdf.text.Image msk = com.itextpdf.text.Image.getInstance(mask, null, true);
1595                msk.makeMask();
1596                msk.setInverted(true);
1597                image.setImageMask(msk);
1598            }
1599            cb.addImage(image, (float)mx[0], (float)mx[1], (float)mx[2], (float)mx[3], (float)mx[4], (float)mx[5]);
1600            Object url = getRenderingHint(HyperLinkKey.KEY_INSTANCE);
1601            if (url != null && !url.equals(HyperLinkKey.VALUE_HYPERLINKKEY_OFF)) {
1602                PdfAction action = new  PdfAction(url.toString());
1603                cb.setAction(action, (float)mx[4], (float)mx[5], (float)(mx[0]+mx[4]), (float)(mx[3]+mx[5]));
1604            }
1605        } catch (Exception ex) {
1606            throw new IllegalArgumentException();
1607        }
1608        if (currentFillGState != 255) {
1609            PdfGState gs = fillGState[currentFillGState];
1610            cb.setGState(gs);
1611        }
1612        return true;
1613    }
1614
1615    private boolean checkNewPaint(Paint oldPaint) {
1616        if (paint == oldPaint)
1617            return false;
1618        return !(paint instanceof Color && paint.equals(oldPaint));
1619    }
1620
1621    private void setFillPaint() {
1622        if (checkNewPaint(paintFill)) {
1623            paintFill = paint;
1624            setPaint(false, 0, 0, true);
1625        }
1626    }
1627
1628    private void setStrokePaint() {
1629        if (checkNewPaint(paintStroke)) {
1630            paintStroke = paint;
1631            setPaint(false, 0, 0, false);
1632        }
1633    }
1634
1635    private void setPaint(boolean invert, double xoffset, double yoffset, boolean fill) {
1636        if (paint instanceof Color) {
1637            Color color = (Color)paint;
1638            int alpha = color.getAlpha();
1639            if (fill) {
1640                if (alpha != currentFillGState) {
1641                    currentFillGState = alpha;
1642                    PdfGState gs = fillGState[alpha];
1643                    if (gs == null) {
1644                        gs = new PdfGState();
1645                        gs.setFillOpacity(alpha / 255f);
1646                        fillGState[alpha] = gs;
1647                    }
1648                    cb.setGState(gs);
1649                }
1650                cb.setColorFill(new BaseColor(color));
1651            }
1652            else {
1653                if (alpha != currentStrokeGState) {
1654                    currentStrokeGState = alpha;
1655                    PdfGState gs = strokeGState[alpha];
1656                    if (gs == null) {
1657                        gs = new PdfGState();
1658                        gs.setStrokeOpacity(alpha / 255f);
1659                        strokeGState[alpha] = gs;
1660                    }
1661                    cb.setGState(gs);
1662                }
1663                cb.setColorStroke(new BaseColor(color));
1664            }
1665        }
1666        else if (paint instanceof GradientPaint) {
1667            GradientPaint gp = (GradientPaint)paint;
1668            Point2D p1 = gp.getPoint1();
1669            transform.transform(p1, p1);
1670            Point2D p2 = gp.getPoint2();
1671            transform.transform(p2, p2);
1672            Color c1 = gp.getColor1();
1673            Color c2 = gp.getColor2();
1674            PdfShading shading = PdfShading.simpleAxial(cb.getPdfWriter(), (float)p1.getX(), normalizeY((float)p1.getY()), (float)p2.getX(), normalizeY((float)p2.getY()), new BaseColor(c1), new BaseColor(c2));
1675            PdfShadingPattern pat = new PdfShadingPattern(shading);
1676            if (fill)
1677                cb.setShadingFill(pat);
1678            else
1679                cb.setShadingStroke(pat);
1680        }
1681        else if (paint instanceof TexturePaint) {
1682            try {
1683                TexturePaint tp = (TexturePaint)paint;
1684                BufferedImage img = tp.getImage();
1685                Rectangle2D rect = tp.getAnchorRect();
1686                com.itextpdf.text.Image image = com.itextpdf.text.Image.getInstance(img, null);
1687                PdfPatternPainter pattern = cb.createPattern(image.getWidth(), image.getHeight());
1688                AffineTransform inverse = this.normalizeMatrix();
1689                inverse.translate(rect.getX(), rect.getY());
1690                inverse.scale(rect.getWidth() / image.getWidth(), -rect.getHeight() / image.getHeight());
1691                double[] mx = new double[6];
1692                inverse.getMatrix(mx);
1693                pattern.setPatternMatrix((float)mx[0], (float)mx[1], (float)mx[2], (float)mx[3], (float)mx[4], (float)mx[5]) ;
1694                image.setAbsolutePosition(0,0);
1695                pattern.addImage(image);
1696                if (fill)
1697                    cb.setPatternFill(pattern);
1698                else
1699                    cb.setPatternStroke(pattern);
1700            } catch (Exception ex) {
1701                if (fill)
1702                    cb.setColorFill(BaseColor.GRAY);
1703                else
1704                    cb.setColorStroke(BaseColor.GRAY);
1705            }
1706        }
1707        else {
1708            try {
1709                BufferedImage img = null;
1710                int type = BufferedImage.TYPE_4BYTE_ABGR;
1711                if (paint.getTransparency() == Transparency.OPAQUE) {
1712                    type = BufferedImage.TYPE_3BYTE_BGR;
1713                }
1714                img = new BufferedImage((int)width, (int)height, type);
1715                Graphics2D g = (Graphics2D)img.getGraphics();
1716                g.transform(transform);
1717                AffineTransform inv = transform.createInverse();
1718                Shape fillRect = new Rectangle2D.Double(0,0,img.getWidth(),img.getHeight());
1719                fillRect = inv.createTransformedShape(fillRect);
1720                g.setPaint(paint);
1721                g.fill(fillRect);
1722                if (invert) {
1723                    AffineTransform tx = new AffineTransform();
1724                    tx.scale(1,-1);
1725                    tx.translate(-xoffset,-yoffset);
1726                    g.drawImage(img,tx,null);
1727                }
1728                g.dispose();
1729                g = null;
1730                com.itextpdf.text.Image image = com.itextpdf.text.Image.getInstance(img, null);
1731                PdfPatternPainter pattern = cb.createPattern(width, height);
1732                image.setAbsolutePosition(0,0);
1733                pattern.addImage(image);
1734                if (fill)
1735                    cb.setPatternFill(pattern);
1736                else
1737                    cb.setPatternStroke(pattern);
1738            } catch (Exception ex) {
1739                if (fill)
1740                    cb.setColorFill(BaseColor.GRAY);
1741                else
1742                    cb.setColorStroke(BaseColor.GRAY);
1743            }
1744        }
1745    }
1746
1747    private synchronized void waitForImage(java.awt.Image image) {
1748        if (mediaTracker == null)
1749            mediaTracker = new MediaTracker(new PdfGraphics2D.FakeComponent());
1750        mediaTracker.addImage(image, 0);
1751        try {
1752            mediaTracker.waitForID(0);
1753        }
1754        catch (InterruptedException e) {
1755            // empty on purpose
1756        }
1757        mediaTracker.removeImage(image);
1758    }
1759
1760    static private class FakeComponent extends Component {
1761
1762                private static final long serialVersionUID = 6450197945596086638L;
1763    }
1764
1765    /**
1766     * @since 2.0.8
1767     */
1768    public static class HyperLinkKey extends RenderingHints.Key
1769        {
1770                public static final HyperLinkKey KEY_INSTANCE = new HyperLinkKey(9999);
1771                public static final Object VALUE_HYPERLINKKEY_OFF = "0";
1772
1773                protected HyperLinkKey(int arg0) {
1774                        super(arg0);
1775                }
1776
1777                @Override
1778        public boolean isCompatibleValue(Object val)
1779                {
1780                        return true;
1781                }
1782                @Override
1783        public String toString()
1784                {
1785                        return "HyperLinkKey";
1786                }
1787        }
1788
1789}