History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/sun/print/PSPathGraphics.java (1 of 18352 files). Show all

Unified · Split

java/pkg/sun/print/PSPathGraphics.java+780−0
@@ -0,0 +1,780 @@
1+/*
2+ * Copyright (c) 1998, 2008, Oracle and/or its affiliates. All rights reserved.
3+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+ *
5+ * This code is free software; you can redistribute it and/or modify it
6+ * under the terms of the GNU General Public License version 2 only, as
7+ * published by the Free Software Foundation. Oracle designates this
8+ * particular file as subject to the "Classpath" exception as provided
9+ * by Oracle in the LICENSE file that accompanied this code.
10+ *
11+ * This code is distributed in the hope that it will be useful, but WITHOUT
12+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+ * version 2 for more details (a copy is included in the LICENSE file that
15+ * accompanied this code).
16+ *
17+ * You should have received a copy of the GNU General Public License version
18+ * 2 along with this work; if not, write to the Free Software Foundation,
19+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+ *
21+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+ * or visit www.oracle.com if you need additional information or have any
23+ * questions.
24+ */
25+
26+package sun.print;
27+
28+import java.awt.Color;
29+import java.awt.Font;
30+import java.awt.Graphics;
31+import java.awt.Graphics2D;
32+import java.awt.Image;
33+import java.awt.Shape;
34+import java.awt.Transparency;
35+
36+import java.awt.font.FontRenderContext;
37+import java.awt.font.TextLayout;
38+
39+import java.awt.geom.AffineTransform;
40+import java.awt.geom.Area;
41+import java.awt.geom.PathIterator;
42+import java.awt.geom.Point2D;
43+import java.awt.geom.Rectangle2D;
44+import java.awt.geom.Line2D;
45+
46+import java.awt.image.BufferedImage;
47+import sun.awt.image.ByteComponentRaster;
48+
49+import java.awt.print.PageFormat;
50+import java.awt.print.Printable;
51+import java.awt.print.PrinterException;
52+import java.awt.print.PrinterJob;
53+
54+/**
55+ * This class converts paths into PostScript
56+ * by breaking all graphics into fills and
57+ * clips of paths.
58+ */
59+
60+class PSPathGraphics extends PathGraphics {
61+
62+ /**
63+ * For a drawing application the initial user space
64+ * resolution is 72dpi.
65+ */
66+ private static final int DEFAULT_USER_RES = 72;
67+
68+ PSPathGraphics(Graphics2D graphics, PrinterJob printerJob,
69+ Printable painter, PageFormat pageFormat, int pageIndex,
70+ boolean canRedraw) {
71+ super(graphics, printerJob, painter, pageFormat, pageIndex, canRedraw);
72+ }
73+
74+ /**
75+ * Creates a new <code>Graphics</code> object that is
76+ * a copy of this <code>Graphics</code> object.
77+ * @return a new graphics context that is a copy of
78+ * this graphics context.
79+ * @since JDK1.0
80+ */
81+ public Graphics create() {
82+
83+ return new PSPathGraphics((Graphics2D) getDelegate().create(),
84+ getPrinterJob(),
85+ getPrintable(),
86+ getPageFormat(),
87+ getPageIndex(),
88+ canDoRedraws());
89+ }
90+
91+
92+ /**
93+ * Override the inherited implementation of fill
94+ * so that we can generate PostScript in user space
95+ * rather than device space.
96+ */
97+ public void fill(Shape s, Color color) {
98+ deviceFill(s.getPathIterator(new AffineTransform()), color);
99+ }
100+
101+ /**
102+ * Draws the text given by the specified string, using this
103+ * graphics context's current font and color. The baseline of the
104+ * first character is at position (<i>x</i>,&nbsp;<i>y</i>) in this
105+ * graphics context's coordinate system.
106+ * @param str the string to be drawn.
107+ * @param x the <i>x</i> coordinate.
108+ * @param y the <i>y</i> coordinate.
109+ * @see java.awt.Graphics#drawBytes
110+ * @see java.awt.Graphics#drawChars
111+ * @since JDK1.0
112+ */
113+ public void drawString(String str, int x, int y) {
114+ drawString(str, (float) x, (float) y);
115+ }
116+
117+ /**
118+ * Renders the text specified by the specified <code>String</code>,
119+ * using the current <code>Font</code> and <code>Paint</code> attributes
120+ * in the <code>Graphics2D</code> context.
121+ * The baseline of the first character is at position
122+ * (<i>x</i>,&nbsp;<i>y</i>) in the User Space.
123+ * The rendering attributes applied include the <code>Clip</code>,
124+ * <code>Transform</code>, <code>Paint</code>, <code>Font</code> and
125+ * <code>Composite</code> attributes. For characters in script systems
126+ * such as Hebrew and Arabic, the glyphs can be rendered from right to
127+ * left, in which case the coordinate supplied is the location of the
128+ * leftmost character on the baseline.
129+ * @param s the <code>String</code> to be rendered
130+ * @param x,&nbsp;y the coordinates where the <code>String</code>
131+ * should be rendered
132+ * @see #setPaint
133+ * @see java.awt.Graphics#setColor
134+ * @see java.awt.Graphics#setFont
135+ * @see #setTransform
136+ * @see #setComposite
137+ * @see #setClip
138+ */
139+ public void drawString(String str, float x, float y) {
140+ drawString(str, x, y, getFont(), getFontRenderContext(), 0f);
141+ }
142+
143+
144+ protected boolean canDrawStringToWidth() {
145+ return true;
146+ }
147+
148+ protected int platformFontCount(Font font, String str) {
149+ PSPrinterJob psPrinterJob = (PSPrinterJob) getPrinterJob();
150+ return psPrinterJob.platformFontCount(font, str);
151+ }
152+
153+ protected void drawString(String str, float x, float y,
154+ Font font, FontRenderContext frc, float w) {
155+ if (str.length() == 0) {
156+ return;
157+ }
158+
159+ /* If the Font has layout attributes we need to delegate to TextLayout.
160+ * TextLayout renders text as GlyphVectors. We try to print those
161+ * using printer fonts - ie using Postscript text operators so
162+ * we may be reinvoked. In that case the "!printingGlyphVector" test
163+ * prevents us recursing and instead sends us into the body of the
164+ * method where we can safely ignore layout attributes as those
165+ * are already handled by TextLayout.
166+ */
167+ if (font.hasLayoutAttributes() && !printingGlyphVector) {
168+ TextLayout layout = new TextLayout(str, font, frc);
169+ layout.draw(this, x, y);
170+ return;
171+ }
172+
173+ Font oldFont = getFont();
174+ if (!oldFont.equals(font)) {
175+ setFont(font);
176+ } else {
177+ oldFont = null;
178+ }
179+
180+ boolean drawnWithPS = false;
181+
182+ float translateX = 0f, translateY = 0f;
183+ boolean fontisTransformed = getFont().isTransformed();
184+
185+ if (fontisTransformed) {
186+ AffineTransform fontTx = getFont().getTransform();
187+ int transformType = fontTx.getType();
188+ /* TYPE_TRANSLATION is a flag bit but we can do "==" here
189+ * because we want to detect when its just that bit set and
190+ *
191+ */
192+ if (transformType == AffineTransform.TYPE_TRANSLATION) {
193+ translateX = (float)(fontTx.getTranslateX());
194+ translateY = (float)(fontTx.getTranslateY());
195+ if (Math.abs(translateX) < 0.00001) translateX = 0f;
196+ if (Math.abs(translateY) < 0.00001) translateY = 0f;
197+ fontisTransformed = false;
198+ }
199+ }
200+
201+ boolean directToPS = !fontisTransformed;
202+
203+ if (!PSPrinterJob.shapeTextProp && directToPS) {
204+
205+ PSPrinterJob psPrinterJob = (PSPrinterJob) getPrinterJob();
206+ if (psPrinterJob.setFont(getFont())) {
207+
208+ /* Set the text color.
209+ * We should not be in this shape printing path
210+ * if the application is drawing with non-solid
211+ * colors. We should be in the raster path. Because
212+ * we are here in the shape path, the cast of the
213+ * paint to a Color should be fine.
214+ */
215+ try {
216+ psPrinterJob.setColor((Color)getPaint());
217+ } catch (ClassCastException e) {
218+ if (oldFont != null) {
219+ setFont(oldFont);
220+ }
221+ throw new IllegalArgumentException(
222+ "Expected a Color instance");
223+ }
224+
225+ psPrinterJob.setTransform(getTransform());
226+ psPrinterJob.setClip(getClip());
227+
228+ drawnWithPS = psPrinterJob.textOut(this, str,
229+ x+translateX, y+translateY,
230+ font, frc, w);
231+ }
232+ }
233+
234+ /* The text could not be converted directly to PS text
235+ * calls so decompose the text into a shape.
236+ */
237+ if (drawnWithPS == false) {
238+ if (oldFont != null) {
239+ setFont(oldFont);
240+ oldFont = null;
241+ }
242+ super.drawString(str, x, y, font, frc, w);
243+ }
244+
245+ if (oldFont != null) {
246+ setFont(oldFont);
247+ }
248+ }
249+
250+ /**
251+ * The various <code>drawImage()</code> methods for
252+ * <code>WPathGraphics</code> are all decomposed
253+ * into an invocation of <code>drawImageToPlatform</code>.
254+ * The portion of the passed in image defined by
255+ * <code>srcX, srcY, srcWidth, and srcHeight</code>
256+ * is transformed by the supplied AffineTransform and
257+ * drawn using PS to the printer context.
258+ *
259+ * @param img The image to be drawn.
260+ * This method does nothing if <code>img</code> is null.
261+ * @param xform Used to tranform the image before drawing.
262+ * This can be null.
263+ * @param bgcolor This color is drawn where the image has transparent
264+ * pixels. If this parameter is null then the
265+ * pixels already in the destination should show
266+ * through.
267+ * @param srcX With srcY this defines the upper-left corner
268+ * of the portion of the image to be drawn.
269+ *
270+ * @param srcY With srcX this defines the upper-left corner
271+ * of the portion of the image to be drawn.
272+ * @param srcWidth The width of the portion of the image to
273+ * be drawn.
274+ * @param srcHeight The height of the portion of the image to
275+ * be drawn.
276+ * @param handlingTransparency if being recursively called to
277+ * print opaque region of transparent image
278+ */
279+ protected boolean drawImageToPlatform(Image image, AffineTransform xform,
280+ Color bgcolor,
281+ int srcX, int srcY,
282+ int srcWidth, int srcHeight,
283+ boolean handlingTransparency) {
284+
285+ BufferedImage img = getBufferedImage(image);
286+ if (img == null) {
287+ return true;
288+ }
289+
290+ PSPrinterJob psPrinterJob = (PSPrinterJob) getPrinterJob();
291+
292+ /* The full transform to be applied to the image is the
293+ * caller's transform concatenated on to the transform
294+ * from user space to device space. If the caller didn't
295+ * supply a transform then we just act as if they passed
296+ * in the identify transform.
297+ */
298+ AffineTransform fullTransform = getTransform();
299+ if (xform == null) {
300+ xform = new AffineTransform();
301+ }
302+ fullTransform.concatenate(xform);
303+
304+ /* Split the full transform into a pair of
305+ * transforms. The first transform holds effects
306+ * such as rotation and shearing. The second transform
307+ * is setup to hold only the scaling effects.
308+ * These transforms are created such that a point,
309+ * p, in user space, when transformed by 'fullTransform'
310+ * lands in the same place as when it is transformed
311+ * by 'rotTransform' and then 'scaleTransform'.
312+ *
313+ * The entire image transformation is not in Java in order
314+ * to minimize the amount of memory needed in the VM. By
315+ * dividing the transform in two, we rotate and shear
316+ * the source image in its own space and only go to
317+ * the, usually, larger, device space when we ask
318+ * PostScript to perform the final scaling.
319+ */
320+ double[] fullMatrix = new double[6];
321+ fullTransform.getMatrix(fullMatrix);
322+
323+ /* Calculate the amount of scaling in the x
324+ * and y directions. This scaling is computed by
325+ * transforming a unit vector along each axis
326+ * and computing the resulting magnitude.
327+ * The computed values 'scaleX' and 'scaleY'
328+ * represent the amount of scaling PS will be asked
329+ * to perform.
330+ * Clamp this to the device scale for better quality printing.
331+ */
332+ Point2D.Float unitVectorX = new Point2D.Float(1, 0);
333+ Point2D.Float unitVectorY = new Point2D.Float(0, 1);
334+ fullTransform.deltaTransform(unitVectorX, unitVectorX);
335+ fullTransform.deltaTransform(unitVectorY, unitVectorY);
336+
337+ Point2D.Float origin = new Point2D.Float(0, 0);
338+ double scaleX = unitVectorX.distance(origin);
339+ double scaleY = unitVectorY.distance(origin);
340+
341+ double devResX = psPrinterJob.getXRes();
342+ double devResY = psPrinterJob.getYRes();
343+ double devScaleX = devResX / DEFAULT_USER_RES;
344+ double devScaleY = devResY / DEFAULT_USER_RES;
345+
346+ /* check if rotated or sheared */
347+ int transformType = fullTransform.getType();
348+ boolean clampScale = ((transformType &
349+ (AffineTransform.TYPE_GENERAL_ROTATION |
350+ AffineTransform.TYPE_GENERAL_TRANSFORM)) != 0);
351+ if (clampScale) {
352+ if (scaleX > devScaleX) scaleX = devScaleX;
353+ if (scaleY > devScaleY) scaleY = devScaleY;
354+ }
355+
356+ /* We do not need to draw anything if either scaling
357+ * factor is zero.
358+ */
359+ if (scaleX != 0 && scaleY != 0) {
360+
361+ /* Here's the transformation we will do with Java2D,
362+ */
363+ AffineTransform rotTransform = new AffineTransform(
364+ fullMatrix[0] / scaleX, //m00
365+ fullMatrix[1] / scaleY, //m10
366+ fullMatrix[2] / scaleX, //m01
367+ fullMatrix[3] / scaleY, //m11
368+ fullMatrix[4] / scaleX, //m02
369+ fullMatrix[5] / scaleY); //m12
370+
371+ /* The scale transform is not used directly: we instead
372+ * directly multiply by scaleX and scaleY.
373+ *
374+ * Conceptually here is what the scaleTransform is:
375+ *
376+ * AffineTransform scaleTransform = new AffineTransform(
377+ * scaleX, //m00
378+ * 0, //m10
379+ * 0, //m01
380+ * scaleY, //m11
381+ * 0, //m02
382+ * 0); //m12
383+ */
384+
385+ /* Convert the image source's rectangle into the rotated
386+ * and sheared space. Once there, we calculate a rectangle
387+ * that encloses the resulting shape. It is this rectangle
388+ * which defines the size of the BufferedImage we need to
389+ * create to hold the transformed image.
390+ */
391+ Rectangle2D.Float srcRect = new Rectangle2D.Float(srcX, srcY,
392+ srcWidth,
393+ srcHeight);
394+
395+ Shape rotShape = rotTransform.createTransformedShape(srcRect);
396+ Rectangle2D rotBounds = rotShape.getBounds2D();
397+
398+ /* add a fudge factor as some fp precision problems have
399+ * been observed which caused pixels to be rounded down and
400+ * out of the image.
401+ */
402+ rotBounds.setRect(rotBounds.getX(), rotBounds.getY(),
403+ rotBounds.getWidth()+0.001,
404+ rotBounds.getHeight()+0.001);
405+
406+ int boundsWidth = (int) rotBounds.getWidth();
407+ int boundsHeight = (int) rotBounds.getHeight();
408+
409+ if (boundsWidth > 0 && boundsHeight > 0) {
410+
411+
412+ /* If the image has transparent or semi-transparent
413+ * pixels then we'll have the application re-render
414+ * the portion of the page covered by the image.
415+ * This will be done in a later call to print using the
416+ * saved graphics state.
417+ * However several special cases can be handled otherwise:
418+ * - bitmask transparency with a solid background colour
419+ * - images which have transparency color models but no
420+ * transparent pixels
421+ * - images with bitmask transparency and an IndexColorModel
422+ * (the common transparent GIF case) can be handled by
423+ * rendering just the opaque pixels.
424+ */
425+ boolean drawOpaque = true;
426+ if (!handlingTransparency && hasTransparentPixels(img)) {
427+ drawOpaque = false;
428+ if (isBitmaskTransparency(img)) {
429+ if (bgcolor == null) {
430+ if (drawBitmaskImage(img, xform, bgcolor,
431+ srcX, srcY,
432+ srcWidth, srcHeight)) {
433+ // image drawn, just return.
434+ return true;
435+ }
436+ } else if (bgcolor.getTransparency()
437+ == Transparency.OPAQUE) {
438+ drawOpaque = true;
439+ }
440+ }
441+ if (!canDoRedraws()) {
442+ drawOpaque = true;
443+ }
444+ } else {
445+ // if there's no transparent pixels there's no need
446+ // for a background colour. This can avoid edge artifacts
447+ // in rotation cases.
448+ bgcolor = null;
449+ }
450+ // if src region extends beyond the image, the "opaque" path
451+ // may blit b/g colour (including white) where it shoudn't.
452+ if ((srcX+srcWidth > img.getWidth(null) ||
453+ srcY+srcHeight > img.getHeight(null))
454+ && canDoRedraws()) {
455+ drawOpaque = false;
456+ }
457+ if (drawOpaque == false) {
458+
459+ fullTransform.getMatrix(fullMatrix);
460+ AffineTransform tx =
461+ new AffineTransform(
462+ fullMatrix[0] / devScaleX, //m00
463+ fullMatrix[1] / devScaleY, //m10
464+ fullMatrix[2] / devScaleX, //m01
465+ fullMatrix[3] / devScaleY, //m11
466+ fullMatrix[4] / devScaleX, //m02
467+ fullMatrix[5] / devScaleY); //m12
468+
469+ Rectangle2D.Float rect =
470+ new Rectangle2D.Float(srcX, srcY, srcWidth, srcHeight);
471+
472+ Shape shape = fullTransform.createTransformedShape(rect);
473+ // Region isn't user space because its potentially
474+ // been rotated for landscape.
475+ Rectangle2D region = shape.getBounds2D();
476+
477+ region.setRect(region.getX(), region.getY(),
478+ region.getWidth()+0.001,
479+ region.getHeight()+0.001);
480+
481+ // Try to limit the amount of memory used to 8Mb, so
482+ // if at device resolution this exceeds a certain
483+ // image size then scale down the region to fit in
484+ // that memory, but never to less than 72 dpi.
485+
486+ int w = (int)region.getWidth();
487+ int h = (int)region.getHeight();
488+ int nbytes = w * h * 3;
489+ int maxBytes = 8 * 1024 * 1024;
490+ double origDpi = (devResX < devResY) ? devResX : devResY;
491+ int dpi = (int)origDpi;
492+ double scaleFactor = 1;
493+
494+ double maxSFX = w/(double)boundsWidth;
495+ double maxSFY = h/(double)boundsHeight;
496+ double maxSF = (maxSFX > maxSFY) ? maxSFY : maxSFX;
497+ int minDpi = (int)(dpi/maxSF);
498+ if (minDpi < DEFAULT_USER_RES) minDpi = DEFAULT_USER_RES;
499+
500+ while (nbytes > maxBytes && dpi > minDpi) {
501+ scaleFactor *= 2;
502+ dpi /= 2;
503+ nbytes /= 4;
504+ }
505+ if (dpi < minDpi) {
506+ scaleFactor = (origDpi / minDpi);
507+ }
508+
509+ region.setRect(region.getX()/scaleFactor,
510+ region.getY()/scaleFactor,
511+ region.getWidth()/scaleFactor,
512+ region.getHeight()/scaleFactor);
513+
514+ /*
515+ * We need to have the clip as part of the saved state,
516+ * either directly, or all the components that are
517+ * needed to reconstitute it (image source area,
518+ * image transform and current graphics transform).
519+ * The clip is described in user space, so we need to
520+ * save the current graphics transform anyway so just
521+ * save these two.
522+ */
523+ psPrinterJob.saveState(getTransform(), getClip(),
524+ region, scaleFactor, scaleFactor);
525+ return true;
526+
527+ /* The image can be rendered directly by PS so we
528+ * copy it into a BufferedImage (this takes care of
529+ * ColorSpace and BufferedImageOp issues) and then
530+ * send that to PS.
531+ */
532+ } else {
533+
534+ /* Create a buffered image big enough to hold the portion
535+ * of the source image being printed.
536+ */
537+ BufferedImage deepImage = new BufferedImage(
538+ (int) rotBounds.getWidth(),
539+ (int) rotBounds.getHeight(),
540+ BufferedImage.TYPE_3BYTE_BGR);
541+
542+ /* Setup a Graphics2D on to the BufferedImage so that the
543+ * source image when copied, lands within the image buffer.
544+ */
545+ Graphics2D imageGraphics = deepImage.createGraphics();
546+ imageGraphics.clipRect(0, 0,
547+ deepImage.getWidth(),
548+ deepImage.getHeight());
549+
550+ imageGraphics.translate(-rotBounds.getX(),
551+ -rotBounds.getY());
552+ imageGraphics.transform(rotTransform);
553+
554+ /* Fill the BufferedImage either with the caller supplied
555+ * color, 'bgColor' or, if null, with white.
556+ */
557+ if (bgcolor == null) {
558+ bgcolor = Color.white;
559+ }
560+
561+ /* REMIND: no need to use scaling here. */
562+ imageGraphics.drawImage(img,
563+ srcX, srcY,
564+ srcX + srcWidth, srcY + srcHeight,
565+ srcX, srcY,
566+ srcX + srcWidth, srcY + srcHeight,
567+ bgcolor, null);
568+
569+ /* In PSPrinterJob images are printed in device space
570+ * and therefore we need to set a device space clip.
571+ * FIX: this is an overly tight coupling of these
572+ * two classes.
573+ * The temporary clip set needs to be an intersection
574+ * with the previous user clip.
575+ * REMIND: two xfms may lose accuracy in clip path.
576+ */
577+ Shape holdClip = getClip();
578+ Shape oldClip =
579+ getTransform().createTransformedShape(holdClip);
580+ AffineTransform sat = AffineTransform.getScaleInstance(
581+ scaleX, scaleY);
582+ Shape imgClip = sat.createTransformedShape(rotShape);
583+ Area imgArea = new Area(imgClip);
584+ Area oldArea = new Area(oldClip);
585+ imgArea.intersect(oldArea);
586+ psPrinterJob.setClip(imgArea);
587+
588+ /* Scale the bounding rectangle by the scale transform.
589+ * Because the scaling transform has only x and y
590+ * scaling components it is equivalent to multiply
591+ * the x components of the bounding rectangle by
592+ * the x scaling factor and to multiply the y components
593+ * by the y scaling factor.
594+ */
595+ Rectangle2D.Float scaledBounds
596+ = new Rectangle2D.Float(
597+ (float) (rotBounds.getX() * scaleX),
598+ (float) (rotBounds.getY() * scaleY),
599+ (float) (rotBounds.getWidth() * scaleX),
600+ (float) (rotBounds.getHeight() * scaleY));
601+
602+
603+ /* Pull the raster data from the buffered image
604+ * and pass it along to PS.
605+ */
606+ ByteComponentRaster tile =
607+ (ByteComponentRaster)deepImage.getRaster();
608+
609+ psPrinterJob.drawImageBGR(tile.getDataStorage(),
610+ scaledBounds.x, scaledBounds.y,
611+ (float)Math.rint(scaledBounds.width+0.5),
612+ (float)Math.rint(scaledBounds.height+0.5),
613+ 0f, 0f,
614+ deepImage.getWidth(), deepImage.getHeight(),
615+ deepImage.getWidth(), deepImage.getHeight());
616+
617+ /* Reset the device clip to match user clip */
618+ psPrinterJob.setClip(
619+ getTransform().createTransformedShape(holdClip));
620+
621+
622+ imageGraphics.dispose();
623+ }
624+
625+ }
626+ }
627+
628+ return true;
629+ }
630+
631+ /** Redraw a rectanglular area using a proxy graphics
632+ * To do this we need to know the rectangular area to redraw and
633+ * the transform & clip in effect at the time of the original drawImage
634+ *
635+ */
636+
637+ public void redrawRegion(Rectangle2D region, double scaleX, double scaleY,
638+ Shape savedClip, AffineTransform savedTransform)
639+
640+ throws PrinterException {
641+
642+ PSPrinterJob psPrinterJob = (PSPrinterJob)getPrinterJob();
643+ Printable painter = getPrintable();
644+ PageFormat pageFormat = getPageFormat();
645+ int pageIndex = getPageIndex();
646+
647+ /* Create a buffered image big enough to hold the portion
648+ * of the source image being printed.
649+ */
650+ BufferedImage deepImage = new BufferedImage(
651+ (int) region.getWidth(),
652+ (int) region.getHeight(),
653+ BufferedImage.TYPE_3BYTE_BGR);
654+
655+ /* Get a graphics for the application to render into.
656+ * We initialize the buffer to white in order to
657+ * match the paper and then we shift the BufferedImage
658+ * so that it covers the area on the page where the
659+ * caller's Image will be drawn.
660+ */
661+ Graphics2D g = deepImage.createGraphics();
662+ ProxyGraphics2D proxy = new ProxyGraphics2D(g, psPrinterJob);
663+ proxy.setColor(Color.white);
664+ proxy.fillRect(0, 0, deepImage.getWidth(), deepImage.getHeight());
665+ proxy.clipRect(0, 0, deepImage.getWidth(), deepImage.getHeight());
666+
667+ proxy.translate(-region.getX(), -region.getY());
668+
669+ /* Calculate the resolution of the source image.
670+ */
671+ float sourceResX = (float)(psPrinterJob.getXRes() / scaleX);
672+ float sourceResY = (float)(psPrinterJob.getYRes() / scaleY);
673+
674+ /* The application expects to see user space at 72 dpi.
675+ * so change user space from image source resolution to
676+ * 72 dpi.
677+ */
678+ proxy.scale(sourceResX / DEFAULT_USER_RES,
679+ sourceResY / DEFAULT_USER_RES);
680+ proxy.translate(
681+ -psPrinterJob.getPhysicalPrintableX(pageFormat.getPaper())
682+ / psPrinterJob.getXRes() * DEFAULT_USER_RES,
683+ -psPrinterJob.getPhysicalPrintableY(pageFormat.getPaper())
684+ / psPrinterJob.getYRes() * DEFAULT_USER_RES);
685+ /* NB User space now has to be at 72 dpi for this calc to be correct */
686+ proxy.transform(new AffineTransform(getPageFormat().getMatrix()));
687+
688+ proxy.setPaint(Color.black);
689+
690+ painter.print(proxy, pageFormat, pageIndex);
691+
692+ g.dispose();
693+
694+ /* In PSPrinterJob images are printed in device space
695+ * and therefore we need to set a device space clip.
696+ */
697+ psPrinterJob.setClip(savedTransform.createTransformedShape(savedClip));
698+
699+
700+ /* Scale the bounding rectangle by the scale transform.
701+ * Because the scaling transform has only x and y
702+ * scaling components it is equivalent to multiply
703+ * the x components of the bounding rectangle by
704+ * the x scaling factor and to multiply the y components
705+ * by the y scaling factor.
706+ */
707+ Rectangle2D.Float scaledBounds
708+ = new Rectangle2D.Float(
709+ (float) (region.getX() * scaleX),
710+ (float) (region.getY() * scaleY),
711+ (float) (region.getWidth() * scaleX),
712+ (float) (region.getHeight() * scaleY));
713+
714+
715+ /* Pull the raster data from the buffered image
716+ * and pass it along to PS.
717+ */
718+ ByteComponentRaster tile = (ByteComponentRaster)deepImage.getRaster();
719+
720+ psPrinterJob.drawImageBGR(tile.getDataStorage(),
721+ scaledBounds.x, scaledBounds.y,
722+ scaledBounds.width,
723+ scaledBounds.height,
724+ 0f, 0f,
725+ deepImage.getWidth(), deepImage.getHeight(),
726+ deepImage.getWidth(), deepImage.getHeight());
727+
728+
729+ }
730+
731+
732+ /*
733+ * Fill the path defined by <code>pathIter</code>
734+ * with the specified color.
735+ * The path is provided in current user space.
736+ */
737+ protected void deviceFill(PathIterator pathIter, Color color) {
738+
739+ PSPrinterJob psPrinterJob = (PSPrinterJob) getPrinterJob();
740+ psPrinterJob.deviceFill(pathIter, color, getTransform(), getClip());
741+ }
742+
743+ /*
744+ * Draw the bounding rectangle using path by calling draw()
745+ * function and passing a rectangle shape.
746+ */
747+ protected void deviceFrameRect(int x, int y, int width, int height,
748+ Color color) {
749+
750+ draw(new Rectangle2D.Float(x, y, width, height));
751+ }
752+
753+ /*
754+ * Draw a line using path by calling draw() function and passing
755+ * a line shape.
756+ */
757+ protected void deviceDrawLine(int xBegin, int yBegin,
758+ int xEnd, int yEnd, Color color) {
759+
760+ draw(new Line2D.Float(xBegin, yBegin, xEnd, yEnd));
761+ }
762+
763+ /*
764+ * Fill the rectangle with the specified color by calling fill().
765+ */
766+ protected void deviceFillRect(int x, int y, int width, int height,
767+ Color color) {
768+ fill(new Rectangle2D.Float(x, y, width, height));
769+ }
770+
771+
772+ /*
773+ * This method should not be invoked by PSPathGraphics.
774+ * FIX: Rework PathGraphics so that this method is
775+ * not an abstract method there.
776+ */
777+ protected void deviceClip(PathIterator pathIter) {
778+ }
779+
780+}