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

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