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