選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

PSRenderer.java 66KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717
  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.ps;
  19. // Java
  20. import java.awt.Color;
  21. import java.awt.geom.AffineTransform;
  22. import java.awt.geom.Rectangle2D;
  23. import java.awt.image.RenderedImage;
  24. import java.io.File;
  25. import java.io.FileNotFoundException;
  26. import java.io.IOException;
  27. import java.io.InputStream;
  28. import java.io.LineNumberReader;
  29. import java.io.OutputStream;
  30. import java.util.Collection;
  31. import java.util.Iterator;
  32. import java.util.List;
  33. import java.util.Map;
  34. import javax.xml.transform.Source;
  35. import org.apache.commons.io.IOUtils;
  36. import org.apache.commons.logging.Log;
  37. import org.apache.commons.logging.LogFactory;
  38. import org.apache.xmlgraphics.image.loader.ImageException;
  39. import org.apache.xmlgraphics.image.loader.ImageFlavor;
  40. import org.apache.xmlgraphics.image.loader.ImageInfo;
  41. import org.apache.xmlgraphics.image.loader.ImageManager;
  42. import org.apache.xmlgraphics.image.loader.ImageSessionContext;
  43. import org.apache.xmlgraphics.image.loader.impl.ImageGraphics2D;
  44. import org.apache.xmlgraphics.image.loader.impl.ImageRawCCITTFax;
  45. import org.apache.xmlgraphics.image.loader.impl.ImageRawEPS;
  46. import org.apache.xmlgraphics.image.loader.impl.ImageRawJPEG;
  47. import org.apache.xmlgraphics.image.loader.impl.ImageRawStream;
  48. import org.apache.xmlgraphics.image.loader.impl.ImageRendered;
  49. import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM;
  50. import org.apache.xmlgraphics.image.loader.pipeline.ImageProviderPipeline;
  51. import org.apache.xmlgraphics.image.loader.util.ImageUtil;
  52. import org.apache.xmlgraphics.ps.DSCConstants;
  53. import org.apache.xmlgraphics.ps.ImageEncoder;
  54. import org.apache.xmlgraphics.ps.PSDictionary;
  55. import org.apache.xmlgraphics.ps.PSDictionaryFormatException;
  56. import org.apache.xmlgraphics.ps.PSGenerator;
  57. import org.apache.xmlgraphics.ps.PSImageUtils;
  58. import org.apache.xmlgraphics.ps.PSPageDeviceDictionary;
  59. import org.apache.xmlgraphics.ps.PSProcSets;
  60. import org.apache.xmlgraphics.ps.PSResource;
  61. import org.apache.xmlgraphics.ps.PSState;
  62. import org.apache.xmlgraphics.ps.dsc.DSCException;
  63. import org.apache.xmlgraphics.ps.dsc.ResourceTracker;
  64. import org.apache.xmlgraphics.ps.dsc.events.DSCCommentBoundingBox;
  65. import org.apache.xmlgraphics.ps.dsc.events.DSCCommentHiResBoundingBox;
  66. import org.apache.fop.apps.FOPException;
  67. import org.apache.fop.apps.FOUserAgent;
  68. import org.apache.fop.area.Area;
  69. import org.apache.fop.area.BlockViewport;
  70. import org.apache.fop.area.CTM;
  71. import org.apache.fop.area.OffDocumentExtensionAttachment;
  72. import org.apache.fop.area.OffDocumentItem;
  73. import org.apache.fop.area.PageViewport;
  74. import org.apache.fop.area.RegionViewport;
  75. import org.apache.fop.area.Trait;
  76. import org.apache.fop.area.inline.AbstractTextArea;
  77. import org.apache.fop.area.inline.Image;
  78. import org.apache.fop.area.inline.InlineParent;
  79. import org.apache.fop.area.inline.Leader;
  80. import org.apache.fop.area.inline.SpaceArea;
  81. import org.apache.fop.area.inline.TextArea;
  82. import org.apache.fop.area.inline.WordArea;
  83. import org.apache.fop.datatypes.URISpecification;
  84. import org.apache.fop.events.ResourceEventProducer;
  85. import org.apache.fop.fo.Constants;
  86. import org.apache.fop.fo.extensions.ExtensionAttachment;
  87. import org.apache.fop.fonts.Font;
  88. import org.apache.fop.fonts.LazyFont;
  89. import org.apache.fop.fonts.SingleByteFont;
  90. import org.apache.fop.fonts.Typeface;
  91. import org.apache.fop.render.AbstractPathOrientedRenderer;
  92. import org.apache.fop.render.Graphics2DAdapter;
  93. import org.apache.fop.render.ImageAdapter;
  94. import org.apache.fop.render.RendererContext;
  95. import org.apache.fop.render.RendererEventProducer;
  96. import org.apache.fop.render.ps.extensions.PSCommentAfter;
  97. import org.apache.fop.render.ps.extensions.PSCommentBefore;
  98. import org.apache.fop.render.ps.extensions.PSExtensionAttachment;
  99. import org.apache.fop.render.ps.extensions.PSSetPageDevice;
  100. import org.apache.fop.render.ps.extensions.PSSetupCode;
  101. import org.apache.fop.util.CharUtilities;
  102. import org.apache.fop.util.ColorUtil;
  103. /**
  104. * Renderer that renders to PostScript.
  105. * <br>
  106. * This class currently generates PostScript Level 2 code. The only exception
  107. * is the FlateEncode filter which is a Level 3 feature. The filters in use
  108. * are hardcoded at the moment.
  109. * <br>
  110. * This class follows the Document Structuring Conventions (DSC) version 3.0.
  111. * If anyone modifies this renderer please make
  112. * sure to also follow the DSC to make it simpler to programmatically modify
  113. * the generated Postscript files (ex. extract pages etc.).
  114. * <br>
  115. * This renderer inserts FOP-specific comments into the PostScript stream which
  116. * may help certain users to do certain types of post-processing of the output.
  117. * These comments all start with "%FOP".
  118. *
  119. * @author <a href="mailto:fop-dev@xmlgraphics.apache.org">Apache FOP Development Team</a>
  120. * @version $Id$
  121. */
  122. public class PSRenderer extends AbstractPathOrientedRenderer
  123. implements ImageAdapter, PSSupportedFlavors {
  124. /** logging instance */
  125. private static Log log = LogFactory.getLog(PSRenderer.class);
  126. /** The MIME type for PostScript */
  127. public static final String MIME_TYPE = "application/postscript";
  128. private static final String AUTO_ROTATE_LANDSCAPE = "auto-rotate-landscape";
  129. private static final String OPTIMIZE_RESOURCES = "optimize-resources";
  130. private static final String LANGUAGE_LEVEL = "language-level";
  131. /** The application producing the PostScript */
  132. private int currentPageNumber = 0;
  133. private final boolean enableComments = true;
  134. private boolean autoRotateLandscape = false;
  135. private int languageLevel = PSGenerator.DEFAULT_LANGUAGE_LEVEL;
  136. /** the OutputStream the PS file is written to */
  137. private OutputStream outputStream;
  138. /** the temporary file in case of two-pass processing */
  139. private File tempFile;
  140. /** The PostScript generator used to output the PostScript */
  141. protected PSGenerator gen;
  142. /** Determines whether the PS file is generated in two passes to minimize file size */
  143. private boolean twoPassGeneration = false;
  144. private boolean ioTrouble = false;
  145. private boolean inTextMode = false;
  146. /** Used to temporarily store PSSetupCode instance until they can be written. */
  147. private List setupCodeList;
  148. /** This is a map of PSResource instances of all fonts defined (key: font key) */
  149. private Map fontResources;
  150. /** This is a map of PSResource instances of all forms (key: uri) */
  151. private Map formResources;
  152. /** encapsulation of dictionary used in setpagedevice instruction **/
  153. private PSPageDeviceDictionary pageDeviceDictionary;
  154. /** Whether or not the safe set page device macro will be used or not */
  155. private boolean safeSetPageDevice = false;
  156. /**
  157. * Whether or not PostScript Document Structuring Conventions (DSC) compliant output are
  158. * enforced.
  159. */
  160. private boolean dscCompliant = true;
  161. /** Is used to determine the document's bounding box */
  162. private Rectangle2D documentBoundingBox;
  163. /** This is a collection holding all document header comments */
  164. private Collection headerComments;
  165. /** This is a collection holding all document footer comments */
  166. private Collection footerComments;
  167. /** {@inheritDoc} */
  168. public void setUserAgent(FOUserAgent agent) {
  169. super.setUserAgent(agent);
  170. Object obj;
  171. obj = agent.getRendererOptions().get(AUTO_ROTATE_LANDSCAPE);
  172. if (obj != null) {
  173. setAutoRotateLandscape(booleanValueOf(obj));
  174. }
  175. obj = agent.getRendererOptions().get(LANGUAGE_LEVEL);
  176. if (obj != null) {
  177. setLanguageLevel(intValueOf(obj));
  178. }
  179. obj = agent.getRendererOptions().get(OPTIMIZE_RESOURCES);
  180. if (obj != null) {
  181. setOptimizeResources(booleanValueOf(obj));
  182. }
  183. }
  184. private boolean booleanValueOf(Object obj) {
  185. if (obj instanceof Boolean) {
  186. return ((Boolean)obj).booleanValue();
  187. } else if (obj instanceof String) {
  188. return Boolean.valueOf((String)obj).booleanValue();
  189. } else {
  190. throw new IllegalArgumentException("Boolean or \"true\" or \"false\" expected.");
  191. }
  192. }
  193. private int intValueOf(Object obj) {
  194. if (obj instanceof Integer) {
  195. return ((Integer)obj).intValue();
  196. } else if (obj instanceof String) {
  197. return Integer.parseInt((String)obj);
  198. } else {
  199. throw new IllegalArgumentException("Integer or String with a number expected.");
  200. }
  201. }
  202. /**
  203. * Sets the landscape mode for this renderer.
  204. * @param value false will normally generate a "pseudo-portrait" page, true will rotate
  205. * a "wider-than-long" page by 90 degrees.
  206. */
  207. public void setAutoRotateLandscape(boolean value) {
  208. this.autoRotateLandscape = value;
  209. }
  210. /** @return true if the renderer is configured to rotate landscape pages */
  211. public boolean isAutoRotateLandscape() {
  212. return this.autoRotateLandscape;
  213. }
  214. /**
  215. * Sets the PostScript language level that the renderer should produce.
  216. * @param level the language level (currently allowed: 2 or 3)
  217. */
  218. public void setLanguageLevel(int level) {
  219. if (level == 2 || level == 3) {
  220. this.languageLevel = level;
  221. } else {
  222. throw new IllegalArgumentException("Only language levels 2 or 3 are allowed/supported");
  223. }
  224. }
  225. /**
  226. * Return the PostScript language level that the renderer produces.
  227. * @return the language level
  228. */
  229. public int getLanguageLevel() {
  230. return this.languageLevel;
  231. }
  232. /**
  233. * Sets the resource optimization mode. If set to true, the renderer does two passes to
  234. * only embed the necessary resources in the PostScript file. This is slower, but produces
  235. * smaller files.
  236. * @param value true to enable the resource optimization
  237. */
  238. public void setOptimizeResources(boolean value) {
  239. this.twoPassGeneration = value;
  240. }
  241. /** @return true if the renderer does two passes to optimize PostScript resources */
  242. public boolean isOptimizeResources() {
  243. return this.twoPassGeneration;
  244. }
  245. /** {@inheritDoc} */
  246. public Graphics2DAdapter getGraphics2DAdapter() {
  247. return new PSGraphics2DAdapter(this);
  248. }
  249. /** {@inheritDoc} */
  250. public ImageAdapter getImageAdapter() {
  251. return this;
  252. }
  253. /**
  254. * Write out a command
  255. * @param cmd PostScript command
  256. */
  257. protected void writeln(String cmd) {
  258. try {
  259. gen.writeln(cmd);
  260. } catch (IOException ioe) {
  261. handleIOTrouble(ioe);
  262. }
  263. }
  264. /**
  265. * Central exception handler for I/O exceptions.
  266. * @param ioe IOException to handle
  267. */
  268. protected void handleIOTrouble(IOException ioe) {
  269. if (!ioTrouble) {
  270. RendererEventProducer eventProducer = RendererEventProducer.Provider.get(
  271. getUserAgent().getEventBroadcaster());
  272. eventProducer.ioError(this, ioe);
  273. ioTrouble = true;
  274. }
  275. }
  276. /**
  277. * Write out a comment
  278. * @param comment Comment to write
  279. */
  280. protected void comment(String comment) {
  281. if (this.enableComments) {
  282. if (comment.startsWith("%")) {
  283. writeln(comment);
  284. } else {
  285. writeln("%" + comment);
  286. }
  287. }
  288. }
  289. /**
  290. * Make sure the cursor is in the right place.
  291. */
  292. protected void movetoCurrPosition() {
  293. moveTo(this.currentIPPosition, this.currentBPPosition);
  294. }
  295. /** {@inheritDoc} */
  296. protected void clip() {
  297. writeln("clip newpath");
  298. }
  299. /** {@inheritDoc} */
  300. protected void clipRect(float x, float y, float width, float height) {
  301. try {
  302. gen.defineRect(x, y, width, height);
  303. clip();
  304. } catch (IOException ioe) {
  305. handleIOTrouble(ioe);
  306. }
  307. }
  308. /** {@inheritDoc} */
  309. protected void moveTo(float x, float y) {
  310. writeln(gen.formatDouble(x) + " " + gen.formatDouble(y) + " M");
  311. }
  312. /**
  313. * Moves the current point by (x, y) relative to the current position,
  314. * omitting any connecting line segment.
  315. * @param x x coordinate
  316. * @param y y coordinate
  317. */
  318. protected void rmoveTo(float x, float y) {
  319. writeln(gen.formatDouble(x) + " " + gen.formatDouble(y) + " RM");
  320. }
  321. /** {@inheritDoc} */
  322. protected void lineTo(float x, float y) {
  323. writeln(gen.formatDouble(x) + " " + gen.formatDouble(y) + " lineto");
  324. }
  325. /** {@inheritDoc} */
  326. protected void closePath() {
  327. writeln("cp");
  328. }
  329. /** {@inheritDoc} */
  330. protected void fillRect(float x, float y, float width, float height) {
  331. if (width != 0 && height != 0) {
  332. try {
  333. gen.defineRect(x, y, width, height);
  334. gen.writeln("fill");
  335. } catch (IOException ioe) {
  336. handleIOTrouble(ioe);
  337. }
  338. }
  339. }
  340. /** {@inheritDoc} */
  341. protected void updateColor(Color col, boolean fill) {
  342. try {
  343. useColor(col);
  344. } catch (IOException ioe) {
  345. handleIOTrouble(ioe);
  346. }
  347. }
  348. /**
  349. * Indicates whether an image should be inlined or added as a PostScript form.
  350. * @param uri the URI of the image
  351. * @return true if the image should be inlined rather than added as a form
  352. */
  353. protected boolean isImageInlined(String uri) {
  354. return !isOptimizeResources() || uri == null || "".equals(uri);
  355. }
  356. /**
  357. * Indicates whether an image should be inlined or added as a PostScript form.
  358. * @param info the ImageInfo object of the image
  359. * @return true if the image should be inlined rather than added as a form
  360. */
  361. protected boolean isImageInlined(ImageInfo info) {
  362. if (isImageInlined(info.getOriginalURI())) {
  363. return true;
  364. }
  365. if (!isOptimizeResources()) {
  366. throw new IllegalStateException("Must not get here if form support is enabled");
  367. }
  368. //Investigate choice for inline mode
  369. ImageFlavor[] inlineFlavors = getInlineFlavors();
  370. ImageManager manager = getUserAgent().getFactory().getImageManager();
  371. ImageProviderPipeline[] inlineCandidates
  372. = manager.getPipelineFactory().determineCandidatePipelines(
  373. info, inlineFlavors);
  374. ImageProviderPipeline inlineChoice = manager.choosePipeline(inlineCandidates);
  375. ImageFlavor inlineFlavor = (inlineChoice != null ? inlineChoice.getTargetFlavor() : null);
  376. //Investigate choice for form mode
  377. ImageFlavor[] formFlavors = getFormFlavors();
  378. ImageProviderPipeline[] formCandidates
  379. = manager.getPipelineFactory().determineCandidatePipelines(
  380. info, formFlavors);
  381. ImageProviderPipeline formChoice = manager.choosePipeline(formCandidates);
  382. ImageFlavor formFlavor = (formChoice != null ? formChoice.getTargetFlavor() : null);
  383. //Inline if form is not supported or if a better choice is available with inline mode
  384. return formFlavor == null || !formFlavor.equals(inlineFlavor);
  385. }
  386. /** {@inheritDoc} */
  387. protected void drawImage(String uri, Rectangle2D pos, Map foreignAttributes) {
  388. endTextObject();
  389. int x = currentIPPosition + (int)Math.round(pos.getX());
  390. int y = currentBPPosition + (int)Math.round(pos.getY());
  391. uri = URISpecification.getURL(uri);
  392. if (log.isDebugEnabled()) {
  393. log.debug("Handling image: " + uri);
  394. }
  395. ImageManager manager = getUserAgent().getFactory().getImageManager();
  396. ImageInfo info = null;
  397. try {
  398. ImageSessionContext sessionContext = getUserAgent().getImageSessionContext();
  399. info = manager.getImageInfo(uri, sessionContext);
  400. int width = (int)pos.getWidth();
  401. int height = (int)pos.getHeight();
  402. //millipoints --> points for PostScript
  403. float ptx = x / 1000f;
  404. float pty = y / 1000f;
  405. float ptw = width / 1000f;
  406. float pth = height / 1000f;
  407. if (isImageInlined(info)) {
  408. if (log.isDebugEnabled()) {
  409. log.debug("Image " + info + " is inlined");
  410. }
  411. //Only now fully load/prepare the image
  412. Map hints = ImageUtil.getDefaultHints(sessionContext);
  413. org.apache.xmlgraphics.image.loader.Image img = manager.getImage(
  414. info, getInlineFlavors(), hints, sessionContext);
  415. //...and embed as inline image
  416. if (img instanceof ImageGraphics2D) {
  417. ImageGraphics2D imageG2D = (ImageGraphics2D)img;
  418. RendererContext context = createRendererContext(
  419. x, y, width, height, foreignAttributes);
  420. getGraphics2DAdapter().paintImage(imageG2D.getGraphics2DImagePainter(),
  421. context, x, y, width, height);
  422. } else if (img instanceof ImageRendered) {
  423. ImageRendered imgRend = (ImageRendered)img;
  424. RenderedImage ri = imgRend.getRenderedImage();
  425. PSImageUtils.renderBitmapImage(ri, ptx, pty, ptw, pth, gen);
  426. } else if (img instanceof ImageXMLDOM) {
  427. ImageXMLDOM imgXML = (ImageXMLDOM)img;
  428. renderDocument(imgXML.getDocument(), imgXML.getRootNamespace(),
  429. pos, foreignAttributes);
  430. } else if (img instanceof ImageRawStream) {
  431. final ImageRawStream raw = (ImageRawStream)img;
  432. if (raw instanceof ImageRawEPS) {
  433. ImageRawEPS eps = (ImageRawEPS)raw;
  434. Rectangle2D bbox = eps.getBoundingBox();
  435. InputStream in = raw.createInputStream();
  436. try {
  437. PSImageUtils.renderEPS(in, uri,
  438. new Rectangle2D.Float(ptx, pty, ptw, pth),
  439. bbox,
  440. gen);
  441. } finally {
  442. IOUtils.closeQuietly(in);
  443. }
  444. } else if (raw instanceof ImageRawCCITTFax) {
  445. final ImageRawCCITTFax ccitt = (ImageRawCCITTFax)raw;
  446. ImageEncoder encoder = new ImageEncoderCCITTFax(ccitt);
  447. Rectangle2D targetRect = new Rectangle2D.Float(
  448. ptx, pty, ptw, pth);
  449. PSImageUtils.writeImage(encoder, info.getSize().getDimensionPx(),
  450. uri, targetRect,
  451. ccitt.getColorSpace(), 1, false, gen);
  452. } else if (raw instanceof ImageRawJPEG) {
  453. ImageRawJPEG jpeg = (ImageRawJPEG)raw;
  454. ImageEncoder encoder = new ImageEncoderJPEG(jpeg);
  455. Rectangle2D targetRect = new Rectangle2D.Float(
  456. ptx, pty, ptw, pth);
  457. PSImageUtils.writeImage(encoder, info.getSize().getDimensionPx(),
  458. uri, targetRect,
  459. jpeg.getColorSpace(), 8, jpeg.isInverted(), gen);
  460. } else {
  461. throw new UnsupportedOperationException("Unsupported raw image: " + info);
  462. }
  463. } else {
  464. throw new UnsupportedOperationException("Unsupported image type: " + img);
  465. }
  466. } else {
  467. if (log.isDebugEnabled()) {
  468. log.debug("Image " + info + " is embedded as a form later");
  469. }
  470. //Don't load image at this time, just put a form placeholder in the stream
  471. PSResource form = getFormForImage(uri);
  472. Rectangle2D targetRect = new Rectangle2D.Double(ptx, pty, ptw, pth);
  473. PSImageUtils.paintForm(form, info.getSize().getDimensionPt(), targetRect, gen);
  474. }
  475. } catch (ImageException ie) {
  476. ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
  477. getUserAgent().getEventBroadcaster());
  478. eventProducer.imageError(this, (info != null ? info.toString() : uri), ie, null);
  479. } catch (FileNotFoundException fe) {
  480. ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
  481. getUserAgent().getEventBroadcaster());
  482. eventProducer.imageNotFound(this, (info != null ? info.toString() : uri), fe, null);
  483. } catch (IOException ioe) {
  484. ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
  485. getUserAgent().getEventBroadcaster());
  486. eventProducer.imageIOError(this, (info != null ? info.toString() : uri), ioe, null);
  487. }
  488. }
  489. private ImageFlavor[] getInlineFlavors() {
  490. ImageFlavor[] flavors;
  491. if (gen.getPSLevel() >= 3) {
  492. flavors = LEVEL_3_FLAVORS_INLINE;
  493. } else {
  494. flavors = LEVEL_2_FLAVORS_INLINE;
  495. }
  496. return flavors;
  497. }
  498. private ImageFlavor[] getFormFlavors() {
  499. ImageFlavor[] flavors;
  500. if (gen.getPSLevel() >= 3) {
  501. flavors = LEVEL_3_FLAVORS_FORM;
  502. } else {
  503. flavors = LEVEL_2_FLAVORS_FORM;
  504. }
  505. return flavors;
  506. }
  507. /**
  508. * Returns a PSResource instance representing a image as a PostScript form.
  509. * @param uri the image URI
  510. * @return a PSResource instance
  511. */
  512. protected PSResource getFormForImage(String uri) {
  513. if (uri == null || "".equals(uri)) {
  514. throw new IllegalArgumentException("uri must not be empty or null");
  515. }
  516. if (this.formResources == null) {
  517. this.formResources = new java.util.HashMap();
  518. }
  519. PSResource form = (PSResource)this.formResources.get(uri);
  520. if (form == null) {
  521. form = new PSImageFormResource(this.formResources.size() + 1, uri);
  522. this.formResources.put(uri, form);
  523. }
  524. return form;
  525. }
  526. /** {@inheritDoc} */
  527. public void paintImage(RenderedImage image, RendererContext context,
  528. int x, int y, int width, int height) throws IOException {
  529. float fx = x / 1000f;
  530. x += currentIPPosition / 1000f;
  531. float fy = y / 1000f;
  532. y += currentBPPosition / 1000f;
  533. float fw = width / 1000f;
  534. float fh = height / 1000f;
  535. PSImageUtils.renderBitmapImage(image, fx, fy, fw, fh, gen);
  536. }
  537. /**
  538. * Draw a line.
  539. *
  540. * @param startx the start x position
  541. * @param starty the start y position
  542. * @param endx the x end position
  543. * @param endy the y end position
  544. */
  545. private void drawLine(float startx, float starty, float endx, float endy) {
  546. writeln(gen.formatDouble(startx) + " "
  547. + gen.formatDouble(starty) + " M "
  548. + gen.formatDouble(endx) + " "
  549. + gen.formatDouble(endy) + " lineto stroke newpath");
  550. }
  551. /** Saves the graphics state of the rendering engine. */
  552. public void saveGraphicsState() {
  553. endTextObject();
  554. try {
  555. //delegate
  556. gen.saveGraphicsState();
  557. } catch (IOException ioe) {
  558. handleIOTrouble(ioe);
  559. }
  560. }
  561. /** Restores the last graphics state of the rendering engine. */
  562. public void restoreGraphicsState() {
  563. try {
  564. endTextObject();
  565. //delegate
  566. gen.restoreGraphicsState();
  567. } catch (IOException ioe) {
  568. handleIOTrouble(ioe);
  569. }
  570. }
  571. /**
  572. * Concats the transformation matrix.
  573. * @param a A part
  574. * @param b B part
  575. * @param c C part
  576. * @param d D part
  577. * @param e E part
  578. * @param f F part
  579. */
  580. protected void concatMatrix(double a, double b,
  581. double c, double d,
  582. double e, double f) {
  583. try {
  584. gen.concatMatrix(a, b, c, d, e, f);
  585. } catch (IOException ioe) {
  586. handleIOTrouble(ioe);
  587. }
  588. }
  589. /**
  590. * Concats the transformations matrix.
  591. * @param matrix Matrix to use
  592. */
  593. protected void concatMatrix(double[] matrix) {
  594. try {
  595. gen.concatMatrix(matrix);
  596. } catch (IOException ioe) {
  597. handleIOTrouble(ioe);
  598. }
  599. }
  600. /** {@inheritDoc} */
  601. protected void concatenateTransformationMatrix(AffineTransform at) {
  602. try {
  603. gen.concatMatrix(at);
  604. } catch (IOException ioe) {
  605. handleIOTrouble(ioe);
  606. }
  607. }
  608. private String getPostScriptNameForFontKey(String key) {
  609. int pos = key.indexOf('_');
  610. String postFix = null;
  611. if (pos > 0) {
  612. postFix = key.substring(pos);
  613. key = key.substring(0, pos);
  614. }
  615. Map fonts = fontInfo.getFonts();
  616. Typeface tf = (Typeface)fonts.get(key);
  617. if (tf instanceof LazyFont) {
  618. tf = ((LazyFont)tf).getRealFont();
  619. }
  620. if (tf == null) {
  621. throw new IllegalStateException("Font not available: " + key);
  622. }
  623. if (postFix == null) {
  624. return tf.getFontName();
  625. } else {
  626. return tf.getFontName() + postFix;
  627. }
  628. }
  629. /**
  630. * Returns the PSResource for the given font key.
  631. * @param key the font key ("F*")
  632. * @return the matching PSResource
  633. */
  634. protected PSResource getPSResourceForFontKey(String key) {
  635. PSResource res = null;
  636. if (this.fontResources != null) {
  637. res = (PSResource)this.fontResources.get(key);
  638. } else {
  639. this.fontResources = new java.util.HashMap();
  640. }
  641. if (res == null) {
  642. res = new PSResource(PSResource.TYPE_FONT, getPostScriptNameForFontKey(key));
  643. this.fontResources.put(key, res);
  644. }
  645. return res;
  646. }
  647. /**
  648. * Changes the currently used font.
  649. * @param key key of the font ("F*")
  650. * @param size font size
  651. */
  652. protected void useFont(String key, int size) {
  653. try {
  654. PSResource res = getPSResourceForFontKey(key);
  655. gen.useFont("/" + res.getName(), size / 1000f);
  656. gen.getResourceTracker().notifyResourceUsageOnPage(res);
  657. } catch (IOException ioe) {
  658. handleIOTrouble(ioe);
  659. }
  660. }
  661. private void useColor(Color col) throws IOException {
  662. gen.useColor(col);
  663. }
  664. /** {@inheritDoc} */
  665. protected void drawBackAndBorders(Area area, float startx, float starty,
  666. float width, float height) {
  667. if (area.hasTrait(Trait.BACKGROUND)
  668. || area.hasTrait(Trait.BORDER_BEFORE)
  669. || area.hasTrait(Trait.BORDER_AFTER)
  670. || area.hasTrait(Trait.BORDER_START)
  671. || area.hasTrait(Trait.BORDER_END)) {
  672. comment("%FOPBeginBackgroundAndBorder: "
  673. + startx + " " + starty + " " + width + " " + height);
  674. super.drawBackAndBorders(area, startx, starty, width, height);
  675. comment("%FOPEndBackgroundAndBorder");
  676. }
  677. }
  678. /** {@inheritDoc} */
  679. protected void drawBorderLine(float x1, float y1, float x2, float y2,
  680. boolean horz, boolean startOrBefore, int style, Color col) {
  681. try {
  682. float w = x2 - x1;
  683. float h = y2 - y1;
  684. if ((w < 0) || (h < 0)) {
  685. log.error("Negative extent received. Border won't be painted.");
  686. return;
  687. }
  688. switch (style) {
  689. case Constants.EN_DASHED:
  690. useColor(col);
  691. if (horz) {
  692. float unit = Math.abs(2 * h);
  693. int rep = (int)(w / unit);
  694. if (rep % 2 == 0) {
  695. rep++;
  696. }
  697. unit = w / rep;
  698. gen.useDash("[" + unit + "] 0");
  699. gen.useLineCap(0);
  700. gen.useLineWidth(h);
  701. float ym = y1 + (h / 2);
  702. drawLine(x1, ym, x2, ym);
  703. } else {
  704. float unit = Math.abs(2 * w);
  705. int rep = (int)(h / unit);
  706. if (rep % 2 == 0) {
  707. rep++;
  708. }
  709. unit = h / rep;
  710. gen.useDash("[" + unit + "] 0");
  711. gen.useLineCap(0);
  712. gen.useLineWidth(w);
  713. float xm = x1 + (w / 2);
  714. drawLine(xm, y1, xm, y2);
  715. }
  716. break;
  717. case Constants.EN_DOTTED:
  718. useColor(col);
  719. gen.useLineCap(1); //Rounded!
  720. if (horz) {
  721. float unit = Math.abs(2 * h);
  722. int rep = (int)(w / unit);
  723. if (rep % 2 == 0) {
  724. rep++;
  725. }
  726. unit = w / rep;
  727. gen.useDash("[0 " + unit + "] 0");
  728. gen.useLineWidth(h);
  729. float ym = y1 + (h / 2);
  730. drawLine(x1, ym, x2, ym);
  731. } else {
  732. float unit = Math.abs(2 * w);
  733. int rep = (int)(h / unit);
  734. if (rep % 2 == 0) {
  735. rep++;
  736. }
  737. unit = h / rep;
  738. gen.useDash("[0 " + unit + "] 0");
  739. gen.useLineWidth(w);
  740. float xm = x1 + (w / 2);
  741. drawLine(xm, y1, xm, y2);
  742. }
  743. break;
  744. case Constants.EN_DOUBLE:
  745. useColor(col);
  746. gen.useDash(null);
  747. if (horz) {
  748. float h3 = h / 3;
  749. gen.useLineWidth(h3);
  750. float ym1 = y1 + (h3 / 2);
  751. float ym2 = ym1 + h3 + h3;
  752. drawLine(x1, ym1, x2, ym1);
  753. drawLine(x1, ym2, x2, ym2);
  754. } else {
  755. float w3 = w / 3;
  756. gen.useLineWidth(w3);
  757. float xm1 = x1 + (w3 / 2);
  758. float xm2 = xm1 + w3 + w3;
  759. drawLine(xm1, y1, xm1, y2);
  760. drawLine(xm2, y1, xm2, y2);
  761. }
  762. break;
  763. case Constants.EN_GROOVE:
  764. case Constants.EN_RIDGE:
  765. float colFactor = (style == EN_GROOVE ? 0.4f : -0.4f);
  766. gen.useDash(null);
  767. if (horz) {
  768. Color uppercol = ColorUtil.lightenColor(col, -colFactor);
  769. Color lowercol = ColorUtil.lightenColor(col, colFactor);
  770. float h3 = h / 3;
  771. gen.useLineWidth(h3);
  772. float ym1 = y1 + (h3 / 2);
  773. gen.useColor(uppercol);
  774. drawLine(x1, ym1, x2, ym1);
  775. gen.useColor(col);
  776. drawLine(x1, ym1 + h3, x2, ym1 + h3);
  777. gen.useColor(lowercol);
  778. drawLine(x1, ym1 + h3 + h3, x2, ym1 + h3 + h3);
  779. } else {
  780. Color leftcol = ColorUtil.lightenColor(col, -colFactor);
  781. Color rightcol = ColorUtil.lightenColor(col, colFactor);
  782. float w3 = w / 3;
  783. gen.useLineWidth(w3);
  784. float xm1 = x1 + (w3 / 2);
  785. gen.useColor(leftcol);
  786. drawLine(xm1, y1, xm1, y2);
  787. gen.useColor(col);
  788. drawLine(xm1 + w3, y1, xm1 + w3, y2);
  789. gen.useColor(rightcol);
  790. drawLine(xm1 + w3 + w3, y1, xm1 + w3 + w3, y2);
  791. }
  792. break;
  793. case Constants.EN_INSET:
  794. case Constants.EN_OUTSET:
  795. colFactor = (style == EN_OUTSET ? 0.4f : -0.4f);
  796. gen.useDash(null);
  797. if (horz) {
  798. Color c = ColorUtil.lightenColor(col, (startOrBefore ? 1 : -1) * colFactor);
  799. gen.useLineWidth(h);
  800. float ym1 = y1 + (h / 2);
  801. gen.useColor(c);
  802. drawLine(x1, ym1, x2, ym1);
  803. } else {
  804. Color c = ColorUtil.lightenColor(col, (startOrBefore ? 1 : -1) * colFactor);
  805. gen.useLineWidth(w);
  806. float xm1 = x1 + (w / 2);
  807. gen.useColor(c);
  808. drawLine(xm1, y1, xm1, y2);
  809. }
  810. break;
  811. case Constants.EN_HIDDEN:
  812. break;
  813. default:
  814. useColor(col);
  815. gen.useDash(null);
  816. gen.useLineCap(0);
  817. if (horz) {
  818. gen.useLineWidth(h);
  819. float ym = y1 + (h / 2);
  820. drawLine(x1, ym, x2, ym);
  821. } else {
  822. gen.useLineWidth(w);
  823. float xm = x1 + (w / 2);
  824. drawLine(xm, y1, xm, y2);
  825. }
  826. }
  827. } catch (IOException ioe) {
  828. handleIOTrouble(ioe);
  829. }
  830. }
  831. /** {@inheritDoc} */
  832. public void startRenderer(OutputStream outputStream)
  833. throws IOException {
  834. log.debug("Rendering areas to PostScript...");
  835. this.outputStream = outputStream;
  836. OutputStream out;
  837. if (isOptimizeResources()) {
  838. this.tempFile = File.createTempFile("fop", null);
  839. out = new java.io.FileOutputStream(this.tempFile);
  840. out = new java.io.BufferedOutputStream(out);
  841. } else {
  842. out = this.outputStream;
  843. }
  844. //Setup for PostScript generation
  845. this.gen = new PSGenerator(out) {
  846. /** Need to subclass PSGenerator to have better URI resolution */
  847. public Source resolveURI(String uri) {
  848. return userAgent.resolveURI(uri);
  849. }
  850. };
  851. this.gen.setPSLevel(getLanguageLevel());
  852. this.currentPageNumber = 0;
  853. //Initial default page device dictionary settings
  854. this.pageDeviceDictionary = new PSPageDeviceDictionary();
  855. pageDeviceDictionary.setFlushOnRetrieval(!this.dscCompliant);
  856. pageDeviceDictionary.put("/ImagingBBox", "null");
  857. }
  858. private void writeHeader() throws IOException {
  859. //PostScript Header
  860. writeln(DSCConstants.PS_ADOBE_30);
  861. gen.writeDSCComment(DSCConstants.CREATOR, new String[] {userAgent.getProducer()});
  862. gen.writeDSCComment(DSCConstants.CREATION_DATE, new Object[] {new java.util.Date()});
  863. gen.writeDSCComment(DSCConstants.LANGUAGE_LEVEL, new Integer(gen.getPSLevel()));
  864. gen.writeDSCComment(DSCConstants.PAGES, new Object[] {DSCConstants.ATEND});
  865. gen.writeDSCComment(DSCConstants.BBOX, DSCConstants.ATEND);
  866. gen.writeDSCComment(DSCConstants.HIRES_BBOX, DSCConstants.ATEND);
  867. this.documentBoundingBox = new Rectangle2D.Double();
  868. gen.writeDSCComment(DSCConstants.DOCUMENT_SUPPLIED_RESOURCES,
  869. new Object[] {DSCConstants.ATEND});
  870. if (headerComments != null) {
  871. for (Iterator iter = headerComments.iterator(); iter.hasNext();) {
  872. PSExtensionAttachment comment = (PSExtensionAttachment)iter.next();
  873. gen.writeln("%" + comment.getContent());
  874. }
  875. }
  876. gen.writeDSCComment(DSCConstants.END_COMMENTS);
  877. //Defaults
  878. gen.writeDSCComment(DSCConstants.BEGIN_DEFAULTS);
  879. gen.writeDSCComment(DSCConstants.END_DEFAULTS);
  880. //Prolog and Setup written right before the first page-sequence, see startPageSequence()
  881. //Do this only once, as soon as we have all the content for the Setup section!
  882. //Prolog
  883. gen.writeDSCComment(DSCConstants.BEGIN_PROLOG);
  884. PSProcSets.writeStdProcSet(gen);
  885. PSProcSets.writeEPSProcSet(gen);
  886. gen.writeDSCComment(DSCConstants.END_PROLOG);
  887. //Setup
  888. gen.writeDSCComment(DSCConstants.BEGIN_SETUP);
  889. writeSetupCodeList(setupCodeList, "SetupCode");
  890. if (!isOptimizeResources()) {
  891. this.fontResources = PSFontUtils.writeFontDict(gen, fontInfo);
  892. } else {
  893. gen.commentln("%FOPFontSetup"); //Place-holder, will be replaced in the second pass
  894. }
  895. gen.writeDSCComment(DSCConstants.END_SETUP);
  896. }
  897. /** {@inheritDoc} */
  898. public void stopRenderer() throws IOException {
  899. //Notify resource usage for font which are not supplied
  900. /* done in useFont now
  901. Map fonts = fontInfo.getUsedFonts();
  902. Iterator e = fonts.keySet().iterator();
  903. while (e.hasNext()) {
  904. String key = (String)e.next();
  905. PSResource res = (PSResource)this.fontResources.get(key);
  906. gen.notifyResourceUsage(res);
  907. }*/
  908. //Write trailer
  909. gen.writeDSCComment(DSCConstants.TRAILER);
  910. if (footerComments != null) {
  911. for (Iterator iter = footerComments.iterator(); iter.hasNext();) {
  912. PSExtensionAttachment comment = (PSExtensionAttachment)iter.next();
  913. gen.commentln("%" + comment.getContent());
  914. }
  915. footerComments.clear();
  916. }
  917. gen.writeDSCComment(DSCConstants.PAGES, new Integer(this.currentPageNumber));
  918. new DSCCommentBoundingBox(this.documentBoundingBox).generate(gen);
  919. new DSCCommentHiResBoundingBox(this.documentBoundingBox).generate(gen);
  920. gen.getResourceTracker().writeResources(false, gen);
  921. gen.writeDSCComment(DSCConstants.EOF);
  922. gen.flush();
  923. log.debug("Rendering to PostScript complete.");
  924. if (isOptimizeResources()) {
  925. IOUtils.closeQuietly(gen.getOutputStream());
  926. rewritePostScriptFile();
  927. }
  928. if (footerComments != null) {
  929. headerComments.clear();
  930. }
  931. if (pageDeviceDictionary != null) {
  932. pageDeviceDictionary.clear();
  933. }
  934. }
  935. /**
  936. * Used for two-pass production. This will rewrite the PostScript file from the temporary
  937. * file while adding all needed resources.
  938. * @throws IOException In case of an I/O error.
  939. */
  940. private void rewritePostScriptFile() throws IOException {
  941. log.debug("Processing PostScript resources...");
  942. long startTime = System.currentTimeMillis();
  943. ResourceTracker resTracker = gen.getResourceTracker();
  944. InputStream in = new java.io.FileInputStream(this.tempFile);
  945. in = new java.io.BufferedInputStream(in);
  946. try {
  947. try {
  948. ResourceHandler.process(this.userAgent, in, this.outputStream,
  949. this.fontInfo, resTracker, this.formResources,
  950. this.currentPageNumber, this.documentBoundingBox);
  951. this.outputStream.flush();
  952. } catch (DSCException e) {
  953. throw new RuntimeException(e.getMessage());
  954. }
  955. } finally {
  956. IOUtils.closeQuietly(in);
  957. if (!this.tempFile.delete()) {
  958. this.tempFile.deleteOnExit();
  959. log.warn("Could not delete temporary file: " + this.tempFile);
  960. }
  961. }
  962. if (log.isDebugEnabled()) {
  963. long duration = System.currentTimeMillis() - startTime;
  964. log.debug("Resource Processing complete in " + duration + " ms.");
  965. }
  966. }
  967. /** {@inheritDoc} */
  968. public void processOffDocumentItem(OffDocumentItem oDI) {
  969. if (log.isDebugEnabled()) {
  970. log.debug("Handling OffDocumentItem: " + oDI.getName());
  971. }
  972. if (oDI instanceof OffDocumentExtensionAttachment) {
  973. ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)oDI).getAttachment();
  974. if (attachment != null) {
  975. if (PSExtensionAttachment.CATEGORY.equals(attachment.getCategory())) {
  976. if (attachment instanceof PSSetupCode) {
  977. if (setupCodeList == null) {
  978. setupCodeList = new java.util.ArrayList();
  979. }
  980. if (!setupCodeList.contains(attachment)) {
  981. setupCodeList.add(attachment);
  982. }
  983. } else if (attachment instanceof PSSetPageDevice) {
  984. /**
  985. * Extract all PSSetPageDevice instances from the
  986. * attachment list on the s-p-m and add all dictionary
  987. * entries to our internal representation of the the
  988. * page device dictionary.
  989. */
  990. PSSetPageDevice setPageDevice = (PSSetPageDevice)attachment;
  991. String content = setPageDevice.getContent();
  992. if (content != null) {
  993. try {
  994. this.pageDeviceDictionary.putAll(PSDictionary.valueOf(content));
  995. } catch (PSDictionaryFormatException e) {
  996. PSEventProducer eventProducer = PSEventProducer.Provider.get(
  997. getUserAgent().getEventBroadcaster());
  998. eventProducer.postscriptDictionaryParseError(this, content, e);
  999. }
  1000. }
  1001. } else if (attachment instanceof PSCommentBefore) {
  1002. if (headerComments == null) {
  1003. headerComments = new java.util.ArrayList();
  1004. }
  1005. headerComments.add(attachment);
  1006. } else if (attachment instanceof PSCommentAfter) {
  1007. if (footerComments == null) {
  1008. footerComments = new java.util.ArrayList();
  1009. }
  1010. footerComments.add(attachment);
  1011. }
  1012. }
  1013. }
  1014. }
  1015. super.processOffDocumentItem(oDI);
  1016. }
  1017. /**
  1018. * Formats and writes a List of PSSetupCode instances to the output stream.
  1019. * @param setupCodeList a List of PSSetupCode instances
  1020. * @param type the type of code section
  1021. */
  1022. private void writeSetupCodeList(List setupCodeList, String type) throws IOException {
  1023. if (setupCodeList != null) {
  1024. Iterator i = setupCodeList.iterator();
  1025. while (i.hasNext()) {
  1026. PSSetupCode setupCode = (PSSetupCode)i.next();
  1027. gen.commentln("%FOPBegin" + type + ": ("
  1028. + (setupCode.getName() != null ? setupCode.getName() : "")
  1029. + ")");
  1030. LineNumberReader reader = new LineNumberReader(
  1031. new java.io.StringReader(setupCode.getContent()));
  1032. String line;
  1033. while ((line = reader.readLine()) != null) {
  1034. line = line.trim();
  1035. if (line.length() > 0) {
  1036. gen.writeln(line.trim());
  1037. }
  1038. }
  1039. gen.commentln("%FOPEnd" + type);
  1040. i.remove();
  1041. }
  1042. }
  1043. }
  1044. /** {@inheritDoc} */
  1045. public void renderPage(PageViewport page)
  1046. throws IOException, FOPException {
  1047. log.debug("renderPage(): " + page);
  1048. if (this.currentPageNumber == 0) {
  1049. writeHeader();
  1050. }
  1051. this.currentPageNumber++;
  1052. gen.getResourceTracker().notifyStartNewPage();
  1053. gen.getResourceTracker().notifyResourceUsageOnPage(PSProcSets.STD_PROCSET);
  1054. gen.writeDSCComment(DSCConstants.PAGE, new Object[]
  1055. {page.getPageNumberString(),
  1056. new Integer(this.currentPageNumber)});
  1057. double pageWidth = Math.round(page.getViewArea().getWidth()) / 1000f;
  1058. double pageHeight = Math.round(page.getViewArea().getHeight()) / 1000f;
  1059. boolean rotate = false;
  1060. List pageSizes = new java.util.ArrayList();
  1061. if (this.autoRotateLandscape && (pageHeight < pageWidth)) {
  1062. rotate = true;
  1063. pageSizes.add(new Long(Math.round(pageHeight)));
  1064. pageSizes.add(new Long(Math.round(pageWidth)));
  1065. } else {
  1066. pageSizes.add(new Long(Math.round(pageWidth)));
  1067. pageSizes.add(new Long(Math.round(pageHeight)));
  1068. }
  1069. pageDeviceDictionary.put("/PageSize", pageSizes);
  1070. if (page.hasExtensionAttachments()) {
  1071. for (Iterator iter = page.getExtensionAttachments().iterator();
  1072. iter.hasNext();) {
  1073. ExtensionAttachment attachment = (ExtensionAttachment) iter.next();
  1074. if (attachment instanceof PSSetPageDevice) {
  1075. /**
  1076. * Extract all PSSetPageDevice instances from the
  1077. * attachment list on the s-p-m and add all
  1078. * dictionary entries to our internal representation
  1079. * of the the page device dictionary.
  1080. */
  1081. PSSetPageDevice setPageDevice = (PSSetPageDevice)attachment;
  1082. String content = setPageDevice.getContent();
  1083. if (content != null) {
  1084. try {
  1085. pageDeviceDictionary.putAll(PSDictionary.valueOf(content));
  1086. } catch (PSDictionaryFormatException e) {
  1087. PSEventProducer eventProducer = PSEventProducer.Provider.get(
  1088. getUserAgent().getEventBroadcaster());
  1089. eventProducer.postscriptDictionaryParseError(this, content, e);
  1090. }
  1091. }
  1092. }
  1093. }
  1094. }
  1095. try {
  1096. if (setupCodeList != null) {
  1097. writeEnclosedExtensionAttachments(setupCodeList);
  1098. setupCodeList.clear();
  1099. }
  1100. } catch (IOException e) {
  1101. log.error(e.getMessage());
  1102. }
  1103. final Integer zero = new Integer(0);
  1104. Rectangle2D pageBoundingBox = new Rectangle2D.Double();
  1105. if (rotate) {
  1106. pageBoundingBox.setRect(0, 0, pageHeight, pageWidth);
  1107. gen.writeDSCComment(DSCConstants.PAGE_BBOX, new Object[] {
  1108. zero, zero, new Long(Math.round(pageHeight)),
  1109. new Long(Math.round(pageWidth)) });
  1110. gen.writeDSCComment(DSCConstants.PAGE_HIRES_BBOX, new Object[] {
  1111. zero, zero, new Double(pageHeight),
  1112. new Double(pageWidth) });
  1113. gen.writeDSCComment(DSCConstants.PAGE_ORIENTATION, "Landscape");
  1114. } else {
  1115. pageBoundingBox.setRect(0, 0, pageWidth, pageHeight);
  1116. gen.writeDSCComment(DSCConstants.PAGE_BBOX, new Object[] {
  1117. zero, zero, new Long(Math.round(pageWidth)),
  1118. new Long(Math.round(pageHeight)) });
  1119. gen.writeDSCComment(DSCConstants.PAGE_HIRES_BBOX, new Object[] {
  1120. zero, zero, new Double(pageWidth),
  1121. new Double(pageHeight) });
  1122. if (autoRotateLandscape) {
  1123. gen.writeDSCComment(DSCConstants.PAGE_ORIENTATION,
  1124. "Portrait");
  1125. }
  1126. }
  1127. this.documentBoundingBox.add(pageBoundingBox);
  1128. gen.writeDSCComment(DSCConstants.PAGE_RESOURCES,
  1129. new Object[] {DSCConstants.ATEND});
  1130. gen.commentln("%FOPSimplePageMaster: " + page.getSimplePageMasterName());
  1131. gen.writeDSCComment(DSCConstants.BEGIN_PAGE_SETUP);
  1132. if (page.hasExtensionAttachments()) {
  1133. List extensionAttachments = page.getExtensionAttachments();
  1134. for (int i = 0; i < extensionAttachments.size(); i++) {
  1135. Object attObj = extensionAttachments.get(i);
  1136. if (attObj instanceof PSExtensionAttachment) {
  1137. PSExtensionAttachment attachment = (PSExtensionAttachment)attObj;
  1138. if (attachment instanceof PSCommentBefore) {
  1139. gen.commentln("%" + attachment.getContent());
  1140. } else if (attachment instanceof PSSetupCode) {
  1141. gen.writeln(attachment.getContent());
  1142. }
  1143. }
  1144. }
  1145. }
  1146. // Write any unwritten changes to page device dictionary
  1147. if (!pageDeviceDictionary.isEmpty()) {
  1148. String content = pageDeviceDictionary.getContent();
  1149. if (safeSetPageDevice) {
  1150. content += " SSPD";
  1151. } else {
  1152. content += " setpagedevice";
  1153. }
  1154. writeEnclosedExtensionAttachment(new PSSetPageDevice(content));
  1155. }
  1156. if (rotate) {
  1157. gen.writeln(Math.round(pageHeight) + " 0 translate");
  1158. gen.writeln("90 rotate");
  1159. }
  1160. concatMatrix(1, 0, 0, -1, 0, pageHeight);
  1161. gen.writeDSCComment(DSCConstants.END_PAGE_SETUP);
  1162. //Process page
  1163. super.renderPage(page);
  1164. //Show page
  1165. writeln("showpage");
  1166. gen.writeDSCComment(DSCConstants.PAGE_TRAILER);
  1167. if (page.hasExtensionAttachments()) {
  1168. List extensionAttachments = page.getExtensionAttachments();
  1169. for (int i = 0; i < extensionAttachments.size(); i++) {
  1170. Object attObj = extensionAttachments.get(i);
  1171. if (attObj instanceof PSExtensionAttachment) {
  1172. PSExtensionAttachment attachment = (PSExtensionAttachment)attObj;
  1173. if (attachment instanceof PSCommentAfter) {
  1174. gen.commentln("%" + attachment.getContent());
  1175. }
  1176. }
  1177. }
  1178. }
  1179. gen.getResourceTracker().writeResources(true, gen);
  1180. }
  1181. /** {@inheritDoc} */
  1182. protected void renderRegionViewport(RegionViewport port) {
  1183. if (port != null) {
  1184. comment("%FOPBeginRegionViewport: " + port.getRegionReference().getRegionName());
  1185. super.renderRegionViewport(port);
  1186. comment("%FOPEndRegionViewport");
  1187. }
  1188. }
  1189. /** Indicates the beginning of a text object. */
  1190. protected void beginTextObject() {
  1191. if (!inTextMode) {
  1192. saveGraphicsState();
  1193. writeln("BT");
  1194. inTextMode = true;
  1195. }
  1196. }
  1197. /** Indicates the end of a text object. */
  1198. protected void endTextObject() {
  1199. if (inTextMode) {
  1200. inTextMode = false; //set before restoreGraphicsState() to avoid recursion
  1201. writeln("ET");
  1202. restoreGraphicsState();
  1203. }
  1204. }
  1205. /** {@inheritDoc} */
  1206. public void renderText(TextArea area) {
  1207. renderInlineAreaBackAndBorders(area);
  1208. String fontkey = getInternalFontNameForArea(area);
  1209. int fontsize = area.getTraitAsInteger(Trait.FONT_SIZE);
  1210. // This assumes that *all* CIDFonts use a /ToUnicode mapping
  1211. Typeface tf = (Typeface) fontInfo.getFonts().get(fontkey);
  1212. //Determine position
  1213. int rx = currentIPPosition + area.getBorderAndPaddingWidthStart();
  1214. int bl = currentBPPosition + area.getOffset() + area.getBaselineOffset();
  1215. Color ct = (Color)area.getTrait(Trait.COLOR);
  1216. if (ct != null) {
  1217. try {
  1218. useColor(ct);
  1219. } catch (IOException ioe) {
  1220. handleIOTrouble(ioe);
  1221. }
  1222. }
  1223. beginTextObject();
  1224. writeln("1 0 0 -1 " + gen.formatDouble(rx / 1000f)
  1225. + " " + gen.formatDouble(bl / 1000f) + " Tm");
  1226. super.renderText(area); //Updates IPD
  1227. renderTextDecoration(tf, fontsize, area, bl, rx);
  1228. }
  1229. /** {@inheritDoc} */
  1230. protected void renderWord(WordArea word) {
  1231. renderText((TextArea)word.getParentArea(), word.getWord(), word.getLetterAdjustArray());
  1232. super.renderWord(word);
  1233. }
  1234. /** {@inheritDoc} */
  1235. protected void renderSpace(SpaceArea space) {
  1236. AbstractTextArea textArea = (AbstractTextArea)space.getParentArea();
  1237. String s = space.getSpace();
  1238. char sp = s.charAt(0);
  1239. Font font = getFontFromArea(textArea);
  1240. int tws = (space.isAdjustable()
  1241. ? ((TextArea) space.getParentArea()).getTextWordSpaceAdjust()
  1242. + 2 * textArea.getTextLetterSpaceAdjust()
  1243. : 0);
  1244. rmoveTo((font.getCharWidth(sp) + tws) / 1000f, 0);
  1245. super.renderSpace(space);
  1246. }
  1247. private Typeface getTypeface(String fontName) {
  1248. Typeface tf = (Typeface)fontInfo.getFonts().get(fontName);
  1249. if (tf instanceof LazyFont) {
  1250. tf = ((LazyFont)tf).getRealFont();
  1251. }
  1252. return tf;
  1253. }
  1254. private void renderText(AbstractTextArea area, String text, int[] letterAdjust) {
  1255. String fontkey = getInternalFontNameForArea(area);
  1256. int fontSize = area.getTraitAsInteger(Trait.FONT_SIZE);
  1257. Font font = getFontFromArea(area);
  1258. Typeface tf = getTypeface(font.getFontName());
  1259. SingleByteFont singleByteFont = null;
  1260. if (tf instanceof SingleByteFont) {
  1261. singleByteFont = (SingleByteFont)tf;
  1262. }
  1263. int textLen = text.length();
  1264. if (singleByteFont != null && singleByteFont.hasAdditionalEncodings()) {
  1265. int start = 0;
  1266. int currentEncoding = -1;
  1267. for (int i = 0; i < textLen; i++) {
  1268. char c = text.charAt(i);
  1269. char mapped = tf.mapChar(c);
  1270. int encoding = mapped / 256;
  1271. if (currentEncoding != encoding) {
  1272. if (i > 0) {
  1273. writeText(area, text, start, i - start, letterAdjust, fontSize, tf);
  1274. }
  1275. if (encoding == 0) {
  1276. useFont(fontkey, fontSize);
  1277. } else {
  1278. useFont(fontkey + "_" + Integer.toString(encoding), fontSize);
  1279. }
  1280. currentEncoding = encoding;
  1281. start = i;
  1282. }
  1283. }
  1284. writeText(area, text, start, textLen - start, letterAdjust, fontSize, tf);
  1285. } else {
  1286. useFont(fontkey, fontSize);
  1287. writeText(area, text, 0, textLen, letterAdjust, fontSize, tf);
  1288. }
  1289. }
  1290. private void writeText(AbstractTextArea area, String text, int start, int len,
  1291. int[] letterAdjust, int fontsize, Typeface tf) {
  1292. int end = start + len;
  1293. int initialSize = text.length();
  1294. initialSize += initialSize / 2;
  1295. StringBuffer sb = new StringBuffer(initialSize);
  1296. if (letterAdjust == null
  1297. && area.getTextLetterSpaceAdjust() == 0
  1298. && area.getTextWordSpaceAdjust() == 0) {
  1299. sb.append("(");
  1300. for (int i = start; i < end; i++) {
  1301. final char c = text.charAt(i);
  1302. final char mapped = (char)(tf.mapChar(c) % 256);
  1303. PSGenerator.escapeChar(mapped, sb);
  1304. }
  1305. sb.append(") t");
  1306. } else {
  1307. sb.append("(");
  1308. int[] offsets = new int[len];
  1309. for (int i = start; i < end; i++) {
  1310. final char c = text.charAt(i);
  1311. final char mapped = tf.mapChar(c);
  1312. char codepoint = (char)(mapped % 256);
  1313. int wordSpace;
  1314. if (CharUtilities.isAdjustableSpace(mapped)) {
  1315. wordSpace = area.getTextWordSpaceAdjust();
  1316. } else {
  1317. wordSpace = 0;
  1318. }
  1319. int cw = tf.getWidth(mapped, fontsize) / 1000;
  1320. int ladj = (letterAdjust != null && i < end - 1 ? letterAdjust[i + 1] : 0);
  1321. int tls = (i < end - 1 ? area.getTextLetterSpaceAdjust() : 0);
  1322. offsets[i - start] = cw + ladj + tls + wordSpace;
  1323. PSGenerator.escapeChar(codepoint, sb);
  1324. }
  1325. sb.append(")" + PSGenerator.LF + "[");
  1326. for (int i = 0; i < len; i++) {
  1327. if (i > 0) {
  1328. if (i % 8 == 0) {
  1329. sb.append(PSGenerator.LF);
  1330. } else {
  1331. sb.append(" ");
  1332. }
  1333. }
  1334. sb.append(gen.formatDouble(offsets[i] / 1000f));
  1335. }
  1336. sb.append("]" + PSGenerator.LF + "xshow");
  1337. }
  1338. writeln(sb.toString());
  1339. }
  1340. /** {@inheritDoc} */
  1341. protected List breakOutOfStateStack() {
  1342. try {
  1343. List breakOutList = new java.util.ArrayList();
  1344. PSState state;
  1345. while (true) {
  1346. if (breakOutList.size() == 0) {
  1347. endTextObject();
  1348. comment("------ break out!");
  1349. }
  1350. state = gen.getCurrentState();
  1351. if (!gen.restoreGraphicsState()) {
  1352. break;
  1353. }
  1354. breakOutList.add(0, state); //Insert because of stack-popping
  1355. }
  1356. return breakOutList;
  1357. } catch (IOException ioe) {
  1358. handleIOTrouble(ioe);
  1359. return null;
  1360. }
  1361. }
  1362. /** {@inheritDoc} */
  1363. protected void restoreStateStackAfterBreakOut(List breakOutList) {
  1364. try {
  1365. comment("------ restoring context after break-out...");
  1366. PSState state;
  1367. Iterator i = breakOutList.iterator();
  1368. while (i.hasNext()) {
  1369. state = (PSState)i.next();
  1370. saveGraphicsState();
  1371. state.reestablish(gen);
  1372. }
  1373. comment("------ done.");
  1374. } catch (IOException ioe) {
  1375. handleIOTrouble(ioe);
  1376. }
  1377. }
  1378. /**
  1379. * {@inheritDoc}
  1380. */
  1381. protected void startVParea(CTM ctm, Rectangle2D clippingRect) {
  1382. saveGraphicsState();
  1383. if (clippingRect != null) {
  1384. clipRect((float)clippingRect.getX() / 1000f,
  1385. (float)clippingRect.getY() / 1000f,
  1386. (float)clippingRect.getWidth() / 1000f,
  1387. (float)clippingRect.getHeight() / 1000f);
  1388. }
  1389. // multiply with current CTM
  1390. final double[] matrix = ctm.toArray();
  1391. matrix[4] /= 1000f;
  1392. matrix[5] /= 1000f;
  1393. concatMatrix(matrix);
  1394. }
  1395. /**
  1396. * {@inheritDoc}
  1397. */
  1398. protected void endVParea() {
  1399. restoreGraphicsState();
  1400. }
  1401. /** {@inheritDoc} */
  1402. protected void renderBlockViewport(BlockViewport bv, List children) {
  1403. comment("%FOPBeginBlockViewport: " + bv.toString());
  1404. super.renderBlockViewport(bv, children);
  1405. comment("%FOPEndBlockViewport");
  1406. }
  1407. /** {@inheritDoc} */
  1408. protected void renderInlineParent(InlineParent ip) {
  1409. super.renderInlineParent(ip);
  1410. }
  1411. /**
  1412. * {@inheritDoc}
  1413. */
  1414. public void renderLeader(Leader area) {
  1415. renderInlineAreaBackAndBorders(area);
  1416. endTextObject();
  1417. saveGraphicsState();
  1418. int style = area.getRuleStyle();
  1419. float startx = (currentIPPosition + area.getBorderAndPaddingWidthStart()) / 1000f;
  1420. float starty = (currentBPPosition + area.getOffset()) / 1000f;
  1421. float endx = (currentIPPosition + area.getBorderAndPaddingWidthStart()
  1422. + area.getIPD()) / 1000f;
  1423. float ruleThickness = area.getRuleThickness() / 1000f;
  1424. Color col = (Color)area.getTrait(Trait.COLOR);
  1425. try {
  1426. switch (style) {
  1427. case EN_SOLID:
  1428. case EN_DASHED:
  1429. case EN_DOUBLE:
  1430. drawBorderLine(startx, starty, endx, starty + ruleThickness,
  1431. true, true, style, col);
  1432. break;
  1433. case EN_DOTTED:
  1434. clipRect(startx, starty, endx - startx, ruleThickness);
  1435. //This displaces the dots to the right by half a dot's width
  1436. //TODO There's room for improvement here
  1437. gen.concatMatrix(1, 0, 0, 1, ruleThickness / 2, 0);
  1438. drawBorderLine(startx, starty, endx, starty + ruleThickness,
  1439. true, true, style, col);
  1440. break;
  1441. case EN_GROOVE:
  1442. case EN_RIDGE:
  1443. float half = area.getRuleThickness() / 2000f;
  1444. gen.useColor(ColorUtil.lightenColor(col, 0.6f));
  1445. moveTo(startx, starty);
  1446. lineTo(endx, starty);
  1447. lineTo(endx, starty + 2 * half);
  1448. lineTo(startx, starty + 2 * half);
  1449. closePath();
  1450. gen.writeln(" fill newpath");
  1451. gen.useColor(col);
  1452. if (style == EN_GROOVE) {
  1453. moveTo(startx, starty);
  1454. lineTo(endx, starty);
  1455. lineTo(endx, starty + half);
  1456. lineTo(startx + half, starty + half);
  1457. lineTo(startx, starty + 2 * half);
  1458. } else {
  1459. moveTo(endx, starty);
  1460. lineTo(endx, starty + 2 * half);
  1461. lineTo(startx, starty + 2 * half);
  1462. lineTo(startx, starty + half);
  1463. lineTo(endx - half, starty + half);
  1464. }
  1465. closePath();
  1466. gen.writeln(" fill newpath");
  1467. break;
  1468. default:
  1469. throw new UnsupportedOperationException("rule style not supported");
  1470. }
  1471. } catch (IOException ioe) {
  1472. handleIOTrouble(ioe);
  1473. }
  1474. restoreGraphicsState();
  1475. super.renderLeader(area);
  1476. }
  1477. /**
  1478. * {@inheritDoc}
  1479. */
  1480. public void renderImage(Image image, Rectangle2D pos) {
  1481. drawImage(image.getURL(), pos, image.getForeignAttributes());
  1482. }
  1483. /**
  1484. * {@inheritDoc}
  1485. */
  1486. protected RendererContext createRendererContext(int x, int y, int width, int height,
  1487. Map foreignAttributes) {
  1488. RendererContext context = super.createRendererContext(
  1489. x, y, width, height, foreignAttributes);
  1490. context.setProperty(PSRendererContextConstants.PS_GENERATOR, this.gen);
  1491. context.setProperty(PSRendererContextConstants.PS_FONT_INFO, fontInfo);
  1492. return context;
  1493. }
  1494. /** {@inheritDoc} */
  1495. public String getMimeType() {
  1496. return MIME_TYPE;
  1497. }
  1498. /**
  1499. * Formats and writes a PSExtensionAttachment to the output stream.
  1500. *
  1501. * @param attachment an PSExtensionAttachment instance
  1502. */
  1503. private void writeEnclosedExtensionAttachment(PSExtensionAttachment attachment)
  1504. throws IOException {
  1505. String info = "";
  1506. if (attachment instanceof PSSetupCode) {
  1507. PSSetupCode setupCodeAttach = (PSSetupCode)attachment;
  1508. String name = setupCodeAttach.getName();
  1509. if (name != null) {
  1510. info += ": (" + name + ")";
  1511. }
  1512. }
  1513. String type = attachment.getType();
  1514. gen.commentln("%FOPBegin" + type + info);
  1515. LineNumberReader reader = new LineNumberReader(
  1516. new java.io.StringReader(attachment.getContent()));
  1517. String line;
  1518. while ((line = reader.readLine()) != null) {
  1519. line = line.trim();
  1520. if (line.length() > 0) {
  1521. gen.writeln(line);
  1522. }
  1523. }
  1524. gen.commentln("%FOPEnd" + type);
  1525. }
  1526. /**
  1527. * Formats and writes a Collection of PSExtensionAttachment instances to
  1528. * the output stream.
  1529. *
  1530. * @param attachmentCollection
  1531. * a Collection of PSExtensionAttachment instances
  1532. */
  1533. private void writeEnclosedExtensionAttachments(Collection attachmentCollection)
  1534. throws IOException {
  1535. Iterator iter = attachmentCollection.iterator();
  1536. while (iter.hasNext()) {
  1537. PSExtensionAttachment attachment = (PSExtensionAttachment)iter
  1538. .next();
  1539. if (attachment != null) {
  1540. writeEnclosedExtensionAttachment(attachment);
  1541. }
  1542. iter.remove();
  1543. }
  1544. }
  1545. /**
  1546. * Sets whether or not the safe set page device macro should be used
  1547. * (as opposed to directly invoking setpagedevice) when setting the
  1548. * postscript page device.
  1549. *
  1550. * This option is a useful option when you want to guard against the possibility
  1551. * of invalid/unsupported postscript key/values being placed in the page device.
  1552. *
  1553. * @param safeSetPageDevice setting to false and the renderer will make a
  1554. * standard "setpagedevice" call, setting to true will make a safe set page
  1555. * device macro call (default is false).
  1556. */
  1557. public void setSafeSetPageDevice(boolean safeSetPageDevice) {
  1558. this.safeSetPageDevice = safeSetPageDevice;
  1559. }
  1560. /**
  1561. * Sets whether or not PostScript Document Structuring Conventions (dsc) compliance are
  1562. * enforced.
  1563. * <p>
  1564. * It can cause problems (unwanted PostScript subsystem initgraphics/erasepage calls)
  1565. * on some printers when the pagedevice is set. If this causes problems on a
  1566. * particular implementation then use this setting with a 'false' value to try and
  1567. * minimize the number of setpagedevice calls in the postscript document output.
  1568. * <p>
  1569. * Set this value to false if you experience unwanted blank pages in your
  1570. * postscript output.
  1571. * @param dscCompliant boolean value (default is true)
  1572. */
  1573. public void setDSCCompliant(boolean dscCompliant) {
  1574. this.dscCompliant = dscCompliant;
  1575. }
  1576. }