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.

PDFPainter.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  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.Dimension;
  21. import java.awt.Paint;
  22. import java.awt.Point;
  23. import java.awt.Rectangle;
  24. import java.awt.geom.AffineTransform;
  25. import java.io.IOException;
  26. import org.w3c.dom.Document;
  27. import org.apache.fop.fonts.Font;
  28. import org.apache.fop.fonts.FontInfo;
  29. import org.apache.fop.fonts.FontTriplet;
  30. import org.apache.fop.fonts.LazyFont;
  31. import org.apache.fop.fonts.SingleByteFont;
  32. import org.apache.fop.fonts.Typeface;
  33. import org.apache.fop.pdf.PDFDocument;
  34. import org.apache.fop.pdf.PDFNumber;
  35. import org.apache.fop.pdf.PDFStructElem;
  36. import org.apache.fop.pdf.PDFTextUtil;
  37. import org.apache.fop.pdf.PDFXObject;
  38. import org.apache.fop.render.RenderingContext;
  39. import org.apache.fop.render.intermediate.AbstractIFPainter;
  40. import org.apache.fop.render.intermediate.IFContext;
  41. import org.apache.fop.render.intermediate.IFException;
  42. import org.apache.fop.render.intermediate.IFState;
  43. import org.apache.fop.render.intermediate.IFUtil;
  44. import org.apache.fop.render.pdf.PDFLogicalStructureHandler.MarkedContentInfo;
  45. import org.apache.fop.traits.BorderProps;
  46. import org.apache.fop.traits.Direction;
  47. import org.apache.fop.traits.RuleStyle;
  48. import org.apache.fop.util.CharUtilities;
  49. /**
  50. * IFPainter implementation that produces PDF.
  51. */
  52. public class PDFPainter extends AbstractIFPainter {
  53. private final PDFDocumentHandler documentHandler;
  54. /** The current content generator */
  55. protected PDFContentGenerator generator;
  56. private final PDFBorderPainter borderPainter;
  57. private boolean accessEnabled;
  58. private MarkedContentInfo imageMCI;
  59. private PDFLogicalStructureHandler logicalStructureHandler;
  60. /**
  61. * Default constructor.
  62. * @param documentHandler the parent document handler
  63. * @param logicalStructureHandler the logical structure handler
  64. */
  65. public PDFPainter(PDFDocumentHandler documentHandler,
  66. PDFLogicalStructureHandler logicalStructureHandler) {
  67. super();
  68. this.documentHandler = documentHandler;
  69. this.logicalStructureHandler = logicalStructureHandler;
  70. this.generator = documentHandler.generator;
  71. this.borderPainter = new PDFBorderPainter(this.generator);
  72. this.state = IFState.create();
  73. accessEnabled = this.getUserAgent().isAccessibilityEnabled();
  74. }
  75. /** {@inheritDoc} */
  76. @Override
  77. protected IFContext getContext() {
  78. return this.documentHandler.getContext();
  79. }
  80. PDFRenderingUtil getPDFUtil() {
  81. return this.documentHandler.pdfUtil;
  82. }
  83. PDFDocument getPDFDoc() {
  84. return this.documentHandler.pdfDoc;
  85. }
  86. FontInfo getFontInfo() {
  87. return this.documentHandler.getFontInfo();
  88. }
  89. /** {@inheritDoc} */
  90. public void startViewport(AffineTransform transform, Dimension size, Rectangle clipRect)
  91. throws IFException {
  92. generator.saveGraphicsState();
  93. generator.concatenate(toPoints(transform));
  94. if (clipRect != null) {
  95. clipRect(clipRect);
  96. }
  97. }
  98. /** {@inheritDoc} */
  99. public void endViewport() throws IFException {
  100. generator.restoreGraphicsState();
  101. }
  102. /** {@inheritDoc} */
  103. public void startGroup(AffineTransform transform) throws IFException {
  104. generator.saveGraphicsState();
  105. generator.concatenate(toPoints(transform));
  106. }
  107. /** {@inheritDoc} */
  108. public void endGroup() throws IFException {
  109. generator.restoreGraphicsState();
  110. }
  111. /** {@inheritDoc} */
  112. public void drawImage(String uri, Rectangle rect)
  113. throws IFException {
  114. PDFXObject xobject = getPDFDoc().getXObject(uri);
  115. if (xobject != null) {
  116. if (accessEnabled) {
  117. PDFStructElem structElem = (PDFStructElem) getContext().getStructureTreeElement();
  118. prepareImageMCID(structElem);
  119. placeImageAccess(rect, xobject);
  120. } else {
  121. placeImage(rect, xobject);
  122. }
  123. } else {
  124. if (accessEnabled) {
  125. PDFStructElem structElem = (PDFStructElem) getContext().getStructureTreeElement();
  126. prepareImageMCID(structElem);
  127. }
  128. drawImageUsingURI(uri, rect);
  129. flushPDFDoc();
  130. }
  131. }
  132. private void prepareImageMCID(PDFStructElem structElem) {
  133. imageMCI = logicalStructureHandler.addImageContentItem(structElem);
  134. }
  135. /** {@inheritDoc} */
  136. @Override
  137. protected RenderingContext createRenderingContext() {
  138. PDFRenderingContext pdfContext = new PDFRenderingContext(
  139. getUserAgent(), generator, this.documentHandler.currentPage, getFontInfo());
  140. pdfContext.setMarkedContentInfo(imageMCI);
  141. return pdfContext;
  142. }
  143. /**
  144. * Places a previously registered image at a certain place on the page.
  145. * @param rect the rectangle for the image
  146. * @param xobj the image XObject
  147. */
  148. private void placeImage(Rectangle rect, PDFXObject xobj) {
  149. generator.saveGraphicsState();
  150. generator.add(format(rect.width) + " 0 0 "
  151. + format(-rect.height) + " "
  152. + format(rect.x) + " "
  153. + format(rect.y + rect.height )
  154. + " cm " + xobj.getName() + " Do\n");
  155. generator.restoreGraphicsState();
  156. }
  157. /**
  158. * Places a previously registered image at a certain place on the page - Accessibility version
  159. * @param rect the rectangle for the image
  160. * @param xobj the image XObject
  161. */
  162. private void placeImageAccess(Rectangle rect, PDFXObject xobj) {
  163. generator.saveGraphicsState(imageMCI.tag, imageMCI.mcid);
  164. generator.add(format(rect.width) + " 0 0 "
  165. + format(-rect.height) + " "
  166. + format(rect.x) + " "
  167. + format(rect.y + rect.height )
  168. + " cm " + xobj.getName() + " Do\n");
  169. generator.restoreGraphicsStateAccess();
  170. }
  171. /** {@inheritDoc} */
  172. public void drawImage(Document doc, Rectangle rect) throws IFException {
  173. if (accessEnabled) {
  174. PDFStructElem structElem = (PDFStructElem) getContext().getStructureTreeElement();
  175. prepareImageMCID(structElem);
  176. }
  177. drawImageUsingDocument(doc, rect);
  178. flushPDFDoc();
  179. }
  180. private void flushPDFDoc() throws IFException {
  181. // output new data
  182. try {
  183. generator.flushPDFDoc();
  184. } catch (IOException ioe) {
  185. throw new IFException("I/O error flushing the PDF document", ioe);
  186. }
  187. }
  188. /**
  189. * Formats a integer value (normally coordinates in millipoints) to a String.
  190. * @param value the value (in millipoints)
  191. * @return the formatted value
  192. */
  193. protected static String format(int value) {
  194. return PDFNumber.doubleOut(value / 1000f);
  195. }
  196. /** {@inheritDoc} */
  197. public void clipRect(Rectangle rect) throws IFException {
  198. generator.endTextObject();
  199. generator.clipRect(rect);
  200. }
  201. /** {@inheritDoc} */
  202. public void fillRect(Rectangle rect, Paint fill) throws IFException {
  203. if (fill == null) {
  204. return;
  205. }
  206. if (rect.width != 0 && rect.height != 0) {
  207. generator.endTextObject();
  208. if (fill != null) {
  209. if (fill instanceof Color) {
  210. generator.updateColor((Color)fill, true, null);
  211. } else {
  212. throw new UnsupportedOperationException("Non-Color paints NYI");
  213. }
  214. }
  215. StringBuffer sb = new StringBuffer();
  216. sb.append(format(rect.x)).append(' ');
  217. sb.append(format(rect.y)).append(' ');
  218. sb.append(format(rect.width)).append(' ');
  219. sb.append(format(rect.height)).append(" re");
  220. if (fill != null) {
  221. sb.append(" f");
  222. }
  223. /* Removed from method signature as it is currently not used
  224. if (stroke != null) {
  225. sb.append(" S");
  226. }*/
  227. sb.append('\n');
  228. generator.add(sb.toString());
  229. }
  230. }
  231. /** {@inheritDoc} */
  232. @Override
  233. public void drawBorderRect(Rectangle rect, BorderProps top, BorderProps bottom,
  234. BorderProps left, BorderProps right) throws IFException {
  235. if (top != null || bottom != null || left != null || right != null) {
  236. generator.endTextObject();
  237. try {
  238. this.borderPainter.drawBorders(rect, top, bottom, left, right);
  239. } catch (IOException ioe) {
  240. throw new IFException("I/O error while drawing borders", ioe);
  241. }
  242. }
  243. }
  244. /** {@inheritDoc} */
  245. @Override
  246. public void drawLine(Point start, Point end, int width, Color color, RuleStyle style)
  247. throws IFException {
  248. generator.endTextObject();
  249. this.borderPainter.drawLine(start, end, width, color, style);
  250. }
  251. private Typeface getTypeface(String fontName) {
  252. if (fontName == null) {
  253. throw new NullPointerException("fontName must not be null");
  254. }
  255. Typeface tf = getFontInfo().getFonts().get(fontName);
  256. if (tf instanceof LazyFont) {
  257. tf = ((LazyFont)tf).getRealFont();
  258. }
  259. return tf;
  260. }
  261. /** {@inheritDoc} */
  262. public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[][] dp,
  263. String text)
  264. throws IFException {
  265. if (accessEnabled) {
  266. PDFStructElem structElem = (PDFStructElem) getContext().getStructureTreeElement();
  267. MarkedContentInfo mci = logicalStructureHandler.addTextContentItem(structElem);
  268. if (generator.getTextUtil().isInTextObject()) {
  269. generator.separateTextElements(mci.tag, mci.mcid);
  270. }
  271. generator.updateColor(state.getTextColor(), true, null);
  272. generator.beginTextObject(mci.tag, mci.mcid);
  273. } else {
  274. generator.updateColor(state.getTextColor(), true, null);
  275. generator.beginTextObject();
  276. }
  277. FontTriplet triplet = new FontTriplet(
  278. state.getFontFamily(), state.getFontStyle(), state.getFontWeight());
  279. if ( ( dp == null ) || IFUtil.isDPOnlyDX ( dp ) ) {
  280. drawTextWithDX ( x, y, text, triplet, letterSpacing,
  281. wordSpacing, IFUtil.convertDPToDX ( dp ) );
  282. } else {
  283. drawTextWithDP ( x, y, text, triplet, letterSpacing,
  284. wordSpacing, dp );
  285. }
  286. }
  287. private void drawTextWithDX ( int x, int y, String text, FontTriplet triplet,
  288. int letterSpacing, int wordSpacing, int[] dx ) {
  289. //TODO Ignored: state.getFontVariant()
  290. //TODO Opportunity for font caching if font state is more heavily used
  291. String fontKey = getFontInfo().getInternalFontKey(triplet);
  292. int sizeMillipoints = state.getFontSize();
  293. float fontSize = sizeMillipoints / 1000f;
  294. // This assumes that *all* CIDFonts use a /ToUnicode mapping
  295. Typeface tf = getTypeface(fontKey);
  296. SingleByteFont singleByteFont = null;
  297. if (tf instanceof SingleByteFont) {
  298. singleByteFont = (SingleByteFont)tf;
  299. }
  300. Font font = getFontInfo().getFontInstance(triplet, sizeMillipoints);
  301. String fontName = font.getFontName();
  302. PDFTextUtil textutil = generator.getTextUtil();
  303. textutil.updateTf(fontKey, fontSize, tf.isMultiByte());
  304. generator.updateCharacterSpacing(letterSpacing / 1000f);
  305. textutil.writeTextMatrix(new AffineTransform(1, 0, 0, -1, x / 1000f, y / 1000f));
  306. int l = text.length();
  307. int dxl = (dx != null ? dx.length : 0);
  308. if (dx != null && dxl > 0 && dx[0] != 0) {
  309. textutil.adjustGlyphTJ(-dx[0] / fontSize);
  310. }
  311. for (int i = 0; i < l; i++) {
  312. char orgChar = text.charAt(i);
  313. char ch;
  314. float glyphAdjust = 0;
  315. if (font.hasChar(orgChar)) {
  316. ch = font.mapChar(orgChar);
  317. ch = selectAndMapSingleByteFont(singleByteFont, fontName, fontSize, textutil, ch);
  318. if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) {
  319. glyphAdjust += wordSpacing;
  320. }
  321. } else {
  322. if (CharUtilities.isFixedWidthSpace(orgChar)) {
  323. //Fixed width space are rendered as spaces so copy/paste works in a reader
  324. ch = font.mapChar(CharUtilities.SPACE);
  325. int spaceDiff = font.getCharWidth(ch) - font.getCharWidth(orgChar);
  326. glyphAdjust = -spaceDiff;
  327. } else {
  328. ch = font.mapChar(orgChar);
  329. if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) {
  330. glyphAdjust += wordSpacing;
  331. }
  332. }
  333. ch = selectAndMapSingleByteFont(singleByteFont, fontName, fontSize,
  334. textutil, ch);
  335. }
  336. textutil.writeTJMappedChar(ch);
  337. if (dx != null && i < dxl - 1) {
  338. glyphAdjust += dx[i + 1];
  339. }
  340. if (glyphAdjust != 0) {
  341. textutil.adjustGlyphTJ(-glyphAdjust / fontSize);
  342. }
  343. }
  344. textutil.writeTJ();
  345. }
  346. private static int[] paZero = new int[4];
  347. private void drawTextWithDP ( int x, int y, String text, FontTriplet triplet,
  348. int letterSpacing, int wordSpacing, int[][] dp ) {
  349. assert text != null;
  350. assert triplet != null;
  351. assert dp != null;
  352. String fk = getFontInfo().getInternalFontKey(triplet);
  353. Typeface tf = getTypeface(fk);
  354. if ( tf.isMultiByte() ) {
  355. int fs = state.getFontSize();
  356. float fsPoints = fs / 1000f;
  357. Font f = getFontInfo().getFontInstance(triplet, fs);
  358. // String fn = f.getFontName();
  359. PDFTextUtil tu = generator.getTextUtil();
  360. double xc = 0f;
  361. double yc = 0f;
  362. double xoLast = 0f;
  363. double yoLast = 0f;
  364. double wox = wordSpacing;
  365. tu.writeTextMatrix ( new AffineTransform ( 1, 0, 0, -1, x / 1000f, y / 1000f ) );
  366. tu.updateTf ( fk, fsPoints, true );
  367. generator.updateCharacterSpacing ( letterSpacing / 1000f );
  368. for ( int i = 0, n = text.length(); i < n; i++ ) {
  369. char ch = text.charAt ( i );
  370. int[] pa = ( i < dp.length ) ? dp [ i ] : paZero;
  371. double xo = xc + pa[0];
  372. double yo = yc + pa[1];
  373. double xa = f.getCharWidth(ch) + maybeWordOffsetX ( wox, ch, null );
  374. double ya = 0;
  375. double xd = ( xo - xoLast ) / 1000f;
  376. double yd = ( yo - yoLast ) / 1000f;
  377. tu.writeTd ( xd, yd );
  378. tu.writeTj ( f.mapChar ( ch ) );
  379. xc += xa + pa[2];
  380. yc += ya + pa[3];
  381. xoLast = xo;
  382. yoLast = yo;
  383. }
  384. }
  385. }
  386. private double maybeWordOffsetX ( double wox, char ch, Direction dir ) {
  387. if ( ( wox != 0 )
  388. && CharUtilities.isAdjustableSpace ( ch )
  389. && ( ( dir == null ) || dir.isHorizontal() ) ) {
  390. return wox;
  391. } else {
  392. return 0;
  393. }
  394. }
  395. /*
  396. private double maybeWordOffsetY ( double woy, char ch, Direction dir ) {
  397. if ( ( woy != 0 )
  398. && CharUtilities.isAdjustableSpace ( ch ) && dir.isVertical()
  399. && ( ( dir != null ) && dir.isVertical() ) ) {
  400. return woy;
  401. } else {
  402. return 0;
  403. }
  404. }
  405. */
  406. private char selectAndMapSingleByteFont(SingleByteFont singleByteFont, String fontName,
  407. float fontSize, PDFTextUtil textutil, char ch) {
  408. if (singleByteFont != null && singleByteFont.hasAdditionalEncodings()) {
  409. int encoding = ch / 256;
  410. if (encoding == 0) {
  411. textutil.updateTf(fontName, fontSize, singleByteFont.isMultiByte());
  412. } else {
  413. textutil.updateTf(fontName + "_" + Integer.toString(encoding),
  414. fontSize, singleByteFont.isMultiByte());
  415. ch = (char)(ch % 256);
  416. }
  417. }
  418. return ch;
  419. }
  420. }