View Javadoc

1   // Copyright 2000-2007, FreeHEP
2   package org.freehep.graphicsio.pdf;
3   
4   import java.awt.BasicStroke;
5   import java.awt.Color;
6   import java.awt.Component;
7   import java.awt.Dimension;
8   import java.awt.Font;
9   import java.awt.GradientPaint;
10  import java.awt.Graphics;
11  import java.awt.GraphicsConfiguration;
12  import java.awt.Insets;
13  import java.awt.Paint;
14  import java.awt.Rectangle;
15  import java.awt.Shape;
16  import java.awt.Stroke;
17  import java.awt.TexturePaint;
18  import java.awt.font.LineMetrics;
19  import java.awt.geom.AffineTransform;
20  import java.awt.geom.Rectangle2D;
21  import java.awt.image.BufferedImage;
22  import java.awt.image.RenderedImage;
23  import java.io.BufferedOutputStream;
24  import java.io.File;
25  import java.io.FileNotFoundException;
26  import java.io.FileOutputStream;
27  import java.io.IOException;
28  import java.io.OutputStream;
29  import java.util.ArrayList;
30  import java.util.Calendar;
31  import java.util.HashMap;
32  import java.util.Iterator;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.Properties;
36  
37  import org.freehep.graphics2d.TagString;
38  import org.freehep.graphics2d.font.FontUtilities;
39  import org.freehep.graphics2d.font.Lookup;
40  import org.freehep.graphicsio.AbstractVectorGraphicsIO;
41  import org.freehep.graphicsio.FontConstants;
42  import org.freehep.graphicsio.ImageConstants;
43  import org.freehep.graphicsio.ImageGraphics2D;
44  import org.freehep.graphicsio.InfoConstants;
45  import org.freehep.graphicsio.MultiPageDocument;
46  import org.freehep.graphicsio.PageConstants;
47  import org.freehep.util.UserProperties;
48  
49  /**
50   * Implementation of <tt>VectorGraphics</tt> that writes the output to a PDF
51   * file. Users of this class have to generate a <tt>PDFWriter</tt> and create
52   * an instance by invoking the factory method or the constructor. Document
53   * specific settings like page size can then be made by the appropriate setter
54   * methods. Before starting to draw, <tt>startExport()</tt> must be called.
55   * When drawing is finished, call <tt>endExport()</tt>.
56   * 
57   * @author Simon Fischer
58   * @author Mark Donszelmann
59   * @version $Id: PDFGraphics2D.java 10516 2007-02-06 21:11:19Z duns $
60   */
61  public class PDFGraphics2D extends AbstractVectorGraphicsIO implements
62  		MultiPageDocument, FontUtilities.ShowString {
63  
64  	/*
65  	 * ================================================================================
66  	 * Table of Contents: ------------------ 1. Constructors & Factory Methods
67  	 * 2. Document Settings 3. Header, Trailer, Multipage & Comments 3.1 Header &
68  	 * Trailer 3.2 MultipageDocument methods 4. Create & Dispose 5. Drawing
69  	 * Methods 5.1. shapes (draw/fill) 5.1.1. lines, rectangles, round
70  	 * rectangles 5.1.2. polylines, polygons 5.1.3. ovals, arcs 5.1.4. shapes
71  	 * 5.2. Images 5.3. Strings 6. Transformations 7. Clipping 8. Graphics State /
72  	 * Settings 8.1. stroke/linewidth 8.2. paint/color 8.3. font 8.4. rendering
73  	 * hints 9. Private/Utility Methods 9.1. drawing, shape creation 9.2. font,
74  	 * strings 9.3. images 9.4. transformations 10. Auxiliary
75  	 * ================================================================================
76  	 */
77  
78  	private static final String rootKey = PDFGraphics2D.class.getName();
79  
80  	public static final String VERSION6 = "Acrobat Reader 6.x";
81  
82  	public static final String VERSION5 = "Acrobat Reader 5.x";
83  
84  	public static final String VERSION4 = "Acrobat Reader 4.x";
85  
86  	public static final String TRANSPARENT = rootKey + "."
87  			+ PageConstants.TRANSPARENT;
88  
89  	public static final String BACKGROUND = rootKey + "."
90  			+ PageConstants.BACKGROUND;
91  
92  	public static final String BACKGROUND_COLOR = rootKey + "."
93  			+ PageConstants.BACKGROUND_COLOR;
94  
95  	public static final String PAGE_SIZE = rootKey + "."
96  			+ PageConstants.PAGE_SIZE;
97  
98  	public static final String PAGE_MARGINS = rootKey + "."
99  			+ PageConstants.PAGE_MARGINS;
100 
101 	public static final String ORIENTATION = rootKey + "."
102 			+ PageConstants.ORIENTATION;
103 
104 	public static final String FIT_TO_PAGE = rootKey + "."
105 			+ PageConstants.FIT_TO_PAGE;
106 
107 	public static final String EMBED_FONTS = rootKey + "."
108 			+ FontConstants.EMBED_FONTS;
109 
110 	public static final String EMBED_FONTS_AS = rootKey + "."
111 			+ FontConstants.EMBED_FONTS_AS;
112 
113 	public static final String THUMBNAILS = rootKey + ".Thumbnails";
114 
115 	public static final String THUMBNAIL_SIZE = rootKey + ".ThumbnailSize";
116 
117 	public static final String COMPRESS = rootKey + ".Compress";
118 
119 	public static final String VERSION = rootKey + ".Version";
120 
121 	public static final String WRITE_IMAGES_AS = rootKey + "."
122 			+ ImageConstants.WRITE_IMAGES_AS;
123 
124 	public static final String AUTHOR = rootKey + "." + InfoConstants.AUTHOR;
125 
126 	public static final String TITLE = rootKey + "." + InfoConstants.TITLE;
127 
128 	public static final String SUBJECT = rootKey + "." + InfoConstants.SUBJECT;
129 
130 	public static final String KEYWORDS = rootKey + "."
131 			+ InfoConstants.KEYWORDS;
132 
133 	private static final UserProperties defaultProperties = new UserProperties();
134 	static {
135 		defaultProperties.setProperty(TRANSPARENT, true);
136 		defaultProperties.setProperty(BACKGROUND, false);
137 		defaultProperties.setProperty(BACKGROUND_COLOR, Color.GRAY);
138 
139 		defaultProperties.setProperty(VERSION, VERSION5);
140 		defaultProperties.setProperty(COMPRESS, false);
141 		defaultProperties.setProperty(PAGE_SIZE, PageConstants.INTERNATIONAL);
142 		defaultProperties.setProperty(PAGE_MARGINS, PageConstants
143 				.getMargins(PageConstants.SMALL));
144 		defaultProperties.setProperty(ORIENTATION, PageConstants.PORTRAIT);
145 		defaultProperties.setProperty(FIT_TO_PAGE, true);
146 		defaultProperties.setProperty(EMBED_FONTS, false);
147 		defaultProperties.setProperty(EMBED_FONTS_AS,
148 				FontConstants.EMBED_FONTS_TYPE3);
149 		defaultProperties.setProperty(THUMBNAILS, defaultProperties
150 				.getProperty(VERSION).equals(VERSION4));
151 		defaultProperties.setProperty(THUMBNAIL_SIZE, new Dimension(128, 128));
152 		defaultProperties.setProperty(WRITE_IMAGES_AS, ImageConstants.SMALLEST);
153 
154 		defaultProperties.setProperty(AUTHOR, "");
155 		defaultProperties.setProperty(TITLE, "");
156 		defaultProperties.setProperty(SUBJECT, "");
157 		defaultProperties.setProperty(KEYWORDS, "");
158 
159 		defaultProperties.setProperty(CLIP, true);
160 		defaultProperties.setProperty(TEXT_AS_SHAPES, true);
161 	}
162 
163 	public static Properties getDefaultProperties() {
164 		return defaultProperties;
165 	}
166 
167 	public static void setDefaultProperties(Properties newProperties) {
168 		defaultProperties.setProperties(newProperties);
169 	}
170 
171 	public static final String version = "$Revision: 10516 $";
172 
173 	private static final String PDF_VERSION = "1.4";
174 
175 	private static final String[] COMPRESS_FILTERS = { ImageConstants.ENCODING_FLATE, ImageConstants.ENCODING_ASCII85};
176 
177 	private static final String[] NO_FILTERS = {};
178 
179 	private static final double FONTSIZE_CORRECTION = 1.0;
180 
181 	/*
182 	 * Not Used private static final CharTable STANDARD_CHAR_TABLES[] = {
183 	 * Lookup.getInstance().getTable("PDFLatin"),
184 	 * Lookup.getInstance().getTable("Symbol"),
185 	 * Lookup.getInstance().getTable("Zapfdingbats") };
186 	 * 
187 	 * private static final Font STANDARD_FONT[] = { null, new Font("Symbol",
188 	 * Font.PLAIN, 10), new Font("ZapfDingbats", Font.PLAIN, 10), };
189 	 */
190 
191 	// output
192 	private OutputStream ros;
193 
194 	private PDFWriter os;
195 
196 	private PDFStream pageStream;
197 
198 	// remember some things to do
199 	private PDFFontTable fontTable; // remember which standard fonts were used
200 
201 	private PDFImageDelayQueue delayImageQueue; // remember images XObjects to
202 
203 	// include in the file
204 
205 	private PDFPaintDelayQueue delayPaintQueue; // remember patterns to include
206 
207 	// in the file
208 
209 	// multipage
210 	private int currentPage;
211 
212 	private boolean multiPage;
213 
214 	private TagString[] headerText;
215 
216 	private int headerUnderline;
217 
218 	private Font headerFont;
219 
220 	private TagString[] footerText;
221 
222 	private int footerUnderline;
223 
224 	private Font footerFont;
225 
226 	private List titles;
227 
228 	// extra pointers
229 	int alphaIndex;
230 
231 	Map extGStates;
232 
233 	/*
234 	 * ================================================================================ |
235 	 * 1. Constructors & Factory Methods
236 	 * ================================================================================
237 	 */
238 
239 	public PDFGraphics2D(File file, Dimension size)
240 			throws FileNotFoundException {
241 		this(new FileOutputStream(file), size);
242 	}
243 
244 	public PDFGraphics2D(File file, Component component)
245 			throws FileNotFoundException {
246 		this(new FileOutputStream(file), component);
247 	}
248 
249 	public PDFGraphics2D(OutputStream ros, Dimension size) {
250 		super(size, false);
251 		init(ros);
252 	}
253 
254 	public PDFGraphics2D(OutputStream ros, Component component) {
255 		super(component, false);
256 		init(ros);
257 	}
258 
259 	private void init(OutputStream ros) {
260 		this.ros = new BufferedOutputStream(ros);
261 
262 		currentPage = 0;
263 		multiPage = false;
264 		titles = new ArrayList();
265 		initProperties(defaultProperties);
266 	}
267 
268 	/** Cloneconstructor */
269 	protected PDFGraphics2D(PDFGraphics2D graphics, boolean doRestoreOnDispose) {
270 		super(graphics, doRestoreOnDispose);
271 
272 		this.os = graphics.os;
273 		this.pageStream = graphics.pageStream;
274 
275 		this.delayImageQueue = graphics.delayImageQueue;
276 		this.delayPaintQueue = graphics.delayPaintQueue;
277 		this.fontTable = graphics.fontTable;
278 
279 		this.currentPage = graphics.currentPage;
280 		this.multiPage = graphics.multiPage;
281 		this.titles = graphics.titles;
282 
283 		this.alphaIndex = graphics.alphaIndex;
284 		this.extGStates = graphics.extGStates;
285 	}
286 
287 	/*
288 	 * ================================================================================ |
289 	 * 2. Document Settings
290 	 * ================================================================================
291 	 */
292 	public void setMultiPage(boolean multiPage) {
293 		this.multiPage = multiPage;
294 	}
295 
296 	public boolean isMultiPage() {
297 		return multiPage;
298 	}
299 
300 	/**
301 	 * Set the clipping enabled flag. This will affect all output operations
302 	 * after this call completes. In some circumstances the clipping region is
303 	 * set incorrectly (not yet understood; AWT seems to not correctly dispose
304 	 * of graphic contexts). A workaround is to simply switch it off.
305 	 */
306 	public static void setClipEnabled(boolean enabled) {
307 		defaultProperties.setProperty(CLIP, enabled);
308 	}
309 
310 	/*
311 	 * ================================================================================ |
312 	 * 3. Header, Trailer, Multipage & Comments
313 	 * ================================================================================
314 	 */
315 	/* 3.1 Header & Trailer */
316 
317 	/**
318 	 * Writes the catalog, docinfo, preferences, and (as we use only single page
319 	 * output the page tree.
320 	 */
321 	public void writeHeader() throws IOException {
322 		os = new PDFWriter(new BufferedOutputStream(ros), PDF_VERSION);
323 
324 		delayImageQueue = new PDFImageDelayQueue(os);
325 		delayPaintQueue = new PDFPaintDelayQueue(os, delayImageQueue);
326 
327 		fontTable = new PDFFontTable(os);
328 
329 		String producer = getClass().getName();
330 		if (!isDeviceIndependent()) {
331 			producer += " " + version.substring(1, version.length() - 1);
332 		}
333 
334 		PDFDocInfo info = os.openDocInfo("DocInfo");
335 		info.setTitle(getProperty(TITLE));
336 		info.setAuthor(getProperty(AUTHOR));
337 		info.setSubject(getProperty(SUBJECT));
338 		info.setKeywords(getProperty(KEYWORDS));
339 
340 		info.setCreator(getCreator());
341 		info.setProducer(producer);
342 		if (!isDeviceIndependent()) {
343 			Calendar now = Calendar.getInstance();
344 			info.setCreationDate(now);
345 			info.setModificationDate(now);
346 		}
347 		info.setTrapped("False");
348 		os.close(info);
349 
350 		// catalog
351 		PDFCatalog catalog = os.openCatalog("Catalog", "RootPage");
352 		catalog.setOutlines("Outlines");
353 		catalog.setPageMode("UseOutlines");
354 		catalog.setViewerPreferences("Preferences");
355 		catalog.setOpenAction(new Object[] { os.ref("Page1"), os.name("Fit") });
356 		os.close(catalog);
357 
358 		// preferences
359 		PDFViewerPreferences prefs = os.openViewerPreferences("Preferences");
360 		prefs.setFitWindow(true);
361 		prefs.setCenterWindow(false);
362 		os.close(prefs);
363 
364 		// extra stuff
365 		alphaIndex = 1;
366 		extGStates = new HashMap();
367 
368 		// hide the multipage functionality to the user in case of single page
369 		// output by opening the first and only page immediately
370 		if (!isMultiPage())
371 			openPage(getSize(), null, getComponent());
372 	}
373 
374 	public void writeBackground() {
375 		if (isProperty(TRANSPARENT)) {
376 			setBackground(null);
377 		} else if (isProperty(BACKGROUND)) {
378 			setBackground(getPropertyColor(BACKGROUND_COLOR));
379 			clearRect(0.0, 0.0, getSize().width, getSize().height);
380 		} else {
381 			setBackground(getComponent() != null ? getComponent()
382 					.getBackground() : Color.WHITE);
383 			clearRect(0.0, 0.0, getSize().width, getSize().height);
384 		}
385 	}
386 
387 	public void writeTrailer() throws IOException {
388 		if (!isMultiPage())
389 			closePage();
390 
391 		// pages
392 		PDFPageTree pages = os.openPageTree("RootPage", null);
393 		for (int i = 1; i <= currentPage; i++) {
394 			pages.addPage("Page" + i);
395 		}
396 		Dimension pageSize = PageConstants.getSize(getProperty(PAGE_SIZE),
397 				getProperty(ORIENTATION));
398 		pages.setMediaBox(0, 0, pageSize.getWidth(), pageSize.getHeight());
399 		pages.setResources("Resources");
400 		os.close(pages);
401 
402 		// ProcSet
403 		os.object("PageProcSet", new Object[] { os.name("PDF"),
404 				os.name("Text"), os.name("ImageC") });
405 
406 		// Font
407 		int nFonts = fontTable.addFontDictionary();
408 
409 		// XObject
410 		int nXObjects = delayImageQueue.addXObjects();
411 
412 		// Pattern
413 		int nPatterns = delayPaintQueue.addPatterns();
414 
415 		// ExtGState
416 		if (extGStates.size() > 0) {
417 			PDFDictionary extGState = os.openDictionary("ExtGState");
418 
419 			for (Iterator i = extGStates.keySet().iterator(); i.hasNext();) {
420 				Float alpha = (Float) i.next();
421 				String alphaName = (String) extGStates.get(alpha);
422 				PDFDictionary alphaDictionary = extGState
423 						.openDictionary(alphaName);
424 				alphaDictionary.entry("ca", alpha.floatValue());
425 				alphaDictionary.entry("CA", alpha.floatValue());
426 				alphaDictionary.entry("BM", os.name("Normal"));
427 				alphaDictionary.entry("AIS", false);
428 				extGState.close(alphaDictionary);
429 			}
430 			os.close(extGState);
431 		}
432 
433 		// resources
434 		PDFDictionary resources = os.openDictionary("Resources");
435 		resources.entry("ProcSet", os.ref("PageProcSet"));
436 		if (nFonts > 0)
437 			resources.entry("Font", os.ref("FontList"));
438 		if (nXObjects > 0)
439 			resources.entry("XObject", os.ref("XObjects"));
440 		if (nPatterns > 0)
441 			resources.entry("Pattern", os.ref("Pattern"));
442 		if (extGStates.size() > 0)
443 			resources.entry("ExtGState", os.ref("ExtGState"));
444 		os.close(resources);
445 
446 		// outlines
447 		PDFOutlineList outlines = os.openOutlineList("Outlines", "Outline1",
448 				"Outline" + currentPage);
449 		os.close(outlines);
450 
451 		for (int i = 1; i <= currentPage; i++) {
452 			String prev = i > 1 ? "Outline" + (i - 1) : null;
453 			String next = i < currentPage ? "Outline" + (i + 1) : null;
454 			PDFOutline outline = os.openOutline("Outline" + i, (String) titles
455 					.get(i - 1), "Outlines", prev, next);
456 			outline
457 					.setDest(new Object[] { os.ref("Page" + i), os.name("Fit") });
458 			os.close(outline);
459 		}
460 
461 		// delayed objects (images, patterns, fonts)
462 		processDelayed();
463 	}
464 
465 	public void closeStream() throws IOException {
466 		os.close();
467 	}
468 
469 	private void processDelayed() throws IOException {
470 		delayImageQueue.processAll();
471 		delayPaintQueue.processAll();
472 		fontTable.embedAll(getFontRenderContext(), isProperty(EMBED_FONTS),
473 				getProperty(EMBED_FONTS_AS));
474 	}
475 
476 	/* 3.2 MultipageDocument methods */
477 	public void openPage(Component component) throws IOException {
478 		openPage(component.getSize(), component.getName(), component);
479 	}
480 
481 	public void openPage(Dimension size, String title) throws IOException {
482 		openPage(size, title, null);
483 	}
484 
485 	private void openPage(Dimension size, String title, Component component)
486 			throws IOException {
487 		if (size == null)
488 			size = component.getSize();
489 
490 		resetClip(new Rectangle(0, 0, size.width, size.height));
491 
492 		if (pageStream != null) {
493 			writeWarning("Page " + currentPage + " already open. "
494 					+ "Call closePage() before starting a new one.");
495 			return;
496 		}
497 
498 		BufferedImage thumbnail = null;
499 		// prepare thumbnail if possible
500 		if ((component != null) && isProperty(PDFGraphics2D.THUMBNAILS)) {
501 			thumbnail = ImageGraphics2D.generateThumbnail(component,
502 					getPropertyDimension(PDFGraphics2D.THUMBNAIL_SIZE));
503 		}
504 
505 		currentPage++;
506 
507 		if (title == null)
508 			title = "Page " + currentPage + " (untitled)";
509 		titles.add(title);
510 
511 		PDFPage page = os.openPage("Page" + currentPage, "RootPage");
512 		page.setContents("PageContents" + currentPage);
513 
514 		if (thumbnail != null)
515 			page.setThumb("Thumb" + currentPage);
516 
517 		os.close(page);
518 
519 		if (thumbnail != null) {
520 			PDFStream thumbnailStream = os.openStream("Thumb" + currentPage);
521 			thumbnailStream.image(thumbnail, Color.black, ImageConstants.ZLIB);
522 			os.close(thumbnailStream);
523 		}
524 
525 		pageStream = os.openStream("PageContents" + currentPage,
526 				isProperty(COMPRESS) ? COMPRESS_FILTERS : NO_FILTERS);
527 
528 		// transform the coordinate system as necessary
529 		// 1. flip the coordinate system down and translate it upwards again
530 		// so that the origin is the upper left corner of the page.
531 		AffineTransform pageTrafo = new AffineTransform();
532 		pageTrafo.scale(1, -1);
533 		Dimension pageSize = PageConstants.getSize(getProperty(PAGE_SIZE),
534 				getProperty(ORIENTATION));
535 		Insets margins = PageConstants.getMargins(
536 				getPropertyInsets(PAGE_MARGINS), getProperty(ORIENTATION));
537 		pageTrafo
538 				.translate(margins.left, -(pageSize.getHeight() - margins.top));
539 
540 		// in between write the header and footer (which should not be scaled!)
541 		writeHeadline(pageTrafo);
542 		writeFootline(pageTrafo);
543 
544 		// 2. check whether we have to rescale the image to fit onto the page
545 		double scaleFactor = Math.min(getWidth() / size.width, getHeight()
546 				/ size.height);
547 		if ((scaleFactor < 1) || isProperty(FIT_TO_PAGE)) {
548 			pageTrafo.scale(scaleFactor, scaleFactor);
549 		} else {
550 			scaleFactor = 1;
551 		}
552 
553 		// 3. center the image on the page
554 		double dx = (getWidth() - size.width * scaleFactor) / 2 / scaleFactor;
555 		double dy = (getHeight() - size.height * scaleFactor) / 2 / scaleFactor;
556 		pageTrafo.translate(dx, dy);
557 
558 		writeTransform(pageTrafo);
559 
560 		// save the graphics context resets before setClip
561 		writeGraphicsSave();
562 
563 		clipRect(0, 0, size.width, size.height);
564 
565 		// save the graphics context resets before setClip
566 		writeGraphicsSave();
567 
568 		delayPaintQueue.setPageMatrix(pageTrafo);
569 
570 		writeGraphicsState();
571 		writeBackground();
572 	}
573 
574 	public void closePage() throws IOException {
575 		if (pageStream == null) {
576 			writeWarning("Page " + currentPage + " already closed. "
577 					+ "Call openPage() to start a new one.");
578 			return;
579 		}
580         writeGraphicsRestore();
581         writeGraphicsRestore();
582 		os.close(pageStream);
583 		pageStream = null;
584 
585 		processDelayed(); // This does not work properly with acrobat reader
586 		// 4!
587 	}
588 
589 	public void setHeader(Font font, TagString left, TagString center,
590 			TagString right, int underlineThickness) {
591 		this.headerFont = font;
592 		this.headerText = new TagString[3];
593 		this.headerText[0] = left;
594 		this.headerText[1] = center;
595 		this.headerText[2] = right;
596 		this.headerUnderline = underlineThickness;
597 	}
598 
599 	public void setFooter(Font font, TagString left, TagString center,
600 			TagString right, int underlineThickness) {
601 		this.footerFont = font;
602 		this.footerText = new TagString[3];
603 		this.footerText[0] = left;
604 		this.footerText[1] = center;
605 		this.footerText[2] = right;
606 		this.footerUnderline = underlineThickness;
607 	}
608 
609 	private void writeHeadline(AffineTransform pageTrafo) throws IOException {
610 		if (headerText != null) {
611 			LineMetrics metrics = headerFont.getLineMetrics("mM",
612 					getFontRenderContext());
613 			writeLine(pageTrafo, headerFont, headerText, -metrics.getLeading()
614 					- headerFont.getSize2D() / 2, TEXT_BOTTOM, -headerFont
615 					.getSize2D() / 2, headerUnderline);
616 
617 		}
618 	}
619 
620 	private void writeFootline(AffineTransform pageTrafo) throws IOException {
621 		if (footerText != null) {
622 			LineMetrics metrics = footerFont.getLineMetrics("mM",
623 					getFontRenderContext());
624 			double y = getHeight() + footerFont.getSize2D() / 2;
625 			writeLine(pageTrafo, footerFont, footerText, y
626 					+ metrics.getLeading(), TEXT_TOP, y, footerUnderline);
627 		}
628 	}
629 
630 	private void writeLine(AffineTransform trafo, Font font, TagString[] text,
631 			double ty, int yAlign, double ly, int underline) throws IOException {
632 		writeGraphicsSave();
633 		setColor(Color.black);
634 		setFont(font);
635 		writeTransform(trafo);
636 		if (text[0] != null)
637 			drawString(text[0], 0, ty, TEXT_LEFT, yAlign);
638 		if (text[1] != null)
639 			drawString(text[1], getWidth() / 2, ty, TEXT_CENTER, yAlign);
640 		if (text[2] != null)
641 			drawString(text[2], getWidth(), ty, TEXT_RIGHT, yAlign);
642 		if (underline >= 0) {
643 			setLineWidth((double) underline);
644 			drawLine(0, ly, getWidth(), ly);
645 		}
646 		writeGraphicsRestore();
647 	}
648 
649 	/*
650 	 * ================================================================================ |
651 	 * 4. Create & Dispose
652 	 * ================================================================================
653 	 */
654 
655 	public Graphics create() {
656 		try {
657 			writeGraphicsSave();
658 		} catch (IOException e) {
659 			handleException(e);
660 		}
661 		return new PDFGraphics2D(this, true);
662 	}
663 
664 	public Graphics create(double x, double y, double width, double height) {
665 		try {
666 			writeGraphicsSave();
667 		} catch (IOException e) {
668 			handleException(e);
669 		}
670 		PDFGraphics2D graphics = new PDFGraphics2D(this, true);
671 		graphics.translate(x, y);
672 		graphics.clipRect(0, 0, width, height);
673 		return graphics;
674 	}
675 
676 	protected void writeGraphicsSave() throws IOException {
677 		pageStream.save();
678 	}
679 
680 	protected void writeGraphicsRestore() throws IOException {
681 		pageStream.restore();
682 	}
683 
684 	/*
685 	 * ================================================================================ |
686 	 * 5. Drawing Methods
687 	 * ================================================================================ /*
688 	 * 5.1.4. shapes
689 	 */
690 	public void draw(Shape s) {
691 		try {
692 			if (getStroke() instanceof BasicStroke) {
693 				// in this case we've already handled the stroke
694 				pageStream.drawPath(s);
695 				pageStream.stroke();
696 			} else {
697 				// otherwise handle it now
698 				pageStream.drawPath(getStroke().createStrokedShape(s));
699 				pageStream.fill();
700 			}
701 		} catch (IOException e) {
702 			handleException(e);
703 		}
704 	}
705 
706 	public void fill(Shape s) {
707 		try {
708 			boolean eofill = pageStream.drawPath(s);
709 			if (eofill) {
710 				pageStream.fillEvenOdd();
711 			} else {
712 				pageStream.fill();
713 			}
714 		} catch (IOException e) {
715 			handleException(e);
716 		}
717 	}
718 
719 	/* 5.2 Images */
720 	public void copyArea(int x, int y, int width, int height, int dx, int dy) {
721 		writeWarning(getClass()
722 				+ ": copyArea(int, int, int, int, int, int) not implemented.");
723 	}
724 
725 	protected void writeImage(RenderedImage image, AffineTransform xform,
726 			Color bkg) throws IOException {
727 		PDFName ref = delayImageQueue.delayImage(image, bkg,
728 				getProperty(WRITE_IMAGES_AS));
729 
730 		AffineTransform imageTransform = new AffineTransform(image.getWidth(),
731 				0.0, 0.0, -image.getHeight(), 0.0, image.getHeight());
732 		xform.concatenate(imageTransform);
733 
734 		writeGraphicsSave();
735 		pageStream.matrix(xform);
736 		pageStream.xObject(ref);
737 		writeGraphicsRestore();
738 	}
739 
740 	/* 5.3. Strings */
741 	protected void writeString(String str, double x, double y)
742 			throws IOException {
743 		// save the graphics context, especially the transformation matrix
744 		writeGraphicsSave();
745 
746 		// translate the offset to x and y
747 		AffineTransform at = new AffineTransform(1, 0, 0, 1, x, y);
748 		// transform for font
749 		at.concatenate(getFont().getTransform());
750 		// mirror the matrix
751 		at.scale(1, -1);
752 
753 		// write transform
754 		writeTransform(at);
755 
756 		pageStream.beginText();
757 		pageStream.text(0, 0);
758 		showCharacterCodes(str);
759 		pageStream.endText();
760 
761 		// restore the transformation matrix
762 		writeGraphicsRestore();
763 	}
764 
765 	/*
766 	 * ================================================================================ |
767 	 * 6. Transformations
768 	 * ================================================================================
769 	 */
770 	/** Write the given transformation matrix to the file. */
771 	protected void writeTransform(AffineTransform t) throws IOException {
772 		pageStream.matrix(t);
773 	}
774 
775 	/*
776 	 * ================================================================================ |
777 	 * 7. Clipping
778 	 * ================================================================================
779 	 */
780 	protected void writeSetClip(Shape s) throws IOException {
781 		// clear old clip
782 		try {
783 			AffineTransform at = getTransform();
784 			Stroke stroke = getStroke();
785 
786 			writeGraphicsRestore();
787 			writeGraphicsSave();
788 
789 			writeStroke(stroke);
790 			writeTransform(at);
791 		} catch (IOException e) {
792 			handleException(e);
793 		}
794 
795 		// write clip
796 		writeClip(s);
797 	}
798 
799 	protected void writeClip(Shape s) throws IOException {
800 		if (s == null || !isProperty(CLIP)) {
801 			return;
802 		}
803 
804 		if (s instanceof Rectangle2D) {
805 			pageStream.move(((Rectangle2D) s).getMinX(), ((Rectangle2D) s)
806 					.getMinY());
807 			pageStream.line(((Rectangle2D) s).getMaxX(), ((Rectangle2D) s)
808 					.getMinY());
809 			pageStream.line(((Rectangle2D) s).getMaxX(), ((Rectangle2D) s)
810 					.getMaxY());
811 			pageStream.line(((Rectangle2D) s).getMinX(), ((Rectangle2D) s)
812 					.getMaxY());
813 			pageStream.closePath();
814 			pageStream.clip();
815 			pageStream.endPath();
816 		} else {
817 			boolean eoclip = pageStream.drawPath(s);
818 			if (eoclip) {
819 				pageStream.clipEvenOdd();
820 			} else {
821 				pageStream.clip();
822 			}
823 
824 			pageStream.endPath();
825 		}
826 	}
827 
828 	/*
829 	 * ================================================================================ |
830 	 * 8. Graphics State
831 	 * ================================================================================
832 	 */
833 	/* 8.1. stroke/linewidth */
834 	protected void writeWidth(float width) throws IOException {
835 		pageStream.width(width);
836 	}
837 
838 	protected void writeCap(int cap) throws IOException {
839 		switch (cap) {
840 		default:
841 		case BasicStroke.CAP_BUTT:
842 			pageStream.cap(0);
843 			break;
844 		case BasicStroke.CAP_ROUND:
845 			pageStream.cap(1);
846 			break;
847 		case BasicStroke.CAP_SQUARE:
848 			pageStream.cap(2);
849 			break;
850 		}
851 	}
852 
853 	protected void writeJoin(int join) throws IOException {
854 		switch (join) {
855 		default:
856 		case BasicStroke.JOIN_MITER:
857 			pageStream.join(0);
858 			break;
859 		case BasicStroke.JOIN_ROUND:
860 			pageStream.join(1);
861 			break;
862 		case BasicStroke.JOIN_BEVEL:
863 			pageStream.join(2);
864 			break;
865 		}
866 	}
867 
868 	protected void writeMiterLimit(float limit) throws IOException {
869 		pageStream.mitterLimit(limit);
870 	}
871 
872 	protected void writeDash(float[] dash, float phase) throws IOException {
873 		pageStream.dash(dash, phase);
874 	}
875 
876 	/* 8.2. paint/color */
877 	public void setPaintMode() {
878 		writeWarning(getClass() + ": setPaintMode() not implemented.");
879 	}
880 
881 	public void setXORMode(Color c1) {
882 		writeWarning(getClass() + ": setXORMode(Color) not implemented.");
883 	}
884 
885 	protected void writePaint(Color c) throws IOException {
886 		float[] cc = c.getRGBComponents(null);
887 		// System.out.println("alpha = "+cc[3]);
888 		Float alpha = new Float(cc[3]);
889 		String alphaName = (String) extGStates.get(alpha);
890 		if (alphaName == null) {
891 			alphaName = "Alpha" + alphaIndex;
892 			alphaIndex++;
893 			extGStates.put(alpha, alphaName);
894 		}
895 		pageStream.state(os.name(alphaName));
896 		pageStream.colorSpace(cc[0], cc[1], cc[2]);
897 		pageStream.colorSpaceStroke(cc[0], cc[1], cc[2]);
898 	}
899 
900 	protected void writePaint(GradientPaint c) throws IOException {
901 		writePaint((Paint) c);
902 	}
903 
904 	protected void writePaint(TexturePaint c) throws IOException {
905 		writePaint((Paint) c);
906 	}
907 
908 	protected void writePaint(Paint paint) throws IOException {
909 		pageStream.colorSpace(os.name("Pattern"));
910 		pageStream.colorSpaceStroke(os.name("Pattern"));
911 		PDFName shadingName = delayPaintQueue.delayPaint(paint, getTransform(),
912 				getProperty(WRITE_IMAGES_AS));
913 		pageStream.colorSpace(null, shadingName);
914 		pageStream.colorSpaceStroke(new double[] {}, shadingName);
915 	}
916 
917 	protected void setNonStrokeColor(Color c) throws IOException {
918 		float[] cc = c.getRGBColorComponents(null);
919 		pageStream.colorSpace(cc[0], cc[1], cc[2]);
920 	}
921 
922 	protected void setStrokeColor(Color c) throws IOException {
923 		float[] cc = c.getRGBColorComponents(null);
924 		pageStream.colorSpaceStroke(cc[0], cc[1], cc[2]);
925 	}
926 
927 	/* 8.3. font */
928 	protected void writeFont(Font font) throws IOException {
929 		// written when needed
930 	}
931 
932 	/*
933 	 * ================================================================================ |
934 	 * 9. Auxiliary
935 	 * ================================================================================
936 	 */
937 	public GraphicsConfiguration getDeviceConfiguration() {
938 		writeWarning(getClass() + ": getDeviceConfiguration() not implemented.");
939 		return null;
940 	}
941 
942 	public void writeComment(String comment) throws IOException {
943 		// comments are ignored and disabled, because they confuse compressed
944 		// streams
945 	}
946 
947 	public String toString() {
948 		return "PDFGraphics2D";
949 	}
950 
951 	/*
952 	 * ================================================================================ |
953 	 * 10. Private/Utility
954 	 * ================================================================================
955 	 */
956 	public void showString(Font font, String str) throws IOException {
957 		String fontRef = fontTable.fontReference(font, isProperty(EMBED_FONTS),
958 				getProperty(EMBED_FONTS_AS));
959 		pageStream.font(os.name(fontRef), font.getSize() * FONTSIZE_CORRECTION);
960 		pageStream.show(str);
961 	}
962 
963 	/**
964 	 * See the comment of VectorGraphicsUtitlies1.
965 	 * 
966 	 * @see FontUtilities#showString(java.awt.Font, String,
967 	 *      org.freehep.graphics2d.font.CharTable,
968 	 *      org.freehep.graphics2d.font.FontUtilities.ShowString)
969 	 */
970 	private void showCharacterCodes(String str) throws IOException {
971 		FontUtilities.showString(getFont(), str, Lookup.getInstance().getTable(
972 				"PDFLatin"), this);
973 	}
974 
975 	private double getWidth() {
976 		Dimension pageSize = PageConstants.getSize(getProperty(PAGE_SIZE),
977 				getProperty(ORIENTATION));
978 		Insets margins = PageConstants.getMargins(
979 				getPropertyInsets(PAGE_MARGINS), getProperty(ORIENTATION));
980 		return pageSize.getWidth() - margins.left - margins.right;
981 	}
982 
983 	private double getHeight() {
984 		Dimension pageSize = PageConstants.getSize(getProperty(PAGE_SIZE),
985 				getProperty(ORIENTATION));
986 		Insets margins = PageConstants.getMargins(
987 				getPropertyInsets(PAGE_MARGINS), getProperty(ORIENTATION));
988 		return pageSize.getHeight() - margins.top - margins.bottom;
989 	}
990 
991 }