View Javadoc

1   package org.freehep.graphicsio.emf;
2   
3   import java.io.IOException;
4   import java.awt.Graphics2D;
5   import java.awt.Dimension;
6   import java.awt.Point;
7   import java.awt.BasicStroke;
8   import java.awt.Stroke;
9   import java.awt.Paint;
10  import java.awt.Color;
11  import java.awt.Shape;
12  import java.awt.AlphaComposite;
13  import java.awt.Image;
14  import java.awt.RenderingHints;
15  import java.awt.Font;
16  import java.awt.image.BufferedImage;
17  import java.awt.font.TextLayout;
18  import java.awt.geom.AffineTransform;
19  import java.awt.geom.GeneralPath;
20  import java.util.logging.Logger;
21  import java.util.Stack;
22  import java.util.Vector;
23  import java.util.Map;
24  
25  import org.freehep.util.io.Tag;
26  import org.freehep.graphicsio.emf.gdi.GDIObject;
27  
28  /**
29   * Standalone EMF renderer.
30   *
31   * @author Daniel Noll (daniel@nuix.com)
32   * @version $Id$
33   */
34  public class EMFRenderer {
35      private static final Logger logger = Logger.getLogger("org.freehep.graphicsio.emf");
36  
37      /**
38       * Header read from the EMFInputStream
39       */
40      private EMFHeader header;
41  
42      /**
43       * Each logical unit is mapped to one twentieth of a
44       * printer's point (1/1440 inch, also called a twip).
45       */
46      public static double TWIP_SCALE = 1d / 1440 * 254;
47  
48      /**
49       * affect by all XXXTo methods, e.g. LinTo. ExtMoveTo creates the
50       * starting point. CloseFigure closes the figure.
51       */
52      private GeneralPath figure = null;
53  
54      /**
55       * AffineTransform which is the base for all rendering
56       * operations.
57       */
58      private AffineTransform initialTransform;
59  
60      /**
61       * origin of the emf window, set by SetWindowOrgEx
62       */
63      private Point windowOrigin = null;
64  
65      /**
66       * origin of the emf viewport, set By SetViewportOrgEx
67       */
68      private Point viewportOrigin = null;
69  
70      /**
71       * size of the emf window, set by SetWindowExtEx
72       */
73      private Dimension windowSize = null;
74  
75      /**
76       * size of the emf viewport, set by SetViewportExtEx
77       */
78      private Dimension viewportSize = null;
79  
80      /**
81       * The MM_ISOTROPIC mode ensures a 1:1 aspect ratio.
82       *  The MM_ANISOTROPIC mode allows the x-coordinates
83       * and y-coordinates to be adjusted independently.
84       */
85      private boolean mapModeIsotropic = false;
86  
87      /**
88       * AffineTransform defined by SetMapMode. Used for
89       * resizing the emf to propper device bounds.
90       */
91      private AffineTransform  mapModeTransform =
92          AffineTransform.getScaleInstance(TWIP_SCALE, TWIP_SCALE);
93  
94  
95      /**
96       * clipping area which is the base for all rendering
97       * operations.
98       */
99      private Shape initialClip;
100 
101     /**
102      * current Graphics2D to paint on. It is set during
103      * {@link #paint(java.awt.Graphics2D)}
104      */
105     private Graphics2D g2;
106 
107     /**
108      * objects used by {@link org.freehep.graphicsio.emf.gdi.SelectObject}.
109      * The array is filled by CreateXXX functions, e.g.
110      * {@link org.freehep.graphicsio.emf.gdi.CreatePen}
111      */
112     private GDIObject[] gdiObjects = new GDIObject[256]; // TODO: Make this more flexible.
113 
114     // Rendering state.
115     private Paint brushPaint = new Color(0, 0, 0, 0);
116     private Paint penPaint = Color.BLACK;
117     private Stroke penStroke = new BasicStroke();
118 
119     private int textAlignMode = 0;
120 
121     /**
122      * color for simple text rendering
123      */
124     private Color textColor = Color.BLACK;
125 
126     /**
127      * written by {@link org.freehep.graphicsio.emf.gdi.SetPolyFillMode} used by
128      * e.g. {@link org.freehep.graphicsio.emf.gdi.PolyPolygon16}
129      */
130     private int windingRule = GeneralPath.WIND_EVEN_ODD;
131 
132     /**
133      * Defined by SetBkModes, either {@link EMFConstants#BKG_OPAQUE} or
134      * {@link EMFConstants#BKG_TRANSPARENT}. Used in
135      * {@link #fillAndDrawOrAppend(java.awt.Graphics2D, java.awt.Shape)}
136      */
137     private int bkMode = EMFConstants.BKG_OPAQUE;
138 
139     /**
140      * The SetBkMode function affects the line styles for lines drawn using a
141      * pen created by the CreatePen function. SetBkMode does not affect lines
142      * drawn using a pen created by the ExtCreatePen function.
143      */
144     private boolean useCreatePen = true;
145 
146     /**
147      * The miter length is defined as the distance from the intersection
148      * of the line walls on the inside of the join to the intersection of
149      * the line walls on the outside of the join. The miter limit is the
150      * maximum allowed ratio of the miter length to the line width.
151      */
152     private int meterLimit = 10;
153 
154     /**
155      * The SetROP2 function sets the current foreground mix mode.
156      * Default is to use the pen.
157      */
158     private int rop2 = EMFConstants.R2_COPYPEN;
159 
160     /**
161      * e.g. {@link Image#SCALE_SMOOTH} for rendering images
162      */
163     private int scaleMode = Image.SCALE_SMOOTH;
164 
165     /**
166      * The brush origin is a pair of coordinates specifying the location of one
167      * pixel in the bitmap. The default brush origin coordinates are (0,0). For
168      * horizontal coordinates, the value 0 corresponds to the leftmost column
169      * of pixels; the width corresponds to the rightmost column. For vertical
170      * coordinates, the value 0 corresponds to the uppermost row of pixels;
171      * the height corresponds to the lowermost row.
172      */
173     private Point brushOrigin = new Point(0, 0);
174 
175     /**
176      * stores the parsed tags. Filled by the constructor. Read by
177      * {@link #paint(java.awt.Graphics2D)}
178      */
179     private Vector tags = new Vector(0);
180 
181     /**
182      * Created by BeginPath and closed by EndPath.
183      */
184     private GeneralPath path = null;
185 
186     /**
187      * The transformations set by ModifyWorldTransform are redirected to
188      * that AffineTransform. They do not affect the current paint context,
189      * after BeginPath is called. Only the figures appended to path
190      * are transformed by this AffineTransform.
191      * BeginPath clears the transformation, ModifyWorldTransform changes ist.
192      */
193     private AffineTransform pathTransform = new AffineTransform();
194 
195     /**
196      * {@link org.freehep.graphicsio.emf.gdi.SaveDC} stores
197      * an Instance of DC if saveDC is read. RestoreDC pops an object.
198      */
199     private Stack dcStack = new Stack();
200 
201     /**
202      * default direction is counterclockwise
203      */
204     private int arcDirection = EMFConstants.AD_COUNTERCLOCKWISE;
205 
206     /**
207      * Class the encapsulate the state of a Graphics2D object.
208      * Instances are store in dcStack by
209      * {@link org.freehep.graphicsio.emf.EMFRenderer#paint(java.awt.Graphics2D)}
210      */
211     private class DC {
212         private Paint paint;
213         private Stroke stroke;
214         private AffineTransform transform;
215         private Shape clip;
216         public GeneralPath path;
217         public int bkMode;
218         public int windingRule;
219         public int meterLimit;
220         public boolean useCreatePen;
221         public int scaleMode;
222         public AffineTransform pathTransform;
223     }
224 
225     /**
226      * Constructs the renderer.
227      *
228      * @param is the input stream to read the EMF records from.
229      * @throws IOException if an error occurs reading the header.
230      */
231     public EMFRenderer(EMFInputStream is) throws IOException {
232         this.header = is.readHeader();
233 
234         // read all tags
235         Tag tag;
236         while ((tag = is.readTag()) != null) {
237             tags.add(tag);
238         }
239         is.close();
240     }
241 
242     /**
243      * Gets the size of a canvas which would be required to render the EMF.
244      *
245      * @return the size.
246      */
247     public Dimension getSize() {
248         return header.getBounds().getSize();
249         // TODO see the mapModePart of resetTransformation()
250         // if uncommented size is too small
251         /* Dimension bounds = header.getBounds().getSize();
252             return new Dimension(
253             (int)Math.ceil(bounds.width * TWIP_SCALE),
254             (int)Math.ceil(bounds.height * TWIP_SCALE));*/
255     }
256 
257     /**
258      * Paints the EMF onto the provided graphics context.
259      *
260      * @param g2 the graphics context to paint onto.
261      */
262     public void paint(Graphics2D g2) {
263         this.g2 = g2;
264 
265         // store at leat clip and transformation
266         Shape clip = g2.getClip();
267         AffineTransform at = g2.getTransform();
268         Map hints = g2.getRenderingHints();
269 
270         // some quality settings
271         g2.setRenderingHint(
272             RenderingHints.KEY_RENDERING,
273             RenderingHints.VALUE_RENDER_QUALITY);
274         g2.setRenderingHint(
275             RenderingHints.KEY_ANTIALIASING,
276             RenderingHints.VALUE_ANTIALIAS_ON);
277         g2.setRenderingHint(
278             RenderingHints.KEY_FRACTIONALMETRICS,
279             RenderingHints.VALUE_FRACTIONALMETRICS_ON);
280         g2.setRenderingHint(
281             RenderingHints.KEY_INTERPOLATION,
282             RenderingHints.VALUE_INTERPOLATION_BICUBIC);
283         g2.setRenderingHint(
284             RenderingHints.KEY_TEXT_ANTIALIASING,
285             RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
286 
287         // used by SetWorldTransform to reset transformation
288         initialTransform = g2.getTransform();
289 
290         // set the initial value, defaults for EMF
291         path = null;
292         figure = null;
293         meterLimit = 10;
294         windingRule = GeneralPath.WIND_EVEN_ODD;
295         bkMode = EMFConstants.BKG_OPAQUE;
296         useCreatePen = true;
297         scaleMode = Image.SCALE_SMOOTH;
298 
299         windowOrigin = null;
300         viewportOrigin = null;
301         windowSize = null;
302         viewportSize = null;
303 
304         mapModeIsotropic = false;
305         mapModeTransform = AffineTransform.getScaleInstance(
306             TWIP_SCALE, TWIP_SCALE);
307 
308         // apply all default settings
309         resetTransformation(g2);
310 
311         // determin initial clip after all basic transformations
312         initialClip = g2.getClip();
313 
314         // iterate and render all tags
315         Tag tag;
316         for (int i = 0; i < tags.size(); i++) {
317             tag = (Tag) tags.get(i);
318             if (tag instanceof EMFTag) {
319                 ((EMFTag) tags.get(i)).render(this);
320             } else {
321                 logger.warning("unknown tag: " + tag);
322             }
323         }
324 
325         // reset Transform and clip
326         g2.setRenderingHints(hints);
327         g2.setTransform(at);
328         g2.setClip(clip);
329     }
330 
331     // ---------------------------------------------------------------------
332     //            complex drawing methods for EMFTags
333     // ---------------------------------------------------------------------
334 
335     /**
336      * set the initial transform, the windowOrigin and viewportOrigin,
337      * scales by viewportSize and windowSize
338      * @param g2 Context to apply transformations
339      */
340     private void resetTransformation(Graphics2D g2) {
341         // rest to device configuration
342         if (initialTransform != null) {
343             g2.setTransform(initialTransform);
344         } else {
345             g2.setTransform(new AffineTransform());
346         }
347 
348         /* TODO mapModeTransform dows not work correctly
349         if (mapModeTransform != null) {
350             g2.transform(mapModeTransform);
351         }*/
352 
353         // move to window origin
354         if (windowOrigin != null) {
355             g2.translate(
356                 - windowOrigin.getX(),
357                 - windowOrigin.getY());
358         }
359         // move to window origin
360         if (viewportOrigin != null) {
361             g2.translate(
362                 - viewportOrigin.getX(),
363                 - viewportOrigin.getY());
364         }
365 
366         // TWIP_SCALE by window and viewport size
367         if (viewportSize != null && windowSize != null) {
368             double scaleX =
369                 viewportSize.getWidth() /
370                 windowSize.getWidth();
371             double scaleY =
372                 viewportSize.getHeight() /
373                 windowSize.getHeight();
374             g2.scale(scaleX,  scaleY);
375         }
376     }
377 
378     /**
379      * Stores the current state. Used by
380      * {@link org.freehep.graphicsio.emf.gdi.SaveDC}
381      */
382     public void saveDC() {
383         // create a DC instance with current settings
384         DC dc = new DC();
385         dc.paint = g2.getPaint();
386         dc.stroke = g2.getStroke();
387         dc.transform = g2.getTransform();
388         dc.pathTransform = pathTransform;
389         dc.clip = g2.getClip();
390         dc.path = path;
391         dc.meterLimit = meterLimit;
392         dc.windingRule = windingRule;
393         dc.bkMode = bkMode;
394         dc.useCreatePen = useCreatePen;
395         dc.scaleMode = scaleMode;
396         // push it on top of the stack
397         dcStack.push(dc);
398     }
399 
400     /**
401      * Retores a saved state. Used by
402      * {@link org.freehep.graphicsio.emf.gdi.RestoreDC}
403      */
404     public void retoreDC() {
405         // is somethoing stored?
406         if (!dcStack.empty()) {
407             // read it
408             DC dc = (DC) dcStack.pop();
409 
410             // use it
411             meterLimit = dc.meterLimit;
412             windingRule = dc.windingRule;
413             path = dc.path;
414             bkMode = dc.bkMode;
415             useCreatePen = dc.useCreatePen;
416             scaleMode = dc.scaleMode;
417             pathTransform = dc.pathTransform;
418             g2.setPaint(dc.paint);
419             g2.setStroke(dc.stroke);
420             g2.setTransform(dc.transform);
421             g2.setClip(dc.clip);
422         } else {
423             // set the default values
424         }
425     }
426 
427     /**
428      * closes and appends the current open figure to the
429      * path
430      */
431     public void closeFigure() {
432         if (figure == null) {
433             return;
434         }
435 
436         try {
437             figure.closePath();
438             appendToPath(figure);
439             figure = null;
440         } catch (java.awt.geom.IllegalPathStateException e) {
441             logger.warning("no figure to close");
442         }
443     }
444 
445     /**
446      * Logical units are mapped to arbitrary units with equally scaled axes;
447      * that is, one unit along the x-axis is equal to one unit along the y-axis.
448      * Use the SetWindowExtEx and SetViewportExtEx functions to specify the
449      * units and the orientation of the axes. Graphics device interface (GDI)
450      * makes adjustments as necessary to ensure the x and y units remain the
451      * same size (When the window extent is set, the viewport will be adjusted
452      * to keep the units isotropic).
453      */
454     public void fixViewportSize() {
455         if (mapModeIsotropic && (windowSize != null && viewportSize != null)) {
456             viewportSize.setSize(
457                 viewportSize.getWidth(),
458                 viewportSize.getWidth() *
459                     (windowSize.getHeight() / windowSize.getWidth())
460             );
461         }
462     }
463 
464     /**
465      * fills a shape using the brushPaint,  penPaint and penStroke
466      * @param g2 Painting context
467      * @param s Shape to fill with current brush
468      */
469     private void fillAndDrawOrAppend(Graphics2D g2, Shape s) {
470         // don't draw, just append the shape if BeginPath
471         // has opened the path
472         if (!appendToPath(s)) {
473             // The SetBkMode function affects the line styles for lines drawn using a
474             // pen created by the CreatePen function. SetBkMode does not affect lines
475             // drawn using a pen created by the ExtCreatePen function.
476             if (useCreatePen) {
477                 // OPAQUE 	Background is filled with the current background
478                 // color before the text, hatched brush, or pen is drawn.
479                 if (bkMode == EMFConstants.BKG_OPAQUE) {
480                     fillShape(g2, s);
481                 } else {
482                     // TRANSPARENT 	Background remains untouched.
483                     // TODO: if we really do nothing some drawings are incomplete
484                     // this needs definitly a fix
485                     fillShape(g2, s);
486                 }
487             } else {
488                 // always fill the background if ExtCreatePen is set
489                 fillShape(g2, s);
490             }
491             drawShape(g2, s);
492         }
493     }
494 
495     /**
496      * draws a shape using the penPaint and penStroke
497      * @param g2 Painting context
498      * @param s Shape to draw with current paen
499      */
500     private void drawOrAppend(Graphics2D g2, Shape s) {
501         // don't draw, just append the shape if BeginPath
502         // opens a GeneralPath
503         if (!appendToPath(s)) {
504             drawShape(g2, s);
505         }
506     }
507 
508     /**
509      * draws the text
510      *
511      * @param text Text
512      * @param x x-Position
513      * @param y y-Position
514      */
515     public void drawOrAppendText(String text, double x, double y) {
516         // TODO: Use explicit widths to pixel-position each character, if present.
517         // TODO: Implement alignment properly.  What we have already seems to work well enough.
518         //            FontRenderContext frc = g2.getFontRenderContext();
519         //            TextLayout layout = new TextLayout(str, g2.getFont(), frc);
520         //            if ((textAlignMode & EMFConstants.TA_CENTER) != 0) {
521         //                layout.draw(g2, x + (width - textWidth) / 2, y);
522         //            } else if ((textAlignMode & EMFConstants.TA_RIGHT) != 0) {
523         //                layout.draw(g2, x + width - textWidth, y);
524         //            } else {
525         //                layout.draw(g2, x, y);
526         //            }
527 
528         if (path != null) {
529             // do not use g2.drawString(str, x, y) to be aware of path
530             TextLayout tl = new TextLayout(
531                 text,
532                 g2.getFont(),
533                 g2.getFontRenderContext());
534             path.append(tl.getOutline(null), false);
535         } else {
536             g2.setPaint(textColor);
537             g2.drawString(text, (int)x, (int)y);
538         }
539     }
540 
541     /**
542      * Append the shape to the current path
543      *
544      * @param s Shape to fill with current brush
545      * @return true, if path was changed
546      */
547     private boolean appendToPath(Shape s) {
548         // don't draw, just append the shape if BeginPath
549         // opens a GeneralPath
550         if (path != null) {
551             // aplly transformation if set
552             if (pathTransform != null) {
553                 s = pathTransform.createTransformedShape(s);
554             }
555             // append the shape
556             path.append(s, false);
557             // current path set
558             return true;
559         }
560         // current path not set
561         return false;
562     }
563 
564     /**
565      * closes the path opened by {@link org.freehep.graphicsio.emf.gdi.BeginPath}
566      */
567     public void closePath() {
568         if (path != null) {
569             try {
570                 path.closePath();
571             } catch (java.awt.geom.IllegalPathStateException e) {
572                 logger.warning("no figure to close");
573             }
574         }
575     }
576 
577     /**
578      * fills a shape using the brushPaint,  penPaint and penStroke.
579      * This method should only be called for path painting. It doesn't check for a
580      * current path.
581      *
582      * @param g2 Painting context
583      * @param s Shape to fill with current brush
584      */
585     private void fillShape(Graphics2D g2, Shape s) {
586         g2.setPaint(brushPaint);
587         g2.fill(s);
588     }
589 
590     /**
591      * draws a shape using the penPaint and penStroke
592      * This method should only be called for path drawing. It doesn't check for a
593      * current path.
594      *
595      * @param g2 Painting context
596      * @param s Shape to draw with current pen
597      */
598     private void drawShape(Graphics2D g2, Shape s) {
599         g2.setStroke(penStroke);
600 
601         // R2_BLACK 	Pixel is always 0.
602         if (rop2 == EMFConstants.R2_BLACK) {
603             g2.setComposite(AlphaComposite.SrcOver);
604             g2.setPaint(Color.black);
605         }
606         // R2_COPYPEN 	Pixel is the pen color.
607         else if (rop2 == EMFConstants.R2_COPYPEN) {
608             g2.setComposite(AlphaComposite.SrcOver);
609             g2.setPaint(penPaint);
610         }
611         // R2_NOP 	Pixel remains unchanged.
612         else if (rop2 == EMFConstants.R2_NOP) {
613             g2.setComposite(AlphaComposite.SrcOver);
614             g2.setPaint(penPaint);
615         }
616         // R2_WHITE 	Pixel is always 1.
617         else if (rop2 == EMFConstants.R2_WHITE) {
618             g2.setComposite(AlphaComposite.SrcOver);
619             g2.setPaint(Color.white);
620         }
621         // R2_NOTCOPYPEN 	Pixel is the inverse of the pen color.
622         else if (rop2 == EMFConstants.R2_NOTCOPYPEN) {
623             g2.setComposite(AlphaComposite.SrcOver);
624             // TODO: set at least inverted color if paint is a color
625         }
626         // R2_XORPEN 	Pixel is a combination of the colors
627         // in the pen and in the screen, but not in both.
628         else if (rop2 == EMFConstants.R2_XORPEN) {
629             g2.setComposite(AlphaComposite.Xor);
630         } else {
631             logger.warning("got unsupported ROP" + rop2);
632             // TODO:
633             //R2_MASKNOTPEN 	Pixel is a combination of the colors common to both the screen and the inverse of the pen.
634             //R2_MASKPEN 	Pixel is a combination of the colors common to both the pen and the screen.
635             //R2_MASKPENNOT 	Pixel is a combination of the colors common to both the pen and the inverse of the screen.
636             //R2_MERGENOTPEN 	Pixel is a combination of the screen color and the inverse of the pen color.
637             //R2_MERGEPEN 	Pixel is a combination of the pen color and the screen color.
638             //R2_MERGEPENNOT 	Pixel is a combination of the pen color and the inverse of the screen color.
639             //R2_NOT 	Pixel is the inverse of the screen color.
640             //R2_NOTCOPYPEN 	Pixel is the inverse of the pen color.
641             //R2_NOTMASKPEN 	Pixel is the inverse of the R2_MASKPEN color.
642             //R2_NOTMERGEPEN 	Pixel is the inverse of the R2_MERGEPEN color.
643             //R2_NOTXORPEN 	Pixel is the inverse of the R2_XORPEN color.
644         }
645 
646         g2.draw(s);
647     }
648 
649     // ---------------------------------------------------------------------
650     //            simple wrapping methods to the painting context
651     // ---------------------------------------------------------------------
652 
653     public void setFont(Font font) {
654         g2.setFont(font);
655     }
656 
657     public AffineTransform getTransform() {
658         return g2.getTransform();
659     }
660 
661     public void transform(AffineTransform transform) {
662         g2.transform(transform);
663     }
664 
665     public void resetTransformation() {
666         resetTransformation(g2);
667     }
668 
669     public void setTransform(AffineTransform at) {
670         g2.setTransform(at);
671     }
672 
673     public void setClip(Shape shape) {
674         g2.setClip(shape);
675     }
676 
677     public void clip(Shape shape) {
678         g2.clip(shape);
679     }
680 
681     public Shape getClip() {
682         return g2.getClip();
683     }
684 
685     public void drawImage(BufferedImage image, AffineTransform transform) {
686         g2.drawImage(image, transform, null);
687     }
688 
689     public void drawImage(BufferedImage image, int x, int y, int width, int height) {
690         g2.drawImage(image, x, y, width,  height,  null);
691     }
692 
693     public void drawShape(Shape shape) {
694         drawShape(g2, shape);
695     }
696 
697     public void fillShape(Shape shape) {
698         fillShape(g2, shape);
699     }
700 
701     public void fillAndDrawOrAppend(Shape s) {
702         fillAndDrawOrAppend(g2, s);
703     }
704 
705     public void drawOrAppend(Shape s) {
706         drawOrAppend(g2, s);
707     }
708 
709     // ---------------------------------------------------------------------
710     //            simple getter / setter methods
711     // ---------------------------------------------------------------------
712 
713     public int getWindingRule() {
714         return windingRule;
715     }
716 
717     public GeneralPath getFigure() {
718         return figure;
719     }
720 
721     public void setFigure(GeneralPath figure) {
722         this.figure = figure;
723     }
724 
725     public GeneralPath getPath() {
726         return path;
727     }
728 
729     public void setPath(GeneralPath path) {
730         this.path = path;
731     }
732 
733     public Shape getInitialClip() {
734         return initialClip;
735     }
736 
737     public AffineTransform getPathTransform() {
738         return pathTransform;
739     }
740 
741     public void setPathTransform(AffineTransform pathTransform) {
742         this.pathTransform = pathTransform;
743     }
744 
745     public void setWindingRule(int windingRule) {
746         this.windingRule = windingRule;
747     }
748 
749     public void setMapModeIsotropic(boolean mapModeIsotropic) {
750         this.mapModeIsotropic = mapModeIsotropic;
751     }
752 
753     public AffineTransform getMapModeTransform() {
754         return mapModeTransform;
755     }
756 
757     public void setMapModeTransform(AffineTransform mapModeTransform) {
758         this.mapModeTransform = mapModeTransform;
759     }
760 
761     public void setWindowOrigin(Point windowOrigin) {
762         this.windowOrigin = windowOrigin;
763     }
764 
765     public void setViewportOrigin(Point viewportOrigin) {
766         this.viewportOrigin = viewportOrigin;
767     }
768 
769     public void setViewportSize(Dimension viewportSize) {
770         this.viewportSize = viewportSize;
771         fixViewportSize();
772         resetTransformation();
773     }
774 
775     public void setWindowSize(Dimension windowSize) {
776         this.windowSize = windowSize;
777         fixViewportSize();
778         resetTransformation();
779     }
780 
781     public GDIObject getGDIObject(int index) {
782         return gdiObjects[index];
783     }
784 
785     public void storeGDIObject(int index, GDIObject tag) {
786         gdiObjects[index] = tag;
787     }
788 
789     public void setUseCreatePen(boolean useCreatePen) {
790         this.useCreatePen = useCreatePen;
791     }
792 
793     public void setPenPaint(Paint penPaint) {
794         this.penPaint = penPaint;
795     }
796 
797     public Stroke getPenStroke() {
798         return penStroke;
799     }
800 
801     public void setPenStroke(Stroke penStroke) {
802         this.penStroke = penStroke;
803     }
804 
805     public void setBrushPaint(Paint brushPaint) {
806         this.brushPaint = brushPaint;
807     }
808 
809     public float getMeterLimit() {
810         return meterLimit;
811     }
812 
813     public void setMeterLimit(int meterLimit) {
814         this.meterLimit = meterLimit;
815     }
816 
817     public void setTextColor(Color textColor) {
818         this.textColor = textColor;
819     }
820 
821     public void setRop2(int rop2) {
822         this.rop2 = rop2;
823     }
824 
825     public void setBkMode(int bkMode) {
826         this.bkMode = bkMode;
827     }
828 
829     public int getTextAlignMode() {
830         return textAlignMode;
831     }
832 
833     public void setTextAlignMode(int textAlignMode) {
834         this.textAlignMode = textAlignMode;
835     }
836 
837     public void setScaleMode(int scaleMode) {
838         this.scaleMode = scaleMode;
839     }
840 
841     public Point getBrushOrigin() {
842         return brushOrigin;
843     }
844 
845     public void setBrushOrigin(Point brushOrigin) {
846         this.brushOrigin = brushOrigin;
847     }
848 
849     public void setArcDirection(int arcDirection) {
850         this.arcDirection = arcDirection;
851 }
852 
853     public int getArcDirection() {
854         return arcDirection;
855     }
856 }