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.

PDFContentGenerator.java 17KB

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