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.

PDFRenderer.java 49KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334
  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.render.pdf;
  19. // Java
  20. import java.awt.Color;
  21. import java.awt.Point;
  22. import java.awt.Rectangle;
  23. import java.awt.geom.AffineTransform;
  24. import java.awt.geom.Point2D;
  25. import java.awt.geom.Rectangle2D;
  26. import java.io.FileNotFoundException;
  27. import java.io.IOException;
  28. import java.io.OutputStream;
  29. import java.util.Iterator;
  30. import java.util.List;
  31. import java.util.Map;
  32. import org.apache.xmlgraphics.image.loader.ImageException;
  33. import org.apache.xmlgraphics.image.loader.ImageFlavor;
  34. import org.apache.xmlgraphics.image.loader.ImageInfo;
  35. import org.apache.xmlgraphics.image.loader.ImageManager;
  36. import org.apache.xmlgraphics.image.loader.ImageSessionContext;
  37. import org.apache.xmlgraphics.image.loader.util.ImageUtil;
  38. import org.apache.fop.apps.FOPException;
  39. import org.apache.fop.apps.FOUserAgent;
  40. import org.apache.fop.apps.MimeConstants;
  41. import org.apache.fop.area.Area;
  42. import org.apache.fop.area.Block;
  43. import org.apache.fop.area.BookmarkData;
  44. import org.apache.fop.area.CTM;
  45. import org.apache.fop.area.DestinationData;
  46. import org.apache.fop.area.LineArea;
  47. import org.apache.fop.area.OffDocumentExtensionAttachment;
  48. import org.apache.fop.area.OffDocumentItem;
  49. import org.apache.fop.area.PageSequence;
  50. import org.apache.fop.area.PageViewport;
  51. import org.apache.fop.area.Trait;
  52. import org.apache.fop.area.inline.AbstractTextArea;
  53. import org.apache.fop.area.inline.Image;
  54. import org.apache.fop.area.inline.InlineArea;
  55. import org.apache.fop.area.inline.InlineParent;
  56. import org.apache.fop.area.inline.Leader;
  57. import org.apache.fop.area.inline.SpaceArea;
  58. import org.apache.fop.area.inline.TextArea;
  59. import org.apache.fop.area.inline.WordArea;
  60. import org.apache.fop.datatypes.URISpecification;
  61. import org.apache.fop.events.ResourceEventProducer;
  62. import org.apache.fop.fo.extensions.ExtensionAttachment;
  63. import org.apache.fop.fo.extensions.xmp.XMPMetadata;
  64. import org.apache.fop.fonts.Font;
  65. import org.apache.fop.fonts.LazyFont;
  66. import org.apache.fop.fonts.SingleByteFont;
  67. import org.apache.fop.fonts.Typeface;
  68. import org.apache.fop.pdf.PDFAMode;
  69. import org.apache.fop.pdf.PDFAction;
  70. import org.apache.fop.pdf.PDFAnnotList;
  71. import org.apache.fop.pdf.PDFDocument;
  72. import org.apache.fop.pdf.PDFEncryptionParams;
  73. import org.apache.fop.pdf.PDFFactory;
  74. import org.apache.fop.pdf.PDFGoTo;
  75. import org.apache.fop.pdf.PDFInfo;
  76. import org.apache.fop.pdf.PDFLink;
  77. import org.apache.fop.pdf.PDFNumber;
  78. import org.apache.fop.pdf.PDFOutline;
  79. import org.apache.fop.pdf.PDFPage;
  80. import org.apache.fop.pdf.PDFResourceContext;
  81. import org.apache.fop.pdf.PDFResources;
  82. import org.apache.fop.pdf.PDFState;
  83. import org.apache.fop.pdf.PDFTextUtil;
  84. import org.apache.fop.pdf.PDFXMode;
  85. import org.apache.fop.pdf.PDFXObject;
  86. import org.apache.fop.render.AbstractPathOrientedRenderer;
  87. import org.apache.fop.render.Graphics2DAdapter;
  88. import org.apache.fop.render.RendererContext;
  89. import org.apache.fop.util.CharUtilities;
  90. /**
  91. * Renderer that renders areas to PDF.
  92. */
  93. public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConfigurationConstants {
  94. /** The MIME type for PDF */
  95. public static final String MIME_TYPE = MimeConstants.MIME_PDF;
  96. /** Normal PDF resolution (72dpi) */
  97. public static final int NORMAL_PDF_RESOLUTION = 72;
  98. /** Controls whether comments are written to the PDF stream. */
  99. protected static final boolean WRITE_COMMENTS = true;
  100. /**
  101. * the PDF Document being created
  102. */
  103. protected PDFDocument pdfDoc;
  104. /**
  105. * Utility class which enables all sorts of features that are not directly connected to the
  106. * normal rendering process.
  107. */
  108. protected PDFRenderingUtil pdfUtil;
  109. /**
  110. * Map of pages using the PageViewport as the key
  111. * this is used for prepared pages that cannot be immediately
  112. * rendered
  113. */
  114. protected Map pages = null;
  115. /**
  116. * Maps unique PageViewport key to PDF page reference
  117. */
  118. protected Map pageReferences = new java.util.HashMap();
  119. /**
  120. * Maps unique PageViewport key back to PageViewport itself
  121. */
  122. //protected Map pvReferences = new java.util.HashMap();
  123. /**
  124. * Maps XSL-FO element IDs to their on-page XY-positions
  125. * Must be used in conjunction with the page reference to fully specify the PDFGoTo details
  126. */
  127. protected Map idPositions = new java.util.HashMap();
  128. /**
  129. * Maps XSL-FO element IDs to PDFGoTo objects targeting the corresponding areas
  130. * These objects may not all be fully filled in yet
  131. */
  132. protected Map idGoTos = new java.util.HashMap();
  133. /**
  134. * The PDFGoTos in idGoTos that are not complete yet
  135. */
  136. protected List unfinishedGoTos = new java.util.ArrayList();
  137. // can't use a Set because PDFGoTo.equals returns true if the target is the same,
  138. // even if the object number differs
  139. /**
  140. * The output stream to write the document to
  141. */
  142. protected OutputStream ostream;
  143. /**
  144. * the /Resources object of the PDF document being created
  145. */
  146. protected PDFResources pdfResources;
  147. /** The current content generator to produce PDF commands with */
  148. protected PDFContentGenerator generator;
  149. /**
  150. * the current annotation list to add annotations to
  151. */
  152. protected PDFResourceContext currentContext = null;
  153. /**
  154. * the current page to add annotations to
  155. */
  156. protected PDFPage currentPage;
  157. /**
  158. * the current page's PDF reference string (to avoid numerous function calls)
  159. */
  160. protected String currentPageRef;
  161. /** page height */
  162. protected int pageHeight;
  163. /** Image handler registry */
  164. private PDFImageHandlerRegistry imageHandlerRegistry = new PDFImageHandlerRegistry();
  165. /**
  166. * create the PDF renderer
  167. */
  168. public PDFRenderer() {
  169. }
  170. /** {@inheritDoc} */
  171. public void setUserAgent(FOUserAgent agent) {
  172. super.setUserAgent(agent);
  173. this.pdfUtil = new PDFRenderingUtil(getUserAgent());
  174. }
  175. PDFRenderingUtil getPDFUtil() {
  176. return this.pdfUtil;
  177. }
  178. PDFContentGenerator getGenerator() {
  179. return this.generator;
  180. }
  181. PDFState getState() {
  182. return getGenerator().getState();
  183. }
  184. /** {@inheritDoc} */
  185. public void startRenderer(OutputStream stream) throws IOException {
  186. if (userAgent == null) {
  187. throw new IllegalStateException("UserAgent must be set before starting the renderer");
  188. }
  189. ostream = stream;
  190. this.pdfDoc = pdfUtil.setupPDFDocument(stream);
  191. }
  192. /**
  193. * Checks if there are any unfinished PDFGoTos left in the list and resolves them
  194. * to a default position on the page. Logs a warning, as this should not happen.
  195. */
  196. protected void finishOpenGoTos() {
  197. int count = unfinishedGoTos.size();
  198. if (count > 0) {
  199. // TODO : page height may not be the same for all targeted pages
  200. Point2D.Float defaultPos = new Point2D.Float(0f, pageHeight / 1000f); // top-o-page
  201. while (!unfinishedGoTos.isEmpty()) {
  202. PDFGoTo gt = (PDFGoTo) unfinishedGoTos.get(0);
  203. finishIDGoTo(gt, defaultPos);
  204. }
  205. PDFEventProducer eventProducer = PDFEventProducer.Provider.get(
  206. getUserAgent().getEventBroadcaster());
  207. eventProducer.nonFullyResolvedLinkTargets(this, count);
  208. // dysfunctional if pageref is null
  209. }
  210. }
  211. /** {@inheritDoc} */
  212. public void stopRenderer() throws IOException {
  213. finishOpenGoTos();
  214. pdfDoc.getResources().addFonts(pdfDoc, fontInfo);
  215. pdfDoc.outputTrailer(ostream);
  216. this.pdfDoc = null;
  217. ostream = null;
  218. pages = null;
  219. pageReferences.clear();
  220. //pvReferences.clear();
  221. pdfResources = null;
  222. this.generator = null;
  223. //currentStream = null;
  224. currentContext = null;
  225. currentPage = null;
  226. //currentState = null;
  227. //this.textutil = null;
  228. idPositions.clear();
  229. idGoTos.clear();
  230. }
  231. /**
  232. * {@inheritDoc}
  233. */
  234. public boolean supportsOutOfOrder() {
  235. //return false;
  236. return true;
  237. }
  238. /**
  239. * {@inheritDoc}
  240. */
  241. public void processOffDocumentItem(OffDocumentItem odi) {
  242. if (odi instanceof DestinationData) {
  243. // render Destinations
  244. renderDestination((DestinationData) odi);
  245. } else if (odi instanceof BookmarkData) {
  246. // render Bookmark-Tree
  247. renderBookmarkTree((BookmarkData) odi);
  248. } else if (odi instanceof OffDocumentExtensionAttachment) {
  249. ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)odi).getAttachment();
  250. if (XMPMetadata.CATEGORY.equals(attachment.getCategory())) {
  251. pdfUtil.renderXMPMetadata((XMPMetadata)attachment);
  252. }
  253. }
  254. }
  255. private void renderDestination(DestinationData dd) {
  256. String targetID = dd.getIDRef();
  257. if (targetID == null || targetID.length() == 0) {
  258. throw new IllegalArgumentException("DestinationData must contain a ID reference");
  259. }
  260. PageViewport pv = dd.getPageViewport();
  261. if (pv != null) {
  262. PDFGoTo gt = getPDFGoToForID(targetID, pv.getKey());
  263. pdfDoc.getFactory().makeDestination(
  264. dd.getIDRef(), gt.makeReference());
  265. } else {
  266. //Warning already issued by AreaTreeHandler (debug level is sufficient)
  267. log.debug("Unresolved destination item received: " + dd.getIDRef());
  268. }
  269. }
  270. /**
  271. * Renders a Bookmark-Tree object
  272. * @param bookmarks the BookmarkData object containing all the Bookmark-Items
  273. */
  274. protected void renderBookmarkTree(BookmarkData bookmarks) {
  275. for (int i = 0; i < bookmarks.getCount(); i++) {
  276. BookmarkData ext = bookmarks.getSubData(i);
  277. renderBookmarkItem(ext, null);
  278. }
  279. }
  280. private void renderBookmarkItem(BookmarkData bookmarkItem,
  281. PDFOutline parentBookmarkItem) {
  282. PDFOutline pdfOutline = null;
  283. String targetID = bookmarkItem.getIDRef();
  284. if (targetID == null || targetID.length() == 0) {
  285. throw new IllegalArgumentException("DestinationData must contain a ID reference");
  286. }
  287. PageViewport pv = bookmarkItem.getPageViewport();
  288. if (pv != null) {
  289. String pvKey = pv.getKey();
  290. PDFGoTo gt = getPDFGoToForID(targetID, pvKey);
  291. // create outline object:
  292. PDFOutline parent = parentBookmarkItem != null
  293. ? parentBookmarkItem
  294. : pdfDoc.getOutlineRoot();
  295. pdfOutline = pdfDoc.getFactory().makeOutline(parent,
  296. bookmarkItem.getBookmarkTitle(), gt, bookmarkItem.showChildItems());
  297. } else {
  298. //Warning already issued by AreaTreeHandler (debug level is sufficient)
  299. log.debug("Bookmark with IDRef \"" + targetID + "\" has a null PageViewport.");
  300. }
  301. for (int i = 0; i < bookmarkItem.getCount(); i++) {
  302. renderBookmarkItem(bookmarkItem.getSubData(i), pdfOutline);
  303. }
  304. }
  305. /** {@inheritDoc} */
  306. public Graphics2DAdapter getGraphics2DAdapter() {
  307. return new PDFGraphics2DAdapter(this);
  308. }
  309. /** {@inheritDoc} */
  310. protected void saveGraphicsState() {
  311. generator.saveGraphicsState();
  312. }
  313. /** {@inheritDoc} */
  314. protected void restoreGraphicsState() {
  315. generator.restoreGraphicsState();
  316. }
  317. /** Indicates the beginning of a text object. */
  318. protected void beginTextObject() {
  319. generator.beginTextObject();
  320. }
  321. /** Indicates the end of a text object. */
  322. protected void endTextObject() {
  323. generator.endTextObject();
  324. }
  325. /**
  326. * Start the next page sequence.
  327. * For the PDF renderer there is no concept of page sequences
  328. * but it uses the first available page sequence title to set
  329. * as the title of the PDF document, and the language of the
  330. * document.
  331. * @param pageSequence the page sequence
  332. */
  333. public void startPageSequence(PageSequence pageSequence) {
  334. super.startPageSequence(pageSequence);
  335. LineArea seqTitle = pageSequence.getTitle();
  336. if (seqTitle != null) {
  337. String str = convertTitleToString(seqTitle);
  338. PDFInfo info = this.pdfDoc.getInfo();
  339. if (info.getTitle() == null) {
  340. info.setTitle(str);
  341. }
  342. }
  343. if (pageSequence.getLanguage() != null) {
  344. String lang = pageSequence.getLanguage();
  345. String country = pageSequence.getCountry();
  346. String langCode = lang + (country != null ? "-" + country : "");
  347. if (pdfDoc.getRoot().getLanguage() == null) {
  348. //Only set if not set already (first non-null is used)
  349. //Note: No checking is performed whether the values are valid!
  350. pdfDoc.getRoot().setLanguage(langCode);
  351. }
  352. }
  353. pdfUtil.generateDefaultXMPMetadata();
  354. }
  355. /**
  356. * The pdf page is prepared by making the page.
  357. * The page is made in the pdf document without any contents
  358. * and then stored to add the contents later.
  359. * The page objects is stored using the area tree PageViewport
  360. * as a key.
  361. *
  362. * @param page the page to prepare
  363. */
  364. public void preparePage(PageViewport page) {
  365. setupPage(page);
  366. if (pages == null) {
  367. pages = new java.util.HashMap();
  368. }
  369. pages.put(page, currentPage);
  370. }
  371. private void setupPage(PageViewport page) {
  372. this.pdfResources = this.pdfDoc.getResources();
  373. Rectangle2D bounds = page.getViewArea();
  374. double w = bounds.getWidth();
  375. double h = bounds.getHeight();
  376. this.currentPage = this.pdfDoc.getFactory().makePage(
  377. this.pdfResources,
  378. (int) Math.round(w / 1000), (int) Math.round(h / 1000),
  379. page.getPageIndex());
  380. pageReferences.put(page.getKey(), currentPage.referencePDF());
  381. //pvReferences.put(page.getKey(), page);
  382. pdfUtil.generatePageLabel(page.getPageIndex(), page.getPageNumberString());
  383. }
  384. /**
  385. * This method creates a PDF stream for the current page
  386. * uses it as the contents of a new page. The page is written
  387. * immediately to the output stream.
  388. * {@inheritDoc}
  389. */
  390. public void renderPage(PageViewport page)
  391. throws IOException, FOPException {
  392. if (pages != null
  393. && (currentPage = (PDFPage) pages.get(page)) != null) {
  394. //Retrieve previously prepared page (out-of-line rendering)
  395. pages.remove(page);
  396. } else {
  397. setupPage(page);
  398. }
  399. currentPageRef = currentPage.referencePDF();
  400. Rectangle2D bounds = page.getViewArea();
  401. double h = bounds.getHeight();
  402. pageHeight = (int) h;
  403. this.generator = new PDFContentGenerator(this.pdfDoc, this.ostream, this.currentPage);
  404. /*
  405. currentStream = this.pdfDoc.getFactory()
  406. .makeStream(PDFFilterList.CONTENT_FILTER, false);
  407. this.textutil = new PDFTextUtil() {
  408. protected void write(String code) {
  409. currentStream.add(code);
  410. }
  411. };
  412. currentState = new PDFState();
  413. */
  414. // Transform the PDF's default coordinate system (0,0 at lower left) to the PDFRenderer's
  415. AffineTransform basicPageTransform = new AffineTransform(1, 0, 0, -1, 0,
  416. pageHeight / 1000f);
  417. generator.concatenate(basicPageTransform);
  418. /*
  419. currentState.concatenate(basicPageTransform);
  420. currentStream.add(CTMHelper.toPDFString(basicPageTransform, false) + " cm\n");
  421. */
  422. super.renderPage(page);
  423. this.pdfDoc.registerObject(generator.getStream());
  424. currentPage.setContents(generator.getStream());
  425. PDFAnnotList annots = currentPage.getAnnotations();
  426. if (annots != null) {
  427. this.pdfDoc.addObject(annots);
  428. }
  429. this.pdfDoc.addObject(currentPage);
  430. this.generator.flushPDFDoc();
  431. this.generator = null;
  432. }
  433. /** {@inheritDoc} */
  434. protected void startVParea(CTM ctm, Rectangle2D clippingRect) {
  435. saveGraphicsState();
  436. // Set the given CTM in the graphics state
  437. /*
  438. currentState.concatenate(
  439. new AffineTransform(CTMHelper.toPDFArray(ctm)));
  440. */
  441. if (clippingRect != null) {
  442. clipRect((float)clippingRect.getX() / 1000f,
  443. (float)clippingRect.getY() / 1000f,
  444. (float)clippingRect.getWidth() / 1000f,
  445. (float)clippingRect.getHeight() / 1000f);
  446. }
  447. // multiply with current CTM
  448. generator.concatenate(new AffineTransform(CTMHelper.toPDFArray(ctm)));
  449. //currentStream.add(CTMHelper.toPDFString(ctm) + " cm\n");
  450. }
  451. /** {@inheritDoc} */
  452. protected void endVParea() {
  453. restoreGraphicsState();
  454. }
  455. /** {@inheritDoc} */
  456. protected void concatenateTransformationMatrix(AffineTransform at) {
  457. generator.concatenate(at);
  458. /*
  459. if (!at.isIdentity()) {
  460. currentState.concatenate(at);
  461. currentStream.add(CTMHelper.toPDFString(at, false) + " cm\n");
  462. }*/
  463. }
  464. /**
  465. * Formats a float value (normally coordinates) as Strings.
  466. * @param value the value
  467. * @return the formatted value
  468. */
  469. protected static String format(float value) {
  470. return PDFNumber.doubleOut(value);
  471. }
  472. /** {@inheritDoc} */
  473. protected void drawBorderLine(float x1, float y1, float x2, float y2,
  474. boolean horz, boolean startOrBefore, int style, Color col) {
  475. PDFBorderPainter.drawBorderLine(generator, x1, y1, x2, y2, horz, startOrBefore, style, col);
  476. }
  477. /** {@inheritDoc} */
  478. protected void clipRect(float x, float y, float width, float height) {
  479. generator.add(format(x) + " " + format(y) + " "
  480. + format(width) + " " + format(height) + " re ");
  481. clip();
  482. }
  483. /**
  484. * Clip an area.
  485. */
  486. protected void clip() {
  487. generator.add("W\n" + "n\n");
  488. }
  489. /**
  490. * Moves the current point to (x, y), omitting any connecting line segment.
  491. * @param x x coordinate
  492. * @param y y coordinate
  493. */
  494. protected void moveTo(float x, float y) {
  495. generator.add(format(x) + " " + format(y) + " m ");
  496. }
  497. /**
  498. * Appends a straight line segment from the current point to (x, y). The
  499. * new current point is (x, y).
  500. * @param x x coordinate
  501. * @param y y coordinate
  502. */
  503. protected void lineTo(float x, float y) {
  504. generator.add(format(x) + " " + format(y) + " l ");
  505. }
  506. /**
  507. * Closes the current subpath by appending a straight line segment from
  508. * the current point to the starting point of the subpath.
  509. */
  510. protected void closePath() {
  511. generator.add("h ");
  512. }
  513. /**
  514. * {@inheritDoc}
  515. */
  516. protected void fillRect(float x, float y, float w, float h) {
  517. if (w != 0 && h != 0) {
  518. generator.add(format(x) + " " + format(y) + " "
  519. + format(w) + " " + format(h) + " re f\n");
  520. }
  521. }
  522. /**
  523. * Draw a line.
  524. *
  525. * @param startx the start x position
  526. * @param starty the start y position
  527. * @param endx the x end position
  528. * @param endy the y end position
  529. */
  530. private void drawLine(float startx, float starty, float endx, float endy) {
  531. generator.add(format(startx) + " " + format(starty) + " m ");
  532. generator.add(format(endx) + " " + format(endy) + " l S\n");
  533. }
  534. /**
  535. * Breaks out of the state stack to handle fixed block-containers.
  536. * @return the saved state stack to recreate later
  537. */
  538. protected List breakOutOfStateStack() {
  539. List breakOutList = new java.util.ArrayList();
  540. PDFState.Data data;
  541. while (true) {
  542. data = getState().getData();
  543. if (getState().pop() == null) {
  544. break;
  545. }
  546. if (breakOutList.size() == 0) {
  547. generator.comment("------ break out!");
  548. }
  549. breakOutList.add(0, data); //Insert because of stack-popping
  550. generator.restoreGraphicsState();
  551. }
  552. return breakOutList;
  553. }
  554. /**
  555. * Restores the state stack after a break out.
  556. * @param breakOutList the state stack to restore.
  557. */
  558. protected void restoreStateStackAfterBreakOut(List breakOutList) {
  559. generator.comment("------ restoring context after break-out...");
  560. PDFState.Data data;
  561. Iterator i = breakOutList.iterator();
  562. while (i.hasNext()) {
  563. data = (PDFState.Data)i.next();
  564. saveGraphicsState();
  565. AffineTransform at = data.getTransform();
  566. concatenateTransformationMatrix(at);
  567. //TODO Break-out: Also restore items such as line width and color
  568. //Left out for now because all this painting stuff is very
  569. //inconsistent. Some values go over PDFState, some don't.
  570. }
  571. generator.comment("------ done.");
  572. }
  573. /**
  574. * Returns area's id if it is the first area in the document with that id
  575. * (i.e. if the area qualifies as a link target).
  576. * Otherwise, or if the area has no id, null is returned.
  577. *
  578. * <i>NOTE</i>: area must be on currentPageViewport, otherwise result may be wrong!
  579. *
  580. * @param area the area for which to return the id
  581. * @return the area's id (null if the area has no id or
  582. * other preceding areas have the same id)
  583. */
  584. protected String getTargetableID(Area area) {
  585. String id = (String) area.getTrait(Trait.PROD_ID);
  586. if (id == null || id.length() == 0
  587. || !currentPageViewport.isFirstWithID(id)
  588. || idPositions.containsKey(id)) {
  589. return null;
  590. } else {
  591. return id;
  592. }
  593. }
  594. /**
  595. * Set XY position in the PDFGoTo and add it to the PDF trailer.
  596. *
  597. * @param gt the PDFGoTo object
  598. * @param position the X,Y position to set
  599. */
  600. protected void finishIDGoTo(PDFGoTo gt, Point2D.Float position) {
  601. gt.setPosition(position);
  602. pdfDoc.addTrailerObject(gt);
  603. unfinishedGoTos.remove(gt);
  604. }
  605. /**
  606. * Set page reference and XY position in the PDFGoTo and add it to the PDF trailer.
  607. *
  608. * @param gt the PDFGoTo object
  609. * @param pdfPageRef the PDF reference string of the target page object
  610. * @param position the X,Y position to set
  611. */
  612. protected void finishIDGoTo(PDFGoTo gt, String pdfPageRef, Point2D.Float position) {
  613. gt.setPageReference(pdfPageRef);
  614. finishIDGoTo(gt, position);
  615. }
  616. /**
  617. * Get a PDFGoTo pointing to the given id. Create one if necessary.
  618. * It is possible that the PDFGoTo is not fully resolved yet. In that case
  619. * it must be completed (and added to the PDF trailer) later.
  620. *
  621. * @param targetID the target id of the PDFGoTo
  622. * @param pvKey the unique key of the target PageViewport
  623. *
  624. * @return the PDFGoTo that was found or created
  625. */
  626. protected PDFGoTo getPDFGoToForID(String targetID, String pvKey) {
  627. // Already a PDFGoTo present for this target? If not, create.
  628. PDFGoTo gt = (PDFGoTo) idGoTos.get(targetID);
  629. if (gt == null) {
  630. String pdfPageRef = (String) pageReferences.get(pvKey);
  631. Point2D.Float position = (Point2D.Float) idPositions.get(targetID);
  632. // can the GoTo already be fully filled in?
  633. if (pdfPageRef != null && position != null) {
  634. // getPDFGoTo shares PDFGoTo objects as much as possible.
  635. // It also takes care of assignObjectNumber and addTrailerObject.
  636. gt = pdfDoc.getFactory().getPDFGoTo(pdfPageRef, position);
  637. } else {
  638. // Not complete yet, can't use getPDFGoTo:
  639. gt = new PDFGoTo(pdfPageRef);
  640. pdfDoc.assignObjectNumber(gt);
  641. // pdfDoc.addTrailerObject() will be called later, from finishIDGoTo()
  642. unfinishedGoTos.add(gt);
  643. }
  644. idGoTos.put(targetID, gt);
  645. }
  646. return gt;
  647. }
  648. /**
  649. * Saves id's absolute position on page for later retrieval by PDFGoTos
  650. *
  651. * @param id the id of the area whose position must be saved
  652. * @param pdfPageRef the PDF page reference string
  653. * @param relativeIPP the *relative* IP position in millipoints
  654. * @param relativeBPP the *relative* BP position in millipoints
  655. * @param tf the transformation to apply once the relative positions have been
  656. * converted to points
  657. */
  658. protected void saveAbsolutePosition(String id, String pdfPageRef,
  659. int relativeIPP, int relativeBPP, AffineTransform tf) {
  660. Point2D.Float position = new Point2D.Float(relativeIPP / 1000f, relativeBPP / 1000f);
  661. tf.transform(position, position);
  662. idPositions.put(id, position);
  663. // is there already a PDFGoTo waiting to be completed?
  664. PDFGoTo gt = (PDFGoTo) idGoTos.get(id);
  665. if (gt != null) {
  666. finishIDGoTo(gt, pdfPageRef, position);
  667. }
  668. /*
  669. // The code below auto-creates a named destination for every id in the document.
  670. // This should probably be controlled by a user-configurable setting, as it may
  671. // make the PDF file grow noticeably.
  672. // *** NOT YET WELL-TESTED ! ***
  673. if (true) {
  674. PDFFactory factory = pdfDoc.getFactory();
  675. if (gt == null) {
  676. gt = factory.getPDFGoTo(pdfPageRef, position);
  677. idGoTos.put(id, gt); // so others can pick it up too
  678. }
  679. factory.makeDestination(id, gt.referencePDF(), currentPageViewport);
  680. // Note: using currentPageViewport is only correct if the id is indeed on
  681. // the current PageViewport. But even if incorrect, it won't interfere with
  682. // what gets created in the PDF.
  683. // For speedup, we should also create a lookup map id -> PDFDestination
  684. }
  685. */
  686. }
  687. /**
  688. * Saves id's absolute position on page for later retrieval by PDFGoTos,
  689. * using the currently valid transformation and the currently valid PDF page reference
  690. *
  691. * @param id the id of the area whose position must be saved
  692. * @param relativeIPP the *relative* IP position in millipoints
  693. * @param relativeBPP the *relative* BP position in millipoints
  694. */
  695. protected void saveAbsolutePosition(String id, int relativeIPP, int relativeBPP) {
  696. saveAbsolutePosition(id, currentPageRef,
  697. relativeIPP, relativeBPP, getState().getTransform());
  698. }
  699. /**
  700. * If the given block area is a possible link target, its id + absolute position will
  701. * be saved. The saved position is only correct if this function is called at the very
  702. * start of renderBlock!
  703. *
  704. * @param block the block area in question
  705. */
  706. protected void saveBlockPosIfTargetable(Block block) {
  707. String id = getTargetableID(block);
  708. if (id != null) {
  709. // FIXME: Like elsewhere in the renderer code, absolute and relative
  710. // directions are happily mixed here. This makes sure that the
  711. // links point to the right location, but it is not correct.
  712. int ipp = block.getXOffset();
  713. int bpp = block.getYOffset() + block.getSpaceBefore();
  714. int positioning = block.getPositioning();
  715. if (!(positioning == Block.FIXED || positioning == Block.ABSOLUTE)) {
  716. ipp += currentIPPosition;
  717. bpp += currentBPPosition;
  718. }
  719. AffineTransform tf = positioning == Block.FIXED
  720. ? getState().getBaseTransform()
  721. : getState().getTransform();
  722. saveAbsolutePosition(id, currentPageRef, ipp, bpp, tf);
  723. }
  724. }
  725. /**
  726. * If the given inline area is a possible link target, its id + absolute position will
  727. * be saved. The saved position is only correct if this function is called at the very
  728. * start of renderInlineArea!
  729. *
  730. * @param inlineArea the inline area in question
  731. */
  732. protected void saveInlinePosIfTargetable(InlineArea inlineArea) {
  733. String id = getTargetableID(inlineArea);
  734. if (id != null) {
  735. int extraMarginBefore = 5000; // millipoints
  736. int ipp = currentIPPosition;
  737. int bpp = currentBPPosition + inlineArea.getOffset() - extraMarginBefore;
  738. saveAbsolutePosition(id, ipp, bpp);
  739. }
  740. }
  741. /**
  742. * {@inheritDoc}
  743. */
  744. protected void renderBlock(Block block) {
  745. saveBlockPosIfTargetable(block);
  746. super.renderBlock(block);
  747. }
  748. /** {@inheritDoc} */
  749. protected void renderLineArea(LineArea line) {
  750. super.renderLineArea(line);
  751. }
  752. /**
  753. * {@inheritDoc}
  754. */
  755. protected void renderInlineArea(InlineArea inlineArea) {
  756. saveInlinePosIfTargetable(inlineArea);
  757. super.renderInlineArea(inlineArea);
  758. }
  759. /**
  760. * Render inline parent area.
  761. * For pdf this handles the inline parent area traits such as
  762. * links, border, background.
  763. * @param ip the inline parent area
  764. */
  765. public void renderInlineParent(InlineParent ip) {
  766. boolean annotsAllowed = pdfDoc.getProfile().isAnnotationAllowed();
  767. // stuff we only need if a link must be created:
  768. Rectangle2D ipRect = null;
  769. PDFFactory factory = null;
  770. PDFAction action = null;
  771. if (annotsAllowed) {
  772. // make sure the rect is determined *before* calling super!
  773. int ipp = currentIPPosition;
  774. int bpp = currentBPPosition + ip.getOffset();
  775. ipRect = new Rectangle2D.Float(ipp / 1000f, bpp / 1000f,
  776. ip.getIPD() / 1000f, ip.getBPD() / 1000f);
  777. AffineTransform transform = getState().getTransform();
  778. ipRect = transform.createTransformedShape(ipRect).getBounds2D();
  779. factory = pdfDoc.getFactory();
  780. }
  781. // render contents
  782. super.renderInlineParent(ip);
  783. boolean linkTraitFound = false;
  784. // try INTERNAL_LINK first
  785. Trait.InternalLink intLink = (Trait.InternalLink) ip.getTrait(Trait.INTERNAL_LINK);
  786. if (intLink != null) {
  787. linkTraitFound = true;
  788. String pvKey = intLink.getPVKey();
  789. String idRef = intLink.getIDRef();
  790. boolean pvKeyOK = pvKey != null && pvKey.length() > 0;
  791. boolean idRefOK = idRef != null && idRef.length() > 0;
  792. if (pvKeyOK && idRefOK) {
  793. if (annotsAllowed) {
  794. action = getPDFGoToForID(idRef, pvKey);
  795. }
  796. } else {
  797. //Warnings already issued by AreaTreeHandler
  798. }
  799. }
  800. // no INTERNAL_LINK, look for EXTERNAL_LINK
  801. if (!linkTraitFound) {
  802. Trait.ExternalLink extLink = (Trait.ExternalLink) ip.getTrait(Trait.EXTERNAL_LINK);
  803. if (extLink != null) {
  804. String extDest = extLink.getDestination();
  805. if (extDest != null && extDest.length() > 0) {
  806. linkTraitFound = true;
  807. if (annotsAllowed) {
  808. action = factory.getExternalAction(extDest, extLink.newWindow());
  809. }
  810. }
  811. }
  812. }
  813. // warn if link trait found but not allowed, else create link
  814. if (linkTraitFound) {
  815. if (!annotsAllowed) {
  816. log.warn("Skipping annotation for a link due to PDF profile: "
  817. + pdfDoc.getProfile());
  818. } else if (action != null) {
  819. PDFLink pdfLink = factory.makeLink(ipRect, action);
  820. currentPage.addAnnotation(pdfLink);
  821. }
  822. }
  823. }
  824. private Typeface getTypeface(String fontName) {
  825. Typeface tf = (Typeface) fontInfo.getFonts().get(fontName);
  826. if (tf instanceof LazyFont) {
  827. tf = ((LazyFont)tf).getRealFont();
  828. }
  829. return tf;
  830. }
  831. /** {@inheritDoc} */
  832. public void renderText(TextArea text) {
  833. renderInlineAreaBackAndBorders(text);
  834. Color ct = (Color) text.getTrait(Trait.COLOR);
  835. updateColor(ct, true);
  836. beginTextObject();
  837. String fontName = getInternalFontNameForArea(text);
  838. int size = ((Integer) text.getTrait(Trait.FONT_SIZE)).intValue();
  839. // This assumes that *all* CIDFonts use a /ToUnicode mapping
  840. Typeface tf = getTypeface(fontName);
  841. PDFTextUtil textutil = generator.getTextUtil();
  842. textutil.updateTf(fontName, size / 1000f, tf.isMultiByte());
  843. // word.getOffset() = only height of text itself
  844. // currentBlockIPPosition: 0 for beginning of line; nonzero
  845. // where previous line area failed to take up entire allocated space
  846. int rx = currentIPPosition + text.getBorderAndPaddingWidthStart();
  847. int bl = currentBPPosition + text.getOffset() + text.getBaselineOffset();
  848. textutil.writeTextMatrix(new AffineTransform(1, 0, 0, -1, rx / 1000f, bl / 1000f));
  849. super.renderText(text);
  850. textutil.writeTJ();
  851. renderTextDecoration(tf, size, text, bl, rx);
  852. }
  853. /** {@inheritDoc} */
  854. public void renderWord(WordArea word) {
  855. Font font = getFontFromArea(word.getParentArea());
  856. String s = word.getWord();
  857. escapeText(s, word.getLetterAdjustArray(),
  858. font, (AbstractTextArea)word.getParentArea());
  859. super.renderWord(word);
  860. }
  861. /** {@inheritDoc} */
  862. public void renderSpace(SpaceArea space) {
  863. Font font = getFontFromArea(space.getParentArea());
  864. String s = space.getSpace();
  865. AbstractTextArea textArea = (AbstractTextArea)space.getParentArea();
  866. escapeText(s, null, font, textArea);
  867. if (space.isAdjustable()) {
  868. int tws = -((TextArea) space.getParentArea()).getTextWordSpaceAdjust()
  869. - 2 * textArea.getTextLetterSpaceAdjust();
  870. if (tws != 0) {
  871. float adjust = tws / (font.getFontSize() / 1000f);
  872. generator.getTextUtil().adjustGlyphTJ(adjust);
  873. }
  874. }
  875. super.renderSpace(space);
  876. }
  877. /**
  878. * Escapes text according to PDF rules.
  879. * @param s Text to escape
  880. * @param letterAdjust an array of widths for letter adjustment (may be null)
  881. * @param font to font in use
  882. * @param parentArea the parent text area to retrieve certain traits from
  883. */
  884. protected void escapeText(String s,
  885. int[] letterAdjust,
  886. Font font, AbstractTextArea parentArea) {
  887. escapeText(s, 0, s.length(), letterAdjust, font, parentArea);
  888. }
  889. /**
  890. * Escapes text according to PDF rules.
  891. * @param s Text to escape
  892. * @param start the start position in the text
  893. * @param end the end position in the text
  894. * @param letterAdjust an array of widths for letter adjustment (may be null)
  895. * @param font to font in use
  896. * @param parentArea the parent text area to retrieve certain traits from
  897. */
  898. protected void escapeText(String s, int start, int end,
  899. int[] letterAdjust,
  900. Font font, AbstractTextArea parentArea) {
  901. String fontName = font.getFontName();
  902. float fontSize = font.getFontSize() / 1000f;
  903. Typeface tf = getTypeface(fontName);
  904. SingleByteFont singleByteFont = null;
  905. if (tf instanceof SingleByteFont) {
  906. singleByteFont = (SingleByteFont)tf;
  907. }
  908. PDFTextUtil textutil = generator.getTextUtil();
  909. int l = s.length();
  910. for (int i = start; i < end; i++) {
  911. char orgChar = s.charAt(i);
  912. char ch;
  913. float glyphAdjust = 0;
  914. if (font.hasChar(orgChar)) {
  915. ch = font.mapChar(orgChar);
  916. if (singleByteFont != null && singleByteFont.hasAdditionalEncodings()) {
  917. int encoding = ch / 256;
  918. if (encoding == 0) {
  919. textutil.updateTf(fontName, fontSize, tf.isMultiByte());
  920. } else {
  921. textutil.updateTf(fontName + "_" + Integer.toString(encoding),
  922. fontSize, tf.isMultiByte());
  923. ch = (char)(ch % 256);
  924. }
  925. }
  926. int tls = (i < l - 1 ? parentArea.getTextLetterSpaceAdjust() : 0);
  927. glyphAdjust -= tls;
  928. } else {
  929. if (CharUtilities.isFixedWidthSpace(orgChar)) {
  930. //Fixed width space are rendered as spaces so copy/paste works in a reader
  931. ch = font.mapChar(CharUtilities.SPACE);
  932. glyphAdjust = font.getCharWidth(ch) - font.getCharWidth(orgChar);
  933. } else {
  934. ch = font.mapChar(orgChar);
  935. }
  936. }
  937. if (letterAdjust != null && i < l - 1) {
  938. glyphAdjust -= letterAdjust[i + 1];
  939. }
  940. textutil.writeTJMappedChar(ch);
  941. float adjust = glyphAdjust / fontSize;
  942. if (adjust != 0) {
  943. textutil.adjustGlyphTJ(adjust);
  944. }
  945. }
  946. }
  947. /** {@inheritDoc} */
  948. protected void updateColor(Color col, boolean fill) {
  949. generator.updateColor(col, fill, null);
  950. }
  951. /** {@inheritDoc} */
  952. public void renderImage(Image image, Rectangle2D pos) {
  953. endTextObject();
  954. String url = image.getURL();
  955. putImage(url, pos, image.getForeignAttributes());
  956. }
  957. /** {@inheritDoc} */
  958. protected void drawImage(String url, Rectangle2D pos, Map foreignAttributes) {
  959. endTextObject();
  960. putImage(url, pos, foreignAttributes);
  961. }
  962. /**
  963. * Adds a PDF XObject (a bitmap or form) to the PDF that will later be referenced.
  964. * @param uri URL of the bitmap
  965. * @param pos Position of the bitmap
  966. * @deprecated Use {@link #putImage(String, Rectangle2D, Map)} instead.
  967. */
  968. protected void putImage(String uri, Rectangle2D pos) {
  969. putImage(uri, pos, null);
  970. }
  971. /**
  972. * Adds a PDF XObject (a bitmap or form) to the PDF that will later be referenced.
  973. * @param uri URL of the bitmap
  974. * @param pos Position of the bitmap
  975. * @param foreignAttributes foreign attributes associated with the image
  976. */
  977. protected void putImage(String uri, Rectangle2D pos, Map foreignAttributes) {
  978. Rectangle posInt = new Rectangle(
  979. (int)pos.getX(),
  980. (int)pos.getY(),
  981. (int)pos.getWidth(),
  982. (int)pos.getHeight());
  983. uri = URISpecification.getURL(uri);
  984. PDFXObject xobject = pdfDoc.getXObject(uri);
  985. if (xobject != null) {
  986. float w = (float) pos.getWidth() / 1000f;
  987. float h = (float) pos.getHeight() / 1000f;
  988. placeImage((float)pos.getX() / 1000f,
  989. (float)pos.getY() / 1000f, w, h, xobject);
  990. return;
  991. }
  992. Point origin = new Point(currentIPPosition, currentBPPosition);
  993. int x = origin.x + posInt.x;
  994. int y = origin.y + posInt.y;
  995. ImageManager manager = getUserAgent().getFactory().getImageManager();
  996. ImageInfo info = null;
  997. try {
  998. ImageSessionContext sessionContext = getUserAgent().getImageSessionContext();
  999. info = manager.getImageInfo(uri, sessionContext);
  1000. Map hints = ImageUtil.getDefaultHints(sessionContext);
  1001. ImageFlavor[] supportedFlavors = imageHandlerRegistry.getSupportedFlavors();
  1002. org.apache.xmlgraphics.image.loader.Image img = manager.getImage(
  1003. info, supportedFlavors, hints, sessionContext);
  1004. //First check for a dynamically registered handler
  1005. PDFImageHandler handler = imageHandlerRegistry.getHandler(img.getClass());
  1006. if (handler != null) {
  1007. if (log.isDebugEnabled()) {
  1008. log.debug("Using PDFImageHandler: " + handler.getClass().getName());
  1009. }
  1010. try {
  1011. RendererContext context = createRendererContext(
  1012. x, y, posInt.width, posInt.height, foreignAttributes);
  1013. handler.generateImage(context, img, origin, posInt);
  1014. } catch (IOException ioe) {
  1015. ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
  1016. getUserAgent().getEventBroadcaster());
  1017. eventProducer.imageWritingError(this, ioe);
  1018. return;
  1019. }
  1020. } else {
  1021. throw new UnsupportedOperationException(
  1022. "No PDFImageHandler available for image: "
  1023. + info + " (" + img.getClass().getName() + ")");
  1024. }
  1025. } catch (ImageException ie) {
  1026. ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
  1027. getUserAgent().getEventBroadcaster());
  1028. eventProducer.imageError(this, (info != null ? info.toString() : uri), ie, null);
  1029. } catch (FileNotFoundException fe) {
  1030. ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
  1031. getUserAgent().getEventBroadcaster());
  1032. eventProducer.imageNotFound(this, (info != null ? info.toString() : uri), fe, null);
  1033. } catch (IOException ioe) {
  1034. ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
  1035. getUserAgent().getEventBroadcaster());
  1036. eventProducer.imageIOError(this, (info != null ? info.toString() : uri), ioe, null);
  1037. }
  1038. // output new data
  1039. try {
  1040. this.generator.flushPDFDoc();
  1041. } catch (IOException ioe) {
  1042. // ioexception will be caught later
  1043. }
  1044. }
  1045. /**
  1046. * Places a previously registered image at a certain place on the page.
  1047. * @param x X coordinate
  1048. * @param y Y coordinate
  1049. * @param w width for image
  1050. * @param h height for image
  1051. * @param xobj the image XObject
  1052. */
  1053. public void placeImage(float x, float y, float w, float h, PDFXObject xobj) {
  1054. saveGraphicsState();
  1055. generator.add(format(w) + " 0 0 "
  1056. + format(-h) + " "
  1057. + format(currentIPPosition / 1000f + x) + " "
  1058. + format(currentBPPosition / 1000f + h + y)
  1059. + " cm\n" + xobj.getName() + " Do\n");
  1060. restoreGraphicsState();
  1061. }
  1062. /** {@inheritDoc} */
  1063. protected RendererContext createRendererContext(int x, int y, int width, int height,
  1064. Map foreignAttributes) {
  1065. RendererContext context = super.createRendererContext(
  1066. x, y, width, height, foreignAttributes);
  1067. context.setProperty(PDFRendererContextConstants.PDF_DOCUMENT, pdfDoc);
  1068. context.setProperty(PDFRendererContextConstants.OUTPUT_STREAM, ostream);
  1069. context.setProperty(PDFRendererContextConstants.PDF_STATE, getState());
  1070. context.setProperty(PDFRendererContextConstants.PDF_PAGE, currentPage);
  1071. context.setProperty(PDFRendererContextConstants.PDF_CONTEXT,
  1072. currentContext == null ? currentPage : currentContext);
  1073. context.setProperty(PDFRendererContextConstants.PDF_CONTEXT, currentContext);
  1074. context.setProperty(PDFRendererContextConstants.PDF_STREAM, generator.getStream());
  1075. context.setProperty(PDFRendererContextConstants.PDF_FONT_INFO, fontInfo);
  1076. context.setProperty(PDFRendererContextConstants.PDF_FONT_NAME, "");
  1077. context.setProperty(PDFRendererContextConstants.PDF_FONT_SIZE, new Integer(0));
  1078. return context;
  1079. }
  1080. /**
  1081. * Render leader area.
  1082. * This renders a leader area which is an area with a rule.
  1083. * @param area the leader area to render
  1084. */
  1085. public void renderLeader(Leader area) {
  1086. renderInlineAreaBackAndBorders(area);
  1087. getState().push();
  1088. saveGraphicsState();
  1089. int style = area.getRuleStyle();
  1090. float startx = (currentIPPosition + area.getBorderAndPaddingWidthStart()) / 1000f;
  1091. float starty = (currentBPPosition + area.getOffset()) / 1000f;
  1092. float endx = (currentIPPosition + area.getBorderAndPaddingWidthStart()
  1093. + area.getIPD()) / 1000f;
  1094. float ruleThickness = area.getRuleThickness() / 1000f;
  1095. Color col = (Color)area.getTrait(Trait.COLOR);
  1096. switch (style) {
  1097. case EN_SOLID:
  1098. case EN_DASHED:
  1099. case EN_DOUBLE:
  1100. drawBorderLine(startx, starty, endx, starty + ruleThickness,
  1101. true, true, style, col);
  1102. break;
  1103. case EN_DOTTED:
  1104. clipRect(startx, starty, endx - startx, ruleThickness);
  1105. //This displaces the dots to the right by half a dot's width
  1106. //TODO There's room for improvement here
  1107. generator.add("1 0 0 1 " + format(ruleThickness / 2) + " 0 cm\n");
  1108. drawBorderLine(startx, starty, endx, starty + ruleThickness,
  1109. true, true, style, col);
  1110. break;
  1111. case EN_GROOVE:
  1112. case EN_RIDGE:
  1113. float half = area.getRuleThickness() / 2000f;
  1114. generator.setColor(lightenColor(col, 0.6f), true);
  1115. generator.add(format(startx) + " " + format(starty) + " m\n");
  1116. generator.add(format(endx) + " " + format(starty) + " l\n");
  1117. generator.add(format(endx) + " " + format(starty + 2 * half) + " l\n");
  1118. generator.add(format(startx) + " " + format(starty + 2 * half) + " l\n");
  1119. generator.add("h\n");
  1120. generator.add("f\n");
  1121. generator.setColor(col, true);
  1122. if (style == EN_GROOVE) {
  1123. generator.add(format(startx) + " " + format(starty) + " m\n");
  1124. generator.add(format(endx) + " " + format(starty) + " l\n");
  1125. generator.add(format(endx) + " " + format(starty + half) + " l\n");
  1126. generator.add(format(startx + half) + " " + format(starty + half) + " l\n");
  1127. generator.add(format(startx) + " " + format(starty + 2 * half) + " l\n");
  1128. } else {
  1129. generator.add(format(endx) + " " + format(starty) + " m\n");
  1130. generator.add(format(endx) + " " + format(starty + 2 * half) + " l\n");
  1131. generator.add(format(startx) + " " + format(starty + 2 * half) + " l\n");
  1132. generator.add(format(startx) + " " + format(starty + half) + " l\n");
  1133. generator.add(format(endx - half) + " " + format(starty + half) + " l\n");
  1134. }
  1135. generator.add("h\n");
  1136. generator.add("f\n");
  1137. break;
  1138. default:
  1139. throw new UnsupportedOperationException("rule style not supported");
  1140. }
  1141. restoreGraphicsState();
  1142. getState().pop();
  1143. beginTextObject();
  1144. super.renderLeader(area);
  1145. }
  1146. /** {@inheritDoc} */
  1147. public String getMimeType() {
  1148. return MIME_TYPE;
  1149. }
  1150. /**
  1151. * Sets the PDF/A mode for the PDF renderer.
  1152. * @param mode the PDF/A mode
  1153. */
  1154. public void setAMode(PDFAMode mode) {
  1155. this.pdfUtil.setAMode(mode);
  1156. }
  1157. /**
  1158. * Sets the PDF/X mode for the PDF renderer.
  1159. * @param mode the PDF/X mode
  1160. */
  1161. public void setXMode(PDFXMode mode) {
  1162. this.pdfUtil.setXMode(mode);
  1163. }
  1164. /**
  1165. * Sets the output color profile for the PDF renderer.
  1166. * @param outputProfileURI the URI to the output color profile
  1167. */
  1168. public void setOutputProfileURI(String outputProfileURI) {
  1169. this.pdfUtil.setOutputProfileURI(outputProfileURI);
  1170. }
  1171. /**
  1172. * Sets the filter map to be used by the PDF renderer.
  1173. * @param filterMap the filter map
  1174. */
  1175. public void setFilterMap(Map filterMap) {
  1176. this.pdfUtil.setFilterMap(filterMap);
  1177. }
  1178. /**
  1179. * Sets the encryption parameters used by the PDF renderer.
  1180. * @param encryptionParams the encryption parameters
  1181. */
  1182. public void setEncryptionParams(PDFEncryptionParams encryptionParams) {
  1183. this.pdfUtil.setEncryptionParams(encryptionParams);
  1184. }
  1185. }