You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PDFGraphics2D.java 65KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.svg;
  19. import java.awt.AlphaComposite;
  20. import java.awt.BasicStroke;
  21. import java.awt.Color;
  22. import java.awt.Dimension;
  23. import java.awt.GradientPaint;
  24. import java.awt.Graphics;
  25. import java.awt.Graphics2D;
  26. import java.awt.GraphicsConfiguration;
  27. import java.awt.Image;
  28. import java.awt.Paint;
  29. import java.awt.PaintContext;
  30. import java.awt.Rectangle;
  31. import java.awt.Shape;
  32. import java.awt.Stroke;
  33. import java.awt.color.ColorSpace;
  34. import java.awt.geom.AffineTransform;
  35. import java.awt.geom.PathIterator;
  36. import java.awt.geom.Point2D;
  37. import java.awt.geom.Rectangle2D;
  38. import java.awt.image.BufferedImage;
  39. import java.awt.image.ColorModel;
  40. import java.awt.image.DataBuffer;
  41. import java.awt.image.DirectColorModel;
  42. import java.awt.image.ImageObserver;
  43. import java.awt.image.Raster;
  44. import java.awt.image.RenderedImage;
  45. import java.awt.image.WritableRaster;
  46. import java.awt.image.renderable.RenderableImage;
  47. import java.io.IOException;
  48. import java.io.OutputStream;
  49. import java.io.StringWriter;
  50. import java.util.List;
  51. import java.util.Map;
  52. import org.apache.batik.ext.awt.LinearGradientPaint;
  53. import org.apache.batik.ext.awt.MultipleGradientPaint;
  54. import org.apache.batik.ext.awt.RadialGradientPaint;
  55. import org.apache.batik.ext.awt.RenderingHintsKeyExt;
  56. import org.apache.batik.gvt.GraphicsNode;
  57. import org.apache.batik.gvt.PatternPaint;
  58. import org.apache.xmlgraphics.image.loader.ImageInfo;
  59. import org.apache.xmlgraphics.image.loader.ImageSize;
  60. import org.apache.xmlgraphics.image.loader.impl.ImageRawCCITTFax;
  61. import org.apache.xmlgraphics.image.loader.impl.ImageRawJPEG;
  62. import org.apache.xmlgraphics.image.loader.impl.ImageRendered;
  63. import org.apache.xmlgraphics.java2d.AbstractGraphics2D;
  64. import org.apache.xmlgraphics.java2d.GraphicContext;
  65. import org.apache.fop.fonts.Font;
  66. import org.apache.fop.fonts.FontInfo;
  67. import org.apache.fop.fonts.FontSetup;
  68. import org.apache.fop.pdf.BitmapImage;
  69. import org.apache.fop.pdf.PDFAnnotList;
  70. import org.apache.fop.pdf.PDFColor;
  71. import org.apache.fop.pdf.PDFConformanceException;
  72. import org.apache.fop.pdf.PDFDeviceColorSpace;
  73. import org.apache.fop.pdf.PDFDocument;
  74. import org.apache.fop.pdf.PDFGState;
  75. import org.apache.fop.pdf.PDFImage;
  76. import org.apache.fop.pdf.PDFImageXObject;
  77. import org.apache.fop.pdf.PDFLink;
  78. import org.apache.fop.pdf.PDFNumber;
  79. import org.apache.fop.pdf.PDFPaintingState;
  80. import org.apache.fop.pdf.PDFPattern;
  81. import org.apache.fop.pdf.PDFResourceContext;
  82. import org.apache.fop.pdf.PDFResources;
  83. import org.apache.fop.pdf.PDFText;
  84. import org.apache.fop.pdf.PDFXObject;
  85. import org.apache.fop.render.pdf.ImageRawCCITTFaxAdapter;
  86. import org.apache.fop.render.pdf.ImageRawJPEGAdapter;
  87. import org.apache.fop.render.pdf.ImageRenderedAdapter;
  88. import org.apache.fop.util.ColorExt;
  89. /**
  90. * PDF Graphics 2D.
  91. * Used for drawing into a pdf document as if it is a graphics object.
  92. * This takes a pdf document and draws into it.
  93. *
  94. * @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a>
  95. * @version $Id$
  96. * @see org.apache.batik.ext.awt.g2d.AbstractGraphics2D
  97. */
  98. public class PDFGraphics2D extends AbstractGraphics2D implements NativeImageHandler {
  99. private static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform();
  100. /** The number of decimal places. */
  101. private static final int DEC = 8;
  102. /** Convenience constant for full opacity */
  103. static final int OPAQUE = 255;
  104. /**
  105. * the PDF Document being created
  106. */
  107. protected PDFDocument pdfDoc;
  108. /**
  109. * The current resource context for adding fonts, patterns etc.
  110. */
  111. protected PDFResourceContext resourceContext;
  112. /**
  113. * The PDF reference of the current page.
  114. */
  115. protected String pageRef;
  116. /**
  117. * The PDF painting state
  118. */
  119. protected PDFPaintingState paintingState;
  120. /**
  121. * The PDF graphics state level that this svg is being drawn into.
  122. */
  123. protected int baseLevel = 0;
  124. /**
  125. * The count of natively handled images added to document so they receive
  126. * unique keys.
  127. */
  128. protected int nativeCount = 0;
  129. /**
  130. * The current font information.
  131. */
  132. protected FontInfo fontInfo;
  133. /**
  134. * The override font state used when drawing text and the font cannot be
  135. * set using java fonts.
  136. */
  137. protected Font ovFontState = null;
  138. /**
  139. * the current stream to add PDF commands to
  140. */
  141. protected StringWriter currentStream = new StringWriter();
  142. /**
  143. * the current (internal) font name
  144. */
  145. protected String currentFontName;
  146. /**
  147. * the current font size in millipoints
  148. */
  149. protected float currentFontSize;
  150. /**
  151. * The output stream for the pdf document.
  152. * If this is set then it can progressively output
  153. * the pdf document objects to reduce memory.
  154. * Especially with images.
  155. */
  156. protected OutputStream outputStream = null;
  157. /**
  158. * Create a new PDFGraphics2D with the given pdf document info.
  159. * This is used to create a Graphics object for use inside an already
  160. * existing document.
  161. *
  162. * @param textAsShapes if true then draw text as shapes
  163. * @param fi the current font information
  164. * @param doc the pdf document for creating pdf objects
  165. * @param page the current resource context or page
  166. * @param pref the PDF reference of the current page
  167. * @param font the current font name
  168. * @param size the current font size
  169. */
  170. public PDFGraphics2D(boolean textAsShapes, FontInfo fi, PDFDocument doc,
  171. PDFResourceContext page, String pref, String font, float size) {
  172. this(textAsShapes);
  173. pdfDoc = doc;
  174. resourceContext = page;
  175. currentFontName = font;
  176. currentFontSize = size;
  177. fontInfo = fi;
  178. pageRef = pref;
  179. paintingState = new PDFPaintingState();
  180. }
  181. /**
  182. * Create a new PDFGraphics2D.
  183. *
  184. * @param textAsShapes true if drawing text as shapes
  185. */
  186. protected PDFGraphics2D(boolean textAsShapes) {
  187. super(textAsShapes);
  188. }
  189. /**
  190. * This constructor supports the create method.
  191. * This is not implemented properly.
  192. *
  193. * @param g the PDF graphics to make a copy of
  194. */
  195. public PDFGraphics2D(PDFGraphics2D g) {
  196. super(g);
  197. this.pdfDoc = g.pdfDoc;
  198. this.resourceContext = g.resourceContext;
  199. this.currentFontName = g.currentFontName;
  200. this.currentFontSize = g.currentFontSize;
  201. this.fontInfo = g.fontInfo;
  202. this.pageRef = g.pageRef;
  203. this.paintingState = g.paintingState;
  204. this.currentStream = g.currentStream;
  205. this.nativeCount = g.nativeCount;
  206. this.outputStream = g.outputStream;
  207. this.ovFontState = g.ovFontState;
  208. }
  209. /**
  210. * Creates a new <code>Graphics</code> object that is
  211. * a copy of this <code>Graphics</code> object.
  212. * @return a new graphics context that is a copy of
  213. * this graphics context.
  214. */
  215. public Graphics create() {
  216. return new PDFGraphics2D(this);
  217. }
  218. /**
  219. * Central handler for IOExceptions for this class.
  220. * @param ioe IOException to handle
  221. */
  222. protected void handleIOException(IOException ioe) {
  223. //TODO Surely, there's a better way to do this.
  224. ioe.printStackTrace();
  225. }
  226. /**
  227. * This method is used by PDFDocumentGraphics2D to prepare a new page if
  228. * necessary.
  229. */
  230. protected void preparePainting() {
  231. //nop, used by PDFDocumentGraphics2D
  232. }
  233. /**
  234. * Set the PDF state to use when starting to draw
  235. * into the PDF graphics.
  236. *
  237. * @param state the PDF state
  238. */
  239. public void setPaintingState(PDFPaintingState state) {
  240. paintingState = state;
  241. baseLevel = paintingState.getStackLevel();
  242. }
  243. /**
  244. * Set the output stream that this PDF document is
  245. * being drawn to. This is so that it can progressively
  246. * use the PDF document to output data such as images.
  247. * This results in a significant saving on memory.
  248. *
  249. * @param os the output stream that is being used for the PDF document
  250. */
  251. public void setOutputStream(OutputStream os) {
  252. outputStream = os;
  253. }
  254. /**
  255. * Get the string containing all the commands written into this
  256. * Graphics.
  257. * @return the string containing the PDF markup
  258. */
  259. public String getString() {
  260. return currentStream.toString();
  261. }
  262. /**
  263. * Get the string buffer from the currentStream, containing all
  264. * the commands written into this Graphics so far.
  265. * @return the StringBuffer containing the PDF markup
  266. */
  267. public StringBuffer getBuffer() {
  268. return currentStream.getBuffer();
  269. }
  270. /**
  271. * Gets the PDF reference of the current page.
  272. * @return the PDF reference of the current page
  273. */
  274. public String getPageReference() {
  275. return this.pageRef;
  276. }
  277. /**
  278. * Set the Graphics context.
  279. * @param c the graphics context to use
  280. */
  281. public void setGraphicContext(GraphicContext c) {
  282. gc = c;
  283. setPrivateHints();
  284. }
  285. private void setPrivateHints() {
  286. setRenderingHint(RenderingHintsKeyExt.KEY_AVOID_TILE_PAINTING,
  287. RenderingHintsKeyExt.VALUE_AVOID_TILE_PAINTING_ON);
  288. }
  289. /**
  290. * Set the override font state for drawing text.
  291. * This is used by the PDF text painter so that it can temporarily
  292. * set the font state when a java font cannot be used.
  293. * The next drawString will use this font state.
  294. *
  295. * @param infont the font state to use
  296. */
  297. public void setOverrideFontState(Font infont) {
  298. ovFontState = infont;
  299. }
  300. /**
  301. * Restore the PDF graphics state to the starting state level.
  302. */
  303. /* seems not to be used
  304. public void restorePDFState() {
  305. for (int count = graphicsState.getStackLevel(); count > baseLevel; count--) {
  306. currentStream.write("Q\n");
  307. }
  308. graphicsState.restoreLevel(baseLevel);
  309. }*/
  310. private void concatMatrix(double[] matrix) {
  311. currentStream.write(PDFNumber.doubleOut(matrix[0], DEC) + " "
  312. + PDFNumber.doubleOut(matrix[1], DEC) + " "
  313. + PDFNumber.doubleOut(matrix[2], DEC) + " "
  314. + PDFNumber.doubleOut(matrix[3], DEC) + " "
  315. + PDFNumber.doubleOut(matrix[4], DEC) + " "
  316. + PDFNumber.doubleOut(matrix[5], DEC) + " cm\n");
  317. }
  318. private void concatMatrix(AffineTransform transform) {
  319. if (!transform.isIdentity()) {
  320. double[] matrix = new double[6];
  321. transform.getMatrix(matrix);
  322. concatMatrix(matrix);
  323. }
  324. }
  325. /**
  326. * This is mainly used for shading patterns which use the document-global coordinate system
  327. * instead of the local one.
  328. * @return the transformation matrix that established the basic user space for this document
  329. */
  330. protected AffineTransform getBaseTransform() {
  331. AffineTransform at = new AffineTransform(paintingState.getTransform());
  332. return at;
  333. }
  334. /**
  335. * This is a pdf specific method used to add a link to the
  336. * pdf document.
  337. *
  338. * @param bounds the bounds of the link in user coordinates
  339. * @param trans the transform of the current drawing position
  340. * @param dest the PDF destination
  341. * @param linkType the type of link, internal or external
  342. */
  343. public void addLink(Rectangle2D bounds, AffineTransform trans, String dest, int linkType) {
  344. if (!pdfDoc.getProfile().isAnnotationAllowed()) {
  345. return;
  346. }
  347. preparePainting();
  348. AffineTransform at = getTransform();
  349. Shape b = at.createTransformedShape(bounds);
  350. b = trans.createTransformedShape(b);
  351. if (b != null) {
  352. Rectangle rect = b.getBounds();
  353. if (linkType != PDFLink.EXTERNAL) {
  354. String pdfdest = "/FitR " + dest;
  355. resourceContext.addAnnotation(
  356. pdfDoc.getFactory().makeLink(rect, getPageReference(), pdfdest));
  357. } else {
  358. resourceContext.addAnnotation(
  359. pdfDoc.getFactory().makeLink(rect, dest, linkType, 0));
  360. }
  361. }
  362. }
  363. /**
  364. * Add a natively handled image directly to the PDF document.
  365. * This is used by the PDFImageElementBridge to draw a natively handled image
  366. * (like JPEG or CCITT images)
  367. * directly into the PDF document rather than converting the image into
  368. * a bitmap and increasing the size.
  369. *
  370. * @param image the image to draw
  371. * @param x the x position
  372. * @param y the y position
  373. * @param width the width to draw the image
  374. * @param height the height to draw the image
  375. */
  376. public void addNativeImage(org.apache.xmlgraphics.image.loader.Image image, float x, float y,
  377. float width, float height) {
  378. preparePainting();
  379. String key = image.getInfo().getOriginalURI();
  380. if (key == null) {
  381. // Need to include hash code as when invoked from FO you
  382. // may have several 'independent' PDFGraphics2D so the
  383. // count is not enough.
  384. key = "__AddNative_" + hashCode() + "_" + nativeCount;
  385. nativeCount++;
  386. }
  387. PDFImage pdfImage;
  388. if (image instanceof ImageRawJPEG) {
  389. pdfImage = new ImageRawJPEGAdapter((ImageRawJPEG)image, key);
  390. } else if (image instanceof ImageRawCCITTFax) {
  391. pdfImage = new ImageRawCCITTFaxAdapter((ImageRawCCITTFax)image, key);
  392. } else {
  393. throw new IllegalArgumentException(
  394. "Unsupported Image subclass: " + image.getClass().getName());
  395. }
  396. PDFXObject xObject = this.pdfDoc.addImage(resourceContext, pdfImage);
  397. flushPDFDocument();
  398. AffineTransform at = new AffineTransform();
  399. at.translate(x, y);
  400. useXObject(xObject, at, width, height);
  401. }
  402. private void flushPDFDocument() {
  403. if (outputStream != null) {
  404. try {
  405. this.pdfDoc.output(outputStream);
  406. } catch (IOException ioe) {
  407. // ignore exception, will be thrown again later
  408. }
  409. }
  410. }
  411. /**
  412. * Draws as much of the specified image as is currently available.
  413. * The image is drawn with its top-left corner at
  414. * (<i>x</i>,&nbsp;<i>y</i>) in this graphics context's coordinate
  415. * space. Transparent pixels in the image do not affect whatever
  416. * pixels are already there.
  417. * <p>
  418. * This method returns immediately in all cases, even if the
  419. * complete image has not yet been loaded, and it has not been dithered
  420. * and converted for the current output device.
  421. * <p>
  422. * If the image has not yet been completely loaded, then
  423. * <code>drawImage</code> returns <code>false</code>. As more of
  424. * the image becomes available, the process that draws the image notifies
  425. * the specified image observer.
  426. * @param img the specified image to be drawn.
  427. * @param x the <i>x</i> coordinate.
  428. * @param y the <i>y</i> coordinate.
  429. * @param observer object to be notified as more of
  430. * the image is converted.
  431. * @return true if the image was drawn
  432. * @see java.awt.Image
  433. * @see java.awt.image.ImageObserver
  434. * @see java.awt.image.ImageObserver#imageUpdate(java.awt.Image, int, int, int, int, int)
  435. */
  436. public boolean drawImage(Image img, int x, int y,
  437. ImageObserver observer) {
  438. preparePainting();
  439. int width = img.getWidth(observer);
  440. int height = img.getHeight(observer);
  441. if (width == -1 || height == -1) {
  442. return false;
  443. }
  444. return drawImage(img, x, y, width, height, observer);
  445. }
  446. private BufferedImage buildBufferedImage(Dimension size) {
  447. return new BufferedImage(size.width, size.height,
  448. BufferedImage.TYPE_INT_ARGB);
  449. }
  450. /** {@inheritDoc} */
  451. public boolean drawImage(Image img, int x, int y, int width, int height,
  452. ImageObserver observer) {
  453. preparePainting();
  454. // first we look to see if we've already added this image to
  455. // the pdf document. If so, we just reuse the reference;
  456. // otherwise we have to build a FopImage and add it to the pdf
  457. // document
  458. String key = "TempImage:" + img.toString();
  459. PDFXObject xObject = pdfDoc.getXObject(key);
  460. if (xObject == null) {
  461. // OK, have to build and add a PDF image
  462. Dimension size = new Dimension(width, height);
  463. BufferedImage buf = buildBufferedImage(size);
  464. java.awt.Graphics2D g = buf.createGraphics();
  465. g.setComposite(AlphaComposite.SrcOver);
  466. g.setBackground(new Color(1, 1, 1, 0));
  467. g.setPaint(new Color(1, 1, 1, 0));
  468. g.fillRect(0, 0, width, height);
  469. int imageWidth = buf.getWidth();
  470. int imageHeight = buf.getHeight();
  471. g.clip(new Rectangle(0, 0, imageWidth, imageHeight));
  472. g.setComposite(gc.getComposite());
  473. boolean drawn = g.drawImage(img, 0, 0, imageWidth, imageHeight, observer);
  474. if (!drawn) {
  475. return false;
  476. }
  477. g.dispose();
  478. xObject = addRenderedImage(key, buf);
  479. } else {
  480. resourceContext.getPDFResources().addXObject(xObject);
  481. }
  482. AffineTransform at = new AffineTransform();
  483. at.translate(x, y);
  484. useXObject(xObject, at, width, height);
  485. return true;
  486. }
  487. /**
  488. * Disposes of this graphics context and releases
  489. * any system resources that it is using.
  490. * A <code>Graphics</code> object cannot be used after
  491. * <code>dispose</code>has been called.
  492. * <p>
  493. * When a Java program runs, a large number of <code>Graphics</code>
  494. * objects can be created within a short time frame.
  495. * Although the finalization process of the garbage collector
  496. * also disposes of the same system resources, it is preferable
  497. * to manually free the associated resources by calling this
  498. * method rather than to rely on a finalization process which
  499. * may not run to completion for a long period of time.
  500. * <p>
  501. * Graphics objects which are provided as arguments to the
  502. * <code>paint</code> and <code>update</code> methods
  503. * of components are automatically released by the system when
  504. * those methods return. For efficiency, programmers should
  505. * call <code>dispose</code> when finished using
  506. * a <code>Graphics</code> object only if it was created
  507. * directly from a component or another <code>Graphics</code> object.
  508. * @see java.awt.Graphics#finalize
  509. * @see java.awt.Component#paint
  510. * @see java.awt.Component#update
  511. * @see java.awt.Component#getGraphics
  512. * @see java.awt.Graphics#create
  513. */
  514. public void dispose() {
  515. pdfDoc = null;
  516. fontInfo = null;
  517. currentStream = null;
  518. currentFontName = null;
  519. }
  520. /**
  521. * Strokes the outline of a <code>Shape</code> using the settings of the
  522. * current <code>Graphics2D</code> context. The rendering attributes
  523. * applied include the <code>Clip</code>, <code>Transform</code>,
  524. * <code>Paint</code>, <code>Composite</code> and
  525. * <code>Stroke</code> attributes.
  526. * @param s the <code>Shape</code> to be rendered
  527. * @see #setStroke
  528. * @see #setPaint
  529. * @see java.awt.Graphics#setColor
  530. * @see #transform
  531. * @see #setTransform
  532. * @see #clip
  533. * @see #setClip
  534. * @see #setComposite
  535. */
  536. public void draw(Shape s) {
  537. preparePainting();
  538. //Transparency shortcut
  539. Color c;
  540. c = getColor();
  541. if (c.getAlpha() == 0) {
  542. return;
  543. }
  544. AffineTransform trans = getTransform();
  545. double[] tranvals = new double[6];
  546. trans.getMatrix(tranvals);
  547. Shape imclip = getClip();
  548. boolean newClip = paintingState.checkClip(imclip);
  549. boolean newTransform = paintingState.checkTransform(trans)
  550. && !trans.isIdentity();
  551. if (newClip || newTransform) {
  552. currentStream.write("q\n");
  553. paintingState.save();
  554. if (newTransform) {
  555. concatMatrix(tranvals);
  556. }
  557. if (newClip) {
  558. writeClip(imclip);
  559. }
  560. }
  561. applyAlpha(OPAQUE, c.getAlpha());
  562. c = getColor();
  563. applyColor(c, false);
  564. c = getBackground();
  565. applyColor(c, true);
  566. Paint paint = getPaint();
  567. if (paintingState.setPaint(paint)) {
  568. if (!applyPaint(paint, false)) {
  569. // Stroke the shape and use it to 'clip'
  570. // the paint contents.
  571. Shape ss = getStroke().createStrokedShape(s);
  572. applyUnknownPaint(paint, ss);
  573. if (newClip || newTransform) {
  574. currentStream.write("Q\n");
  575. paintingState.restore();
  576. }
  577. return;
  578. }
  579. }
  580. applyStroke(getStroke());
  581. PathIterator iter = s.getPathIterator(IDENTITY_TRANSFORM);
  582. processPathIterator(iter);
  583. doDrawing(false, true, false);
  584. if (newClip || newTransform) {
  585. currentStream.write("Q\n");
  586. paintingState.restore();
  587. }
  588. }
  589. /*
  590. // in theory we could set the clip using these methods
  591. // it doesn't seem to improve the file sizes much
  592. // and makes everything more complicated
  593. Shape lastClip = null;
  594. public void clip(Shape cl) {
  595. super.clip(cl);
  596. Shape newClip = getClip();
  597. if (newClip == null || lastClip == null
  598. || !(new Area(newClip).equals(new Area(lastClip)))) {
  599. graphicsState.setClip(newClip);
  600. writeClip(newClip);
  601. }
  602. lastClip = newClip;
  603. }
  604. public void setClip(Shape cl) {
  605. super.setClip(cl);
  606. Shape newClip = getClip();
  607. if (newClip == null || lastClip == null
  608. || !(new Area(newClip).equals(new Area(lastClip)))) {
  609. for (int count = graphicsState.getStackLevel(); count > baseLevel; count--) {
  610. currentStream.write("Q\n");
  611. }
  612. graphicsState.restoreLevel(baseLevel);
  613. currentStream.write("q\n");
  614. graphicsState.push();
  615. if (newClip != null) {
  616. graphicsState.setClip(newClip);
  617. }
  618. writeClip(newClip);
  619. }
  620. lastClip = newClip;
  621. }
  622. */
  623. /**
  624. * Set the clipping shape for future PDF drawing in the current graphics state.
  625. * This sets creates and writes a clipping shape that will apply
  626. * to future drawings in the current graphics state.
  627. *
  628. * @param s the clipping shape
  629. */
  630. protected void writeClip(Shape s) {
  631. if (s == null) {
  632. return;
  633. }
  634. PathIterator iter = s.getPathIterator(IDENTITY_TRANSFORM);
  635. if (iter.isDone()) {
  636. // no segments available. Not worth doing anything
  637. return;
  638. }
  639. preparePainting();
  640. processPathIterator(iter);
  641. // clip area
  642. currentStream.write("W\n");
  643. currentStream.write("n\n");
  644. }
  645. /**
  646. * Apply the java Color to PDF.
  647. * This converts the java colour to a PDF colour and
  648. * sets it for the next drawing.
  649. *
  650. * @param col the java colour
  651. * @param fill true if the colour will be used for filling
  652. */
  653. protected void applyColor(Color col, boolean fill) {
  654. preparePainting();
  655. Color c = col;
  656. if (col instanceof ColorExt) {
  657. PDFColor currentColour = new PDFColor(this.pdfDoc, col);
  658. currentStream.write(currentColour.getColorSpaceOut(fill));
  659. } else if (c.getColorSpace().getType()
  660. == ColorSpace.TYPE_RGB) {
  661. PDFColor currentColour = new PDFColor(c.getRed(), c.getGreen(),
  662. c.getBlue());
  663. currentStream.write(currentColour.getColorSpaceOut(fill));
  664. } else if (c.getColorSpace().getType()
  665. == ColorSpace.TYPE_CMYK) {
  666. if (pdfDoc.getProfile().getPDFAMode().isPDFA1LevelB()) {
  667. //See PDF/A-1, ISO 19005:1:2005(E), 6.2.3.3
  668. //FOP is currently restricted to DeviceRGB if PDF/A-1 is active.
  669. throw new PDFConformanceException(
  670. "PDF/A-1 does not allow mixing DeviceRGB and DeviceCMYK.");
  671. }
  672. PDFColor currentColour = new PDFColor(c);
  673. currentStream.write(currentColour.getColorSpaceOut(fill));
  674. } else if (c.getColorSpace().getType()
  675. == ColorSpace.TYPE_2CLR) {
  676. // used for black/magenta
  677. float[] cComps = c.getColorComponents(new float[1]);
  678. double[] blackMagenta = new double[1];
  679. for (int i = 0; i < 1; i++) {
  680. blackMagenta[i] = cComps[i];
  681. }
  682. //PDFColor currentColour = new PDFColor(blackMagenta[0], blackMagenta[1]);
  683. //currentStream.write(currentColour.getColorSpaceOut(fill));
  684. } else {
  685. throw new UnsupportedOperationException(
  686. "Color Space not supported by PDFGraphics2D");
  687. }
  688. }
  689. /**
  690. * Apply the java paint to the PDF.
  691. * This takes the java paint sets up the appropraite PDF commands
  692. * for the drawing with that paint.
  693. * Currently this supports the gradients and patterns from batik.
  694. *
  695. * @param paint the paint to convert to PDF
  696. * @param fill true if the paint should be set for filling
  697. * @return true if the paint is handled natively, false if the paint should be rasterized
  698. */
  699. protected boolean applyPaint(Paint paint, boolean fill) {
  700. preparePainting();
  701. if (paint instanceof Color) {
  702. return true;
  703. }
  704. // convert java.awt.GradientPaint to LinearGradientPaint to avoid rasterization
  705. if (paint instanceof GradientPaint) {
  706. GradientPaint gpaint = (GradientPaint) paint;
  707. paint = new LinearGradientPaint(
  708. (float) gpaint.getPoint1().getX(),
  709. (float) gpaint.getPoint1().getY(),
  710. (float) gpaint.getPoint2().getX(),
  711. (float) gpaint.getPoint2().getY(),
  712. new float[] {0, 1},
  713. new Color[] {gpaint.getColor1(), gpaint.getColor2()},
  714. gpaint.isCyclic() ? LinearGradientPaint.REPEAT : LinearGradientPaint.NO_CYCLE);
  715. }
  716. if (paint instanceof LinearGradientPaint) {
  717. LinearGradientPaint gp = (LinearGradientPaint)paint;
  718. // This code currently doesn't support 'repeat'.
  719. // For linear gradients it is possible to construct
  720. // a 'tile' that is repeated with a PDF pattern, but
  721. // it would be very tricky as you would have to rotate
  722. // the coordinate system so the repeat was axially
  723. // aligned. At this point I'm just going to rasterize it.
  724. MultipleGradientPaint.CycleMethodEnum cycle = gp.getCycleMethod();
  725. if (cycle != MultipleGradientPaint.NO_CYCLE) {
  726. return false;
  727. }
  728. Color[] cols = gp.getColors();
  729. float[] fractions = gp.getFractions();
  730. // Build proper transform from gradient space to page space
  731. // ('Patterns' don't get userspace transform).
  732. AffineTransform transform;
  733. transform = new AffineTransform(getBaseTransform());
  734. transform.concatenate(getTransform());
  735. transform.concatenate(gp.getTransform());
  736. List theMatrix = new java.util.ArrayList();
  737. double [] mat = new double[6];
  738. transform.getMatrix(mat);
  739. for (int idx = 0; idx < mat.length; idx++) {
  740. theMatrix.add(new Double(mat[idx]));
  741. }
  742. Point2D p1 = gp.getStartPoint();
  743. Point2D p2 = gp.getEndPoint();
  744. List theCoords = new java.util.ArrayList();
  745. theCoords.add(new Double(p1.getX()));
  746. theCoords.add(new Double(p1.getY()));
  747. theCoords.add(new Double(p2.getX()));
  748. theCoords.add(new Double(p2.getY()));
  749. List theExtend = new java.util.ArrayList();
  750. theExtend.add(new Boolean(true));
  751. theExtend.add(new Boolean(true));
  752. List theDomain = new java.util.ArrayList();
  753. theDomain.add(new Double(0));
  754. theDomain.add(new Double(1));
  755. List theEncode = new java.util.ArrayList();
  756. theEncode.add(new Double(0));
  757. theEncode.add(new Double(1));
  758. theEncode.add(new Double(0));
  759. theEncode.add(new Double(1));
  760. List theBounds = new java.util.ArrayList();
  761. List someColors = new java.util.ArrayList();
  762. for (int count = 0; count < cols.length; count++) {
  763. Color c1 = cols[count];
  764. if (c1.getAlpha() != 255) {
  765. return false; // PDF can't do alpha
  766. }
  767. PDFColor color1 = new PDFColor(c1.getRed(), c1.getGreen(),
  768. c1.getBlue());
  769. someColors.add(color1);
  770. if (count > 0 && count < cols.length - 1) {
  771. theBounds.add(new Double(fractions[count]));
  772. }
  773. }
  774. PDFDeviceColorSpace aColorSpace;
  775. aColorSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB);
  776. PDFPattern myPat = pdfDoc.getFactory().makeGradient(
  777. resourceContext, false, aColorSpace,
  778. someColors, theBounds, theCoords, theMatrix);
  779. currentStream.write(myPat.getColorSpaceOut(fill));
  780. return true;
  781. }
  782. if (paint instanceof RadialGradientPaint) {
  783. RadialGradientPaint rgp = (RadialGradientPaint)paint;
  784. // There is essentially no way to support repeats
  785. // in PDF for radial gradients (the one option would
  786. // be to 'grow' the outer circle until it fully covered
  787. // the bounds and then grow the stops accordingly, the
  788. // problem is that this may require an extremely large
  789. // number of stops for cases where the focus is near
  790. // the edge of the outer circle). so we rasterize.
  791. MultipleGradientPaint.CycleMethodEnum cycle = rgp.getCycleMethod();
  792. if (cycle != MultipleGradientPaint.NO_CYCLE) {
  793. return false;
  794. }
  795. AffineTransform transform;
  796. transform = new AffineTransform(getBaseTransform());
  797. transform.concatenate(getTransform());
  798. transform.concatenate(rgp.getTransform());
  799. List theMatrix = new java.util.ArrayList();
  800. double [] mat = new double[6];
  801. transform.getMatrix(mat);
  802. for (int idx = 0; idx < mat.length; idx++) {
  803. theMatrix.add(new Double(mat[idx]));
  804. }
  805. double ar = rgp.getRadius();
  806. Point2D ac = rgp.getCenterPoint();
  807. Point2D af = rgp.getFocusPoint();
  808. List theCoords = new java.util.ArrayList();
  809. double dx = af.getX() - ac.getX();
  810. double dy = af.getY() - ac.getY();
  811. double d = Math.sqrt(dx * dx + dy * dy);
  812. if (d > ar) {
  813. // the center point af must be within the circle with
  814. // radius ar centered at ac so limit it to that.
  815. double scale = (ar * .9999) / d;
  816. dx = dx * scale;
  817. dy = dy * scale;
  818. }
  819. theCoords.add(new Double(ac.getX() + dx)); // Fx
  820. theCoords.add(new Double(ac.getY() + dy)); // Fy
  821. theCoords.add(new Double(0));
  822. theCoords.add(new Double(ac.getX()));
  823. theCoords.add(new Double(ac.getY()));
  824. theCoords.add(new Double(ar));
  825. Color[] cols = rgp.getColors();
  826. List someColors = new java.util.ArrayList();
  827. for (int count = 0; count < cols.length; count++) {
  828. Color cc = cols[count];
  829. if (cc.getAlpha() != 255) {
  830. return false; // PDF can't do alpha
  831. }
  832. someColors.add(new PDFColor(cc.getRed(), cc.getGreen(),
  833. cc.getBlue()));
  834. }
  835. float[] fractions = rgp.getFractions();
  836. List theBounds = new java.util.ArrayList();
  837. for (int count = 1; count < fractions.length - 1; count++) {
  838. float offset = fractions[count];
  839. theBounds.add(new Double(offset));
  840. }
  841. PDFDeviceColorSpace colSpace;
  842. colSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB);
  843. PDFPattern myPat = pdfDoc.getFactory().makeGradient
  844. (resourceContext, true, colSpace,
  845. someColors, theBounds, theCoords, theMatrix);
  846. currentStream.write(myPat.getColorSpaceOut(fill));
  847. return true;
  848. }
  849. if (paint instanceof PatternPaint) {
  850. PatternPaint pp = (PatternPaint)paint;
  851. return createPattern(pp, fill);
  852. }
  853. return false; // unknown paint
  854. }
  855. private boolean createPattern(PatternPaint pp, boolean fill) {
  856. preparePainting();
  857. FontInfo specialFontInfo = new FontInfo();
  858. FontSetup.setup(specialFontInfo);
  859. PDFResources res = pdfDoc.getFactory().makeResources();
  860. PDFResourceContext context = new PDFResourceContext(res);
  861. PDFGraphics2D pattGraphic = new PDFGraphics2D(textAsShapes, specialFontInfo,
  862. pdfDoc, context, getPageReference(),
  863. "", 0);
  864. pattGraphic.setGraphicContext(new GraphicContext());
  865. pattGraphic.gc.validateTransformStack();
  866. pattGraphic.setRenderingHints(this.getRenderingHints());
  867. pattGraphic.setOutputStream(outputStream);
  868. GraphicsNode gn = pp.getGraphicsNode();
  869. Rectangle2D gnBBox = gn.getBounds();
  870. Rectangle2D rect = pp.getPatternRect();
  871. // if (!pp.getOverflow()) {
  872. gn.paint(pattGraphic);
  873. // } else {
  874. // /* Commented out until SVN version of Batik is included */
  875. // // For overflow we need to paint the content from
  876. // // all the tiles who's overflow will intersect one
  877. // // tile (left->right, top->bottom). Then we can
  878. // // simply replicate that tile as normal.
  879. // double gnMinX = gnBBox.getX();
  880. // double gnMaxX = gnBBox.getX() + gnBBox.getWidth();
  881. // double gnMinY = gnBBox.getY();
  882. // double gnMaxY = gnBBox.getY() + gnBBox.getHeight();
  883. // double patMaxX = rect.getX() + rect.getWidth();
  884. // double patMaxY = rect.getY() + rect.getHeight();
  885. // double stepX = rect.getWidth();
  886. // double stepY = rect.getHeight();
  887. //
  888. // int startX = (int)((rect.getX() - gnMaxX)/stepX);
  889. // int startY = (int)((rect.getY() - gnMaxY)/stepY);
  890. //
  891. // int endX = (int)((patMaxX - gnMinX)/stepX);
  892. // int endY = (int)((patMaxY - gnMinY)/stepY);
  893. //
  894. // pattGraphic.translate(startX*stepX, startY*stepY);
  895. // for (int yIdx=startY; yIdx<=endY; yIdx++) {
  896. // for (int xIdx=startX; xIdx<=endX; xIdx++) {
  897. // gn.paint(pattGraphic);
  898. // pattGraphic.translate(stepX,0);
  899. // }
  900. // pattGraphic.translate(-(endX-startX+1)*stepX, stepY);
  901. // }
  902. // }
  903. List bbox = new java.util.ArrayList();
  904. bbox.add(new Double(rect.getX()));
  905. bbox.add(new Double(rect.getHeight() + rect.getY()));
  906. bbox.add(new Double(rect.getWidth() + rect.getX()));
  907. bbox.add(new Double(rect.getY()));
  908. AffineTransform transform;
  909. transform = new AffineTransform(getBaseTransform());
  910. transform.concatenate(getTransform());
  911. transform.concatenate(pp.getPatternTransform());
  912. List theMatrix = new java.util.ArrayList();
  913. double [] mat = new double[6];
  914. transform.getMatrix(mat);
  915. for (int idx = 0; idx < mat.length; idx++) {
  916. theMatrix.add(new Double(mat[idx]));
  917. }
  918. /** @todo see if pdfDoc and res can be linked here,
  919. (currently res <> PDFDocument's resources) so addFonts()
  920. can be moved to PDFDocument class */
  921. res.addFonts(pdfDoc, specialFontInfo);
  922. PDFPattern myPat = pdfDoc.getFactory().makePattern(
  923. resourceContext, 1, res, 1, 1, bbox,
  924. rect.getWidth(), rect.getHeight(),
  925. theMatrix, null,
  926. pattGraphic.getBuffer());
  927. currentStream.write(myPat.getColorSpaceOut(fill));
  928. PDFAnnotList annots = context.getAnnotations();
  929. if (annots != null) {
  930. this.pdfDoc.addObject(annots);
  931. }
  932. flushPDFDocument();
  933. return true;
  934. }
  935. protected boolean applyUnknownPaint(Paint paint, Shape shape) {
  936. preparePainting();
  937. Shape clip = getClip();
  938. Rectangle2D usrClipBounds, usrBounds;
  939. usrBounds = shape.getBounds2D();
  940. if (clip != null) {
  941. usrClipBounds = clip.getBounds2D();
  942. if (!usrClipBounds.intersects(usrBounds)) {
  943. return true;
  944. }
  945. Rectangle2D.intersect(usrBounds, usrClipBounds, usrBounds);
  946. }
  947. double usrX = usrBounds.getX();
  948. double usrY = usrBounds.getY();
  949. double usrW = usrBounds.getWidth();
  950. double usrH = usrBounds.getHeight();
  951. Rectangle devShapeBounds, devClipBounds, devBounds;
  952. AffineTransform at = getTransform();
  953. devShapeBounds = at.createTransformedShape(shape).getBounds();
  954. if (clip != null) {
  955. devClipBounds = at.createTransformedShape(clip).getBounds();
  956. if (!devClipBounds.intersects(devShapeBounds)) {
  957. return true;
  958. }
  959. devBounds = devShapeBounds.intersection(devClipBounds);
  960. } else {
  961. devBounds = devShapeBounds;
  962. }
  963. int devX = devBounds.x;
  964. int devY = devBounds.y;
  965. int devW = devBounds.width;
  966. int devH = devBounds.height;
  967. ColorSpace rgbCS = ColorSpace.getInstance(ColorSpace.CS_sRGB);
  968. ColorModel rgbCM = new DirectColorModel
  969. (rgbCS, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000,
  970. false, DataBuffer.TYPE_BYTE);
  971. PaintContext pctx = paint.createContext(rgbCM, devBounds, usrBounds,
  972. at, getRenderingHints());
  973. PDFXObject imageInfo = pdfDoc.getXObject
  974. ("TempImage:" + pctx.toString());
  975. if (imageInfo != null) {
  976. resourceContext.getPDFResources().addXObject(imageInfo);
  977. } else {
  978. Raster r = pctx.getRaster(devX, devY, devW, devH);
  979. WritableRaster wr = (WritableRaster)r;
  980. wr = wr.createWritableTranslatedChild(0, 0);
  981. ColorModel pcm = pctx.getColorModel();
  982. BufferedImage bi = new BufferedImage
  983. (pcm, wr, pcm.isAlphaPremultiplied(), null);
  984. final byte[] rgb = new byte[devW * devH * 3];
  985. final int[] line = new int[devW];
  986. final byte[] mask;
  987. int x, y, val, rgbIdx = 0;
  988. if (pcm.hasAlpha()) {
  989. mask = new byte[devW * devH];
  990. int maskIdx = 0;
  991. for (y = 0; y < devH; y++) {
  992. bi.getRGB(0, y, devW, 1, line, 0, devW);
  993. for (x = 0; x < devW; x++) {
  994. val = line[x];
  995. mask[maskIdx++] = (byte)(val >>> 24);
  996. rgb[rgbIdx++] = (byte)((val >> 16) & 0x0FF);
  997. rgb[rgbIdx++] = (byte)((val >> 8 ) & 0x0FF);
  998. rgb[rgbIdx++] = (byte)((val ) & 0x0FF);
  999. }
  1000. }
  1001. } else {
  1002. mask = null;
  1003. for (y = 0; y < devH; y++) {
  1004. bi.getRGB(0, y, devW, 1, line, 0, devW);
  1005. for (x = 0; x < devW; x++) {
  1006. val = line[x];
  1007. rgb[rgbIdx++] = (byte)((val >> 16) & 0x0FF);
  1008. rgb[rgbIdx++] = (byte)((val >> 8 ) & 0x0FF);
  1009. rgb[rgbIdx++] = (byte)((val ) & 0x0FF);
  1010. }
  1011. }
  1012. }
  1013. String maskRef = null;
  1014. if (mask != null) {
  1015. BitmapImage fopimg = new BitmapImage
  1016. ("TempImageMask:" + pctx.toString(), devW, devH, mask, null);
  1017. fopimg.setColorSpace(new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_GRAY));
  1018. PDFImageXObject xobj = pdfDoc.addImage(resourceContext, fopimg);
  1019. maskRef = xobj.referencePDF();
  1020. flushPDFDocument();
  1021. }
  1022. BitmapImage fopimg;
  1023. fopimg = new BitmapImage("TempImage:" + pctx.toString(),
  1024. devW, devH, rgb, maskRef);
  1025. fopimg.setTransparent(new PDFColor(255, 255, 255));
  1026. imageInfo = pdfDoc.addImage(resourceContext, fopimg);
  1027. flushPDFDocument();
  1028. }
  1029. currentStream.write("q\n");
  1030. writeClip(shape);
  1031. currentStream.write("" + usrW + " 0 0 " + (-usrH) + " " + usrX
  1032. + " " + (usrY + usrH) + " cm\n"
  1033. + imageInfo.getName() + " Do\nQ\n");
  1034. return true;
  1035. }
  1036. /**
  1037. * Apply the stroke to the PDF.
  1038. * This takes the java stroke and outputs the appropriate settings
  1039. * to the PDF so that the stroke attributes are handled.
  1040. *
  1041. * @param stroke the java stroke
  1042. */
  1043. protected void applyStroke(Stroke stroke) {
  1044. preparePainting();
  1045. if (stroke instanceof BasicStroke) {
  1046. BasicStroke bs = (BasicStroke)stroke;
  1047. float[] da = bs.getDashArray();
  1048. if (da != null) {
  1049. currentStream.write("[");
  1050. for (int count = 0; count < da.length; count++) {
  1051. currentStream.write(PDFNumber.doubleOut(da[count]));
  1052. if (count < da.length - 1) {
  1053. currentStream.write(" ");
  1054. }
  1055. }
  1056. currentStream.write("] ");
  1057. float offset = bs.getDashPhase();
  1058. currentStream.write(PDFNumber.doubleOut(offset) + " d\n");
  1059. }
  1060. int ec = bs.getEndCap();
  1061. switch (ec) {
  1062. case BasicStroke.CAP_BUTT:
  1063. currentStream.write(0 + " J\n");
  1064. break;
  1065. case BasicStroke.CAP_ROUND:
  1066. currentStream.write(1 + " J\n");
  1067. break;
  1068. case BasicStroke.CAP_SQUARE:
  1069. currentStream.write(2 + " J\n");
  1070. break;
  1071. }
  1072. int lj = bs.getLineJoin();
  1073. switch (lj) {
  1074. case BasicStroke.JOIN_MITER:
  1075. currentStream.write(0 + " j\n");
  1076. break;
  1077. case BasicStroke.JOIN_ROUND:
  1078. currentStream.write(1 + " j\n");
  1079. break;
  1080. case BasicStroke.JOIN_BEVEL:
  1081. currentStream.write(2 + " j\n");
  1082. break;
  1083. }
  1084. float lw = bs.getLineWidth();
  1085. currentStream.write(PDFNumber.doubleOut(lw) + " w\n");
  1086. float ml = bs.getMiterLimit();
  1087. currentStream.write(PDFNumber.doubleOut(ml) + " M\n");
  1088. }
  1089. }
  1090. /** {@inheritDoc} */
  1091. public void drawRenderedImage(RenderedImage img, AffineTransform xform) {
  1092. String key = "TempImage:" + img.toString();
  1093. drawInnerRenderedImage(key, img, xform);
  1094. }
  1095. /** {@inheritDoc} */
  1096. public void drawInnerRenderedImage(String key, RenderedImage img, AffineTransform xform) {
  1097. preparePainting();
  1098. PDFXObject xObject = pdfDoc.getXObject(key);
  1099. if (xObject == null) {
  1100. xObject = addRenderedImage(key, img);
  1101. } else {
  1102. resourceContext.getPDFResources().addXObject(xObject);
  1103. }
  1104. useXObject(xObject, xform, img.getWidth(), img.getHeight());
  1105. }
  1106. private void useXObject(PDFXObject xObject, AffineTransform xform, float width, float height) {
  1107. // now do any transformation required and add the actual image
  1108. // placement instance
  1109. currentStream.write("q\n");
  1110. concatMatrix(getTransform());
  1111. Shape imclip = getClip();
  1112. writeClip(imclip);
  1113. concatMatrix(xform);
  1114. String w = PDFNumber.doubleOut(width, DEC);
  1115. String h = PDFNumber.doubleOut(height, DEC);
  1116. currentStream.write("" + w + " 0 0 -" + h + " 0 " + h + " cm\n"
  1117. + xObject.getName() + " Do\nQ\n");
  1118. }
  1119. private PDFXObject addRenderedImage(String key, RenderedImage img) {
  1120. ImageInfo info = new ImageInfo(null, "image/unknown");
  1121. ImageSize size = new ImageSize(img.getWidth(), img.getHeight(), 72);
  1122. info.setSize(size);
  1123. ImageRendered imgRend = new ImageRendered(info, img, null);
  1124. ImageRenderedAdapter adapter = new ImageRenderedAdapter(imgRend, key);
  1125. PDFXObject xObject = pdfDoc.addImage(resourceContext, adapter);
  1126. flushPDFDocument();
  1127. return xObject;
  1128. }
  1129. /** {@inheritDoc} */
  1130. public void drawRenderableImage(RenderableImage img,
  1131. AffineTransform xform) {
  1132. //TODO Check if this is good enough
  1133. drawRenderedImage(img.createDefaultRendering(), xform);
  1134. }
  1135. /**
  1136. * Renders the text specified by the specified <code>String</code>,
  1137. * using the current <code>Font</code> and <code>Paint</code> attributes
  1138. * in the <code>Graphics2D</code> context.
  1139. * The baseline of the first character is at position
  1140. * (<i>x</i>,&nbsp;<i>y</i>) in the User Space.
  1141. * The rendering attributes applied include the <code>Clip</code>,
  1142. * <code>Transform</code>, <code>Paint</code>, <code>Font</code> and
  1143. * <code>Composite</code> attributes. For characters in script systems
  1144. * such as Hebrew and Arabic, the glyphs can be rendered from right to
  1145. * left, in which case the coordinate supplied is the location of the
  1146. * leftmost character on the baseline.
  1147. * @param s the <code>String</code> to be rendered
  1148. * @param x the coordinate where the <code>String</code>
  1149. * should be rendered
  1150. * @param y the coordinate where the <code>String</code>
  1151. * should be rendered
  1152. * @see #setPaint
  1153. * @see java.awt.Graphics#setColor
  1154. * @see java.awt.Graphics#setFont
  1155. * @see #setTransform
  1156. * @see #setComposite
  1157. * @see #setClip
  1158. */
  1159. public void drawString(String s, float x, float y) {
  1160. preparePainting();
  1161. Font fontState;
  1162. AffineTransform fontTransform = null;
  1163. if (ovFontState == null) {
  1164. java.awt.Font gFont = getFont();
  1165. fontTransform = gFont.getTransform();
  1166. fontState = fontInfo.getFontInstanceForAWTFont(gFont);
  1167. } else {
  1168. fontState = fontInfo.getFontInstance(
  1169. ovFontState.getFontTriplet(), ovFontState.getFontSize());
  1170. ovFontState = null;
  1171. }
  1172. updateCurrentFont(fontState);
  1173. currentStream.write("q\n");
  1174. Color c = getColor();
  1175. applyColor(c, true);
  1176. applyPaint(getPaint(), true);
  1177. applyAlpha(c.getAlpha(), OPAQUE);
  1178. Map kerning = fontState.getKerning();
  1179. boolean kerningAvailable = (kerning != null && !kerning.isEmpty());
  1180. boolean useMultiByte = isMultiByteFont(currentFontName);
  1181. // String startText = useMultiByte ? "<FEFF" : "(";
  1182. String startText = useMultiByte ? "<" : "(";
  1183. String endText = useMultiByte ? "> " : ") ";
  1184. AffineTransform trans = getTransform();
  1185. //trans.translate(x, y);
  1186. double[] vals = new double[6];
  1187. trans.getMatrix(vals);
  1188. concatMatrix(vals);
  1189. Shape imclip = getClip();
  1190. writeClip(imclip);
  1191. currentStream.write("BT\n");
  1192. AffineTransform localTransform = new AffineTransform();
  1193. localTransform.translate(x, y);
  1194. if (fontTransform != null) {
  1195. localTransform.concatenate(fontTransform);
  1196. }
  1197. localTransform.scale(1, -1);
  1198. double[] lt = new double[6];
  1199. localTransform.getMatrix(lt);
  1200. currentStream.write(PDFNumber.doubleOut(lt[0]) + " "
  1201. + PDFNumber.doubleOut(lt[1]) + " " + PDFNumber.doubleOut(lt[2]) + " "
  1202. + PDFNumber.doubleOut(lt[3]) + " " + PDFNumber.doubleOut(lt[4]) + " "
  1203. + PDFNumber.doubleOut(lt[5]) + " Tm [" + startText);
  1204. int l = s.length();
  1205. for (int i = 0; i < l; i++) {
  1206. char ch = fontState.mapChar(s.charAt(i));
  1207. if (!useMultiByte) {
  1208. if (ch > 127) {
  1209. currentStream.write("\\");
  1210. currentStream.write(Integer.toOctalString(ch));
  1211. } else {
  1212. switch (ch) {
  1213. case '(':
  1214. case ')':
  1215. case '\\':
  1216. currentStream.write("\\");
  1217. break;
  1218. default:
  1219. }
  1220. currentStream.write(ch);
  1221. }
  1222. } else {
  1223. currentStream.write(PDFText.toUnicodeHex(ch));
  1224. }
  1225. if (kerningAvailable && (i + 1) < l) {
  1226. addKerning(currentStream, (new Integer(ch)),
  1227. (new Integer(fontState.mapChar(s.charAt(i + 1)))),
  1228. kerning, startText, endText);
  1229. }
  1230. }
  1231. currentStream.write(endText);
  1232. currentStream.write("] TJ\n");
  1233. currentStream.write("ET\n");
  1234. currentStream.write("Q\n");
  1235. }
  1236. /**
  1237. * Applies the given alpha values for filling and stroking.
  1238. * @param fillAlpha A value between 0 and 255 (=OPAQUE) for filling
  1239. * @param strokeAlpha A value between 0 and 255 (=OPAQUE) for stroking
  1240. */
  1241. protected void applyAlpha(int fillAlpha, int strokeAlpha) {
  1242. if (fillAlpha != OPAQUE || strokeAlpha != OPAQUE) {
  1243. checkTransparencyAllowed();
  1244. Map vals = new java.util.HashMap();
  1245. if (fillAlpha != OPAQUE) {
  1246. vals.put(PDFGState.GSTATE_ALPHA_NONSTROKE, new Float(fillAlpha / 255f));
  1247. }
  1248. if (strokeAlpha != OPAQUE) {
  1249. vals.put(PDFGState.GSTATE_ALPHA_STROKE, new Float(strokeAlpha / 255f));
  1250. }
  1251. PDFGState gstate = pdfDoc.getFactory().makeGState(
  1252. vals, paintingState.getGState());
  1253. resourceContext.addGState(gstate);
  1254. currentStream.write("/" + gstate.getName() + " gs\n");
  1255. }
  1256. }
  1257. /**
  1258. * Updates the currently selected font.
  1259. * @param font the new font to use
  1260. */
  1261. protected void updateCurrentFont(Font font) {
  1262. String name = font.getFontName();
  1263. float size = font.getFontSize() / 1000f;
  1264. //Only update if necessary
  1265. if ((!name.equals(this.currentFontName))
  1266. || (size != this.currentFontSize)) {
  1267. this.currentFontName = name;
  1268. this.currentFontSize = size;
  1269. currentStream.write("/" + name + " " + size + " Tf\n");
  1270. }
  1271. }
  1272. /**
  1273. * Returns a suitable internal font given an AWT Font instance.
  1274. * @param awtFont the AWT font
  1275. * @return the internal Font
  1276. * @deprecated use FontInfo.getFontInstanceForAWTFont(java.awt.Font awtFont) instead
  1277. */
  1278. protected Font getInternalFontForAWTFont(java.awt.Font awtFont) {
  1279. return fontInfo.getFontInstanceForAWTFont(awtFont);
  1280. }
  1281. /**
  1282. * Determines whether the font with the given name is a multi-byte font.
  1283. * @param name the name of the font
  1284. * @return true if it's a multi-byte font
  1285. */
  1286. protected boolean isMultiByteFont(String name) {
  1287. // This assumes that *all* CIDFonts use a /ToUnicode mapping
  1288. org.apache.fop.fonts.Typeface f
  1289. = (org.apache.fop.fonts.Typeface)fontInfo.getFonts().get(name);
  1290. return f.isMultiByte();
  1291. }
  1292. private void addKerning(StringWriter buf, Integer ch1, Integer ch2,
  1293. Map kerning, String startText,
  1294. String endText) {
  1295. preparePainting();
  1296. Map kernPair = (Map)kerning.get(ch1);
  1297. if (kernPair != null) {
  1298. Integer width = (Integer)kernPair.get(ch2);
  1299. if (width != null) {
  1300. currentStream.write(endText + (-width.intValue()) + " " + startText);
  1301. }
  1302. }
  1303. }
  1304. /**
  1305. * Renders the text of the specified iterator, using the
  1306. * <code>Graphics2D</code> context's current <code>Paint</code>. The
  1307. * iterator must specify a font
  1308. * for each character. The baseline of the
  1309. * first character is at position (<i>x</i>,&nbsp;<i>y</i>) in the
  1310. * User Space.
  1311. * The rendering attributes applied include the <code>Clip</code>,
  1312. * <code>Transform</code>, <code>Paint</code>, and
  1313. * <code>Composite</code> attributes.
  1314. * For characters in script systems such as Hebrew and Arabic,
  1315. * the glyphs can be rendered from right to left, in which case the
  1316. * coordinate supplied is the location of the leftmost character
  1317. * on the baseline.
  1318. * @param iterator the iterator whose text is to be rendered
  1319. * @param x the coordinate where the iterator's text is to be
  1320. * rendered
  1321. * @param y the coordinate where the iterator's text is to be
  1322. * rendered
  1323. * @see #setPaint
  1324. * @see java.awt.Graphics#setColor
  1325. * @see #setTransform
  1326. * @see #setComposite
  1327. * @see #setClip
  1328. *//* TODO Reimplement for higher efficiency similar to the way it was done in PDFTextPainter
  1329. public void drawString(AttributedCharacterIterator iterator, float x,
  1330. float y) {
  1331. preparePainting();
  1332. Font fontState = null;
  1333. Shape imclip = getClip();
  1334. writeClip(imclip);
  1335. Color c = getColor();
  1336. applyColor(c, true);
  1337. applyPaint(getPaint(), true);
  1338. boolean fill = true;
  1339. boolean stroke = false;
  1340. if (true) {
  1341. Stroke currentStroke = getStroke();
  1342. stroke = true;
  1343. applyStroke(currentStroke);
  1344. applyColor(c, false);
  1345. applyPaint(getPaint(), false);
  1346. }
  1347. currentStream.write("BT\n");
  1348. // set text rendering mode:
  1349. // 0 - fill, 1 - stroke, 2 - fill then stroke
  1350. int textr = 0;
  1351. if (fill && stroke) {
  1352. textr = 2;
  1353. } else if (stroke) {
  1354. textr = 1;
  1355. }
  1356. currentStream.write(textr + " Tr\n");
  1357. AffineTransform trans = getTransform();
  1358. trans.translate(x, y);
  1359. double[] vals = new double[6];
  1360. trans.getMatrix(vals);
  1361. for (char ch = iterator.first(); ch != CharacterIterator.DONE;
  1362. ch = iterator.next()) {
  1363. //Map attr = iterator.getAttributes();
  1364. String name = fontState.getFontName();
  1365. int size = fontState.getFontSize();
  1366. if ((!name.equals(this.currentFontName))
  1367. || (size != this.currentFontSize)) {
  1368. this.currentFontName = name;
  1369. this.currentFontSize = size;
  1370. currentStream.write("/" + name + " " + (size / 1000)
  1371. + " Tf\n");
  1372. }
  1373. currentStream.write(PDFNumber.doubleOut(vals[0], DEC) + " "
  1374. + PDFNumber.doubleOut(vals[1], DEC) + " "
  1375. + PDFNumber.doubleOut(vals[2], DEC) + " "
  1376. + PDFNumber.doubleOut(vals[3], DEC) + " "
  1377. + PDFNumber.doubleOut(vals[4], DEC) + " "
  1378. + PDFNumber.doubleOut(vals[5], DEC) + " Tm (" + ch
  1379. + ") Tj\n");
  1380. }
  1381. currentStream.write("ET\n");
  1382. }*/
  1383. /**
  1384. * Fills the interior of a <code>Shape</code> using the settings of the
  1385. * <code>Graphics2D</code> context. The rendering attributes applied
  1386. * include the <code>Clip</code>, <code>Transform</code>,
  1387. * <code>Paint</code>, and <code>Composite</code>.
  1388. * @param s the <code>Shape</code> to be filled
  1389. * @see #setPaint
  1390. * @see java.awt.Graphics#setColor
  1391. * @see #transform
  1392. * @see #setTransform
  1393. * @see #setComposite
  1394. * @see #clip
  1395. * @see #setClip
  1396. */
  1397. public void fill(Shape s) {
  1398. preparePainting();
  1399. //Transparency shortcut
  1400. Color c;
  1401. c = getBackground();
  1402. if (c.getAlpha() == 0) {
  1403. c = getColor();
  1404. if (c.getAlpha() == 0) {
  1405. return;
  1406. }
  1407. }
  1408. AffineTransform trans = getTransform();
  1409. double[] tranvals = new double[6];
  1410. trans.getMatrix(tranvals);
  1411. Shape imclip = getClip();
  1412. boolean newClip = paintingState.checkClip(imclip);
  1413. boolean newTransform = paintingState.checkTransform(trans)
  1414. && !trans.isIdentity();
  1415. if (newClip || newTransform) {
  1416. currentStream.write("q\n");
  1417. paintingState.save();
  1418. if (newTransform) {
  1419. concatMatrix(tranvals);
  1420. }
  1421. if (newClip) {
  1422. writeClip(imclip);
  1423. }
  1424. }
  1425. applyAlpha(c.getAlpha(), OPAQUE);
  1426. c = getColor();
  1427. applyColor(c, true);
  1428. c = getBackground();
  1429. applyColor(c, false);
  1430. Paint paint = getPaint();
  1431. if (paintingState.setPaint(paint)) {
  1432. if (!applyPaint(paint, true)) {
  1433. // Use the shape to 'clip' the paint contents.
  1434. applyUnknownPaint(paint, s);
  1435. if (newClip || newTransform) {
  1436. currentStream.write("Q\n");
  1437. paintingState.restore();
  1438. }
  1439. return;
  1440. }
  1441. }
  1442. //PathIterator iter = s.getPathIterator(getTransform());
  1443. PathIterator iter = s.getPathIterator(IDENTITY_TRANSFORM);
  1444. processPathIterator(iter);
  1445. doDrawing(true, false,
  1446. iter.getWindingRule() == PathIterator.WIND_EVEN_ODD);
  1447. if (newClip || newTransform) {
  1448. currentStream.write("Q\n");
  1449. paintingState.restore();
  1450. }
  1451. }
  1452. /** Checks whether the use of transparency is allowed. */
  1453. protected void checkTransparencyAllowed() {
  1454. pdfDoc.getProfile().verifyTransparencyAllowed("Java2D graphics");
  1455. }
  1456. /**
  1457. * Processes a path iterator generating the necessary painting operations.
  1458. * @param iter PathIterator to process
  1459. */
  1460. public void processPathIterator(PathIterator iter) {
  1461. while (!iter.isDone()) {
  1462. double[] vals = new double[6];
  1463. int type = iter.currentSegment(vals);
  1464. switch (type) {
  1465. case PathIterator.SEG_CUBICTO:
  1466. currentStream.write(PDFNumber.doubleOut(vals[0], DEC) + " "
  1467. + PDFNumber.doubleOut(vals[1], DEC) + " "
  1468. + PDFNumber.doubleOut(vals[2], DEC) + " "
  1469. + PDFNumber.doubleOut(vals[3], DEC) + " "
  1470. + PDFNumber.doubleOut(vals[4], DEC) + " "
  1471. + PDFNumber.doubleOut(vals[5], DEC) + " c\n");
  1472. break;
  1473. case PathIterator.SEG_LINETO:
  1474. currentStream.write(PDFNumber.doubleOut(vals[0], DEC) + " "
  1475. + PDFNumber.doubleOut(vals[1], DEC) + " l\n");
  1476. break;
  1477. case PathIterator.SEG_MOVETO:
  1478. currentStream.write(PDFNumber.doubleOut(vals[0], DEC) + " "
  1479. + PDFNumber.doubleOut(vals[1], DEC) + " m\n");
  1480. break;
  1481. case PathIterator.SEG_QUADTO:
  1482. currentStream.write(PDFNumber.doubleOut(vals[0], DEC) + " "
  1483. + PDFNumber.doubleOut(vals[1], DEC) + " "
  1484. + PDFNumber.doubleOut(vals[2], DEC) + " "
  1485. + PDFNumber.doubleOut(vals[3], DEC) + " y\n");
  1486. break;
  1487. case PathIterator.SEG_CLOSE:
  1488. currentStream.write("h\n");
  1489. break;
  1490. default:
  1491. break;
  1492. }
  1493. iter.next();
  1494. }
  1495. }
  1496. /**
  1497. * Do the PDF drawing command.
  1498. * This does the PDF drawing command according to fill
  1499. * stroke and winding rule.
  1500. *
  1501. * @param fill true if filling the path
  1502. * @param stroke true if stroking the path
  1503. * @param nonzero true if using the non-zero winding rule
  1504. */
  1505. protected void doDrawing(boolean fill, boolean stroke, boolean nonzero) {
  1506. preparePainting();
  1507. if (fill) {
  1508. if (stroke) {
  1509. if (nonzero) {
  1510. currentStream.write("B*\n");
  1511. } else {
  1512. currentStream.write("B\n");
  1513. }
  1514. } else {
  1515. if (nonzero) {
  1516. currentStream.write("f*\n");
  1517. } else {
  1518. currentStream.write("f\n");
  1519. }
  1520. }
  1521. } else {
  1522. // if (stroke)
  1523. currentStream.write("S\n");
  1524. }
  1525. }
  1526. /**
  1527. * Returns the device configuration associated with this
  1528. * <code>Graphics2D</code>.
  1529. *
  1530. * @return the PDF graphics configuration
  1531. */
  1532. public GraphicsConfiguration getDeviceConfiguration() {
  1533. return new PDFGraphicsConfiguration();
  1534. }
  1535. /**
  1536. * Used to create proper font metrics
  1537. */
  1538. private Graphics2D fmg;
  1539. {
  1540. BufferedImage bi = new BufferedImage(1, 1,
  1541. BufferedImage.TYPE_INT_ARGB);
  1542. fmg = bi.createGraphics();
  1543. }
  1544. /**
  1545. * Gets the font metrics for the specified font.
  1546. * @return the font metrics for the specified font.
  1547. * @param f the specified font
  1548. * @see java.awt.Graphics#getFont
  1549. * @see java.awt.FontMetrics
  1550. * @see java.awt.Graphics#getFontMetrics()
  1551. */
  1552. public java.awt.FontMetrics getFontMetrics(java.awt.Font f) {
  1553. return fmg.getFontMetrics(f);
  1554. }
  1555. /**
  1556. * Sets the paint mode of this graphics context to alternate between
  1557. * this graphics context's current color and the new specified color.
  1558. * This specifies that logical pixel operations are performed in the
  1559. * XOR mode, which alternates pixels between the current color and
  1560. * a specified XOR color.
  1561. * <p>
  1562. * When drawing operations are performed, pixels which are the
  1563. * current color are changed to the specified color, and vice versa.
  1564. * <p>
  1565. * Pixels that are of colors other than those two colors are changed
  1566. * in an unpredictable but reversible manner; if the same figure is
  1567. * drawn twice, then all pixels are restored to their original values.
  1568. * @param c1 the XOR alternation color
  1569. */
  1570. public void setXORMode(Color c1) {
  1571. //NYI
  1572. }
  1573. /**
  1574. * Copies an area of the component by a distance specified by
  1575. * <code>dx</code> and <code>dy</code>. From the point specified
  1576. * by <code>x</code> and <code>y</code>, this method
  1577. * copies downwards and to the right. To copy an area of the
  1578. * component to the left or upwards, specify a negative value for
  1579. * <code>dx</code> or <code>dy</code>.
  1580. * If a portion of the source rectangle lies outside the bounds
  1581. * of the component, or is obscured by another window or component,
  1582. * <code>copyArea</code> will be unable to copy the associated
  1583. * pixels. The area that is omitted can be refreshed by calling
  1584. * the component's <code>paint</code> method.
  1585. * @param x the <i>x</i> coordinate of the source rectangle.
  1586. * @param y the <i>y</i> coordinate of the source rectangle.
  1587. * @param width the width of the source rectangle.
  1588. * @param height the height of the source rectangle.
  1589. * @param dx the horizontal distance to copy the pixels.
  1590. * @param dy the vertical distance to copy the pixels.
  1591. */
  1592. public void copyArea(int x, int y, int width, int height, int dx,
  1593. int dy) {
  1594. //NYI
  1595. }
  1596. }