Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

PDFGraphics2D.java 65KB

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