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.

AFPRenderer.java 66KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880
  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.afp;
  19. import java.awt.Color;
  20. import java.awt.Point;
  21. import java.awt.Rectangle;
  22. import java.awt.geom.AffineTransform;
  23. import java.awt.geom.Point2D;
  24. import java.awt.geom.Rectangle2D;
  25. import java.awt.image.RenderedImage;
  26. import java.io.FileNotFoundException;
  27. import java.io.IOException;
  28. import java.io.InputStream;
  29. import java.io.OutputStream;
  30. import java.io.UnsupportedEncodingException;
  31. import java.util.ArrayList;
  32. import java.util.HashMap;
  33. import java.util.Iterator;
  34. import java.util.List;
  35. import java.util.Map;
  36. import org.apache.commons.io.IOUtils;
  37. import org.apache.commons.io.output.ByteArrayOutputStream;
  38. import org.apache.xmlgraphics.image.codec.tiff.TIFFImage;
  39. import org.apache.xmlgraphics.image.loader.ImageException;
  40. import org.apache.xmlgraphics.image.loader.ImageFlavor;
  41. import org.apache.xmlgraphics.image.loader.ImageInfo;
  42. import org.apache.xmlgraphics.image.loader.ImageManager;
  43. import org.apache.xmlgraphics.image.loader.ImageSessionContext;
  44. import org.apache.xmlgraphics.image.loader.impl.ImageGraphics2D;
  45. import org.apache.xmlgraphics.image.loader.impl.ImageRawCCITTFax;
  46. import org.apache.xmlgraphics.image.loader.impl.ImageRendered;
  47. import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM;
  48. import org.apache.xmlgraphics.image.loader.util.ImageUtil;
  49. import org.apache.xmlgraphics.ps.ImageEncodingHelper;
  50. import org.apache.fop.apps.FOUserAgent;
  51. import org.apache.fop.apps.MimeConstants;
  52. import org.apache.fop.area.Block;
  53. import org.apache.fop.area.BlockViewport;
  54. import org.apache.fop.area.BodyRegion;
  55. import org.apache.fop.area.CTM;
  56. import org.apache.fop.area.NormalFlow;
  57. import org.apache.fop.area.OffDocumentItem;
  58. import org.apache.fop.area.PageViewport;
  59. import org.apache.fop.area.RegionReference;
  60. import org.apache.fop.area.RegionViewport;
  61. import org.apache.fop.area.Trait;
  62. import org.apache.fop.area.inline.Image;
  63. import org.apache.fop.area.inline.Leader;
  64. import org.apache.fop.area.inline.SpaceArea;
  65. import org.apache.fop.area.inline.TextArea;
  66. import org.apache.fop.area.inline.WordArea;
  67. import org.apache.fop.datatypes.URISpecification;
  68. import org.apache.fop.fo.Constants;
  69. import org.apache.fop.fo.extensions.ExtensionAttachment;
  70. import org.apache.fop.fonts.FontInfo;
  71. import org.apache.fop.fonts.FontTriplet;
  72. import org.apache.fop.fonts.base14.Courier;
  73. import org.apache.fop.fonts.base14.Helvetica;
  74. import org.apache.fop.fonts.base14.TimesRoman;
  75. import org.apache.fop.render.AbstractPathOrientedRenderer;
  76. import org.apache.fop.render.Graphics2DAdapter;
  77. import org.apache.fop.render.RendererContext;
  78. import org.apache.fop.render.afp.extensions.AFPElementMapping;
  79. import org.apache.fop.render.afp.extensions.AFPPageSetup;
  80. import org.apache.fop.render.afp.fonts.AFPFont;
  81. import org.apache.fop.render.afp.fonts.AFPFontInfo;
  82. import org.apache.fop.render.afp.fonts.CharacterSet;
  83. import org.apache.fop.render.afp.fonts.FopCharacterSet;
  84. import org.apache.fop.render.afp.fonts.OutlineFont;
  85. import org.apache.fop.render.afp.modca.AFPConstants;
  86. import org.apache.fop.render.afp.modca.AFPDataStream;
  87. import org.apache.fop.render.afp.modca.ImageObject;
  88. import org.apache.fop.render.afp.modca.PageObject;
  89. /**
  90. * This is an implementation of a FOP Renderer that renders areas to AFP.
  91. * <p>
  92. * A renderer is primarily designed to convert a given area tree into the output
  93. * document format. It should be able to produce pages and fill the pages with
  94. * the text and graphical content. Usually the output is sent to an output
  95. * stream. Some output formats may support extra information that is not
  96. * available from the area tree or depends on the destination of the document.
  97. * Each renderer is given an area tree to render to its output format. The area
  98. * tree is simply a representation of the pages and the placement of text and
  99. * graphical objects on those pages.
  100. * </p>
  101. * <p>
  102. * The renderer will be given each page as it is ready and an output stream to
  103. * write the data out. All pages are supplied in the order they appear in the
  104. * document. In order to save memory it is possible to render the pages out of
  105. * order. Any page that is not ready to be rendered is setup by the renderer
  106. * first so that it can reserve a space or reference for when the page is ready
  107. * to be rendered.The renderer is responsible for managing the output format and
  108. * associated data and flow.
  109. * </p>
  110. * <p>
  111. * Each renderer is totally responsible for its output format. Because font
  112. * metrics (and therefore layout) are obtained in two different ways depending
  113. * on the renderer, the renderer actually sets up the fonts being used. The font
  114. * metrics are used during the layout process to determine the size of
  115. * characters.
  116. * </p>
  117. * <p>
  118. * The render context is used by handlers. It contains information about the
  119. * current state of the renderer, such as the page, the position, and any other
  120. * miscellaneous objects that are required to draw into the page.
  121. * </p>
  122. * <p>
  123. * A renderer is created by implementing the Renderer interface. However, the
  124. * AbstractRenderer does most of what is needed, including iterating through the
  125. * tree parts, so it is this that is extended. This means that this object only
  126. * need to implement the basic functionality such as text, images, and lines.
  127. * AbstractRenderer's methods can easily be overridden to handle things in a
  128. * different way or do some extra processing.
  129. * </p>
  130. * <p>
  131. * The relevant AreaTree structures that will need to be rendered are Page,
  132. * Viewport, Region, Span, Block, Line, Inline. A renderer implementation
  133. * renders each individual page, clips and aligns child areas to a viewport,
  134. * handle all types of inline area, text, image etc and draws various lines and
  135. * rectangles.
  136. * </p>
  137. *
  138. * Note: There are specific extensions that have been added to the
  139. * FO. They are specific to their location within the FO and have to be
  140. * processed accordingly (ie. at the start or end of the page).
  141. *
  142. */
  143. public class AFPRenderer extends AbstractPathOrientedRenderer {
  144. /**
  145. * The default afp renderer output resolution
  146. */
  147. private static final int DEFAULT_DPI_RESOLUTION = 240;
  148. /**
  149. * The afp factor for calculating resolutions (e.g. 72000/240 = 300)
  150. */
  151. private static final int DPI_CONVERSION_FACTOR = 72000;
  152. /**
  153. * The afp data stream object responsible for generating afp data
  154. */
  155. private AFPDataStream afpDataStream = null;
  156. /**
  157. * The map of afp root extensions
  158. */
  159. // UNUSED
  160. // private HashMap rootExtensionMap = null;
  161. /**
  162. * The map of page segments
  163. */
  164. private HashMap pageSegmentsMap = null;
  165. /**
  166. * The fonts on the current page
  167. */
  168. private HashMap currentPageFonts = null;
  169. /**
  170. * The current color object
  171. */
  172. private Color currentColor = null;
  173. /**
  174. * The page font number counter, used to determine the next font reference
  175. */
  176. private int pageFontCounter = 0;
  177. /**
  178. * The current font family
  179. */
  180. // UNUSED
  181. // private String currentFontFamily = "";
  182. /**
  183. * The current font size
  184. */
  185. private int currentFontSize = 0;
  186. /**
  187. * The Options to be set on the AFPRenderer
  188. */
  189. // UNUSED
  190. // private Map afpOptions = null;
  191. /**
  192. * The page width
  193. */
  194. private int pageWidth = 0;
  195. /**
  196. * The page height
  197. */
  198. private int pageHeight = 0;
  199. /**
  200. * The current page sequence id
  201. */
  202. // UNUSED
  203. // private String pageSequenceId = null;
  204. /**
  205. * The portrait rotation
  206. */
  207. private int portraitRotation = 0;
  208. /**
  209. * The landscape rotation
  210. */
  211. private int landscapeRotation = 270;
  212. /**
  213. * The line cache, avoids drawing duplicate lines in tables.
  214. */
  215. // UNUSED
  216. // private HashSet lineCache = null;
  217. /**
  218. * The current x position for line drawing
  219. */
  220. // UNUSED
  221. // private float x;
  222. /**
  223. * The current y position for line drawing
  224. */
  225. // UNUSED
  226. // private float y;
  227. /**
  228. * The map of saved incomplete pages
  229. */
  230. private Map pages = null;
  231. /**
  232. * Flag to the set the output object type for images
  233. */
  234. private boolean colorImages = false;
  235. /**
  236. * Default value for image depth
  237. */
  238. private int bitsPerPixel = 8;
  239. /**
  240. * The output resolution
  241. */
  242. private int resolution = DEFAULT_DPI_RESOLUTION;
  243. /**
  244. * Constructor for AFPRenderer.
  245. */
  246. public AFPRenderer() {
  247. super();
  248. }
  249. /**
  250. * Set up the font info
  251. *
  252. * @param inFontInfo font info to set up
  253. */
  254. public void setupFontInfo(FontInfo inFontInfo) {
  255. this.fontInfo = inFontInfo;
  256. int num = 1;
  257. if (this.fontList != null && this.fontList.size() > 0) {
  258. for (Iterator it = this.fontList.iterator(); it.hasNext();) {
  259. AFPFontInfo afi = (AFPFontInfo)it.next();
  260. AFPFont bf = (AFPFont)afi.getAFPFont();
  261. for (Iterator it2 = afi.getFontTriplets().iterator(); it2.hasNext();) {
  262. FontTriplet ft = (FontTriplet)it2.next();
  263. this.fontInfo.addFontProperties("F" + num, ft.getName()
  264. , ft.getStyle(), ft.getWeight());
  265. this.fontInfo.addMetrics("F" + num, bf);
  266. num++;
  267. }
  268. }
  269. } else {
  270. log.warn("No AFP fonts configured - using default setup");
  271. }
  272. if (this.fontInfo.fontLookup("sans-serif", "normal", 400) == null) {
  273. CharacterSet cs = new FopCharacterSet("T1V10500", "Cp500", "CZH200 ",
  274. 1, new Helvetica());
  275. AFPFont bf = new OutlineFont("Helvetica", cs);
  276. this.fontInfo.addFontProperties("F" + num, "sans-serif", "normal", 400);
  277. this.fontInfo.addMetrics("F" + num, bf);
  278. num++;
  279. }
  280. if (this.fontInfo.fontLookup("serif", "normal", 400) == null) {
  281. CharacterSet cs = new FopCharacterSet("T1V10500", "Cp500", "CZN200 ",
  282. 1, new TimesRoman());
  283. AFPFont bf = new OutlineFont("Helvetica", cs);
  284. this.fontInfo.addFontProperties("F" + num, "serif", "normal", 400);
  285. this.fontInfo.addMetrics("F" + num, bf);
  286. num++;
  287. }
  288. if (this.fontInfo.fontLookup("monospace", "normal", 400) == null) {
  289. CharacterSet cs = new FopCharacterSet("T1V10500", "Cp500", "CZ4200 ",
  290. 1, new Courier());
  291. AFPFont bf = new OutlineFont("Helvetica", cs);
  292. this.fontInfo.addFontProperties("F" + num, "monospace", "normal", 400);
  293. this.fontInfo.addMetrics("F" + num, bf);
  294. num++;
  295. }
  296. if (this.fontInfo.fontLookup("any", "normal", 400) == null) {
  297. FontTriplet ft = this.fontInfo.fontLookup("sans-serif", "normal", 400);
  298. this.fontInfo.addFontProperties(
  299. this.fontInfo.getInternalFontKey(ft), "any", "normal", 400);
  300. }
  301. }
  302. /**
  303. * {@inheritDoc}
  304. */
  305. public void setUserAgent(FOUserAgent agent) {
  306. super.setUserAgent(agent);
  307. }
  308. /**
  309. * {@inheritDoc}
  310. */
  311. public void startRenderer(OutputStream outputStream) throws IOException {
  312. currentPageFonts = new HashMap();
  313. currentColor = new Color(255, 255, 255);
  314. afpDataStream = new AFPDataStream();
  315. afpDataStream.setPortraitRotation(portraitRotation);
  316. afpDataStream.setLandscapeRotation(landscapeRotation);
  317. afpDataStream.startDocument(outputStream);
  318. }
  319. /**
  320. * {@inheritDoc}
  321. */
  322. public void stopRenderer() throws IOException {
  323. afpDataStream.endDocument();
  324. }
  325. /**
  326. * {@inheritDoc}
  327. */
  328. public boolean supportsOutOfOrder() {
  329. //return false;
  330. return true;
  331. }
  332. /**
  333. * Prepare a page for rendering. This is called if the renderer supports
  334. * out of order rendering. The renderer should prepare the page so that a
  335. * page further on in the set of pages can be rendered. The body of the
  336. * page should not be rendered. The page will be rendered at a later time
  337. * by the call to render page.
  338. *
  339. * {@inheritDoc}
  340. */
  341. public void preparePage(PageViewport page) {
  342. // initializeRootExtensions(page);
  343. // this.currentFontFamily = "";
  344. this.currentFontSize = 0;
  345. this.pageFontCounter = 0;
  346. this.currentPageFonts.clear();
  347. // this.lineCache = new HashSet();
  348. Rectangle2D bounds = page.getViewArea();
  349. this.pageWidth = mpts2units(bounds.getWidth());
  350. this.pageHeight = mpts2units(bounds.getHeight());
  351. // renderPageGroupExtensions(page);
  352. final int pageRotation = 0;
  353. this.afpDataStream.startPage(pageWidth, pageHeight, pageRotation,
  354. getResolution(), getResolution());
  355. renderPageObjectExtensions(page);
  356. if (this.pages == null) {
  357. this.pages = new HashMap();
  358. }
  359. this.pages.put(page, afpDataStream.savePage());
  360. }
  361. /**
  362. * {@inheritDoc}
  363. */
  364. public void processOffDocumentItem(OffDocumentItem odi) {
  365. // TODO
  366. }
  367. /** {@inheritDoc} */
  368. public Graphics2DAdapter getGraphics2DAdapter() {
  369. return new AFPGraphics2DAdapter();
  370. }
  371. /**
  372. * {@inheritDoc}
  373. */
  374. public void startVParea(CTM ctm, Rectangle2D clippingRect) {
  375. // dummy not used
  376. }
  377. /**
  378. * {@inheritDoc}
  379. */
  380. public void endVParea() {
  381. // dummy not used
  382. }
  383. /**
  384. * Renders a region viewport. <p>
  385. *
  386. * The region may clip the area and it establishes a position from where
  387. * the region is placed.</p>
  388. *
  389. * @param port The region viewport to be rendered
  390. */
  391. public void renderRegionViewport(RegionViewport port) {
  392. if (port != null) {
  393. Rectangle2D view = port.getViewArea();
  394. // The CTM will transform coordinates relative to
  395. // this region-reference area into page coords, so
  396. // set origin for the region to 0,0.
  397. currentBPPosition = 0;
  398. currentIPPosition = 0;
  399. RegionReference regionReference = port.getRegionReference();
  400. handleRegionTraits(port);
  401. /*
  402. _afpDataStream.startOverlay(mpts2units(view.getX())
  403. , mpts2units(view.getY())
  404. , mpts2units(view.getWidth())
  405. , mpts2units(view.getHeight())
  406. , rotation);
  407. */
  408. pushViewPortPos(new ViewPortPos(view, regionReference.getCTM()));
  409. if (regionReference.getRegionClass() == FO_REGION_BODY) {
  410. renderBodyRegion((BodyRegion) regionReference);
  411. } else {
  412. renderRegion(regionReference);
  413. }
  414. /*
  415. _afpDataStream.endOverlay();
  416. */
  417. popViewPortPos();
  418. }
  419. }
  420. /** {@inheritDoc} */
  421. protected void renderBlockViewport(BlockViewport bv, List children) {
  422. // clip and position viewport if necessary
  423. // save positions
  424. int saveIP = currentIPPosition;
  425. int saveBP = currentBPPosition;
  426. CTM ctm = bv.getCTM();
  427. int borderPaddingStart = bv.getBorderAndPaddingWidthStart();
  428. int borderPaddingBefore = bv.getBorderAndPaddingWidthBefore();
  429. //This is the content-rect
  430. float width = (float)bv.getIPD() / 1000f;
  431. float height = (float)bv.getBPD() / 1000f;
  432. if (bv.getPositioning() == Block.ABSOLUTE
  433. || bv.getPositioning() == Block.FIXED) {
  434. //For FIXED, we need to break out of the current viewports to the
  435. //one established by the page. We save the state stack for restoration
  436. //after the block-container has been painted. See below.
  437. List breakOutList = null;
  438. if (bv.getPositioning() == Block.FIXED) {
  439. breakOutList = breakOutOfStateStack();
  440. }
  441. AffineTransform positionTransform = new AffineTransform();
  442. positionTransform.translate(bv.getXOffset(), bv.getYOffset());
  443. //"left/"top" (bv.getX/YOffset()) specify the position of the content rectangle
  444. positionTransform.translate(-borderPaddingStart, -borderPaddingBefore);
  445. //skipping fox:transform here
  446. //saveGraphicsState();
  447. //Viewport position
  448. //concatenateTransformationMatrix(mptToPt(positionTransform));
  449. //Background and borders
  450. float bpwidth = (borderPaddingStart + bv.getBorderAndPaddingWidthEnd()) / 1000f;
  451. float bpheight = (borderPaddingBefore + bv.getBorderAndPaddingWidthAfter()) / 1000f;
  452. Point2D ptSrc = new Point(0, 0);
  453. Point2D ptDst = positionTransform.transform(ptSrc, null);
  454. Rectangle2D borderRect = new Rectangle2D.Double(ptDst.getX(), ptDst.getY(),
  455. 1000 * (width + bpwidth), 1000 * (height + bpheight));
  456. pushViewPortPos(new ViewPortPos(borderRect, new CTM(positionTransform)));
  457. drawBackAndBorders(bv, 0, 0, width + bpwidth, height + bpheight);
  458. //Shift to content rectangle after border painting
  459. AffineTransform contentRectTransform = new AffineTransform();
  460. contentRectTransform.translate(borderPaddingStart, borderPaddingBefore);
  461. //concatenateTransformationMatrix(mptToPt(contentRectTransform));
  462. ptSrc = new Point(0, 0);
  463. ptDst = contentRectTransform.transform(ptSrc, null);
  464. Rectangle2D contentRect = new Rectangle2D.Double(ptDst.getX(), ptDst.getY(),
  465. 1000 * width, 1000 * height);
  466. pushViewPortPos(new ViewPortPos(contentRect, new CTM(contentRectTransform)));
  467. //Clipping is not supported, yet
  468. //Rectangle2D clippingRect = null;
  469. //clippingRect = new Rectangle(0, 0, bv.getIPD(), bv.getBPD());
  470. //saveGraphicsState();
  471. //Set up coordinate system for content rectangle
  472. AffineTransform contentTransform = ctm.toAffineTransform();
  473. //concatenateTransformationMatrix(mptToPt(contentTransform));
  474. contentRect = new Rectangle2D.Double(0, 0, 1000 * width, 1000 * height);
  475. pushViewPortPos(new ViewPortPos(contentRect, new CTM(contentTransform)));
  476. currentIPPosition = 0;
  477. currentBPPosition = 0;
  478. renderBlocks(bv, children);
  479. popViewPortPos();
  480. popViewPortPos();
  481. //restoreGraphicsState();
  482. popViewPortPos();
  483. //restoreGraphicsState();
  484. if (breakOutList != null) {
  485. restoreStateStackAfterBreakOut(breakOutList);
  486. }
  487. currentIPPosition = saveIP;
  488. currentBPPosition = saveBP;
  489. } else {
  490. currentBPPosition += bv.getSpaceBefore();
  491. //borders and background in the old coordinate system
  492. handleBlockTraits(bv);
  493. //Advance to start of content area
  494. currentIPPosition += bv.getStartIndent();
  495. CTM tempctm = new CTM(containingIPPosition, currentBPPosition);
  496. ctm = tempctm.multiply(ctm);
  497. //Now adjust for border/padding
  498. currentBPPosition += borderPaddingBefore;
  499. Rectangle2D clippingRect = null;
  500. clippingRect = new Rectangle(currentIPPosition, currentBPPosition,
  501. bv.getIPD(), bv.getBPD());
  502. //startVParea(ctm, clippingRect);
  503. pushViewPortPos(new ViewPortPos(clippingRect, ctm));
  504. currentIPPosition = 0;
  505. currentBPPosition = 0;
  506. renderBlocks(bv, children);
  507. //endVParea();
  508. popViewPortPos();
  509. currentIPPosition = saveIP;
  510. currentBPPosition = saveBP;
  511. currentBPPosition += (int)(bv.getAllocBPD());
  512. }
  513. }
  514. /** {@inheritDoc} */
  515. protected void renderReferenceArea(Block block) {
  516. //TODO Remove this method once concatenateTransformationMatrix() is implemented
  517. // save position and offset
  518. int saveIP = currentIPPosition;
  519. int saveBP = currentBPPosition;
  520. //Establish a new coordinate system
  521. AffineTransform at = new AffineTransform();
  522. at.translate(currentIPPosition, currentBPPosition);
  523. at.translate(block.getXOffset(), block.getYOffset());
  524. at.translate(0, block.getSpaceBefore());
  525. if (!at.isIdentity()) {
  526. Rectangle2D contentRect
  527. = new Rectangle2D.Double(at.getTranslateX(), at.getTranslateY(),
  528. block.getAllocIPD(), block.getAllocBPD());
  529. pushViewPortPos(new ViewPortPos(contentRect, new CTM(at)));
  530. }
  531. currentIPPosition = 0;
  532. currentBPPosition = 0;
  533. handleBlockTraits(block);
  534. List children = block.getChildAreas();
  535. if (children != null) {
  536. renderBlocks(block, children);
  537. }
  538. if (!at.isIdentity()) {
  539. popViewPortPos();
  540. }
  541. // stacked and relative blocks effect stacking
  542. currentIPPosition = saveIP;
  543. currentBPPosition = saveBP;
  544. }
  545. /** {@inheritDoc} */
  546. protected void renderFlow(NormalFlow flow) {
  547. // save position and offset
  548. int saveIP = currentIPPosition;
  549. int saveBP = currentBPPosition;
  550. //Establish a new coordinate system
  551. AffineTransform at = new AffineTransform();
  552. at.translate(currentIPPosition, currentBPPosition);
  553. if (!at.isIdentity()) {
  554. Rectangle2D contentRect
  555. = new Rectangle2D.Double(at.getTranslateX(), at.getTranslateY(),
  556. flow.getAllocIPD(), flow.getAllocBPD());
  557. pushViewPortPos(new ViewPortPos(contentRect, new CTM(at)));
  558. }
  559. currentIPPosition = 0;
  560. currentBPPosition = 0;
  561. super.renderFlow(flow);
  562. if (!at.isIdentity()) {
  563. popViewPortPos();
  564. }
  565. // stacked and relative blocks effect stacking
  566. currentIPPosition = saveIP;
  567. currentBPPosition = saveBP;
  568. }
  569. /** {@inheritDoc} */
  570. protected void concatenateTransformationMatrix(AffineTransform at) {
  571. //Not used here since AFPRenderer defines its own renderBlockViewport() method.
  572. throw new UnsupportedOperationException("NYI");
  573. }
  574. /**
  575. * {@inheritDoc}
  576. */
  577. public void renderPage(PageViewport pageViewport) {
  578. // initializeRootExtensions(page);
  579. // this.currentFontFamily = "";
  580. this.currentFontSize = 0;
  581. this.pageFontCounter = 0;
  582. this.currentPageFonts.clear();
  583. // this.lineCache = new HashSet();
  584. Rectangle2D bounds = pageViewport.getViewArea();
  585. this.pageWidth = mpts2units(bounds.getWidth());
  586. this.pageHeight = mpts2units(bounds.getHeight());
  587. if (pages != null && pages.containsKey(pageViewport)) {
  588. this.afpDataStream.restorePage((PageObject) pages.remove(pageViewport));
  589. } else {
  590. // renderPageGroupExtensions(page);
  591. final int pageRotation = 0;
  592. this.afpDataStream.startPage(pageWidth, pageHeight, pageRotation,
  593. getResolution(), getResolution());
  594. renderPageObjectExtensions(pageViewport);
  595. }
  596. pushViewPortPos(new ViewPortPos());
  597. renderPageAreas(pageViewport.getPage());
  598. Iterator i = currentPageFonts.values().iterator();
  599. while (i.hasNext()) {
  600. AFPFontAttributes afpFontAttributes = (AFPFontAttributes) i.next();
  601. afpDataStream.createFont(
  602. (byte)afpFontAttributes.getFontReference(),
  603. afpFontAttributes.getFont(),
  604. afpFontAttributes.getPointSize());
  605. }
  606. try {
  607. afpDataStream.endPage();
  608. } catch (IOException ioex) {
  609. // TODO What shall we do?
  610. }
  611. popViewPortPos();
  612. }
  613. /**
  614. * {@inheritDoc}
  615. */
  616. public void clip() {
  617. // TODO
  618. }
  619. /**
  620. * {@inheritDoc}
  621. */
  622. public void clipRect(float x, float y, float width, float height) {
  623. // TODO
  624. }
  625. /**
  626. * {@inheritDoc}
  627. */
  628. public void moveTo(float x, float y) {
  629. // TODO
  630. }
  631. /**
  632. * {@inheritDoc}
  633. */
  634. public void lineTo(float x, float y) {
  635. // TODO
  636. }
  637. /**
  638. * {@inheritDoc}
  639. */
  640. public void closePath() {
  641. // TODO
  642. }
  643. /**
  644. * {@inheritDoc}
  645. */
  646. public void fillRect(float x, float y, float width, float height) {
  647. /*
  648. afpDataStream.createShading(
  649. pts2units(x),
  650. pts2units(y),
  651. pts2units(width),
  652. pts2units(height),
  653. currentColor.getRed(),
  654. currentColor.getGreen(),
  655. currentColor.getBlue());
  656. */
  657. afpDataStream.createLine(
  658. pts2units(x),
  659. pts2units(y),
  660. pts2units(x + width),
  661. pts2units(y),
  662. pts2units(height),
  663. currentColor);
  664. }
  665. /**
  666. * {@inheritDoc}
  667. */
  668. public void drawBorderLine(float x1, float y1, float x2, float y2,
  669. boolean horz, boolean startOrBefore, int style, Color col) {
  670. float w = x2 - x1;
  671. float h = y2 - y1;
  672. if ((w < 0) || (h < 0)) {
  673. log.error("Negative extent received. Border won't be painted.");
  674. return;
  675. }
  676. switch (style) {
  677. case Constants.EN_DOUBLE:
  678. if (horz) {
  679. float h3 = h / 3;
  680. float ym1 = y1;
  681. float ym2 = ym1 + h3 + h3;
  682. afpDataStream.createLine(
  683. pts2units(x1),
  684. pts2units(ym1),
  685. pts2units(x2),
  686. pts2units(ym1),
  687. pts2units(h3),
  688. col
  689. );
  690. afpDataStream.createLine(
  691. pts2units(x1),
  692. pts2units(ym2),
  693. pts2units(x2),
  694. pts2units(ym2),
  695. pts2units(h3),
  696. col
  697. );
  698. } else {
  699. float w3 = w / 3;
  700. float xm1 = x1;
  701. float xm2 = xm1 + w3 + w3;
  702. afpDataStream.createLine(
  703. pts2units(xm1),
  704. pts2units(y1),
  705. pts2units(xm1),
  706. pts2units(y2),
  707. pts2units(w3),
  708. col
  709. );
  710. afpDataStream.createLine(
  711. pts2units(xm2),
  712. pts2units(y1),
  713. pts2units(xm2),
  714. pts2units(y2),
  715. pts2units(w3),
  716. col
  717. );
  718. }
  719. break;
  720. case Constants.EN_DASHED:
  721. if (horz) {
  722. float w2 = 2 * h;
  723. while (x1 + w2 < x2) {
  724. afpDataStream.createLine(
  725. pts2units(x1),
  726. pts2units(y1),
  727. pts2units(x1 + w2),
  728. pts2units(y1),
  729. pts2units(h),
  730. col
  731. );
  732. x1 += 2 * w2;
  733. }
  734. } else {
  735. float h2 = 2 * w;
  736. while (y1 + h2 < y2) {
  737. afpDataStream.createLine(
  738. pts2units(x1),
  739. pts2units(y1),
  740. pts2units(x1),
  741. pts2units(y1 + h2),
  742. pts2units(w),
  743. col
  744. );
  745. y1 += 2 * h2;
  746. }
  747. }
  748. break;
  749. case Constants.EN_DOTTED:
  750. if (horz) {
  751. while (x1 + h < x2) {
  752. afpDataStream.createLine(
  753. pts2units(x1),
  754. pts2units(y1),
  755. pts2units(x1 + h),
  756. pts2units(y1),
  757. pts2units(h),
  758. col
  759. );
  760. x1 += 2 * h;
  761. }
  762. } else {
  763. while (y1 + w < y2) {
  764. afpDataStream.createLine(
  765. pts2units(x1),
  766. pts2units(y1),
  767. pts2units(x1),
  768. pts2units(y1 + w),
  769. pts2units(w),
  770. col
  771. );
  772. y1 += 2 * w;
  773. }
  774. }
  775. break;
  776. case Constants.EN_GROOVE:
  777. case Constants.EN_RIDGE:
  778. {
  779. float colFactor = (style == EN_GROOVE ? 0.4f : -0.4f);
  780. if (horz) {
  781. Color uppercol = lightenColor(col, -colFactor);
  782. Color lowercol = lightenColor(col, colFactor);
  783. float h3 = h / 3;
  784. float ym1 = y1;
  785. afpDataStream.createLine(
  786. pts2units(x1),
  787. pts2units(ym1),
  788. pts2units(x2),
  789. pts2units(ym1),
  790. pts2units(h3),
  791. uppercol
  792. );
  793. afpDataStream.createLine(
  794. pts2units(x1),
  795. pts2units(ym1 + h3),
  796. pts2units(x2),
  797. pts2units(ym1 + h3),
  798. pts2units(h3),
  799. col
  800. );
  801. afpDataStream.createLine(
  802. pts2units(x1),
  803. pts2units(ym1 + h3 + h3),
  804. pts2units(x2),
  805. pts2units(ym1 + h3 + h3),
  806. pts2units(h3),
  807. lowercol
  808. );
  809. } else {
  810. Color leftcol = lightenColor(col, -colFactor);
  811. Color rightcol = lightenColor(col, colFactor);
  812. float w3 = w / 3;
  813. float xm1 = x1 + (w3 / 2);
  814. afpDataStream.createLine(
  815. pts2units(xm1),
  816. pts2units(y1),
  817. pts2units(xm1),
  818. pts2units(y2),
  819. pts2units(w3),
  820. leftcol
  821. );
  822. afpDataStream.createLine(
  823. pts2units(xm1 + w3),
  824. pts2units(y1),
  825. pts2units(xm1 + w3),
  826. pts2units(y2),
  827. pts2units(w3),
  828. col
  829. );
  830. afpDataStream.createLine(
  831. pts2units(xm1 + w3 + w3),
  832. pts2units(y1),
  833. pts2units(xm1 + w3 + w3),
  834. pts2units(y2),
  835. pts2units(w3),
  836. rightcol
  837. );
  838. }
  839. break;
  840. }
  841. case Constants.EN_HIDDEN:
  842. break;
  843. case Constants.EN_INSET:
  844. case Constants.EN_OUTSET:
  845. default:
  846. afpDataStream.createLine(
  847. pts2units(x1),
  848. pts2units(y1),
  849. pts2units(horz ? x2 : x1),
  850. pts2units(horz ? y1 : y2),
  851. pts2units(Math.abs(horz ? (y2 - y1) : (x2 - x1))),
  852. col
  853. );
  854. }
  855. }
  856. /**
  857. * {@inheritDoc}
  858. */
  859. protected RendererContext createRendererContext(int x, int y, int width, int height,
  860. Map foreignAttributes) {
  861. RendererContext context;
  862. context = super.createRendererContext(x, y, width, height, foreignAttributes);
  863. context.setProperty(AFPRendererContextConstants.AFP_GRAYSCALE,
  864. new Boolean(!this.colorImages));
  865. return context;
  866. }
  867. private static final ImageFlavor[] FLAVORS = new ImageFlavor[]
  868. {ImageFlavor.RAW_CCITTFAX,
  869. ImageFlavor.GRAPHICS2D,
  870. ImageFlavor.BUFFERED_IMAGE,
  871. ImageFlavor.RENDERED_IMAGE,
  872. ImageFlavor.XML_DOM};
  873. /** {@inheritDoc} */
  874. public void drawImage(String uri, Rectangle2D pos, Map foreignAttributes) {
  875. uri = URISpecification.getURL(uri);
  876. Rectangle posInt = new Rectangle(
  877. (int)pos.getX(),
  878. (int)pos.getY(),
  879. (int)pos.getWidth(),
  880. (int)pos.getHeight());
  881. Point origin = new Point(currentIPPosition, currentBPPosition);
  882. int x = origin.x + posInt.x;
  883. int y = origin.y + posInt.y;
  884. String name = null;
  885. if (pageSegmentsMap != null) {
  886. name = (String) pageSegmentsMap.get(uri);
  887. }
  888. if (name != null) {
  889. afpDataStream.createIncludePageSegment(name, mpts2units(x), mpts2units(y));
  890. } else {
  891. ImageManager manager = getUserAgent().getFactory().getImageManager();
  892. ImageInfo info = null;
  893. try {
  894. ImageSessionContext sessionContext = getUserAgent().getImageSessionContext();
  895. info = manager.getImageInfo(uri, sessionContext);
  896. //Only now fully load/prepare the image
  897. Map hints = ImageUtil.getDefaultHints(sessionContext);
  898. org.apache.xmlgraphics.image.loader.Image img = manager.getImage(
  899. info, FLAVORS, hints, sessionContext);
  900. //...and process the image
  901. if (img instanceof ImageGraphics2D) {
  902. ImageGraphics2D imageG2D = (ImageGraphics2D)img;
  903. RendererContext context = createRendererContext(
  904. posInt.x, posInt.y,
  905. posInt.width, posInt.height, foreignAttributes);
  906. getGraphics2DAdapter().paintImage(imageG2D.getGraphics2DImagePainter(),
  907. context,
  908. origin.x + posInt.x, origin.y + posInt.y,
  909. posInt.width, posInt.height);
  910. } else if (img instanceof ImageRendered) {
  911. ImageRendered imgRend = (ImageRendered)img;
  912. RenderedImage ri = imgRend.getRenderedImage();
  913. drawBufferedImage(ri, getResolution(),
  914. posInt.x + currentIPPosition,
  915. posInt.y + currentBPPosition,
  916. posInt.width,
  917. posInt.height);
  918. } else if (img instanceof ImageRawCCITTFax) {
  919. ImageRawCCITTFax ccitt = (ImageRawCCITTFax)img;
  920. int afpx = mpts2units(posInt.x + currentIPPosition);
  921. int afpy = mpts2units(posInt.y + currentBPPosition);
  922. int afpw = mpts2units(posInt.getWidth());
  923. int afph = mpts2units(posInt.getHeight());
  924. int afpres = getResolution();
  925. ImageObject io = afpDataStream.getImageObject(afpx, afpy, afpw, afph,
  926. afpres, afpres);
  927. io.setImageParameters(
  928. (int) (ccitt.getSize().getDpiHorizontal() * 10),
  929. (int) (ccitt.getSize().getDpiVertical() * 10),
  930. ccitt.getSize().getWidthPx(),
  931. ccitt.getSize().getHeightPx());
  932. int compression = ccitt.getCompression();
  933. switch (compression) {
  934. case TIFFImage.COMP_FAX_G3_1D :
  935. io.setImageEncoding((byte) 0x80);
  936. break;
  937. case TIFFImage.COMP_FAX_G3_2D :
  938. io.setImageEncoding((byte) 0x81);
  939. break;
  940. case TIFFImage.COMP_FAX_G4_2D :
  941. io.setImageEncoding((byte) 0x82);
  942. break;
  943. default:
  944. throw new IllegalStateException(
  945. "Invalid compression scheme: " + compression);
  946. }
  947. InputStream in = ccitt.createInputStream();
  948. try {
  949. byte[] buf = IOUtils.toByteArray(in);
  950. io.setImageData(buf);
  951. } finally {
  952. IOUtils.closeQuietly(in);
  953. }
  954. } else if (img instanceof ImageXMLDOM) {
  955. ImageXMLDOM imgXML = (ImageXMLDOM)img;
  956. renderDocument(imgXML.getDocument(), imgXML.getRootNamespace(),
  957. pos, foreignAttributes);
  958. } else {
  959. throw new UnsupportedOperationException("Unsupported image type: " + img);
  960. }
  961. } catch (ImageException ie) {
  962. log.error("Error while processing image: "
  963. + (info != null ? info.toString() : uri), ie);
  964. } catch (FileNotFoundException fe) {
  965. log.error(fe.getMessage());
  966. } catch (IOException ioe) {
  967. log.error("I/O error while processing image: "
  968. + (info != null ? info.toString() : uri), ioe);
  969. }
  970. /*
  971. ImageFactory fact = userAgent.getFactory().getImageFactory();
  972. FopImage fopimage = fact.getImage(url, userAgent);
  973. if (fopimage == null) {
  974. return;
  975. }
  976. if (!fopimage.load(FopImage.DIMENSIONS)) {
  977. return;
  978. }
  979. String mime = fopimage.getMimeType();
  980. if ("text/xml".equals(mime) || MimeConstants.MIME_SVG.equals(mime)) {
  981. if (!fopimage.load(FopImage.ORIGINAL_DATA)) {
  982. return;
  983. }
  984. Document doc = ((XMLImage) fopimage).getDocument();
  985. String ns = ((XMLImage) fopimage).getNameSpace();
  986. renderDocument(doc, ns, pos, foreignAttributes);
  987. } else if (MimeConstants.MIME_EPS.equals(mime)) {
  988. log.warn("EPS images are not supported by this renderer");
  989. */
  990. /*
  991. * } else if (MimeConstants.MIME_JPEG.equals(mime)) { if
  992. * (!fopimage.load(FopImage.ORIGINAL_DATA)) { return; }
  993. * fact.releaseImage(url, userAgent);
  994. *
  995. * int x = mpts2units(pos.getX() + currentIPPosition); int y =
  996. * mpts2units(pos.getY() + currentBPPosition); int w =
  997. * mpts2units(pos.getWidth()); int h =
  998. * mpts2units(pos.getHeight()); ImageObject io =
  999. * _afpDataStream.getImageObject(); io.setImageViewport(x, y, w,
  1000. * h); io.setImageParameters(
  1001. * (int)(fopimage.getHorizontalResolution() * 10),
  1002. * (int)(fopimage.getVerticalResolution() * 10),
  1003. * fopimage.getWidth(), fopimage.getHeight() );
  1004. * io.setImageIDESize((byte)fopimage.getBitsPerPixel());
  1005. * io.setImageEncoding((byte)0x83);
  1006. * io.setImageData(fopimage.getRessourceBytes());
  1007. *//*
  1008. } else if (MimeConstants.MIME_TIFF.equals(mime)
  1009. && fopimage instanceof TIFFImage) {
  1010. TIFFImage tiffImage = (TIFFImage) fopimage;
  1011. int x = mpts2units(pos.getX() + currentIPPosition);
  1012. int y = mpts2units(pos.getY() + currentBPPosition);
  1013. int w = mpts2units(pos.getWidth());
  1014. int h = mpts2units(pos.getHeight());
  1015. ImageObject io = afpDataStream.getImageObject(x, y, w, h,
  1016. getResolution(), getResolution());
  1017. io.setImageParameters(
  1018. (int)(fopimage.getHorizontalResolution() * 10),
  1019. (int)(fopimage.getVerticalResolution() * 10),
  1020. fopimage.getWidth(),
  1021. fopimage.getHeight()
  1022. );
  1023. if (tiffImage.getStripCount() == 1) {
  1024. int comp = tiffImage.getCompression();
  1025. if (comp == 3) {
  1026. if (!fopimage.load(FopImage.ORIGINAL_DATA)) {
  1027. return;
  1028. }
  1029. io.setImageEncoding((byte)0x81);
  1030. io.setImageData(fopimage.getRessourceBytes());
  1031. } else if (comp == 4) {
  1032. if (!fopimage.load(FopImage.ORIGINAL_DATA)) {
  1033. return;
  1034. }
  1035. io.setImageEncoding((byte)0x82);
  1036. io.setImageData(fopimage.getRessourceBytes());
  1037. } else {
  1038. if (!fopimage.load(FopImage.BITMAP)) {
  1039. return;
  1040. }
  1041. convertToGrayScaleImage(io, fopimage.getBitmaps(),
  1042. fopimage.getWidth(), fopimage.getHeight());
  1043. }
  1044. } else {
  1045. if (!fopimage.load(FopImage.BITMAP)) {
  1046. return;
  1047. }
  1048. convertToGrayScaleImage(io, fopimage.getBitmaps(),
  1049. fopimage.getWidth(), fopimage.getHeight());
  1050. }
  1051. } else {
  1052. if (!fopimage.load(FopImage.BITMAP)) {
  1053. return;
  1054. }
  1055. fact.releaseImage(url, userAgent);
  1056. int x = mpts2units(pos.getX() + currentIPPosition);
  1057. int y = mpts2units(pos.getY() + currentBPPosition);
  1058. int w = mpts2units(pos.getWidth());
  1059. int h = mpts2units(pos.getHeight());
  1060. ImageObject io = afpDataStream.getImageObject(x, y, w, h,
  1061. getResolution(), getResolution());
  1062. io.setImageParameters(
  1063. (int)(fopimage.getHorizontalResolution() * 10),
  1064. (int)(fopimage.getVerticalResolution() * 10),
  1065. fopimage.getWidth(),
  1066. fopimage.getHeight()
  1067. );
  1068. if (colorImages) {
  1069. io.setImageIDESize((byte)24);
  1070. io.setImageData(fopimage.getBitmaps());
  1071. } else {
  1072. convertToGrayScaleImage(io, fopimage.getBitmaps(),
  1073. fopimage.getWidth(), fopimage.getHeight());
  1074. }
  1075. }*/
  1076. }
  1077. }
  1078. /**
  1079. * Writes a RenderedImage to an OutputStream as raw sRGB bitmaps.
  1080. *
  1081. * @param image
  1082. * the RenderedImage
  1083. * @param out
  1084. * the OutputStream
  1085. * @throws IOException
  1086. * In case of an I/O error.
  1087. */
  1088. public static void writeImage(RenderedImage image, OutputStream out)
  1089. throws IOException {
  1090. ImageEncodingHelper.encodeRenderedImageAsRGB(image, out);
  1091. }
  1092. /**
  1093. * Draws a BufferedImage to AFP.
  1094. *
  1095. * @param image
  1096. * the RenderedImage
  1097. * @param imageResolution
  1098. * the resolution of the BufferedImage
  1099. * @param x
  1100. * the x coordinate (in mpt)
  1101. * @param y
  1102. * the y coordinate (in mpt)
  1103. * @param w
  1104. * the width of the viewport (in mpt)
  1105. * @param h
  1106. * the height of the viewport (in mpt)
  1107. */
  1108. public void drawBufferedImage(RenderedImage image, int imageResolution, int x,
  1109. int y, int w, int h) {
  1110. int afpx = mpts2units(x);
  1111. int afpy = mpts2units(y);
  1112. int afpw = mpts2units(w);
  1113. int afph = mpts2units(h);
  1114. int afpres = getResolution();
  1115. ByteArrayOutputStream baout = new ByteArrayOutputStream();
  1116. try {
  1117. // Serialize image
  1118. //TODO Eventually, this should be changed not to buffer as this increases the
  1119. //memory consumption (see PostScript output)
  1120. writeImage(image, baout);
  1121. byte[] buf = baout.toByteArray();
  1122. // Generate image
  1123. ImageObject io = afpDataStream.getImageObject(afpx, afpy, afpw,
  1124. afph, afpres, afpres);
  1125. io.setImageParameters(imageResolution, imageResolution,
  1126. image.getWidth(), image.getHeight());
  1127. if (colorImages) {
  1128. io.setImageIDESize((byte)24);
  1129. io.setImageData(buf);
  1130. } else {
  1131. // TODO Teach it how to handle grayscale BufferedImages directly
  1132. // because this is pretty inefficient
  1133. convertToGrayScaleImage(io, buf,
  1134. image.getWidth(), image.getHeight(), this.bitsPerPixel);
  1135. }
  1136. } catch (IOException ioe) {
  1137. log.error("Error while serializing bitmap: " + ioe.getMessage(), ioe);
  1138. }
  1139. }
  1140. /**
  1141. * Establishes a new foreground or fill color.
  1142. * {@inheritDoc}
  1143. */
  1144. public void updateColor(Color col, boolean fill) {
  1145. if (fill) {
  1146. currentColor = col;
  1147. }
  1148. }
  1149. /** {@inheritDoc} */
  1150. public List breakOutOfStateStack() {
  1151. log.debug("Block.FIXED --> break out");
  1152. List breakOutList = new java.util.ArrayList();
  1153. //Don't pop the last ViewPortPos (created by renderPage())
  1154. while (this.viewPortPositions.size() > 1) {
  1155. breakOutList.add(0, popViewPortPos());
  1156. }
  1157. return breakOutList;
  1158. }
  1159. /** {@inheritDoc} */
  1160. public void restoreStateStackAfterBreakOut(List breakOutList) {
  1161. log.debug("Block.FIXED --> restoring context after break-out");
  1162. for (int i = 0, c = breakOutList.size(); i < c; i++) {
  1163. ViewPortPos vps = (ViewPortPos)breakOutList.get(i);
  1164. pushViewPortPos(vps);
  1165. }
  1166. }
  1167. /** Saves the graphics state of the rendering engine. */
  1168. public void saveGraphicsState() {
  1169. }
  1170. /** Restores the last graphics state of the rendering engine. */
  1171. public void restoreGraphicsState() {
  1172. }
  1173. /** Indicates the beginning of a text object. */
  1174. public void beginTextObject() {
  1175. }
  1176. /** Indicates the end of a text object. */
  1177. public void endTextObject() {
  1178. }
  1179. /**
  1180. * {@inheritDoc}
  1181. */
  1182. public void renderImage(Image image, Rectangle2D pos) {
  1183. String url = image.getURL();
  1184. drawImage(url, pos);
  1185. }
  1186. /**
  1187. * {@inheritDoc}
  1188. */
  1189. public void renderText(TextArea text) {
  1190. renderInlineAreaBackAndBorders(text);
  1191. String name = getInternalFontNameForArea(text);
  1192. currentFontSize = ((Integer) text.getTrait(Trait.FONT_SIZE)).intValue();
  1193. AFPFont tf = (AFPFont) fontInfo.getFonts().get(name);
  1194. Color col = (Color) text.getTrait(Trait.COLOR);
  1195. int vsci = mpts2units(tf.getWidth(' ', currentFontSize) / 1000
  1196. + text.getTextWordSpaceAdjust()
  1197. + text.getTextLetterSpaceAdjust());
  1198. // word.getOffset() = only height of text itself
  1199. // currentBlockIPPosition: 0 for beginning of line; nonzero
  1200. // where previous line area failed to take up entire allocated space
  1201. int rx = currentIPPosition + text.getBorderAndPaddingWidthStart();
  1202. int bl = currentBPPosition + text.getOffset() + text.getBaselineOffset();
  1203. // Set letterSpacing
  1204. //float ls = fs.getLetterSpacing() / this.currentFontSize;
  1205. String worddata = text.getText();
  1206. // Create an AFPFontAttributes object from the current font details
  1207. AFPFontAttributes afpFontAttributes = new AFPFontAttributes(name, tf, currentFontSize);
  1208. if (!currentPageFonts.containsKey(afpFontAttributes.getFontKey())) {
  1209. // Font not found on current page, so add the new one
  1210. pageFontCounter++;
  1211. afpFontAttributes.setFontReference(pageFontCounter);
  1212. currentPageFonts.put(
  1213. afpFontAttributes.getFontKey(),
  1214. afpFontAttributes);
  1215. } else {
  1216. // Use the previously stored font attributes
  1217. afpFontAttributes = (AFPFontAttributes) currentPageFonts.get(
  1218. afpFontAttributes.getFontKey());
  1219. }
  1220. // Try and get the encoding to use for the font
  1221. String encoding = null;
  1222. try {
  1223. encoding = tf.getCharacterSet(currentFontSize).getEncoding();
  1224. } catch (Throwable ex) {
  1225. encoding = AFPConstants.EBCIDIC_ENCODING;
  1226. log.warn(
  1227. "renderText():: Error getting encoding for font "
  1228. + " - using default encoding "
  1229. + encoding);
  1230. }
  1231. try {
  1232. afpDataStream.createText(
  1233. afpFontAttributes.getFontReference(),
  1234. mpts2units(rx),
  1235. mpts2units(bl),
  1236. col,
  1237. vsci,
  1238. mpts2units(text.getTextLetterSpaceAdjust()),
  1239. worddata.getBytes(encoding));
  1240. } catch (UnsupportedEncodingException usee) {
  1241. log.error(
  1242. "renderText:: Font "
  1243. + afpFontAttributes.getFontKey()
  1244. + " caused UnsupportedEncodingException");
  1245. }
  1246. super.renderText(text);
  1247. renderTextDecoration(tf, currentFontSize, text, bl, rx);
  1248. }
  1249. /**
  1250. * {@inheritDoc}
  1251. */
  1252. public void renderWord(WordArea word) {
  1253. // UNUSED
  1254. // String name = getInternalFontNameForArea(word.getParentArea());
  1255. // int size = ((Integer)
  1256. // word.getParentArea().getTrait(Trait.FONT_SIZE)).intValue();
  1257. // AFPFont tf = (AFPFont) fontInfo.getFonts().get(name);
  1258. //
  1259. // String s = word.getWord();
  1260. //
  1261. // FontMetrics metrics = fontInfo.getMetricsFor(name);
  1262. super.renderWord(word);
  1263. }
  1264. /**
  1265. * {@inheritDoc}
  1266. */
  1267. public void renderSpace(SpaceArea space) {
  1268. // UNUSED
  1269. // String name = getInternalFontNameForArea(space.getParentArea());
  1270. // int size = ((Integer)
  1271. // space.getParentArea().getTrait(Trait.FONT_SIZE)).intValue();
  1272. // AFPFont tf = (AFPFont) fontInfo.getFonts().get(name);
  1273. //
  1274. // String s = space.getSpace();
  1275. //
  1276. // FontMetrics metrics = fontInfo.getMetricsFor(name);
  1277. super.renderSpace(space);
  1278. }
  1279. /**
  1280. * Render leader area.
  1281. * This renders a leader area which is an area with a rule.
  1282. * @param area the leader area to render
  1283. */
  1284. public void renderLeader(Leader area) {
  1285. renderInlineAreaBackAndBorders(area);
  1286. int style = area.getRuleStyle();
  1287. float startx = (currentIPPosition + area.getBorderAndPaddingWidthStart()) / 1000f;
  1288. float starty = (currentBPPosition + area.getOffset()) / 1000f;
  1289. float endx = (currentIPPosition + area.getBorderAndPaddingWidthStart()
  1290. + area.getIPD()) / 1000f;
  1291. float ruleThickness = area.getRuleThickness() / 1000f;
  1292. Color col = (Color)area.getTrait(Trait.COLOR);
  1293. switch (style) {
  1294. case EN_SOLID:
  1295. case EN_DASHED:
  1296. case EN_DOUBLE:
  1297. case EN_DOTTED:
  1298. case EN_GROOVE:
  1299. case EN_RIDGE:
  1300. drawBorderLine(startx, starty, endx, starty + ruleThickness,
  1301. true, true, style, col);
  1302. break;
  1303. default:
  1304. throw new UnsupportedOperationException("rule style not supported");
  1305. }
  1306. super.renderLeader(area);
  1307. }
  1308. /**
  1309. * Sets the AFPRenderer options
  1310. * @param options the <code>Map</code> containing the options
  1311. */
  1312. // UNUSED
  1313. // public void setOptions(Map options) {
  1314. //
  1315. // this.afpOptions = options;
  1316. //
  1317. // }
  1318. /**
  1319. * Determines the orientation from the string representation, this method
  1320. * guarantees to return a value of either 0, 90, 180 or 270.
  1321. *
  1322. * @return the orientation
  1323. */
  1324. // UNUSED
  1325. // private int getOrientation(String orientationString) {
  1326. //
  1327. // int orientation = 0;
  1328. // if (orientationString != null && orientationString.length() > 0) {
  1329. // try {
  1330. // orientation = Integer.parseInt(orientationString);
  1331. // } catch (NumberFormatException nfe) {
  1332. // log.error("Cannot use orientation of " + orientation
  1333. // + " defaulting to zero.");
  1334. // orientation = 0;
  1335. // }
  1336. // } else {
  1337. // orientation = 0;
  1338. // }
  1339. // switch (orientation) {
  1340. // case 0:
  1341. // break;
  1342. // case 90:
  1343. // break;
  1344. // case 180:
  1345. // break;
  1346. // case 270:
  1347. // break;
  1348. // default:
  1349. // log.error("Cannot use orientation of " + orientation
  1350. // + " defaulting to zero.");
  1351. // orientation = 0;
  1352. // break;
  1353. // }
  1354. //
  1355. // return orientation;
  1356. //
  1357. // }
  1358. /**
  1359. * Sets the rotation to be used for portrait pages, valid values are 0
  1360. * (default), 90, 180, 270.
  1361. *
  1362. * @param rotation
  1363. * The rotation in degrees.
  1364. */
  1365. public void setPortraitRotation(int rotation) {
  1366. if (rotation == 0
  1367. || rotation == 90
  1368. || rotation == 180
  1369. || rotation == 270) {
  1370. portraitRotation = rotation;
  1371. } else {
  1372. throw new IllegalArgumentException("The portrait rotation must be one"
  1373. + " of the values 0, 90, 180, 270");
  1374. }
  1375. }
  1376. /**
  1377. * Sets the rotation to be used for landsacpe pages, valid values are 0, 90,
  1378. * 180, 270 (default).
  1379. *
  1380. * @param rotation
  1381. * The rotation in degrees.
  1382. */
  1383. public void setLandscapeRotation(int rotation) {
  1384. if (rotation == 0
  1385. || rotation == 90
  1386. || rotation == 180
  1387. || rotation == 270) {
  1388. landscapeRotation = rotation;
  1389. } else {
  1390. throw new IllegalArgumentException("The landscape rotation must be one"
  1391. + " of the values 0, 90, 180, 270");
  1392. }
  1393. }
  1394. /**
  1395. * Get the MIME type of the renderer.
  1396. *
  1397. * @return The MIME type of the renderer
  1398. */
  1399. public String getMimeType() {
  1400. return MimeConstants.MIME_AFP;
  1401. }
  1402. /**
  1403. * Method to render the page extension.
  1404. * <p>
  1405. *
  1406. * @param pageViewport the page object
  1407. */
  1408. private void renderPageObjectExtensions(PageViewport pageViewport) {
  1409. pageSegmentsMap = null;
  1410. if (pageViewport.getExtensionAttachments() != null
  1411. && pageViewport.getExtensionAttachments().size() > 0) {
  1412. // Extract all AFPPageSetup instances from the attachment list on
  1413. // the s-p-m
  1414. Iterator i = pageViewport.getExtensionAttachments().iterator();
  1415. while (i.hasNext()) {
  1416. ExtensionAttachment attachment = (ExtensionAttachment)i.next();
  1417. if (AFPPageSetup.CATEGORY.equals(attachment.getCategory())) {
  1418. AFPPageSetup aps = (AFPPageSetup) attachment;
  1419. String element = aps.getElementName();
  1420. if (AFPElementMapping.INCLUDE_PAGE_OVERLAY.equals(element)) {
  1421. String overlay = aps.getName();
  1422. if (overlay != null) {
  1423. afpDataStream.createIncludePageOverlay(overlay);
  1424. }
  1425. } else if (AFPElementMapping.INCLUDE_PAGE_SEGMENT
  1426. .equals(element)) {
  1427. String name = aps.getName();
  1428. String source = aps.getValue();
  1429. if (pageSegmentsMap == null) {
  1430. pageSegmentsMap = new HashMap();
  1431. }
  1432. pageSegmentsMap.put(source, name);
  1433. } else if (AFPElementMapping.TAG_LOGICAL_ELEMENT
  1434. .equals(element)) {
  1435. String name = aps.getName();
  1436. String value = aps.getValue();
  1437. if (pageSegmentsMap == null) {
  1438. pageSegmentsMap = new HashMap();
  1439. }
  1440. afpDataStream.createTagLogicalElement(name, value);
  1441. } else if (AFPElementMapping.NO_OPERATION.equals(element)) {
  1442. String content = aps.getContent();
  1443. if (content != null) {
  1444. afpDataStream.createNoOperation(content);
  1445. }
  1446. }
  1447. }
  1448. }
  1449. }
  1450. }
  1451. /**
  1452. * Converts FOP mpt measurement to afp measurement units
  1453. * @param mpt the millipoints value
  1454. */
  1455. private int mpts2units(int mpt) {
  1456. return mpts2units((double) mpt);
  1457. }
  1458. /**
  1459. * Converts FOP pt measurement to afp measurement units
  1460. * @param mpt the millipoints value
  1461. */
  1462. private int pts2units(float mpt) {
  1463. return mpts2units(mpt * 1000d);
  1464. }
  1465. /**
  1466. * Converts FOP mpt measurement to afp measurement units
  1467. *
  1468. * @param mpt
  1469. * the millipoints value
  1470. * @return afp measurement unit value
  1471. */
  1472. private int mpts2units(double mpt) {
  1473. return (int)Math.round(mpt / (DPI_CONVERSION_FACTOR / getResolution()));
  1474. }
  1475. /**
  1476. * Converts a byte array containing 24 bit RGB image data to a grayscale
  1477. * image.
  1478. *
  1479. * @param io
  1480. * the target image object
  1481. * @param raw
  1482. * the buffer containing the RGB image data
  1483. * @param width
  1484. * the width of the image in pixels
  1485. * @param height
  1486. * the height of the image in pixels
  1487. * @param bitsPerPixel
  1488. * the number of bits to use per pixel
  1489. */
  1490. protected static void convertToGrayScaleImage(ImageObject io, byte[] raw, int width,
  1491. int height, int bitsPerPixel) {
  1492. int pixelsPerByte = 8 / bitsPerPixel;
  1493. int bytewidth = (width / pixelsPerByte);
  1494. if ((width % pixelsPerByte) != 0) {
  1495. bytewidth++;
  1496. }
  1497. byte[] bw = new byte[height * bytewidth];
  1498. byte ib;
  1499. for (int y = 0; y < height; y++) {
  1500. ib = 0;
  1501. int i = 3 * y * width;
  1502. for (int x = 0; x < width; x++, i += 3) {
  1503. // see http://www.jguru.com/faq/view.jsp?EID=221919
  1504. double greyVal = 0.212671d * ((int) raw[i] & 0xff) + 0.715160d
  1505. * ((int) raw[i + 1] & 0xff) + 0.072169d
  1506. * ((int) raw[i + 2] & 0xff);
  1507. switch (bitsPerPixel) {
  1508. case 1:
  1509. if (greyVal < 128) {
  1510. ib |= (byte) (1 << (7 - (x % 8)));
  1511. }
  1512. break;
  1513. case 4:
  1514. greyVal /= 16;
  1515. ib |= (byte) ((byte) greyVal << ((1 - (x % 2)) * 4));
  1516. break;
  1517. case 8:
  1518. ib = (byte) greyVal;
  1519. break;
  1520. default:
  1521. throw new UnsupportedOperationException(
  1522. "Unsupported bits per pixel: " + bitsPerPixel);
  1523. }
  1524. if ((x % pixelsPerByte) == (pixelsPerByte - 1)
  1525. || ((x + 1) == width)) {
  1526. bw[(y * bytewidth) + (x / pixelsPerByte)] = ib;
  1527. ib = 0;
  1528. }
  1529. }
  1530. }
  1531. io.setImageIDESize((byte) bitsPerPixel);
  1532. io.setImageData(bw);
  1533. }
  1534. private final class ViewPortPos {
  1535. private int x = 0;
  1536. private int y = 0;
  1537. private int rot = 0;
  1538. private ViewPortPos() {
  1539. }
  1540. private ViewPortPos(Rectangle2D view, CTM ctm) {
  1541. ViewPortPos currentVP = (ViewPortPos) viewPortPositions
  1542. .get(viewPortPositions.size() - 1);
  1543. int xOrigin;
  1544. int yOrigin;
  1545. int width;
  1546. int height;
  1547. switch (currentVP.rot) {
  1548. case 90:
  1549. width = mpts2units(view.getHeight());
  1550. height = mpts2units(view.getWidth());
  1551. xOrigin = pageWidth - width - mpts2units(view.getY())
  1552. - currentVP.y;
  1553. yOrigin = mpts2units(view.getX()) + currentVP.x;
  1554. break;
  1555. case 180:
  1556. width = mpts2units(view.getWidth());
  1557. height = mpts2units(view.getHeight());
  1558. xOrigin = pageWidth - width - mpts2units(view.getX())
  1559. - currentVP.x;
  1560. yOrigin = pageHeight - height - mpts2units(view.getY())
  1561. - currentVP.y;
  1562. break;
  1563. case 270:
  1564. width = mpts2units(view.getHeight());
  1565. height = mpts2units(view.getWidth());
  1566. xOrigin = mpts2units(view.getY()) + currentVP.y;
  1567. yOrigin = pageHeight - height - mpts2units(view.getX())
  1568. - currentVP.x;
  1569. break;
  1570. default:
  1571. xOrigin = mpts2units(view.getX()) + currentVP.x;
  1572. yOrigin = mpts2units(view.getY()) + currentVP.y;
  1573. width = mpts2units(view.getWidth());
  1574. height = mpts2units(view.getHeight());
  1575. break;
  1576. }
  1577. this.rot = currentVP.rot;
  1578. double[] ctmf = ctm.toArray();
  1579. if (ctmf[0] == 0.0d && ctmf[1] == -1.0d && ctmf[2] == 1.0d
  1580. && ctmf[3] == 0.d) {
  1581. this.rot += 270;
  1582. } else if (ctmf[0] == -1.0d && ctmf[1] == 0.0d && ctmf[2] == 0.0d
  1583. && ctmf[3] == -1.0d) {
  1584. this.rot += 180;
  1585. } else if (ctmf[0] == 0.0d && ctmf[1] == 1.0d && ctmf[2] == -1.0d
  1586. && ctmf[3] == 0.0d) {
  1587. this.rot += 90;
  1588. }
  1589. this.rot %= 360;
  1590. switch (this.rot) {
  1591. /*
  1592. * case 0: this.x = mpts2units(view.getX()) + x; this.y =
  1593. * mpts2units(view.getY()) + y; break; case 90: this.x =
  1594. * mpts2units(view.getY()) + y; this.y = _pageWidth -
  1595. * mpts2units(view.getX() + view.getWidth()) - x; break; case 180:
  1596. * this.x = _pageWidth - mpts2units(view.getX() + view.getWidth()) -
  1597. * x; this.y = _pageHeight - mpts2units(view.getY() +
  1598. * view.getHeight()) - y; break; case 270: this.x = _pageHeight -
  1599. * mpts2units(view.getY() + view.getHeight()) - y; this.y =
  1600. * mpts2units(view.getX()) + x; break;
  1601. */
  1602. case 0:
  1603. this.x = xOrigin;
  1604. this.y = yOrigin;
  1605. break;
  1606. case 90:
  1607. this.x = yOrigin;
  1608. this.y = pageWidth - width - xOrigin;
  1609. break;
  1610. case 180:
  1611. this.x = pageWidth - width - xOrigin;
  1612. this.y = pageHeight - height - yOrigin;
  1613. break;
  1614. case 270:
  1615. this.x = pageHeight - height - yOrigin;
  1616. this.y = xOrigin;
  1617. break;
  1618. default:
  1619. }
  1620. }
  1621. public String toString() {
  1622. return "x:" + x + " y:" + y + " rot:" + rot;
  1623. }
  1624. }
  1625. private List viewPortPositions = new ArrayList();
  1626. private void pushViewPortPos(ViewPortPos vpp) {
  1627. viewPortPositions.add(vpp);
  1628. afpDataStream.setOffsets(vpp.x, vpp.y, vpp.rot);
  1629. }
  1630. private ViewPortPos popViewPortPos() {
  1631. ViewPortPos current = (ViewPortPos)viewPortPositions.remove(viewPortPositions.size() - 1);
  1632. if (viewPortPositions.size() > 0) {
  1633. ViewPortPos vpp = (ViewPortPos)viewPortPositions.get(viewPortPositions.size() - 1);
  1634. afpDataStream.setOffsets(vpp.x, vpp.y, vpp.rot);
  1635. }
  1636. return current;
  1637. }
  1638. /**
  1639. * Sets the number of bits used per pixel
  1640. *
  1641. * @param bitsPerPixel
  1642. * number of bits per pixel
  1643. */
  1644. public void setBitsPerPixel(int bitsPerPixel) {
  1645. this.bitsPerPixel = bitsPerPixel;
  1646. switch (bitsPerPixel) {
  1647. case 1:
  1648. case 4:
  1649. case 8:
  1650. break;
  1651. default:
  1652. log.warn("Invalid bits_per_pixel value, must be 1, 4 or 8.");
  1653. bitsPerPixel = 8;
  1654. break;
  1655. }
  1656. }
  1657. /**
  1658. * Sets whether images are color or not
  1659. *
  1660. * @param colorImages
  1661. * color image output
  1662. */
  1663. public void setColorImages(boolean colorImages) {
  1664. this.colorImages = colorImages;
  1665. }
  1666. /**
  1667. * Sets the output/device resolution
  1668. *
  1669. * @param resolution
  1670. * the output resolution (dpi)
  1671. */
  1672. public void setResolution(int resolution) {
  1673. if (log.isDebugEnabled()) {
  1674. log.debug("renderer-resolution set to: " + resolution + "dpi");
  1675. }
  1676. this.resolution = resolution;
  1677. }
  1678. /**
  1679. * Returns the output/device resolution.
  1680. * @return the resolution in dpi
  1681. */
  1682. public int getResolution() {
  1683. return this.resolution;
  1684. }
  1685. }