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.

PDFContentGenerator.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  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. import java.awt.Color;
  20. import java.awt.Rectangle;
  21. import java.awt.geom.AffineTransform;
  22. import java.io.IOException;
  23. import java.io.OutputStream;
  24. import org.apache.fop.pdf.PDFColorHandler;
  25. import org.apache.fop.pdf.PDFDocument;
  26. import org.apache.fop.pdf.PDFFilterList;
  27. import org.apache.fop.pdf.PDFLinearization;
  28. import org.apache.fop.pdf.PDFNumber;
  29. import org.apache.fop.pdf.PDFPaintingState;
  30. import org.apache.fop.pdf.PDFReference;
  31. import org.apache.fop.pdf.PDFResourceContext;
  32. import org.apache.fop.pdf.PDFStream;
  33. import org.apache.fop.pdf.PDFText;
  34. import org.apache.fop.pdf.PDFTextUtil;
  35. import org.apache.fop.pdf.PDFXObject;
  36. import org.apache.fop.render.intermediate.IFContext;
  37. /**
  38. * Generator class encapsulating all object references and state necessary to generate a
  39. * PDF content stream.
  40. */
  41. public class PDFContentGenerator {
  42. /** Controls whether comments are written to the PDF stream. */
  43. protected static final boolean WRITE_COMMENTS = true;
  44. private PDFDocument document;
  45. private OutputStream outputStream;
  46. private PDFResourceContext resourceContext;
  47. /** the current stream to add PDF commands to */
  48. private PDFStream currentStream;
  49. private PDFColorHandler colorHandler;
  50. /** drawing state */
  51. protected PDFPaintingState currentState;
  52. /** Text generation utility holding the current font status */
  53. protected PDFTextUtil textutil;
  54. private boolean inMarkedContentSequence;
  55. private boolean inArtifactMode;
  56. private AffineTransform transform;
  57. private IFContext context;
  58. /**
  59. * Main constructor. Creates a new PDF stream and additional helper classes for text painting
  60. * and state management.
  61. * @param document the PDF document
  62. * @param out the output stream the PDF document is generated to
  63. * @param resourceContext the resource context
  64. */
  65. public PDFContentGenerator(PDFDocument document, OutputStream out,
  66. PDFResourceContext resourceContext) {
  67. this(document, out, resourceContext, null);
  68. }
  69. public PDFContentGenerator(PDFDocument document, OutputStream out,
  70. PDFResourceContext resourceContext, IFContext context) {
  71. this.document = document;
  72. this.outputStream = out;
  73. this.resourceContext = resourceContext;
  74. this.currentStream = document.getFactory()
  75. .makeStream(PDFFilterList.CONTENT_FILTER, false);
  76. this.textutil = new PDFTextUtil() {
  77. protected void write(String code) {
  78. currentStream.add(code);
  79. }
  80. protected void write(StringBuffer code) {
  81. currentStream.add(code);
  82. }
  83. };
  84. this.currentState = new PDFPaintingState();
  85. this.colorHandler = new PDFColorHandler(document.getResources());
  86. this.context = context;
  87. }
  88. public AffineTransform getAffineTransform() {
  89. return transform;
  90. }
  91. /**
  92. * Returns the applicable resource context for the generator.
  93. * @return the resource context
  94. */
  95. public PDFDocument getDocument() {
  96. return this.document;
  97. }
  98. /**
  99. * Returns the output stream the PDF document is written to.
  100. * @return the output stream
  101. */
  102. public OutputStream getOutputStream() {
  103. return this.outputStream;
  104. }
  105. /**
  106. * Returns the applicable resource context for the generator.
  107. * @return the resource context
  108. */
  109. public PDFResourceContext getResourceContext() {
  110. return this.resourceContext;
  111. }
  112. /**
  113. * Returns the {@link PDFStream} associated with this instance.
  114. * @return the PDF stream
  115. */
  116. public PDFStream getStream() {
  117. return this.currentStream;
  118. }
  119. /**
  120. * Returns the {@link PDFPaintingState} associated with this instance.
  121. * @return the PDF state
  122. */
  123. public PDFPaintingState getState() {
  124. return this.currentState;
  125. }
  126. /**
  127. * Returns the {@link PDFTextUtil} associated with this instance.
  128. * @return the text utility
  129. */
  130. public PDFTextUtil getTextUtil() {
  131. return this.textutil;
  132. }
  133. /**
  134. * Flushes all queued PDF objects ready to be written to the output stream.
  135. * @throws IOException if an error occurs while flushing the PDF objects
  136. */
  137. public void flushPDFDoc() throws IOException {
  138. if (document.isLinearizationEnabled()) {
  139. new PDFLinearization(document).outputPages(outputStream);
  140. }
  141. this.document.output(this.outputStream);
  142. }
  143. /**
  144. * Writes out a comment.
  145. * @param text text for the comment
  146. */
  147. protected void comment(String text) {
  148. if (WRITE_COMMENTS) {
  149. getStream().add("% " + text + "\n");
  150. }
  151. }
  152. /** Save graphics state. */
  153. protected void saveGraphicsState() {
  154. endTextObject();
  155. getState().save();
  156. getStream().add("q\n");
  157. }
  158. /** Save graphics state with optional layer. */
  159. protected void saveGraphicsState(String layer) {
  160. endTextObject();
  161. getState().save();
  162. maybeBeginLayer(layer);
  163. getStream().add("q\n");
  164. }
  165. /**
  166. * Save graphics state.
  167. * @param structElemType an element type
  168. * @param sequenceNum a sequence number
  169. */
  170. protected void saveGraphicsState(String structElemType, int sequenceNum) {
  171. endTextObject();
  172. getState().save();
  173. beginMarkedContentSequence(structElemType, sequenceNum);
  174. getStream().add("q\n");
  175. }
  176. /**
  177. * Begins a new marked content sequence (BDC or BMC). If {@code structElemType} is
  178. * null, a BMC operator with an "Artifact" tag is generated. Otherwise, a BDC operator
  179. * with {@code structElemType} as a tag is generated, and the given mcid stored in its
  180. * property list.
  181. *
  182. * @param structElemType the type of the associated structure element
  183. * @param mcid the marked content identifier
  184. */
  185. protected void beginMarkedContentSequence(String structElemType, int mcid) {
  186. beginMarkedContentSequence(structElemType, mcid, null);
  187. }
  188. /**
  189. * Begins a new marked content sequence (BDC or BMC). If {@code structElemType} is
  190. * null, a BMC operator with an "Artifact" tag is generated. Otherwise, a BDC operator
  191. * with {@code structElemType} as a tag is generated, and the given mcid and actual
  192. * text are stored in its property list.
  193. *
  194. * @param structElemType the type of the associated structure element
  195. * @param mcid the marked content identifier
  196. * @param actualText the replacement text for the marked content
  197. */
  198. protected void beginMarkedContentSequence(String structElemType, int mcid, String actualText) {
  199. assert !this.inMarkedContentSequence;
  200. assert !this.inArtifactMode;
  201. if (structElemType != null) {
  202. String actualTextProperty = actualText == null ? ""
  203. : " /ActualText " + PDFText.escapeText(actualText);
  204. getStream().add(structElemType + " <</MCID " + String.valueOf(mcid)
  205. + actualTextProperty + ">>\n"
  206. + "BDC\n");
  207. } else {
  208. if (context != null && context.getRegionType() != null) {
  209. getStream().add("/Artifact\n<</Type /Pagination\n/Subtype /" + context.getRegionType() + ">>\nBDC\n");
  210. } else {
  211. getStream().add("/Artifact\nBMC\n");
  212. }
  213. this.inArtifactMode = true;
  214. }
  215. this.inMarkedContentSequence = true;
  216. }
  217. void endMarkedContentSequence() {
  218. getStream().add("EMC\n");
  219. this.inMarkedContentSequence = false;
  220. this.inArtifactMode = false;
  221. }
  222. /**
  223. * Restored the graphics state valid before the previous {@link #saveGraphicsState()}.
  224. * @param popState true if the state should also be popped, false if only the PDF command
  225. * should be issued
  226. */
  227. protected void restoreGraphicsState(boolean popState) {
  228. endTextObject();
  229. getStream().add("Q\n");
  230. maybeEndLayer();
  231. if (popState) {
  232. getState().restore();
  233. }
  234. }
  235. /**
  236. * Same as {@link #restoreGraphicsState(boolean)}, with <code>true</code> as
  237. * a parameter.
  238. */
  239. protected void restoreGraphicsState() {
  240. restoreGraphicsState(true);
  241. }
  242. /**
  243. * Same as {@link #restoreGraphicsState()}, additionally ending the current
  244. * marked content sequence if any.
  245. */
  246. protected void restoreGraphicsStateAccess() {
  247. endTextObject();
  248. getStream().add("Q\n");
  249. if (this.inMarkedContentSequence) {
  250. endMarkedContentSequence();
  251. }
  252. getState().restore();
  253. }
  254. private void maybeBeginLayer(String layer) {
  255. if ((layer != null) && (layer.length() > 0)) {
  256. getState().setLayer(layer);
  257. beginOptionalContent(layer);
  258. }
  259. }
  260. private void maybeEndLayer() {
  261. if (getState().getLayerChanged()) {
  262. endOptionalContent();
  263. }
  264. }
  265. private int ocNameIndex;
  266. private void beginOptionalContent(String layerId) {
  267. String name;
  268. PDFReference layer = document.resolveExtensionReference(layerId);
  269. if (layer != null) {
  270. name = "oc" + ++ocNameIndex;
  271. document.getResources().addProperty(name, layer);
  272. } else {
  273. name = "unknown";
  274. }
  275. getStream().add("/OC /" + name + " BDC\n");
  276. }
  277. private void endOptionalContent() {
  278. getStream().add("EMC\n");
  279. }
  280. /** Indicates the beginning of a text object. */
  281. protected void beginTextObject() {
  282. if (!textutil.isInTextObject()) {
  283. textutil.beginTextObject();
  284. }
  285. }
  286. /**
  287. * Indicates the beginning of a marked-content text object.
  288. *
  289. * @param structElemType structure element type
  290. * @param mcid sequence number
  291. * @see #beginTextObject()
  292. * @see #beginMarkedContentSequence(String, int)
  293. */
  294. protected void beginTextObject(String structElemType, int mcid) {
  295. beginTextObject(structElemType, mcid, null);
  296. }
  297. /**
  298. * Indicates the beginning of a marked-content text object.
  299. *
  300. * @param structElemType structure element type
  301. * @param mcid sequence number
  302. * @param actualText the replacement text for the marked content
  303. * @see #beginTextObject()
  304. * @see #beginMarkedContentSequence
  305. */
  306. protected void beginTextObject(String structElemType, int mcid, String actualText) {
  307. if (!textutil.isInTextObject()) {
  308. beginMarkedContentSequence(structElemType, mcid, actualText);
  309. textutil.beginTextObject();
  310. }
  311. }
  312. /** Indicates the end of a text object. */
  313. protected void endTextObject() {
  314. if (textutil.isInTextObject()) {
  315. textutil.endTextObject();
  316. if (this.inMarkedContentSequence) {
  317. endMarkedContentSequence();
  318. }
  319. }
  320. }
  321. /**
  322. * Concatenates the given transformation matrix with the current one.
  323. * @param transform the transformation matrix (in points)
  324. */
  325. public void concatenate(AffineTransform transform) {
  326. this.transform = transform;
  327. if (!transform.isIdentity()) {
  328. getState().concatenate(transform);
  329. getStream().add(CTMHelper.toPDFString(transform, false) + " cm\n");
  330. }
  331. }
  332. /**
  333. * Intersects the current clip region with the given rectangle.
  334. * @param rect the clip rectangle
  335. */
  336. public void clipRect(Rectangle rect) {
  337. StringBuffer sb = new StringBuffer();
  338. sb.append(format(rect.x / 1000f)).append(' ');
  339. sb.append(format(rect.y / 1000f)).append(' ');
  340. sb.append(format(rect.width / 1000f)).append(' ');
  341. sb.append(format(rect.height / 1000f)).append(" re W n\n");
  342. add(sb.toString());
  343. }
  344. /**
  345. * Adds content to the stream.
  346. * @param content the PDF content
  347. */
  348. public void add(String content) {
  349. getStream().add(content);
  350. }
  351. /**
  352. * Formats a float value (normally coordinates in points) as Strings.
  353. * @param value the value
  354. * @return the formatted value
  355. */
  356. public static final String format(float value) {
  357. return PDFNumber.doubleOut(value);
  358. }
  359. /**
  360. * Sets the current line width in points.
  361. * @param width line width in points
  362. */
  363. public void updateLineWidth(float width) {
  364. if (getState().setLineWidth(width)) {
  365. //Only write if value has changed WRT the current line width
  366. getStream().add(format(width) + " w\n");
  367. }
  368. }
  369. /**
  370. * Sets the current character spacing (Tc) value.
  371. * @param value the Tc value (in unscaled text units)
  372. */
  373. public void updateCharacterSpacing(float value) {
  374. if (getState().setCharacterSpacing(value)) {
  375. getStream().add(format(value) + " Tc\n");
  376. }
  377. }
  378. /**
  379. * Establishes a new foreground or fill color.
  380. * @param col the color to apply
  381. * @param fill true to set the fill color, false for the foreground color
  382. * @param stream the PDFStream to write the PDF code to
  383. */
  384. public void setColor(Color col, boolean fill, PDFStream stream) {
  385. assert stream != null;
  386. StringBuffer sb = new StringBuffer();
  387. setColor(col, fill, sb);
  388. stream.add(sb.toString());
  389. }
  390. /**
  391. * Establishes a new foreground or fill color.
  392. * @param col the color to apply
  393. * @param fill true to set the fill color, false for the foreground color
  394. */
  395. public void setColor(Color col, boolean fill) {
  396. setColor(col, fill, getStream());
  397. }
  398. /**
  399. * Establishes a new foreground or fill color. In contrast to updateColor
  400. * this method does not check the PDFState for optimization possibilities.
  401. * @param col the color to apply
  402. * @param fill true to set the fill color, false for the foreground color
  403. * @param pdf StringBuffer to write the PDF code to, if null, the code is
  404. * written to the current stream.
  405. */
  406. protected void setColor(Color col, boolean fill, StringBuffer pdf) {
  407. if (pdf != null) {
  408. colorHandler.establishColor(pdf, col, fill);
  409. } else {
  410. setColor(col, fill, getStream());
  411. }
  412. }
  413. /**
  414. * Establishes a new foreground or fill color.
  415. * @param col the color to apply (null skips this operation)
  416. * @param fill true to set the fill color, false for the foreground color
  417. * @param pdf StringBuffer to write the PDF code to, if null, the code is
  418. * written to the current stream.
  419. */
  420. public void updateColor(Color col, boolean fill, StringBuffer pdf) {
  421. if (col == null) {
  422. return;
  423. }
  424. boolean update = false;
  425. if (fill) {
  426. update = getState().setBackColor(col);
  427. } else {
  428. update = getState().setColor(col);
  429. }
  430. if (update) {
  431. setColor(col, fill, pdf);
  432. }
  433. }
  434. /**
  435. * Places a previously registered image at a certain place on the page.
  436. * @param x X coordinate
  437. * @param y Y coordinate
  438. * @param w width for image
  439. * @param h height for image
  440. * @param xobj the image XObject
  441. */
  442. public void placeImage(float x, float y, float w, float h, PDFXObject xobj) {
  443. saveGraphicsState();
  444. add(format(w) + " 0 0 "
  445. + format(-h) + " "
  446. + format(x) + " "
  447. + format(y + h)
  448. + " cm\n" + xobj.getName() + " Do\n");
  449. restoreGraphicsState();
  450. }
  451. public void placeImage(AffineTransform at, String stream) {
  452. saveGraphicsState();
  453. concatenate(at);
  454. add(stream);
  455. restoreGraphicsState();
  456. }
  457. /**
  458. * Places a previously registered image at a certain place on the page,
  459. * bracketing it as a marked-content sequence.
  460. *
  461. * @param x X coordinate
  462. * @param y Y coordinate
  463. * @param w width for image
  464. * @param h height for image
  465. * @param xobj the image XObject
  466. * @param structElemType structure element type
  467. * @param mcid sequence number
  468. * @see #beginMarkedContentSequence(String, int)
  469. */
  470. public void placeImage(float x, float y, float w, float h, PDFXObject xobj,
  471. String structElemType, int mcid) {
  472. saveGraphicsState(structElemType, mcid);
  473. add(format(w) + " 0 0 "
  474. + format(-h) + " "
  475. + format(x) + " "
  476. + format(y + h)
  477. + " cm\n" + xobj.getName() + " Do\n");
  478. restoreGraphicsStateAccess();
  479. }
  480. }