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.

PSRenderer.java 66KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715
  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.PSPageDeviceDictionary;
  56. import org.apache.xmlgraphics.ps.PSDictionaryFormatException;
  57. import org.apache.xmlgraphics.ps.PSGenerator;
  58. import org.apache.xmlgraphics.ps.PSImageUtils;
  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 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 = (float)x / 1000f;
  530. x += currentIPPosition / 1000f;
  531. float fy = (float)y / 1000f;
  532. y += currentBPPosition / 1000f;
  533. float fw = (float)width / 1000f;
  534. float fh = (float)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. }
  1141. }
  1142. }
  1143. }
  1144. // Write any unwritten changes to page device dictionary
  1145. if (!pageDeviceDictionary.isEmpty()) {
  1146. String content = pageDeviceDictionary.getContent();
  1147. if (safeSetPageDevice) {
  1148. content += " SSPD";
  1149. } else {
  1150. content += " setpagedevice";
  1151. }
  1152. writeEnclosedExtensionAttachment(new PSSetPageDevice(content));
  1153. }
  1154. if (rotate) {
  1155. gen.writeln(Math.round(pageHeight) + " 0 translate");
  1156. gen.writeln("90 rotate");
  1157. }
  1158. concatMatrix(1, 0, 0, -1, 0, pageHeight);
  1159. gen.writeDSCComment(DSCConstants.END_PAGE_SETUP);
  1160. //Process page
  1161. super.renderPage(page);
  1162. //Show page
  1163. writeln("showpage");
  1164. gen.writeDSCComment(DSCConstants.PAGE_TRAILER);
  1165. if (page.hasExtensionAttachments()) {
  1166. List extensionAttachments = page.getExtensionAttachments();
  1167. for (int i = 0; i < extensionAttachments.size(); i++) {
  1168. Object attObj = extensionAttachments.get(i);
  1169. if (attObj instanceof PSExtensionAttachment) {
  1170. PSExtensionAttachment attachment = (PSExtensionAttachment)attObj;
  1171. if (attachment instanceof PSCommentAfter) {
  1172. gen.commentln("%" + attachment.getContent());
  1173. }
  1174. }
  1175. }
  1176. }
  1177. gen.getResourceTracker().writeResources(true, gen);
  1178. }
  1179. /** {@inheritDoc} */
  1180. protected void renderRegionViewport(RegionViewport port) {
  1181. if (port != null) {
  1182. comment("%FOPBeginRegionViewport: " + port.getRegionReference().getRegionName());
  1183. super.renderRegionViewport(port);
  1184. comment("%FOPEndRegionViewport");
  1185. }
  1186. }
  1187. /** Indicates the beginning of a text object. */
  1188. protected void beginTextObject() {
  1189. if (!inTextMode) {
  1190. saveGraphicsState();
  1191. writeln("BT");
  1192. inTextMode = true;
  1193. }
  1194. }
  1195. /** Indicates the end of a text object. */
  1196. protected void endTextObject() {
  1197. if (inTextMode) {
  1198. inTextMode = false; //set before restoreGraphicsState() to avoid recursion
  1199. writeln("ET");
  1200. restoreGraphicsState();
  1201. }
  1202. }
  1203. /** {@inheritDoc} */
  1204. public void renderText(TextArea area) {
  1205. renderInlineAreaBackAndBorders(area);
  1206. String fontkey = getInternalFontNameForArea(area);
  1207. int fontsize = area.getTraitAsInteger(Trait.FONT_SIZE);
  1208. // This assumes that *all* CIDFonts use a /ToUnicode mapping
  1209. Typeface tf = (Typeface) fontInfo.getFonts().get(fontkey);
  1210. //Determine position
  1211. int rx = currentIPPosition + area.getBorderAndPaddingWidthStart();
  1212. int bl = currentBPPosition + area.getOffset() + area.getBaselineOffset();
  1213. Color ct = (Color)area.getTrait(Trait.COLOR);
  1214. if (ct != null) {
  1215. try {
  1216. useColor(ct);
  1217. } catch (IOException ioe) {
  1218. handleIOTrouble(ioe);
  1219. }
  1220. }
  1221. beginTextObject();
  1222. writeln("1 0 0 -1 " + gen.formatDouble(rx / 1000f)
  1223. + " " + gen.formatDouble(bl / 1000f) + " Tm");
  1224. super.renderText(area); //Updates IPD
  1225. renderTextDecoration(tf, fontsize, area, bl, rx);
  1226. }
  1227. /** {@inheritDoc} */
  1228. protected void renderWord(WordArea word) {
  1229. renderText((TextArea)word.getParentArea(), word.getWord(), word.getLetterAdjustArray());
  1230. super.renderWord(word);
  1231. }
  1232. /** {@inheritDoc} */
  1233. protected void renderSpace(SpaceArea space) {
  1234. AbstractTextArea textArea = (AbstractTextArea)space.getParentArea();
  1235. String s = space.getSpace();
  1236. char sp = s.charAt(0);
  1237. Font font = getFontFromArea(textArea);
  1238. int tws = (space.isAdjustable()
  1239. ? ((TextArea) space.getParentArea()).getTextWordSpaceAdjust()
  1240. + 2 * textArea.getTextLetterSpaceAdjust()
  1241. : 0);
  1242. rmoveTo((font.getCharWidth(sp) + tws) / 1000f, 0);
  1243. super.renderSpace(space);
  1244. }
  1245. private Typeface getTypeface(String fontName) {
  1246. Typeface tf = (Typeface)fontInfo.getFonts().get(fontName);
  1247. if (tf instanceof LazyFont) {
  1248. tf = ((LazyFont)tf).getRealFont();
  1249. }
  1250. return tf;
  1251. }
  1252. private void renderText(AbstractTextArea area, String text, int[] letterAdjust) {
  1253. String fontkey = getInternalFontNameForArea(area);
  1254. int fontSize = area.getTraitAsInteger(Trait.FONT_SIZE);
  1255. Font font = getFontFromArea(area);
  1256. Typeface tf = getTypeface(font.getFontName());
  1257. SingleByteFont singleByteFont = null;
  1258. if (tf instanceof SingleByteFont) {
  1259. singleByteFont = (SingleByteFont)tf;
  1260. }
  1261. int textLen = text.length();
  1262. if (singleByteFont != null && singleByteFont.hasAdditionalEncodings()) {
  1263. int start = 0;
  1264. int currentEncoding = -1;
  1265. for (int i = 0; i < textLen; i++) {
  1266. char c = text.charAt(i);
  1267. char mapped = tf.mapChar(c);
  1268. int encoding = mapped / 256;
  1269. if (currentEncoding != encoding) {
  1270. if (i > 0) {
  1271. writeText(area, text, start, i - start, letterAdjust, fontSize, tf);
  1272. }
  1273. if (encoding == 0) {
  1274. useFont(fontkey, fontSize);
  1275. } else {
  1276. useFont(fontkey + "_" + Integer.toString(encoding), fontSize);
  1277. }
  1278. currentEncoding = encoding;
  1279. start = i;
  1280. }
  1281. }
  1282. writeText(area, text, start, textLen - start, letterAdjust, fontSize, tf);
  1283. } else {
  1284. useFont(fontkey, fontSize);
  1285. writeText(area, text, 0, textLen, letterAdjust, fontSize, tf);
  1286. }
  1287. }
  1288. private void writeText(AbstractTextArea area, String text, int start, int len,
  1289. int[] letterAdjust, int fontsize, Typeface tf) {
  1290. int end = start + len;
  1291. int initialSize = text.length();
  1292. initialSize += initialSize / 2;
  1293. StringBuffer sb = new StringBuffer(initialSize);
  1294. if (letterAdjust == null
  1295. && area.getTextLetterSpaceAdjust() == 0
  1296. && area.getTextWordSpaceAdjust() == 0) {
  1297. sb.append("(");
  1298. for (int i = start; i < end; i++) {
  1299. final char c = text.charAt(i);
  1300. final char mapped = (char)(tf.mapChar(c) % 256);
  1301. PSGenerator.escapeChar(mapped, sb);
  1302. }
  1303. sb.append(") t");
  1304. } else {
  1305. sb.append("(");
  1306. int[] offsets = new int[len];
  1307. for (int i = start; i < end; i++) {
  1308. final char c = text.charAt(i);
  1309. final char mapped = tf.mapChar(c);
  1310. char codepoint = (char)(mapped % 256);
  1311. int wordSpace;
  1312. if (CharUtilities.isAdjustableSpace(mapped)) {
  1313. wordSpace = area.getTextWordSpaceAdjust();
  1314. } else {
  1315. wordSpace = 0;
  1316. }
  1317. int cw = tf.getWidth(mapped, fontsize) / 1000;
  1318. int ladj = (letterAdjust != null && i < end - 1 ? letterAdjust[i + 1] : 0);
  1319. int tls = (i < end - 1 ? area.getTextLetterSpaceAdjust() : 0);
  1320. offsets[i - start] = cw + ladj + tls + wordSpace;
  1321. PSGenerator.escapeChar(codepoint, sb);
  1322. }
  1323. sb.append(")" + PSGenerator.LF + "[");
  1324. for (int i = 0; i < len; i++) {
  1325. if (i > 0) {
  1326. if (i % 8 == 0) {
  1327. sb.append(PSGenerator.LF);
  1328. } else {
  1329. sb.append(" ");
  1330. }
  1331. }
  1332. sb.append(gen.formatDouble(offsets[i] / 1000f));
  1333. }
  1334. sb.append("]" + PSGenerator.LF + "xshow");
  1335. }
  1336. writeln(sb.toString());
  1337. }
  1338. /** {@inheritDoc} */
  1339. protected List breakOutOfStateStack() {
  1340. try {
  1341. List breakOutList = new java.util.ArrayList();
  1342. PSState state;
  1343. while (true) {
  1344. if (breakOutList.size() == 0) {
  1345. endTextObject();
  1346. comment("------ break out!");
  1347. }
  1348. state = gen.getCurrentState();
  1349. if (!gen.restoreGraphicsState()) {
  1350. break;
  1351. }
  1352. breakOutList.add(0, state); //Insert because of stack-popping
  1353. }
  1354. return breakOutList;
  1355. } catch (IOException ioe) {
  1356. handleIOTrouble(ioe);
  1357. return null;
  1358. }
  1359. }
  1360. /** {@inheritDoc} */
  1361. protected void restoreStateStackAfterBreakOut(List breakOutList) {
  1362. try {
  1363. comment("------ restoring context after break-out...");
  1364. PSState state;
  1365. Iterator i = breakOutList.iterator();
  1366. while (i.hasNext()) {
  1367. state = (PSState)i.next();
  1368. saveGraphicsState();
  1369. state.reestablish(gen);
  1370. }
  1371. comment("------ done.");
  1372. } catch (IOException ioe) {
  1373. handleIOTrouble(ioe);
  1374. }
  1375. }
  1376. /**
  1377. * {@inheritDoc}
  1378. */
  1379. protected void startVParea(CTM ctm, Rectangle2D clippingRect) {
  1380. saveGraphicsState();
  1381. if (clippingRect != null) {
  1382. clipRect((float)clippingRect.getX() / 1000f,
  1383. (float)clippingRect.getY() / 1000f,
  1384. (float)clippingRect.getWidth() / 1000f,
  1385. (float)clippingRect.getHeight() / 1000f);
  1386. }
  1387. // multiply with current CTM
  1388. final double[] matrix = ctm.toArray();
  1389. matrix[4] /= 1000f;
  1390. matrix[5] /= 1000f;
  1391. concatMatrix(matrix);
  1392. }
  1393. /**
  1394. * {@inheritDoc}
  1395. */
  1396. protected void endVParea() {
  1397. restoreGraphicsState();
  1398. }
  1399. /** {@inheritDoc} */
  1400. protected void renderBlockViewport(BlockViewport bv, List children) {
  1401. comment("%FOPBeginBlockViewport: " + bv.toString());
  1402. super.renderBlockViewport(bv, children);
  1403. comment("%FOPEndBlockViewport");
  1404. }
  1405. /** {@inheritDoc} */
  1406. protected void renderInlineParent(InlineParent ip) {
  1407. super.renderInlineParent(ip);
  1408. }
  1409. /**
  1410. * {@inheritDoc}
  1411. */
  1412. public void renderLeader(Leader area) {
  1413. renderInlineAreaBackAndBorders(area);
  1414. endTextObject();
  1415. saveGraphicsState();
  1416. int style = area.getRuleStyle();
  1417. float startx = (currentIPPosition + area.getBorderAndPaddingWidthStart()) / 1000f;
  1418. float starty = (currentBPPosition + area.getOffset()) / 1000f;
  1419. float endx = (currentIPPosition + area.getBorderAndPaddingWidthStart()
  1420. + area.getIPD()) / 1000f;
  1421. float ruleThickness = area.getRuleThickness() / 1000f;
  1422. Color col = (Color)area.getTrait(Trait.COLOR);
  1423. try {
  1424. switch (style) {
  1425. case EN_SOLID:
  1426. case EN_DASHED:
  1427. case EN_DOUBLE:
  1428. drawBorderLine(startx, starty, endx, starty + ruleThickness,
  1429. true, true, style, col);
  1430. break;
  1431. case EN_DOTTED:
  1432. clipRect(startx, starty, endx - startx, ruleThickness);
  1433. //This displaces the dots to the right by half a dot's width
  1434. //TODO There's room for improvement here
  1435. gen.concatMatrix(1, 0, 0, 1, ruleThickness / 2, 0);
  1436. drawBorderLine(startx, starty, endx, starty + ruleThickness,
  1437. true, true, style, col);
  1438. break;
  1439. case EN_GROOVE:
  1440. case EN_RIDGE:
  1441. float half = area.getRuleThickness() / 2000f;
  1442. gen.useColor(ColorUtil.lightenColor(col, 0.6f));
  1443. moveTo(startx, starty);
  1444. lineTo(endx, starty);
  1445. lineTo(endx, starty + 2 * half);
  1446. lineTo(startx, starty + 2 * half);
  1447. closePath();
  1448. gen.writeln(" fill newpath");
  1449. gen.useColor(col);
  1450. if (style == EN_GROOVE) {
  1451. moveTo(startx, starty);
  1452. lineTo(endx, starty);
  1453. lineTo(endx, starty + half);
  1454. lineTo(startx + half, starty + half);
  1455. lineTo(startx, starty + 2 * half);
  1456. } else {
  1457. moveTo(endx, starty);
  1458. lineTo(endx, starty + 2 * half);
  1459. lineTo(startx, starty + 2 * half);
  1460. lineTo(startx, starty + half);
  1461. lineTo(endx - half, starty + half);
  1462. }
  1463. closePath();
  1464. gen.writeln(" fill newpath");
  1465. break;
  1466. default:
  1467. throw new UnsupportedOperationException("rule style not supported");
  1468. }
  1469. } catch (IOException ioe) {
  1470. handleIOTrouble(ioe);
  1471. }
  1472. restoreGraphicsState();
  1473. super.renderLeader(area);
  1474. }
  1475. /**
  1476. * {@inheritDoc}
  1477. */
  1478. public void renderImage(Image image, Rectangle2D pos) {
  1479. drawImage(image.getURL(), pos);
  1480. }
  1481. /**
  1482. * {@inheritDoc}
  1483. */
  1484. protected RendererContext createRendererContext(int x, int y, int width, int height,
  1485. Map foreignAttributes) {
  1486. RendererContext context = super.createRendererContext(
  1487. x, y, width, height, foreignAttributes);
  1488. context.setProperty(PSRendererContextConstants.PS_GENERATOR, this.gen);
  1489. context.setProperty(PSRendererContextConstants.PS_FONT_INFO, fontInfo);
  1490. return context;
  1491. }
  1492. /** {@inheritDoc} */
  1493. public String getMimeType() {
  1494. return MIME_TYPE;
  1495. }
  1496. /**
  1497. * Formats and writes a PSExtensionAttachment to the output stream.
  1498. *
  1499. * @param attachment an PSExtensionAttachment instance
  1500. */
  1501. private void writeEnclosedExtensionAttachment(PSExtensionAttachment attachment)
  1502. throws IOException {
  1503. String info = "";
  1504. if (attachment instanceof PSSetupCode) {
  1505. PSSetupCode setupCodeAttach = (PSSetupCode)attachment;
  1506. String name = setupCodeAttach.getName();
  1507. if (name != null) {
  1508. info += ": (" + name + ")";
  1509. }
  1510. }
  1511. String type = attachment.getType();
  1512. gen.commentln("%FOPBegin" + type + info);
  1513. LineNumberReader reader = new LineNumberReader(
  1514. new java.io.StringReader(attachment.getContent()));
  1515. String line;
  1516. while ((line = reader.readLine()) != null) {
  1517. line = line.trim();
  1518. if (line.length() > 0) {
  1519. gen.writeln(line);
  1520. }
  1521. }
  1522. gen.commentln("%FOPEnd" + type);
  1523. }
  1524. /**
  1525. * Formats and writes a Collection of PSExtensionAttachment instances to
  1526. * the output stream.
  1527. *
  1528. * @param attachmentCollection
  1529. * a Collection of PSExtensionAttachment instances
  1530. */
  1531. private void writeEnclosedExtensionAttachments(Collection attachmentCollection)
  1532. throws IOException {
  1533. Iterator iter = attachmentCollection.iterator();
  1534. while (iter.hasNext()) {
  1535. PSExtensionAttachment attachment = (PSExtensionAttachment)iter
  1536. .next();
  1537. if (attachment != null) {
  1538. writeEnclosedExtensionAttachment(attachment);
  1539. }
  1540. iter.remove();
  1541. }
  1542. }
  1543. /**
  1544. * Sets whether or not the safe set page device macro should be used
  1545. * (as opposed to directly invoking setpagedevice) when setting the
  1546. * postscript page device.
  1547. *
  1548. * This option is a useful option when you want to guard against the possibility
  1549. * of invalid/unsupported postscript key/values being placed in the page device.
  1550. *
  1551. * @param safeSetPageDevice setting to false and the renderer will make a
  1552. * standard "setpagedevice" call, setting to true will make a safe set page
  1553. * device macro call (default is false).
  1554. */
  1555. public void setSafeSetPageDevice(boolean safeSetPageDevice) {
  1556. this.safeSetPageDevice = safeSetPageDevice;
  1557. }
  1558. /**
  1559. * Sets whether or not PostScript Document Structuring Conventions (dsc) compliance are
  1560. * enforced.
  1561. * <p>
  1562. * It can cause problems (unwanted PostScript subsystem initgraphics/erasepage calls)
  1563. * on some printers when the pagedevice is set. If this causes problems on a
  1564. * particular implementation then use this setting with a 'false' value to try and
  1565. * minimize the number of setpagedevice calls in the postscript document output.
  1566. * <p>
  1567. * Set this value to false if you experience unwanted blank pages in your
  1568. * postscript output.
  1569. * @param dscCompliant boolean value (default is true)
  1570. */
  1571. public void setDSCCompliant(boolean dscCompliant) {
  1572. this.dscCompliant = dscCompliant;
  1573. }
  1574. }