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

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