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.

IFRenderer.java 54KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472
  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.intermediate;
  19. import java.awt.Color;
  20. import java.awt.Dimension;
  21. import java.awt.Point;
  22. import java.awt.Rectangle;
  23. import java.awt.geom.AffineTransform;
  24. import java.awt.geom.Rectangle2D;
  25. import java.io.IOException;
  26. import java.io.OutputStream;
  27. import java.util.Iterator;
  28. import java.util.List;
  29. import java.util.Locale;
  30. import java.util.Map;
  31. import java.util.Stack;
  32. import javax.xml.transform.stream.StreamResult;
  33. import org.w3c.dom.Document;
  34. import org.xml.sax.SAXException;
  35. import org.apache.commons.logging.Log;
  36. import org.apache.commons.logging.LogFactory;
  37. import org.apache.batik.parser.AWTTransformProducer;
  38. import org.apache.xmlgraphics.xmp.Metadata;
  39. import org.apache.xmlgraphics.xmp.schemas.DublinCoreAdapter;
  40. import org.apache.xmlgraphics.xmp.schemas.DublinCoreSchema;
  41. import org.apache.xmlgraphics.xmp.schemas.XMPBasicAdapter;
  42. import org.apache.xmlgraphics.xmp.schemas.XMPBasicSchema;
  43. import org.apache.fop.Version;
  44. import org.apache.fop.accessibility.StructureTreeElement;
  45. import org.apache.fop.apps.FOPException;
  46. import org.apache.fop.apps.FOUserAgent;
  47. import org.apache.fop.apps.MimeConstants;
  48. import org.apache.fop.area.Area;
  49. import org.apache.fop.area.AreaTreeObject;
  50. import org.apache.fop.area.Block;
  51. import org.apache.fop.area.BlockViewport;
  52. import org.apache.fop.area.BookmarkData;
  53. import org.apache.fop.area.CTM;
  54. import org.apache.fop.area.DestinationData;
  55. import org.apache.fop.area.OffDocumentExtensionAttachment;
  56. import org.apache.fop.area.OffDocumentItem;
  57. import org.apache.fop.area.PageSequence;
  58. import org.apache.fop.area.PageViewport;
  59. import org.apache.fop.area.RegionViewport;
  60. import org.apache.fop.area.Trait;
  61. import org.apache.fop.area.inline.AbstractTextArea;
  62. import org.apache.fop.area.inline.ForeignObject;
  63. import org.apache.fop.area.inline.Image;
  64. import org.apache.fop.area.inline.InlineArea;
  65. import org.apache.fop.area.inline.InlineParent;
  66. import org.apache.fop.area.inline.InlineViewport;
  67. import org.apache.fop.area.inline.Leader;
  68. import org.apache.fop.area.inline.SpaceArea;
  69. import org.apache.fop.area.inline.TextArea;
  70. import org.apache.fop.area.inline.WordArea;
  71. import org.apache.fop.datatypes.URISpecification;
  72. import org.apache.fop.fo.extensions.ExtensionAttachment;
  73. import org.apache.fop.fo.extensions.xmp.XMPMetadata;
  74. import org.apache.fop.fonts.Font;
  75. import org.apache.fop.fonts.FontInfo;
  76. import org.apache.fop.fonts.FontTriplet;
  77. import org.apache.fop.fonts.LazyFont;
  78. import org.apache.fop.fonts.Typeface;
  79. import org.apache.fop.render.AbstractPathOrientedRenderer;
  80. import org.apache.fop.render.Renderer;
  81. import org.apache.fop.render.intermediate.extensions.AbstractAction;
  82. import org.apache.fop.render.intermediate.extensions.ActionSet;
  83. import org.apache.fop.render.intermediate.extensions.Bookmark;
  84. import org.apache.fop.render.intermediate.extensions.BookmarkTree;
  85. import org.apache.fop.render.intermediate.extensions.GoToXYAction;
  86. import org.apache.fop.render.intermediate.extensions.Link;
  87. import org.apache.fop.render.intermediate.extensions.NamedDestination;
  88. import org.apache.fop.render.intermediate.extensions.URIAction;
  89. import org.apache.fop.render.pdf.PDFEventProducer;
  90. import org.apache.fop.traits.BorderProps;
  91. import org.apache.fop.traits.RuleStyle;
  92. /**
  93. * This renderer implementation is an adapter to the {@link IFPainter} interface. It is used
  94. * to generate content using FOP's intermediate format.
  95. */
  96. public class IFRenderer extends AbstractPathOrientedRenderer {
  97. //TODO Many parts of the Renderer infrastructure are using floats (coordinates in points)
  98. //instead of ints (in millipoints). A lot of conversion to and from is performed.
  99. //When the new IF is established, the Renderer infrastructure should be revisited so check
  100. //if optimizations can be done to avoid int->float->int conversions.
  101. /** logging instance */
  102. protected static final Log log = LogFactory.getLog(IFRenderer.class);
  103. /** XML MIME type */
  104. public static final String IF_MIME_TYPE = MimeConstants.MIME_FOP_IF;
  105. private IFDocumentHandler documentHandler;
  106. private IFPainter painter;
  107. /** If not null, the XMLRenderer will mimic another renderer by using its font setup. */
  108. protected Renderer mimic;
  109. private boolean inPageSequence;
  110. private Stack graphicContextStack = new Stack();
  111. private Stack viewportDimensionStack = new Stack();
  112. private IFGraphicContext graphicContext = new IFGraphicContext();
  113. //private Stack groupStack = new Stack();
  114. private Metadata documentMetadata;
  115. /**
  116. * Maps XSL-FO element IDs to their on-page XY-positions
  117. * Must be used in conjunction with the page reference to fully specify the details
  118. * of a "go-to" action.
  119. */
  120. private Map idPositions = new java.util.HashMap();
  121. /**
  122. * The "go-to" actions in idGoTos that are not complete yet
  123. */
  124. private List unfinishedGoTos = new java.util.ArrayList();
  125. // can't use a Set because PDFGoTo.equals returns true if the target is the same,
  126. // even if the object number differs
  127. /** Maps unique PageViewport key to page indices (for link target handling) */
  128. protected Map pageIndices = new java.util.HashMap();
  129. private BookmarkTree bookmarkTree;
  130. private List deferredDestinations = new java.util.ArrayList();
  131. private List deferredLinks = new java.util.ArrayList();
  132. private ActionSet actionSet = new ActionSet();
  133. private TextUtil textUtil = new TextUtil();
  134. private Stack<String> ids = new Stack<String>();
  135. /**
  136. * Main constructor
  137. *
  138. * @param userAgent the user agent that contains configuration details. This cannot be null.
  139. */
  140. public IFRenderer(FOUserAgent userAgent) {
  141. super(userAgent);
  142. }
  143. /** {@inheritDoc} */
  144. public String getMimeType() {
  145. return IF_MIME_TYPE;
  146. }
  147. /**
  148. * Sets the {@link IFDocumentHandler} to be used by the {@link IFRenderer}.
  149. * @param documentHandler the {@link IFDocumentHandler}
  150. */
  151. public void setDocumentHandler(IFDocumentHandler documentHandler) {
  152. this.documentHandler = documentHandler;
  153. }
  154. /** {@inheritDoc} */
  155. public void setupFontInfo(FontInfo inFontInfo) throws FOPException {
  156. if (this.documentHandler == null) {
  157. this.documentHandler = createDefaultDocumentHandler();
  158. }
  159. IFUtil.setupFonts(this.documentHandler, inFontInfo);
  160. this.fontInfo = inFontInfo;
  161. }
  162. private void handleIFException(IFException ife) {
  163. if (ife.getCause() instanceof SAXException) {
  164. throw new RuntimeException(ife.getCause());
  165. } else {
  166. throw new RuntimeException(ife);
  167. }
  168. }
  169. private void handleIFExceptionWithIOException(IFException ife) throws IOException {
  170. Throwable cause = ife.getCause();
  171. if (cause instanceof IOException) {
  172. throw (IOException) cause;
  173. } else {
  174. handleIFException(ife);
  175. }
  176. }
  177. /** {@inheritDoc} */
  178. public boolean supportsOutOfOrder() {
  179. return (this.documentHandler != null
  180. ? this.documentHandler.supportsPagesOutOfOrder() : false);
  181. }
  182. /**
  183. * Returns the document navigation handler if available/supported.
  184. * @return the document navigation handler or null if not supported
  185. */
  186. protected IFDocumentNavigationHandler getDocumentNavigationHandler() {
  187. return this.documentHandler.getDocumentNavigationHandler();
  188. }
  189. /**
  190. * Indicates whether document navigation features are supported by the document handler.
  191. * @return true if document navigation features are available
  192. */
  193. protected boolean hasDocumentNavigation() {
  194. return getDocumentNavigationHandler() != null;
  195. }
  196. /**
  197. * Creates a default {@link IFDocumentHandler} when none has been set.
  198. * @return the default IFDocumentHandler
  199. */
  200. protected IFDocumentHandler createDefaultDocumentHandler() {
  201. FOUserAgent userAgent = getUserAgent();
  202. IFSerializer serializer = new IFSerializer(new IFContext(userAgent));
  203. if (userAgent.isAccessibilityEnabled()) {
  204. userAgent.setStructureTreeEventHandler(serializer.getStructureTreeEventHandler());
  205. }
  206. return serializer;
  207. }
  208. /** {@inheritDoc} */
  209. public void startRenderer(OutputStream outputStream)
  210. throws IOException {
  211. try {
  212. if (outputStream != null) {
  213. StreamResult result = new StreamResult(outputStream);
  214. if (getUserAgent().getOutputFile() != null) {
  215. result.setSystemId(
  216. getUserAgent().getOutputFile().toURI().toURL().toExternalForm());
  217. }
  218. if (this.documentHandler == null) {
  219. this.documentHandler = createDefaultDocumentHandler();
  220. }
  221. this.documentHandler.setResult(result);
  222. }
  223. super.startRenderer(null);
  224. if (log.isDebugEnabled()) {
  225. log.debug("Rendering areas via IF document handler ("
  226. + this.documentHandler.getClass().getName() + ")...");
  227. }
  228. documentHandler.startDocument();
  229. documentHandler.startDocumentHeader();
  230. } catch (IFException e) {
  231. handleIFExceptionWithIOException(e);
  232. }
  233. }
  234. /** {@inheritDoc} */
  235. public void stopRenderer() throws IOException {
  236. try {
  237. if (this.inPageSequence) {
  238. documentHandler.endPageSequence();
  239. this.inPageSequence = false;
  240. }
  241. documentHandler.startDocumentTrailer();
  242. //Wrap up document navigation
  243. if (hasDocumentNavigation()) {
  244. finishOpenGoTos();
  245. Iterator iter = this.deferredDestinations.iterator();
  246. while (iter.hasNext()) {
  247. NamedDestination dest = (NamedDestination)iter.next();
  248. iter.remove();
  249. getDocumentNavigationHandler().renderNamedDestination(dest);
  250. }
  251. if (this.bookmarkTree != null) {
  252. getDocumentNavigationHandler().renderBookmarkTree(this.bookmarkTree);
  253. }
  254. }
  255. documentHandler.endDocumentTrailer();
  256. documentHandler.endDocument();
  257. } catch (IFException e) {
  258. handleIFExceptionWithIOException(e);
  259. }
  260. pageIndices.clear();
  261. idPositions.clear();
  262. actionSet.clear();
  263. super.stopRenderer();
  264. log.debug("Rendering finished.");
  265. }
  266. @Override
  267. public void setDocumentLocale(Locale locale) {
  268. documentHandler.setDocumentLocale(locale);
  269. }
  270. /** {@inheritDoc} */
  271. public void processOffDocumentItem(OffDocumentItem odi) {
  272. if (odi instanceof DestinationData) {
  273. // render Destinations
  274. renderDestination((DestinationData) odi);
  275. } else if (odi instanceof BookmarkData) {
  276. // render Bookmark-Tree
  277. renderBookmarkTree((BookmarkData) odi);
  278. } else if (odi instanceof OffDocumentExtensionAttachment) {
  279. ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)odi).getAttachment();
  280. if (XMPMetadata.CATEGORY.equals(attachment.getCategory())) {
  281. renderXMPMetadata((XMPMetadata)attachment);
  282. } else {
  283. try {
  284. this.documentHandler.handleExtensionObject(attachment);
  285. } catch (IFException ife) {
  286. handleIFException(ife);
  287. }
  288. }
  289. }
  290. }
  291. private void renderDestination(DestinationData dd) {
  292. if (!hasDocumentNavigation()) {
  293. return;
  294. }
  295. String targetID = dd.getIDRef();
  296. if (targetID == null || targetID.length() == 0) {
  297. throw new IllegalArgumentException("DestinationData must contain a ID reference");
  298. }
  299. PageViewport pv = dd.getPageViewport();
  300. if (pv != null) {
  301. GoToXYAction action = getGoToActionForID(targetID, pv.getPageIndex());
  302. NamedDestination namedDestination = new NamedDestination(targetID, action);
  303. this.deferredDestinations.add(namedDestination);
  304. } else {
  305. //Warning already issued by AreaTreeHandler (debug level is sufficient)
  306. log.debug("Unresolved destination item received: " + dd.getIDRef());
  307. }
  308. }
  309. /**
  310. * Renders a Bookmark-Tree object
  311. * @param bookmarks the BookmarkData object containing all the Bookmark-Items
  312. */
  313. protected void renderBookmarkTree(BookmarkData bookmarks) {
  314. assert this.bookmarkTree == null;
  315. if (!hasDocumentNavigation()) {
  316. return;
  317. }
  318. this.bookmarkTree = new BookmarkTree();
  319. for (int i = 0; i < bookmarks.getCount(); i++) {
  320. BookmarkData ext = bookmarks.getSubData(i);
  321. Bookmark b = renderBookmarkItem(ext);
  322. bookmarkTree.addBookmark(b);
  323. }
  324. }
  325. private Bookmark renderBookmarkItem(BookmarkData bookmarkItem) {
  326. String targetID = bookmarkItem.getIDRef();
  327. if (targetID == null || targetID.length() == 0) {
  328. throw new IllegalArgumentException("DestinationData must contain a ID reference");
  329. }
  330. GoToXYAction action = null;
  331. PageViewport pv = bookmarkItem.getPageViewport();
  332. if (pv != null) {
  333. action = getGoToActionForID(targetID, pv.getPageIndex());
  334. } else {
  335. //Warning already issued by AreaTreeHandler (debug level is sufficient)
  336. log.debug("Bookmark with IDRef \"" + targetID + "\" has a null PageViewport.");
  337. }
  338. Bookmark b = new Bookmark(
  339. bookmarkItem.getBookmarkTitle(),
  340. bookmarkItem.showChildItems(),
  341. action);
  342. for (int i = 0; i < bookmarkItem.getCount(); i++) {
  343. b.addChildBookmark(renderBookmarkItem(bookmarkItem.getSubData(i)));
  344. }
  345. return b;
  346. }
  347. private void renderXMPMetadata(XMPMetadata metadata) {
  348. this.documentMetadata = metadata.getMetadata();
  349. }
  350. private GoToXYAction getGoToActionForID(String targetID, int pageIndex) {
  351. // Already a GoToXY present for this target? If not, create.
  352. GoToXYAction action = (GoToXYAction)actionSet.get(targetID);
  353. //GoToXYAction action = (GoToXYAction)idGoTos.get(targetID);
  354. if (action == null) {
  355. /* if (pageIndex < 0) {
  356. //pageIndex = page
  357. } */
  358. Point position = (Point)idPositions.get(targetID);
  359. // can the GoTo already be fully filled in?
  360. if (pageIndex >= 0 && position != null) {
  361. action = new GoToXYAction(targetID, pageIndex, position, documentHandler.getContext());
  362. } else {
  363. // Not complete yet, can't use getPDFGoTo:
  364. action = new GoToXYAction(targetID, pageIndex, null, documentHandler.getContext());
  365. unfinishedGoTos.add(action);
  366. }
  367. action = (GoToXYAction)actionSet.put(action);
  368. //idGoTos.put(targetID, action);
  369. }
  370. return action;
  371. }
  372. private void finishOpenGoTos() {
  373. int count = unfinishedGoTos.size();
  374. if (count > 0) {
  375. Point defaultPos = new Point(0, 0); // top-o-page
  376. while (!unfinishedGoTos.isEmpty()) {
  377. GoToXYAction action = (GoToXYAction)unfinishedGoTos.get(0);
  378. noteGoToPosition(action, defaultPos);
  379. }
  380. PDFEventProducer eventProducer = PDFEventProducer.Provider.get(
  381. getUserAgent().getEventBroadcaster());
  382. eventProducer.nonFullyResolvedLinkTargets(this, count);
  383. // dysfunctional if pageref is null
  384. }
  385. }
  386. private void noteGoToPosition(GoToXYAction action, Point position) {
  387. action.setTargetLocation(position);
  388. try {
  389. getDocumentNavigationHandler().addResolvedAction(action);
  390. } catch (IFException ife) {
  391. handleIFException(ife);
  392. }
  393. unfinishedGoTos.remove(action);
  394. }
  395. private void noteGoToPosition(GoToXYAction action, PageViewport pv, Point position) {
  396. action.setPageIndex(pv.getPageIndex());
  397. noteGoToPosition(action, position);
  398. }
  399. private void saveAbsolutePosition(String id, PageViewport pv,
  400. int relativeIPP, int relativeBPP, AffineTransform tf) {
  401. Point position = new Point(relativeIPP, relativeBPP);
  402. tf.transform(position, position);
  403. idPositions.put(id, position);
  404. // is there already a GoTo action waiting to be completed?
  405. GoToXYAction action = (GoToXYAction)actionSet.get(id);
  406. if (action != null) {
  407. noteGoToPosition(action, pv, position);
  408. }
  409. }
  410. private void saveAbsolutePosition(String id, int relativeIPP, int relativeBPP) {
  411. saveAbsolutePosition(id, this.currentPageViewport,
  412. relativeIPP, relativeBPP, graphicContext.getTransform());
  413. }
  414. private void saveBlockPosIfTargetable(Block block) {
  415. String id = getTargetableID(block);
  416. if (hasDocumentNavigation() && id != null) {
  417. // FIXME: Like elsewhere in the renderer code, absolute and relative
  418. // directions are happily mixed here. This makes sure that the
  419. // links point to the right location, but it is not correct.
  420. int ipp = block.getXOffset();
  421. int bpp = block.getYOffset() + block.getSpaceBefore();
  422. int positioning = block.getPositioning();
  423. if (!(positioning == Block.FIXED || positioning == Block.ABSOLUTE)) {
  424. ipp += currentIPPosition;
  425. bpp += currentBPPosition;
  426. }
  427. saveAbsolutePosition(id, currentPageViewport, ipp, bpp, graphicContext.getTransform());
  428. }
  429. }
  430. private void saveInlinePosIfTargetable(InlineArea inlineArea) {
  431. String id = getTargetableID(inlineArea);
  432. if (hasDocumentNavigation() && id != null) {
  433. int extraMarginBefore = 5000; // millipoints
  434. int ipp = currentIPPosition;
  435. int bpp = currentBPPosition
  436. + inlineArea.getBlockProgressionOffset() - extraMarginBefore;
  437. saveAbsolutePosition(id, ipp, bpp);
  438. }
  439. }
  440. private String getTargetableID(Area area) {
  441. String id = (String) area.getTrait(Trait.PROD_ID);
  442. if (id == null || id.length() == 0
  443. || !currentPageViewport.isFirstWithID(id)
  444. || idPositions.containsKey(id)) {
  445. return null;
  446. } else {
  447. return id;
  448. }
  449. }
  450. /** {@inheritDoc} */
  451. public void startPageSequence(PageSequence pageSequence) {
  452. try {
  453. if (this.inPageSequence) {
  454. documentHandler.endPageSequence();
  455. documentHandler.getContext().setLanguage(null);
  456. } else {
  457. if (this.documentMetadata == null) {
  458. this.documentMetadata = createDefaultDocumentMetadata();
  459. }
  460. documentHandler.handleExtensionObject(this.documentMetadata);
  461. documentHandler.endDocumentHeader();
  462. this.inPageSequence = true;
  463. }
  464. establishForeignAttributes(pageSequence.getForeignAttributes());
  465. documentHandler.getContext().setLanguage(pageSequence.getLocale());
  466. documentHandler.startPageSequence(null);
  467. resetForeignAttributes();
  468. processExtensionAttachments(pageSequence);
  469. } catch (IFException e) {
  470. handleIFException(e);
  471. }
  472. }
  473. private Metadata createDefaultDocumentMetadata() {
  474. Metadata xmp = new Metadata();
  475. DublinCoreAdapter dc = DublinCoreSchema.getAdapter(xmp);
  476. if (getUserAgent().getTitle() != null) {
  477. dc.setTitle(getUserAgent().getTitle());
  478. }
  479. if (getUserAgent().getAuthor() != null) {
  480. dc.addCreator(getUserAgent().getAuthor());
  481. }
  482. if (getUserAgent().getKeywords() != null) {
  483. dc.addSubject(getUserAgent().getKeywords());
  484. }
  485. XMPBasicAdapter xmpBasic = XMPBasicSchema.getAdapter(xmp);
  486. if (getUserAgent().getProducer() != null) {
  487. xmpBasic.setCreatorTool(getUserAgent().getProducer());
  488. } else {
  489. xmpBasic.setCreatorTool(Version.getVersion());
  490. }
  491. xmpBasic.setMetadataDate(new java.util.Date());
  492. if (getUserAgent().getCreationDate() != null) {
  493. xmpBasic.setCreateDate(getUserAgent().getCreationDate());
  494. } else {
  495. xmpBasic.setCreateDate(xmpBasic.getMetadataDate());
  496. }
  497. return xmp;
  498. }
  499. /** {@inheritDoc} */
  500. public void preparePage(PageViewport page) {
  501. super.preparePage(page);
  502. }
  503. /** {@inheritDoc} */
  504. public void renderPage(PageViewport page) throws IOException, FOPException {
  505. if (log.isTraceEnabled()) {
  506. log.trace("renderPage() " + page);
  507. }
  508. try {
  509. pageIndices.put(page.getKey(), page.getPageIndex());
  510. Rectangle viewArea = page.getViewArea();
  511. Dimension dim = new Dimension(viewArea.width, viewArea.height);
  512. establishForeignAttributes(page.getForeignAttributes());
  513. documentHandler.getContext().setPageIndex(page.getPageIndex());
  514. documentHandler.getContext().setPageNumber(page.getPageNumber());
  515. documentHandler.startPage(page.getPageIndex(), page.getPageNumberString(),
  516. page.getSimplePageMasterName(), dim);
  517. resetForeignAttributes();
  518. documentHandler.startPageHeader();
  519. //Add page attachments to page header
  520. processExtensionAttachments(page);
  521. documentHandler.endPageHeader();
  522. this.painter = documentHandler.startPageContent();
  523. super.renderPage(page);
  524. this.painter = null;
  525. documentHandler.endPageContent();
  526. documentHandler.startPageTrailer();
  527. if (hasDocumentNavigation()) {
  528. Iterator iter = this.deferredLinks.iterator();
  529. while (iter.hasNext()) {
  530. Link link = (Link)iter.next();
  531. iter.remove();
  532. getDocumentNavigationHandler().renderLink(link);
  533. }
  534. }
  535. documentHandler.endPageTrailer();
  536. establishForeignAttributes(page.getForeignAttributes());
  537. documentHandler.endPage();
  538. documentHandler.getContext().setPageIndex(-1);
  539. resetForeignAttributes();
  540. } catch (IFException e) {
  541. handleIFException(e);
  542. }
  543. }
  544. private void processExtensionAttachments(AreaTreeObject area) throws IFException {
  545. if (area.hasExtensionAttachments()) {
  546. for (ExtensionAttachment attachment : area.getExtensionAttachments()) {
  547. this.documentHandler.handleExtensionObject(attachment);
  548. }
  549. }
  550. }
  551. private void establishForeignAttributes(Map foreignAttributes) {
  552. documentHandler.getContext().setForeignAttributes(foreignAttributes);
  553. }
  554. private void resetForeignAttributes() {
  555. documentHandler.getContext().resetForeignAttributes();
  556. }
  557. private void establishStructureTreeElement(StructureTreeElement structureTreeElement) {
  558. documentHandler.getContext().setStructureTreeElement(structureTreeElement);
  559. }
  560. private void resetStructurePointer() {
  561. documentHandler.getContext().resetStructureTreeElement();
  562. }
  563. /** {@inheritDoc} */
  564. protected void saveGraphicsState() {
  565. graphicContextStack.push(graphicContext);
  566. graphicContext = (IFGraphicContext)graphicContext.clone();
  567. }
  568. /** {@inheritDoc} */
  569. protected void restoreGraphicsState() {
  570. while (graphicContext.getGroupStackSize() > 0) {
  571. IFGraphicContext.Group[] groups = graphicContext.dropGroups();
  572. for (int i = groups.length - 1; i >= 0; i--) {
  573. try {
  574. groups[i].end(painter);
  575. } catch (IFException ife) {
  576. handleIFException(ife);
  577. }
  578. }
  579. }
  580. graphicContext = (IFGraphicContext)graphicContextStack.pop();
  581. }
  582. private void pushGroup(IFGraphicContext.Group group) {
  583. graphicContext.pushGroup(group);
  584. try {
  585. group.start(painter);
  586. } catch (IFException ife) {
  587. handleIFException(ife);
  588. }
  589. }
  590. /** {@inheritDoc} */
  591. protected List breakOutOfStateStack() {
  592. log.debug("Block.FIXED --> break out");
  593. List breakOutList = new java.util.ArrayList();
  594. while (!this.graphicContextStack.empty()) {
  595. //Handle groups
  596. IFGraphicContext.Group[] groups = graphicContext.getGroups();
  597. for (int j = groups.length - 1; j >= 0; j--) {
  598. try {
  599. groups[j].end(painter);
  600. } catch (IFException ife) {
  601. handleIFException(ife);
  602. }
  603. }
  604. breakOutList.add(0, this.graphicContext);
  605. graphicContext = (IFGraphicContext)graphicContextStack.pop();
  606. }
  607. return breakOutList;
  608. }
  609. /** {@inheritDoc} */
  610. protected void restoreStateStackAfterBreakOut(List breakOutList) {
  611. log.debug("Block.FIXED --> restoring context after break-out");
  612. for (Object aBreakOutList : breakOutList) {
  613. graphicContextStack.push(graphicContext);
  614. this.graphicContext = (IFGraphicContext) aBreakOutList;
  615. //Handle groups
  616. IFGraphicContext.Group[] groups = graphicContext.getGroups();
  617. for (IFGraphicContext.Group group : groups) {
  618. try {
  619. group.start(painter);
  620. } catch (IFException ife) {
  621. handleIFException(ife);
  622. }
  623. }
  624. }
  625. log.debug("restored.");
  626. }
  627. /** {@inheritDoc} */
  628. protected void concatenateTransformationMatrix(AffineTransform at) {
  629. if (!at.isIdentity()) {
  630. concatenateTransformationMatrixMpt(ptToMpt(at), false);
  631. }
  632. }
  633. private void concatenateTransformationMatrixMpt(AffineTransform at, boolean force) {
  634. if (force || !at.isIdentity()) {
  635. if (log.isTraceEnabled()) {
  636. log.trace("-----concatenateTransformationMatrix: " + at);
  637. }
  638. IFGraphicContext.Group group = new IFGraphicContext.Group(at);
  639. pushGroup(group);
  640. }
  641. }
  642. /** {@inheritDoc} */
  643. protected void beginTextObject() {
  644. //nop - Ignore, handled by painter internally
  645. }
  646. /** {@inheritDoc} */
  647. protected void endTextObject() {
  648. //nop - Ignore, handled by painter internally
  649. }
  650. /** {@inheritDoc} */
  651. protected void renderRegionViewport(RegionViewport viewport) {
  652. Dimension dim = new Dimension(viewport.getIPD(), viewport.getBPD());
  653. viewportDimensionStack.push(dim);
  654. documentHandler.getContext().setRegionType(viewport.getRegionReference().getRegionClass());
  655. super.renderRegionViewport(viewport);
  656. viewportDimensionStack.pop();
  657. }
  658. /** {@inheritDoc} */
  659. protected void renderBlockViewport(BlockViewport bv, List children) {
  660. //Essentially the same code as in the super class but optimized for the IF
  661. // Handle new layer.
  662. boolean inNewLayer = false;
  663. if (maybeStartLayer(bv)) {
  664. inNewLayer = true;
  665. }
  666. //This is the content-rect
  667. Dimension dim = new Dimension(bv.getIPD(), bv.getBPD());
  668. viewportDimensionStack.push(dim);
  669. // save positions
  670. int saveIP = currentIPPosition;
  671. int saveBP = currentBPPosition;
  672. CTM ctm = bv.getCTM();
  673. int borderPaddingStart = bv.getBorderAndPaddingWidthStart();
  674. int borderPaddingBefore = bv.getBorderAndPaddingWidthBefore();
  675. if (bv.getPositioning() == Block.ABSOLUTE
  676. || bv.getPositioning() == Block.FIXED) {
  677. //For FIXED, we need to break out of the current viewports to the
  678. //one established by the page. We save the state stack for restoration
  679. //after the block-container has been painted. See below.
  680. List breakOutList = null;
  681. if (bv.getPositioning() == Block.FIXED) {
  682. breakOutList = breakOutOfStateStack();
  683. }
  684. AffineTransform positionTransform = new AffineTransform();
  685. positionTransform.translate(bv.getXOffset(), bv.getYOffset());
  686. //"left/"top" (bv.getX/YOffset()) specify the position of the content rectangle
  687. positionTransform.translate(-borderPaddingStart, -borderPaddingBefore);
  688. //Free transformation for the block-container viewport
  689. String transf;
  690. transf = bv.getForeignAttributeValue(FOX_TRANSFORM);
  691. if (transf != null) {
  692. AffineTransform freeTransform = AWTTransformProducer.createAffineTransform(transf);
  693. positionTransform.concatenate(freeTransform);
  694. }
  695. saveGraphicsState();
  696. //Viewport position
  697. concatenateTransformationMatrixMpt(positionTransform, false);
  698. //Background and borders
  699. float bpwidth = (borderPaddingStart + bv.getBorderAndPaddingWidthEnd());
  700. float bpheight = (borderPaddingBefore + bv.getBorderAndPaddingWidthAfter());
  701. drawBackAndBorders(bv, 0, 0,
  702. (dim.width + bpwidth) / 1000f, (dim.height + bpheight) / 1000f);
  703. //Shift to content rectangle after border painting
  704. AffineTransform contentRectTransform = new AffineTransform();
  705. contentRectTransform.translate(borderPaddingStart, borderPaddingBefore);
  706. concatenateTransformationMatrixMpt(contentRectTransform, false);
  707. //saveGraphicsState();
  708. //Set up coordinate system for content rectangle
  709. AffineTransform contentTransform = ctm.toAffineTransform();
  710. //concatenateTransformationMatrixMpt(contentTransform);
  711. startViewport(contentTransform, bv.getClipRectangle());
  712. currentIPPosition = 0;
  713. currentBPPosition = 0;
  714. renderBlocks(bv, children);
  715. endViewport();
  716. //restoreGraphicsState();
  717. restoreGraphicsState();
  718. if (breakOutList != null) {
  719. restoreStateStackAfterBreakOut(breakOutList);
  720. }
  721. currentIPPosition = saveIP;
  722. currentBPPosition = saveBP;
  723. } else {
  724. currentBPPosition += bv.getSpaceBefore();
  725. //borders and background in the old coordinate system
  726. handleBlockTraits(bv);
  727. //Advance to start of content area
  728. currentIPPosition += bv.getStartIndent();
  729. CTM tempctm = new CTM(containingIPPosition, currentBPPosition);
  730. ctm = tempctm.multiply(ctm);
  731. //Now adjust for border/padding
  732. currentBPPosition += borderPaddingBefore;
  733. startVParea(ctm, bv.getClipRectangle());
  734. currentIPPosition = 0;
  735. currentBPPosition = 0;
  736. renderBlocks(bv, children);
  737. endVParea();
  738. currentIPPosition = saveIP;
  739. currentBPPosition = saveBP;
  740. currentBPPosition += bv.getAllocBPD();
  741. }
  742. viewportDimensionStack.pop();
  743. maybeEndLayer(bv, inNewLayer);
  744. }
  745. /** {@inheritDoc} */
  746. public void renderInlineViewport(InlineViewport viewport) {
  747. StructureTreeElement structElem
  748. = (StructureTreeElement) viewport.getTrait(Trait.STRUCTURE_TREE_ELEMENT);
  749. establishStructureTreeElement(structElem);
  750. pushID(viewport);
  751. Dimension dim = new Dimension(viewport.getIPD(), viewport.getBPD());
  752. viewportDimensionStack.push(dim);
  753. super.renderInlineViewport(viewport);
  754. viewportDimensionStack.pop();
  755. resetStructurePointer();
  756. popID(viewport);
  757. }
  758. /** {@inheritDoc} */
  759. protected void startVParea(CTM ctm, Rectangle clippingRect) {
  760. if (log.isTraceEnabled()) {
  761. log.trace("startVParea() ctm=" + ctm + ", clippingRect=" + clippingRect);
  762. }
  763. AffineTransform at = new AffineTransform(ctm.toArray());
  764. startViewport(at, clippingRect);
  765. if (log.isTraceEnabled()) {
  766. log.trace("startVPArea: " + at + " --> " + graphicContext.getTransform());
  767. }
  768. }
  769. private void startViewport(AffineTransform at, Rectangle clipRect) {
  770. saveGraphicsState();
  771. try {
  772. IFGraphicContext.Viewport viewport = new IFGraphicContext.Viewport(
  773. at, (Dimension)viewportDimensionStack.peek(), clipRect);
  774. graphicContext.pushGroup(viewport);
  775. viewport.start(painter);
  776. } catch (IFException e) {
  777. handleIFException(e);
  778. }
  779. }
  780. /** {@inheritDoc} */
  781. protected void endVParea() {
  782. log.trace("endVParea()");
  783. endViewport();
  784. if (log.isTraceEnabled()) {
  785. log.trace("endVPArea() --> " + graphicContext.getTransform());
  786. }
  787. }
  788. private void endViewport() {
  789. restoreGraphicsState();
  790. }
  791. /** {@inheritDoc} */
  792. protected void startLayer(String layer) {
  793. if (log.isTraceEnabled()) {
  794. log.trace("startLayer() layer=" + layer);
  795. }
  796. saveGraphicsState();
  797. pushGroup(new IFGraphicContext.Group(layer));
  798. }
  799. /** {@inheritDoc} */
  800. protected void endLayer() {
  801. if (log.isTraceEnabled()) {
  802. log.trace("endLayer()");
  803. }
  804. restoreGraphicsState();
  805. }
  806. /** {@inheritDoc} */
  807. protected void renderInlineArea(InlineArea inlineArea) {
  808. saveInlinePosIfTargetable(inlineArea);
  809. pushID(inlineArea);
  810. super.renderInlineArea(inlineArea);
  811. popID(inlineArea);
  812. }
  813. /** {@inheritDoc} */
  814. public void renderInlineParent(InlineParent ip) {
  815. // stuff we only need if a link must be created:
  816. Rectangle ipRect = null;
  817. AbstractAction action = null;
  818. // make sure the rect is determined *before* calling super!
  819. int ipp = currentIPPosition;
  820. int bpp = currentBPPosition + ip.getBlockProgressionOffset();
  821. ipRect = new Rectangle(ipp, bpp, ip.getIPD(), ip.getBPD());
  822. AffineTransform transform = graphicContext.getTransform();
  823. ipRect = transform.createTransformedShape(ipRect).getBounds();
  824. // render contents
  825. super.renderInlineParent(ip);
  826. boolean linkTraitFound = false;
  827. // try INTERNAL_LINK first
  828. Trait.InternalLink intLink = (Trait.InternalLink) ip.getTrait(Trait.INTERNAL_LINK);
  829. if (intLink != null) {
  830. linkTraitFound = true;
  831. String pvKey = intLink.getPVKey();
  832. String idRef = intLink.getIDRef();
  833. boolean pvKeyOK = pvKey != null && pvKey.length() > 0;
  834. boolean idRefOK = idRef != null && idRef.length() > 0;
  835. if (pvKeyOK && idRefOK) {
  836. Integer pageIndex = (Integer)pageIndices.get(pvKey);
  837. action = getGoToActionForID(idRef, (pageIndex != null ? pageIndex : -1));
  838. } else {
  839. //Warnings already issued by AreaTreeHandler
  840. }
  841. }
  842. // no INTERNAL_LINK, look for EXTERNAL_LINK
  843. if (!linkTraitFound) {
  844. Trait.ExternalLink extLink = (Trait.ExternalLink) ip.getTrait(Trait.EXTERNAL_LINK);
  845. if (extLink != null) {
  846. String extDest = extLink.getDestination();
  847. if (extDest != null && extDest.length() > 0) {
  848. linkTraitFound = true;
  849. action = new URIAction(extDest, extLink.newWindow());
  850. action = actionSet.put(action);
  851. }
  852. }
  853. }
  854. // warn if link trait found but not allowed, else create link
  855. if (linkTraitFound) {
  856. StructureTreeElement structElem
  857. = (StructureTreeElement) ip.getTrait(Trait.STRUCTURE_TREE_ELEMENT);
  858. action.setStructureTreeElement(structElem);
  859. Link link = new Link(action, ipRect);
  860. this.deferredLinks.add(link);
  861. }
  862. }
  863. /** {@inheritDoc} */
  864. protected void renderBlock(Block block) {
  865. if (log.isTraceEnabled()) {
  866. log.trace("renderBlock() " + block);
  867. }
  868. saveBlockPosIfTargetable(block);
  869. pushID(block);
  870. IFContext context = documentHandler.getContext();
  871. Locale oldLocale = context.getLanguage();
  872. context.setLanguage(block.getLocale());
  873. String oldLocation = context.getLocation();
  874. context.setLocation(block.getLocation());
  875. super.renderBlock(block);
  876. context.setLocation(oldLocation);
  877. context.setLanguage(oldLocale);
  878. popID(block);
  879. }
  880. private void pushID(Area area) {
  881. String prodID = (String) area.getTrait(Trait.PROD_ID);
  882. if (prodID != null) {
  883. ids.push(prodID);
  884. documentHandler.getContext().setID(prodID);
  885. }
  886. }
  887. private void popID(Area area) {
  888. String prodID = (String) area.getTrait(Trait.PROD_ID);
  889. if (prodID != null) {
  890. ids.pop();
  891. documentHandler.getContext().setID(ids.empty() ? "" : ids.peek());
  892. }
  893. }
  894. private Typeface getTypeface(String fontName) {
  895. Typeface tf = fontInfo.getFonts().get(fontName);
  896. if (tf instanceof LazyFont) {
  897. tf = ((LazyFont)tf).getRealFont();
  898. }
  899. return tf;
  900. }
  901. /** {@inheritDoc} */
  902. protected void renderText(TextArea text) {
  903. if (log.isTraceEnabled()) {
  904. log.trace("renderText() " + text);
  905. }
  906. renderInlineAreaBackAndBorders(text);
  907. Color ct = (Color) text.getTrait(Trait.COLOR);
  908. beginTextObject();
  909. String fontName = getInternalFontNameForArea(text);
  910. int size = (Integer) text.getTrait(Trait.FONT_SIZE);
  911. StructureTreeElement structElem
  912. = (StructureTreeElement) text.getTrait(Trait.STRUCTURE_TREE_ELEMENT);
  913. establishStructureTreeElement(structElem);
  914. // This assumes that *all* CIDFonts use a /ToUnicode mapping
  915. Typeface tf = getTypeface(fontName);
  916. FontTriplet triplet = (FontTriplet)text.getTrait(Trait.FONT);
  917. try {
  918. painter.setFont(triplet.getName(), triplet.getStyle(), triplet.getWeight(),
  919. "normal", size, ct);
  920. } catch (IFException e) {
  921. handleIFException(e);
  922. }
  923. int rx = currentIPPosition + text.getBorderAndPaddingWidthStart();
  924. int bl = currentBPPosition + text.getBlockProgressionOffset() + text.getBaselineOffset();
  925. textUtil.flush();
  926. textUtil.setStartPosition(rx, bl);
  927. textUtil.setSpacing(text.getTextLetterSpaceAdjust(), text.getTextWordSpaceAdjust());
  928. documentHandler.getContext().setHyphenated(text.isHyphenated());
  929. super.renderText(text);
  930. textUtil.flush();
  931. renderTextDecoration(tf, size, text, bl, rx);
  932. documentHandler.getContext().setHyphenated(false);
  933. resetStructurePointer();
  934. }
  935. /** {@inheritDoc} */
  936. protected void renderWord(WordArea word) {
  937. Font font = getFontFromArea(word.getParentArea());
  938. String s = word.getWord();
  939. int[][] dp = word.getGlyphPositionAdjustments();
  940. Area parentArea = word.getParentArea();
  941. assert (parentArea instanceof AbstractTextArea);
  942. if (dp == null) {
  943. renderTextWithAdjustments(s, word.getLetterAdjustArray(), word.isReversed(),
  944. font, (AbstractTextArea) parentArea);
  945. } else if (IFUtil.isDPOnlyDX(dp)) {
  946. renderTextWithAdjustments(s, IFUtil.convertDPToDX(dp), word.isReversed(),
  947. font, (AbstractTextArea) parentArea);
  948. } else {
  949. renderTextWithAdjustments(s, dp, word.isReversed(),
  950. font, (AbstractTextArea) parentArea);
  951. }
  952. textUtil.nextIsSpace = word.isNextIsSpace();
  953. super.renderWord(word);
  954. }
  955. /** {@inheritDoc} */
  956. protected void renderSpace(SpaceArea space) {
  957. Font font = getFontFromArea(space.getParentArea());
  958. String s = space.getSpace();
  959. Area parentArea = space.getParentArea();
  960. assert (parentArea instanceof AbstractTextArea);
  961. AbstractTextArea textArea = (AbstractTextArea) parentArea;
  962. renderTextWithAdjustments(s, (int[]) null, false, font, textArea);
  963. /* COMBINED is always false
  964. if (textUtil.COMBINED && space.isAdjustable()) {
  965. //Used for justified text, for example
  966. int tws = textArea.getTextWordSpaceAdjust()
  967. + 2 * textArea.getTextLetterSpaceAdjust();
  968. if (tws != 0) {
  969. textUtil.adjust(tws);
  970. }
  971. }
  972. */
  973. super.renderSpace(space);
  974. }
  975. /**
  976. * Does low-level rendering of text using DX only position adjustments.
  977. * @param s text to render
  978. * @param dx an array of widths for letter adjustment (may be null)
  979. * @param reversed if true then text has been reversed (from logical order)
  980. * @param font to font in use
  981. * @param parentArea the parent text area to retrieve certain traits from
  982. */
  983. private void renderTextWithAdjustments(String s,
  984. int[] dx, boolean reversed,
  985. Font font, AbstractTextArea parentArea) {
  986. int l = s.length();
  987. if (l == 0) {
  988. return;
  989. }
  990. for (int i = 0; i < l; i++) {
  991. char ch = s.charAt(i);
  992. textUtil.addChar(ch);
  993. int glyphAdjust = 0;
  994. /* COMBINED is always false
  995. if (textUtil.COMBINED && font.hasChar(ch)) {
  996. int tls = (i < l - 1 ? parentArea.getTextLetterSpaceAdjust() : 0);
  997. glyphAdjust += tls;
  998. }
  999. */
  1000. if (dx != null && i < l) {
  1001. glyphAdjust += dx[i];
  1002. }
  1003. textUtil.adjust(glyphAdjust);
  1004. }
  1005. }
  1006. /**
  1007. * Does low-level rendering of text using generalized position adjustments.
  1008. * @param s text to render
  1009. * @param dp an array of 4-tuples, expressing [X,Y] placment
  1010. * adjustments and [X,Y] advancement adjustments, in that order (may be null)
  1011. * @param reversed if true then text has been reversed (from logical order)
  1012. * @param font to font in use
  1013. * @param parentArea the parent text area to retrieve certain traits from
  1014. */
  1015. private void renderTextWithAdjustments(String s,
  1016. int[][] dp, boolean reversed,
  1017. Font font, AbstractTextArea parentArea) {
  1018. // assert !textUtil.COMBINED;
  1019. for (int i = 0, n = s.length(); i < n; i++) {
  1020. textUtil.addChar(s.charAt(i));
  1021. if (dp != null) {
  1022. textUtil.adjust(dp[i]);
  1023. }
  1024. }
  1025. }
  1026. private class TextUtil {
  1027. private static final int INITIAL_BUFFER_SIZE = 16;
  1028. private int[][] dp = new int[INITIAL_BUFFER_SIZE][];
  1029. private final StringBuffer text = new StringBuffer();
  1030. private int startx;
  1031. private int starty;
  1032. private int tls;
  1033. private int tws;
  1034. private boolean nextIsSpace;
  1035. void addChar(char ch) {
  1036. text.append(ch);
  1037. }
  1038. void adjust(int dx) {
  1039. if (dx != 0) {
  1040. adjust(new int[] {
  1041. dx, // xPlaAdjust
  1042. 0, // yPlaAdjust
  1043. dx, // xAdvAdjust
  1044. 0 // yAdvAdjust
  1045. });
  1046. }
  1047. }
  1048. void adjust(int[] pa) {
  1049. if (!IFUtil.isPAIdentity(pa)) {
  1050. int idx = text.length();
  1051. if (idx > dp.length - 1) {
  1052. int newSize = Math.max(dp.length, idx + 1) + INITIAL_BUFFER_SIZE;
  1053. int[][] newDP = new int[newSize][];
  1054. // reuse prior DP[0]...DP[dp.length-1]
  1055. System.arraycopy(dp, 0, newDP, 0, dp.length);
  1056. // switch to new DP, leaving DP[dp.length]...DP[newDP.length-1] unpopulated
  1057. dp = newDP;
  1058. }
  1059. if (dp[idx - 1] == null) {
  1060. dp[idx - 1] = new int[4];
  1061. }
  1062. IFUtil.adjustPA(dp[idx - 1], pa);
  1063. }
  1064. }
  1065. void reset() {
  1066. if (text.length() > 0) {
  1067. text.setLength(0);
  1068. for (int i = 0, n = dp.length; i < n; i++) {
  1069. dp[i] = null;
  1070. }
  1071. }
  1072. }
  1073. void setStartPosition(int x, int y) {
  1074. this.startx = x;
  1075. this.starty = y;
  1076. }
  1077. void setSpacing(int tls, int tws) {
  1078. this.tls = tls;
  1079. this.tws = tws;
  1080. }
  1081. void flush() {
  1082. if (text.length() > 0) {
  1083. try {
  1084. /* if (COMBINED) { // COMBINED is always false
  1085. painter.drawText(startx, starty, 0, 0,
  1086. trimAdjustments(dp, text.length()), text.toString());
  1087. } else { */
  1088. painter.drawText(startx, starty, tls, tws,
  1089. trimAdjustments(dp, text.length()), text.toString(), nextIsSpace);
  1090. /* } */
  1091. } catch (IFException e) {
  1092. handleIFException(e);
  1093. }
  1094. reset();
  1095. }
  1096. }
  1097. void drawText(int x, int y, int letterSpacing, int wordSpacing, int[][] dx, String text, boolean nextIsSpace)
  1098. throws IFException {
  1099. painter.drawText(startx, starty, tls, tws, dx, text, nextIsSpace);
  1100. }
  1101. /**
  1102. * Trim adjustments array <code>dp</code> to be no greater length than
  1103. * text length, and where trailing all-zero entries are removed.
  1104. * @param dp a position adjustments array (or null)
  1105. * @param textLength the length of the associated text
  1106. * @return either the original value of <code>dp</code> or a copy
  1107. * of its first N significant adjustment entries, such that N is
  1108. * no greater than text length, and the last entry has a non-zero
  1109. * adjustment.
  1110. */
  1111. private int[][] trimAdjustments(int[][] dp, int textLength) {
  1112. if (dp != null) {
  1113. int tl = textLength;
  1114. int pl = dp.length;
  1115. int i = (tl < pl) ? tl : pl;
  1116. while (i > 0) {
  1117. int[] pa = dp [ i - 1 ];
  1118. if ((pa != null) && !IFUtil.isPAIdentity(pa)) {
  1119. break;
  1120. } else {
  1121. i--;
  1122. }
  1123. }
  1124. if (i == 0) {
  1125. dp = null;
  1126. } else if (i < pl) {
  1127. dp = IFUtil.copyDP(dp, 0, i);
  1128. }
  1129. }
  1130. return dp;
  1131. }
  1132. }
  1133. /** {@inheritDoc} */
  1134. public void renderImage(Image image, Rectangle2D pos) {
  1135. drawImage(image.getURL(), pos, image.getForeignAttributes());
  1136. }
  1137. /** {@inheritDoc} */
  1138. protected void drawImage(String uri, Rectangle2D pos, Map foreignAttributes) {
  1139. Rectangle posInt = new Rectangle(
  1140. currentIPPosition + (int)pos.getX(),
  1141. currentBPPosition + (int)pos.getY(),
  1142. (int)pos.getWidth(),
  1143. (int)pos.getHeight());
  1144. uri = URISpecification.getURL(uri);
  1145. try {
  1146. establishForeignAttributes(foreignAttributes);
  1147. painter.drawImage(uri, posInt);
  1148. resetForeignAttributes();
  1149. } catch (IFException ife) {
  1150. handleIFException(ife);
  1151. }
  1152. }
  1153. /** {@inheritDoc} */
  1154. public void renderForeignObject(ForeignObject fo, Rectangle2D pos) {
  1155. endTextObject();
  1156. Rectangle posInt = new Rectangle(
  1157. currentIPPosition + (int)pos.getX(),
  1158. currentBPPosition + (int)pos.getY(),
  1159. (int)pos.getWidth(),
  1160. (int)pos.getHeight());
  1161. Document doc = fo.getDocument();
  1162. try {
  1163. establishForeignAttributes(fo.getForeignAttributes());
  1164. painter.drawImage(doc, posInt);
  1165. resetForeignAttributes();
  1166. } catch (IFException ife) {
  1167. handleIFException(ife);
  1168. }
  1169. }
  1170. /** {@inheritDoc} */
  1171. public void renderLeader(Leader area) {
  1172. renderInlineAreaBackAndBorders(area);
  1173. int style = area.getRuleStyle();
  1174. int ruleThickness = area.getRuleThickness();
  1175. int startx = currentIPPosition + area.getBorderAndPaddingWidthStart();
  1176. int starty = currentBPPosition + area.getBlockProgressionOffset() + (ruleThickness / 2);
  1177. int endx = currentIPPosition
  1178. + area.getBorderAndPaddingWidthStart()
  1179. + area.getIPD();
  1180. Color col = (Color)area.getTrait(Trait.COLOR);
  1181. Point start = new Point(startx, starty);
  1182. Point end = new Point(endx, starty);
  1183. try {
  1184. painter.drawLine(start, end, ruleThickness, col, RuleStyle.valueOf(style));
  1185. } catch (IFException ife) {
  1186. handleIFException(ife);
  1187. }
  1188. super.renderLeader(area);
  1189. }
  1190. /** {@inheritDoc} */
  1191. protected void clip() {
  1192. throw new IllegalStateException("Not used");
  1193. }
  1194. /** {@inheritDoc} */
  1195. protected void clipRect(float x, float y, float width, float height) {
  1196. pushGroup(new IFGraphicContext.Group());
  1197. try {
  1198. painter.clipRect(toMillipointRectangle(x, y, width, height));
  1199. } catch (IFException ife) {
  1200. handleIFException(ife);
  1201. }
  1202. }
  1203. /** {@inheritDoc} */
  1204. protected void clipBackground(float startx, float starty,
  1205. float width, float height,
  1206. BorderProps bpsBefore, BorderProps bpsAfter,
  1207. BorderProps bpsStart, BorderProps bpsEnd) {
  1208. pushGroup(new IFGraphicContext.Group());
  1209. Rectangle rect = toMillipointRectangle(startx, starty, width, height);
  1210. try {
  1211. painter.clipBackground(rect,
  1212. bpsBefore, bpsAfter, bpsStart, bpsEnd);
  1213. } catch (IFException ife) {
  1214. handleIFException(ife);
  1215. }
  1216. }
  1217. /** {@inheritDoc} */
  1218. protected void closePath() {
  1219. throw new IllegalStateException("Not used");
  1220. }
  1221. /** {@inheritDoc} */
  1222. protected void drawBackground(float startx, float starty,
  1223. float width, float height,
  1224. Trait.Background back,
  1225. BorderProps bpsBefore, BorderProps bpsAfter,
  1226. BorderProps bpsStart, BorderProps bpsEnd) {
  1227. if (painter.isBackgroundRequired(bpsBefore, bpsAfter, bpsStart, bpsEnd)) {
  1228. super.drawBackground(startx, starty, width, height,
  1229. back, bpsBefore, bpsAfter,
  1230. bpsStart, bpsEnd);
  1231. }
  1232. }
  1233. /** {@inheritDoc} */
  1234. protected void drawBorders(float startx, float starty,
  1235. float width, float height,
  1236. BorderProps bpsBefore, BorderProps bpsAfter,
  1237. BorderProps bpsStart, BorderProps bpsEnd, int level, Color innerBackgroundColor) {
  1238. Rectangle rect = toMillipointRectangle(startx, starty, width, height);
  1239. try {
  1240. BorderProps bpsTop = bpsBefore;
  1241. BorderProps bpsBottom = bpsAfter;
  1242. BorderProps bpsLeft;
  1243. BorderProps bpsRight;
  1244. if ((level == -1) || ((level & 1) == 0)) {
  1245. bpsLeft = bpsStart;
  1246. bpsRight = bpsEnd;
  1247. } else {
  1248. bpsLeft = bpsEnd;
  1249. bpsRight = bpsStart;
  1250. }
  1251. painter.drawBorderRect(rect, bpsTop, bpsBottom, bpsLeft, bpsRight, innerBackgroundColor);
  1252. } catch (IFException ife) {
  1253. handleIFException(ife);
  1254. }
  1255. }
  1256. /** {@inheritDoc} */
  1257. protected void drawBorderLine(float x1, float y1, float x2, float y2, boolean horz,
  1258. boolean startOrBefore, int style, Color col) {
  1259. //Simplified implementation that is only used by renderTextDecoration()
  1260. //drawBorders() is overridden and uses the Painter's high-level method drawBorderRect()
  1261. updateColor(col, true);
  1262. fillRect(x1, y1, x2 - x1, y2 - y1);
  1263. }
  1264. private int toMillipoints(float coordinate) {
  1265. return Math.round(coordinate * 1000);
  1266. }
  1267. private Rectangle toMillipointRectangle(float x, float y, float width, float height) {
  1268. return new Rectangle(
  1269. toMillipoints(x),
  1270. toMillipoints(y),
  1271. toMillipoints(width),
  1272. toMillipoints(height));
  1273. }
  1274. /** {@inheritDoc} */
  1275. protected void fillRect(float x, float y, float width, float height) {
  1276. try {
  1277. painter.fillRect(
  1278. toMillipointRectangle(x, y, width, height),
  1279. this.graphicContext.getPaint());
  1280. } catch (IFException e) {
  1281. handleIFException(e);
  1282. }
  1283. }
  1284. /** {@inheritDoc} */
  1285. protected void moveTo(float x, float y) {
  1286. throw new IllegalStateException("Not used");
  1287. }
  1288. /** {@inheritDoc} */
  1289. protected void lineTo(float x, float y) {
  1290. throw new IllegalStateException("Not used");
  1291. }
  1292. /** {@inheritDoc} */
  1293. protected void updateColor(Color col, boolean fill) {
  1294. if (fill) {
  1295. this.graphicContext.setPaint(col);
  1296. } else {
  1297. this.graphicContext.setColor(col);
  1298. }
  1299. }
  1300. }