| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* $Id$ */ |
| |
| package org.apache.fop.render.intermediate; |
| |
| import java.awt.Color; |
| import java.awt.Dimension; |
| import java.awt.Point; |
| import java.awt.Rectangle; |
| import java.awt.geom.AffineTransform; |
| import java.awt.geom.Rectangle2D; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Stack; |
| |
| import javax.xml.transform.stream.StreamResult; |
| |
| import org.w3c.dom.Document; |
| import org.xml.sax.SAXException; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| import org.apache.batik.parser.AWTTransformProducer; |
| |
| import org.apache.xmlgraphics.xmp.Metadata; |
| import org.apache.xmlgraphics.xmp.schemas.DublinCoreAdapter; |
| import org.apache.xmlgraphics.xmp.schemas.DublinCoreSchema; |
| import org.apache.xmlgraphics.xmp.schemas.XMPBasicAdapter; |
| import org.apache.xmlgraphics.xmp.schemas.XMPBasicSchema; |
| |
| import org.apache.fop.Version; |
| import org.apache.fop.accessibility.StructureTreeElement; |
| import org.apache.fop.apps.FOPException; |
| import org.apache.fop.apps.FOUserAgent; |
| import org.apache.fop.apps.MimeConstants; |
| import org.apache.fop.area.Area; |
| import org.apache.fop.area.AreaTreeObject; |
| import org.apache.fop.area.Block; |
| import org.apache.fop.area.BlockViewport; |
| import org.apache.fop.area.BookmarkData; |
| import org.apache.fop.area.CTM; |
| import org.apache.fop.area.DestinationData; |
| import org.apache.fop.area.OffDocumentExtensionAttachment; |
| import org.apache.fop.area.OffDocumentItem; |
| import org.apache.fop.area.PageSequence; |
| import org.apache.fop.area.PageViewport; |
| import org.apache.fop.area.RegionViewport; |
| import org.apache.fop.area.Trait; |
| import org.apache.fop.area.inline.AbstractTextArea; |
| import org.apache.fop.area.inline.BasicLinkArea; |
| import org.apache.fop.area.inline.ForeignObject; |
| import org.apache.fop.area.inline.Image; |
| import org.apache.fop.area.inline.InlineArea; |
| import org.apache.fop.area.inline.InlineParent; |
| import org.apache.fop.area.inline.InlineViewport; |
| import org.apache.fop.area.inline.Leader; |
| import org.apache.fop.area.inline.SpaceArea; |
| import org.apache.fop.area.inline.TextArea; |
| import org.apache.fop.area.inline.WordArea; |
| import org.apache.fop.datatypes.URISpecification; |
| import org.apache.fop.fo.extensions.ExtensionAttachment; |
| import org.apache.fop.fo.extensions.xmp.XMPMetadata; |
| import org.apache.fop.fonts.Font; |
| import org.apache.fop.fonts.FontInfo; |
| import org.apache.fop.fonts.FontTriplet; |
| import org.apache.fop.fonts.LazyFont; |
| import org.apache.fop.fonts.Typeface; |
| import org.apache.fop.render.AbstractPathOrientedRenderer; |
| import org.apache.fop.render.Renderer; |
| import org.apache.fop.render.intermediate.extensions.AbstractAction; |
| import org.apache.fop.render.intermediate.extensions.ActionSet; |
| import org.apache.fop.render.intermediate.extensions.Bookmark; |
| import org.apache.fop.render.intermediate.extensions.BookmarkTree; |
| import org.apache.fop.render.intermediate.extensions.GoToXYAction; |
| import org.apache.fop.render.intermediate.extensions.Link; |
| import org.apache.fop.render.intermediate.extensions.NamedDestination; |
| import org.apache.fop.render.intermediate.extensions.URIAction; |
| import org.apache.fop.traits.BorderProps; |
| import org.apache.fop.traits.RuleStyle; |
| |
| /** |
| * This renderer implementation is an adapter to the {@link IFPainter} interface. It is used |
| * to generate content using FOP's intermediate format. |
| */ |
| public class IFRenderer extends AbstractPathOrientedRenderer { |
| |
| //TODO Many parts of the Renderer infrastructure are using floats (coordinates in points) |
| //instead of ints (in millipoints). A lot of conversion to and from is performed. |
| //When the new IF is established, the Renderer infrastructure should be revisited so check |
| //if optimizations can be done to avoid int->float->int conversions. |
| |
| /** logging instance */ |
| protected static final Log log = LogFactory.getLog(IFRenderer.class); |
| |
| /** XML MIME type */ |
| public static final String IF_MIME_TYPE = MimeConstants.MIME_FOP_IF; |
| |
| private IFDocumentHandler documentHandler; |
| private IFPainter painter; |
| |
| /** If not null, the XMLRenderer will mimic another renderer by using its font setup. */ |
| protected Renderer mimic; |
| |
| private boolean inPageSequence; |
| |
| private Stack graphicContextStack = new Stack(); |
| private Stack viewportDimensionStack = new Stack(); |
| private IFGraphicContext graphicContext = new IFGraphicContext(); |
| //private Stack groupStack = new Stack(); |
| |
| private Metadata documentMetadata; |
| |
| /** |
| * Maps XSL-FO element IDs to their on-page XY-positions |
| * Must be used in conjunction with the page reference to fully specify the details |
| * of a "go-to" action. |
| */ |
| private Map idPositions = new java.util.HashMap(); |
| |
| /** |
| * The "go-to" actions in idGoTos that are not complete yet |
| */ |
| private List unfinishedGoTos = new java.util.ArrayList(); |
| // can't use a Set because PDFGoTo.equals returns true if the target is the same, |
| // even if the object number differs |
| |
| /** Maps unique PageViewport key to page indices (for link target handling) */ |
| protected Map pageIndices = new java.util.HashMap(); |
| |
| private BookmarkTree bookmarkTree; |
| private List deferredDestinations = new java.util.ArrayList(); |
| private List deferredLinks = new java.util.ArrayList(); |
| private ActionSet actionSet = new ActionSet(); |
| |
| private TextUtil textUtil = new TextUtil(); |
| |
| private Stack<String> ids = new Stack<String>(); |
| |
| /** |
| * Main constructor |
| * |
| * @param userAgent the user agent that contains configuration details. This cannot be null. |
| */ |
| public IFRenderer(FOUserAgent userAgent) { |
| super(userAgent); |
| } |
| |
| /** {@inheritDoc} */ |
| public String getMimeType() { |
| return IF_MIME_TYPE; |
| } |
| |
| /** |
| * Sets the {@link IFDocumentHandler} to be used by the {@link IFRenderer}. |
| * @param documentHandler the {@link IFDocumentHandler} |
| */ |
| public void setDocumentHandler(IFDocumentHandler documentHandler) { |
| this.documentHandler = documentHandler; |
| } |
| |
| /** {@inheritDoc} */ |
| public void setupFontInfo(FontInfo inFontInfo) throws FOPException { |
| if (this.documentHandler == null) { |
| this.documentHandler = createDefaultDocumentHandler(); |
| } |
| IFUtil.setupFonts(this.documentHandler, inFontInfo); |
| this.fontInfo = inFontInfo; |
| } |
| |
| private void handleIFException(IFException ife) { |
| if (ife.getCause() instanceof SAXException) { |
| throw new RuntimeException(ife.getCause()); |
| } else { |
| throw new RuntimeException(ife); |
| } |
| } |
| |
| private void handleIFExceptionWithIOException(IFException ife) throws IOException { |
| Throwable cause = ife.getCause(); |
| if (cause instanceof IOException) { |
| throw (IOException) cause; |
| } else { |
| handleIFException(ife); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public boolean supportsOutOfOrder() { |
| return (this.documentHandler != null |
| ? this.documentHandler.supportsPagesOutOfOrder() : false); |
| } |
| |
| /** |
| * Returns the document navigation handler if available/supported. |
| * @return the document navigation handler or null if not supported |
| */ |
| protected IFDocumentNavigationHandler getDocumentNavigationHandler() { |
| return this.documentHandler.getDocumentNavigationHandler(); |
| } |
| |
| /** |
| * Indicates whether document navigation features are supported by the document handler. |
| * @return true if document navigation features are available |
| */ |
| protected boolean hasDocumentNavigation() { |
| return getDocumentNavigationHandler() != null; |
| } |
| |
| /** |
| * Creates a default {@link IFDocumentHandler} when none has been set. |
| * @return the default IFDocumentHandler |
| */ |
| protected IFDocumentHandler createDefaultDocumentHandler() { |
| FOUserAgent userAgent = getUserAgent(); |
| IFSerializer serializer = new IFSerializer(new IFContext(userAgent)); |
| if (userAgent.isAccessibilityEnabled()) { |
| userAgent.setStructureTreeEventHandler(serializer.getStructureTreeEventHandler()); |
| } |
| return serializer; |
| } |
| |
| /** {@inheritDoc} */ |
| public void startRenderer(OutputStream outputStream) |
| throws IOException { |
| try { |
| if (outputStream != null) { |
| StreamResult result = new StreamResult(outputStream); |
| if (getUserAgent().getOutputFile() != null) { |
| result.setSystemId( |
| getUserAgent().getOutputFile().toURI().toURL().toExternalForm()); |
| } |
| if (this.documentHandler == null) { |
| this.documentHandler = createDefaultDocumentHandler(); |
| } |
| this.documentHandler.setResult(result); |
| } |
| super.startRenderer(null); |
| if (log.isDebugEnabled()) { |
| log.debug("Rendering areas via IF document handler (" |
| + this.documentHandler.getClass().getName() + ")..."); |
| } |
| documentHandler.startDocument(); |
| documentHandler.startDocumentHeader(); |
| } catch (IFException e) { |
| handleIFExceptionWithIOException(e); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public void stopRenderer() throws IOException { |
| try { |
| if (this.inPageSequence) { |
| documentHandler.endPageSequence(); |
| this.inPageSequence = false; |
| } |
| documentHandler.startDocumentTrailer(); |
| |
| //Wrap up document navigation |
| if (hasDocumentNavigation()) { |
| finishOpenGoTos(); |
| Iterator iter = this.deferredDestinations.iterator(); |
| while (iter.hasNext()) { |
| NamedDestination dest = (NamedDestination)iter.next(); |
| iter.remove(); |
| getDocumentNavigationHandler().renderNamedDestination(dest); |
| } |
| |
| if (this.bookmarkTree != null) { |
| getDocumentNavigationHandler().renderBookmarkTree(this.bookmarkTree); |
| } |
| } |
| |
| documentHandler.endDocumentTrailer(); |
| documentHandler.endDocument(); |
| } catch (IFException e) { |
| handleIFExceptionWithIOException(e); |
| } |
| pageIndices.clear(); |
| idPositions.clear(); |
| actionSet.clear(); |
| super.stopRenderer(); |
| log.debug("Rendering finished."); |
| } |
| |
| @Override |
| public void setDocumentLocale(Locale locale) { |
| documentHandler.setDocumentLocale(locale); |
| } |
| |
| /** {@inheritDoc} */ |
| public void processOffDocumentItem(OffDocumentItem odi) { |
| if (odi instanceof DestinationData) { |
| // render Destinations |
| renderDestination((DestinationData) odi); |
| } else if (odi instanceof BookmarkData) { |
| // render Bookmark-Tree |
| renderBookmarkTree((BookmarkData) odi); |
| } else if (odi instanceof OffDocumentExtensionAttachment) { |
| ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)odi).getAttachment(); |
| if (XMPMetadata.CATEGORY.equals(attachment.getCategory())) { |
| renderXMPMetadata((XMPMetadata)attachment); |
| } else { |
| try { |
| this.documentHandler.handleExtensionObject(attachment); |
| } catch (IFException ife) { |
| handleIFException(ife); |
| } |
| } |
| } |
| } |
| |
| private void renderDestination(DestinationData dd) { |
| if (!hasDocumentNavigation()) { |
| return; |
| } |
| String targetID = dd.getIDRef(); |
| if (targetID == null || targetID.length() == 0) { |
| throw new IllegalArgumentException("DestinationData must contain a ID reference"); |
| } |
| PageViewport pv = dd.getPageViewport(); |
| if (pv != null) { |
| GoToXYAction action = getGoToActionForID(targetID, pv.getPageIndex()); |
| NamedDestination namedDestination = new NamedDestination(targetID, action); |
| this.deferredDestinations.add(namedDestination); |
| } else { |
| //Warning already issued by AreaTreeHandler (debug level is sufficient) |
| log.debug("Unresolved destination item received: " + dd.getIDRef()); |
| } |
| } |
| |
| /** |
| * Renders a Bookmark-Tree object |
| * @param bookmarks the BookmarkData object containing all the Bookmark-Items |
| */ |
| protected void renderBookmarkTree(BookmarkData bookmarks) { |
| assert this.bookmarkTree == null; |
| if (!hasDocumentNavigation()) { |
| return; |
| } |
| this.bookmarkTree = new BookmarkTree(); |
| for (int i = 0; i < bookmarks.getCount(); i++) { |
| BookmarkData ext = bookmarks.getSubData(i); |
| Bookmark b = renderBookmarkItem(ext); |
| bookmarkTree.addBookmark(b); |
| } |
| } |
| |
| private Bookmark renderBookmarkItem(BookmarkData bookmarkItem) { |
| |
| String targetID = bookmarkItem.getIDRef(); |
| if (targetID == null || targetID.length() == 0) { |
| throw new IllegalArgumentException("DestinationData must contain a ID reference"); |
| } |
| GoToXYAction action = null; |
| PageViewport pv = bookmarkItem.getPageViewport(); |
| |
| if (pv != null) { |
| action = getGoToActionForID(targetID, pv.getPageIndex()); |
| } else { |
| //Warning already issued by AreaTreeHandler (debug level is sufficient) |
| log.debug("Bookmark with IDRef \"" + targetID + "\" has a null PageViewport."); |
| } |
| |
| Bookmark b = new Bookmark( |
| bookmarkItem.getBookmarkTitle(), |
| bookmarkItem.showChildItems(), |
| action); |
| for (int i = 0; i < bookmarkItem.getCount(); i++) { |
| b.addChildBookmark(renderBookmarkItem(bookmarkItem.getSubData(i))); |
| } |
| return b; |
| } |
| |
| private void renderXMPMetadata(XMPMetadata metadata) { |
| this.documentMetadata = metadata.getMetadata(); |
| } |
| |
| private GoToXYAction getGoToActionForID(String targetID, int pageIndex) { |
| // Already a GoToXY present for this target? If not, create. |
| GoToXYAction action = (GoToXYAction)actionSet.get(targetID); |
| //GoToXYAction action = (GoToXYAction)idGoTos.get(targetID); |
| if (action == null) { |
| /* if (pageIndex < 0) { |
| //pageIndex = page |
| } */ |
| Point position = (Point)idPositions.get(targetID); |
| // can the GoTo already be fully filled in? |
| if (pageIndex >= 0 && position != null) { |
| action = new GoToXYAction(targetID, pageIndex, position, documentHandler.getContext()); |
| } else { |
| // Not complete yet, can't use getPDFGoTo: |
| action = new GoToXYAction(targetID, pageIndex, null, documentHandler.getContext()); |
| unfinishedGoTos.add(action); |
| } |
| action = (GoToXYAction)actionSet.put(action); |
| //idGoTos.put(targetID, action); |
| } |
| return action; |
| } |
| |
| private void finishOpenGoTos() { |
| int count = unfinishedGoTos.size(); |
| if (count > 0) { |
| Point defaultPos = new Point(0, 0); // top-o-page |
| while (!unfinishedGoTos.isEmpty()) { |
| GoToXYAction action = (GoToXYAction)unfinishedGoTos.get(0); |
| noteGoToPosition(action, defaultPos); |
| } |
| // dysfunctional if pageref is null |
| } |
| } |
| |
| private void noteGoToPosition(GoToXYAction action, Point position) { |
| action.setTargetLocation(position); |
| try { |
| getDocumentNavigationHandler().addResolvedAction(action); |
| } catch (IFException ife) { |
| handleIFException(ife); |
| } |
| unfinishedGoTos.remove(action); |
| } |
| |
| private void noteGoToPosition(GoToXYAction action, PageViewport pv, Point position) { |
| action.setPageIndex(pv.getPageIndex()); |
| noteGoToPosition(action, position); |
| } |
| |
| private void saveAbsolutePosition(String id, PageViewport pv, |
| int relativeIPP, int relativeBPP, AffineTransform tf) { |
| Point position = new Point(relativeIPP, relativeBPP); |
| tf.transform(position, position); |
| idPositions.put(id, position); |
| // is there already a GoTo action waiting to be completed? |
| GoToXYAction action = (GoToXYAction)actionSet.get(id); |
| if (action != null) { |
| noteGoToPosition(action, pv, position); |
| } |
| } |
| |
| private void saveAbsolutePosition(String id, int relativeIPP, int relativeBPP) { |
| saveAbsolutePosition(id, this.currentPageViewport, |
| relativeIPP, relativeBPP, graphicContext.getTransform()); |
| } |
| |
| private void saveBlockPosIfTargetable(Block block) { |
| String id = getTargetableID(block); |
| if (hasDocumentNavigation() && id != null) { |
| // FIXME: Like elsewhere in the renderer code, absolute and relative |
| // directions are happily mixed here. This makes sure that the |
| // links point to the right location, but it is not correct. |
| int ipp = block.getXOffset(); |
| int bpp = block.getYOffset() + block.getSpaceBefore(); |
| int positioning = block.getPositioning(); |
| if (!(positioning == Block.FIXED || positioning == Block.ABSOLUTE)) { |
| ipp += currentIPPosition; |
| bpp += currentBPPosition; |
| } |
| saveAbsolutePosition(id, currentPageViewport, ipp, bpp, graphicContext.getTransform()); |
| } |
| } |
| |
| private void saveInlinePosIfTargetable(InlineArea inlineArea) { |
| String id = getTargetableID(inlineArea); |
| if (hasDocumentNavigation() && id != null) { |
| int extraMarginBefore = 5000; // millipoints |
| int ipp = currentIPPosition; |
| int bpp = currentBPPosition |
| + inlineArea.getBlockProgressionOffset() - extraMarginBefore; |
| saveAbsolutePosition(id, ipp, bpp); |
| } |
| } |
| |
| private String getTargetableID(Area area) { |
| String id = (String) area.getTrait(Trait.PROD_ID); |
| if (id == null || id.length() == 0 |
| || !currentPageViewport.isFirstWithID(id) |
| || idPositions.containsKey(id)) { |
| return null; |
| } else { |
| return id; |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public void startPageSequence(PageSequence pageSequence) { |
| try { |
| if (this.inPageSequence) { |
| documentHandler.endPageSequence(); |
| documentHandler.getContext().setLanguage(null); |
| } else { |
| if (this.documentMetadata == null) { |
| this.documentMetadata = createDefaultDocumentMetadata(); |
| } |
| documentHandler.handleExtensionObject(this.documentMetadata); |
| documentHandler.endDocumentHeader(); |
| this.inPageSequence = true; |
| } |
| establishForeignAttributes(pageSequence.getForeignAttributes()); |
| documentHandler.getContext().setLanguage(pageSequence.getLocale()); |
| documentHandler.startPageSequence(null); |
| resetForeignAttributes(); |
| processExtensionAttachments(pageSequence); |
| } catch (IFException e) { |
| handleIFException(e); |
| } |
| } |
| |
| private Metadata createDefaultDocumentMetadata() { |
| Metadata xmp = new Metadata(); |
| DublinCoreAdapter dc = DublinCoreSchema.getAdapter(xmp); |
| if (getUserAgent().getTitle() != null) { |
| dc.setTitle(getUserAgent().getTitle()); |
| } |
| if (getUserAgent().getAuthor() != null) { |
| dc.addCreator(getUserAgent().getAuthor()); |
| } |
| if (getUserAgent().getKeywords() != null) { |
| dc.addSubject(getUserAgent().getKeywords()); |
| } |
| XMPBasicAdapter xmpBasic = XMPBasicSchema.getAdapter(xmp); |
| if (getUserAgent().getProducer() != null) { |
| xmpBasic.setCreatorTool(getUserAgent().getProducer()); |
| } else { |
| xmpBasic.setCreatorTool(Version.getVersion()); |
| } |
| xmpBasic.setMetadataDate(new java.util.Date()); |
| if (getUserAgent().getCreationDate() != null) { |
| xmpBasic.setCreateDate(getUserAgent().getCreationDate()); |
| } else { |
| xmpBasic.setCreateDate(xmpBasic.getMetadataDate()); |
| } |
| return xmp; |
| } |
| |
| /** {@inheritDoc} */ |
| public void preparePage(PageViewport page) { |
| super.preparePage(page); |
| } |
| |
| /** {@inheritDoc} */ |
| public void renderPage(PageViewport page) throws IOException, FOPException { |
| if (log.isTraceEnabled()) { |
| log.trace("renderPage() " + page); |
| } |
| try { |
| pageIndices.put(page.getKey(), page.getPageIndex()); |
| Rectangle viewArea = page.getViewArea(); |
| Dimension dim = new Dimension(viewArea.width, viewArea.height); |
| |
| establishForeignAttributes(page.getForeignAttributes()); |
| documentHandler.getContext().setPageIndex(page.getPageIndex()); |
| documentHandler.getContext().setPageNumber(page.getPageNumber()); |
| documentHandler.startPage(page.getPageIndex(), page.getPageNumberString(), |
| page.getSimplePageMasterName(), dim); |
| resetForeignAttributes(); |
| documentHandler.startPageHeader(); |
| |
| //Add page attachments to page header |
| processExtensionAttachments(page); |
| |
| documentHandler.endPageHeader(); |
| this.painter = documentHandler.startPageContent(); |
| super.renderPage(page); |
| this.painter = null; |
| documentHandler.endPageContent(); |
| |
| documentHandler.startPageTrailer(); |
| if (hasDocumentNavigation()) { |
| Iterator iter = this.deferredLinks.iterator(); |
| while (iter.hasNext()) { |
| Link link = (Link)iter.next(); |
| iter.remove(); |
| getDocumentNavigationHandler().renderLink(link); |
| } |
| } |
| documentHandler.endPageTrailer(); |
| |
| establishForeignAttributes(page.getForeignAttributes()); |
| documentHandler.endPage(); |
| documentHandler.getContext().setPageIndex(-1); |
| resetForeignAttributes(); |
| } catch (IFException e) { |
| handleIFException(e); |
| } |
| } |
| |
| private void processExtensionAttachments(AreaTreeObject area) throws IFException { |
| if (area.hasExtensionAttachments()) { |
| for (ExtensionAttachment attachment : area.getExtensionAttachments()) { |
| this.documentHandler.handleExtensionObject(attachment); |
| } |
| } |
| } |
| |
| private void establishForeignAttributes(Map foreignAttributes) { |
| documentHandler.getContext().setForeignAttributes(foreignAttributes); |
| } |
| |
| private void resetForeignAttributes() { |
| documentHandler.getContext().resetForeignAttributes(); |
| } |
| |
| private void establishStructureTreeElement(StructureTreeElement structureTreeElement) { |
| documentHandler.getContext().setStructureTreeElement(structureTreeElement); |
| } |
| |
| private void resetStructurePointer() { |
| documentHandler.getContext().resetStructureTreeElement(); |
| } |
| |
| /** {@inheritDoc} */ |
| protected void saveGraphicsState() { |
| graphicContextStack.push(graphicContext); |
| graphicContext = (IFGraphicContext)graphicContext.clone(); |
| } |
| |
| /** {@inheritDoc} */ |
| protected void restoreGraphicsState() { |
| while (graphicContext.getGroupStackSize() > 0) { |
| IFGraphicContext.Group[] groups = graphicContext.dropGroups(); |
| for (int i = groups.length - 1; i >= 0; i--) { |
| try { |
| groups[i].end(painter); |
| } catch (IFException ife) { |
| handleIFException(ife); |
| } |
| } |
| } |
| graphicContext = (IFGraphicContext)graphicContextStack.pop(); |
| } |
| |
| private void pushGroup(IFGraphicContext.Group group) { |
| graphicContext.pushGroup(group); |
| try { |
| group.start(painter); |
| } catch (IFException ife) { |
| handleIFException(ife); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| protected List breakOutOfStateStack() { |
| log.debug("Block.FIXED --> break out"); |
| List breakOutList = new java.util.ArrayList(); |
| while (!this.graphicContextStack.empty()) { |
| //Handle groups |
| IFGraphicContext.Group[] groups = graphicContext.getGroups(); |
| for (int j = groups.length - 1; j >= 0; j--) { |
| try { |
| groups[j].end(painter); |
| } catch (IFException ife) { |
| handleIFException(ife); |
| } |
| } |
| |
| breakOutList.add(0, this.graphicContext); |
| graphicContext = (IFGraphicContext)graphicContextStack.pop(); |
| } |
| return breakOutList; |
| } |
| |
| /** {@inheritDoc} */ |
| protected void restoreStateStackAfterBreakOut(List breakOutList) { |
| log.debug("Block.FIXED --> restoring context after break-out"); |
| for (Object aBreakOutList : breakOutList) { |
| graphicContextStack.push(graphicContext); |
| this.graphicContext = (IFGraphicContext) aBreakOutList; |
| |
| //Handle groups |
| IFGraphicContext.Group[] groups = graphicContext.getGroups(); |
| for (IFGraphicContext.Group group : groups) { |
| try { |
| group.start(painter); |
| } catch (IFException ife) { |
| handleIFException(ife); |
| } |
| } |
| } |
| log.debug("restored."); |
| } |
| |
| /** {@inheritDoc} */ |
| protected void concatenateTransformationMatrix(AffineTransform at) { |
| if (!at.isIdentity()) { |
| concatenateTransformationMatrixMpt(ptToMpt(at), false); |
| } |
| } |
| |
| private void concatenateTransformationMatrixMpt(AffineTransform at, boolean force) { |
| if (force || !at.isIdentity()) { |
| if (log.isTraceEnabled()) { |
| log.trace("-----concatenateTransformationMatrix: " + at); |
| } |
| IFGraphicContext.Group group = new IFGraphicContext.Group(at); |
| pushGroup(group); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| protected void beginTextObject() { |
| //nop - Ignore, handled by painter internally |
| } |
| |
| /** {@inheritDoc} */ |
| protected void endTextObject() { |
| //nop - Ignore, handled by painter internally |
| } |
| |
| /** {@inheritDoc} */ |
| protected void renderRegionViewport(RegionViewport viewport) { |
| Dimension dim = new Dimension(viewport.getIPD(), viewport.getBPD()); |
| viewportDimensionStack.push(dim); |
| documentHandler.getContext().setRegionType(viewport.getRegionReference().getRegionClass()); |
| super.renderRegionViewport(viewport); |
| viewportDimensionStack.pop(); |
| } |
| |
| /** {@inheritDoc} */ |
| protected void renderBlockViewport(BlockViewport bv, List children) { |
| //Essentially the same code as in the super class but optimized for the IF |
| |
| // Handle new layer. |
| boolean inNewLayer = false; |
| if (maybeStartLayer(bv)) { |
| inNewLayer = true; |
| } |
| |
| //This is the content-rect |
| Dimension dim = new Dimension(bv.getIPD(), bv.getBPD()); |
| viewportDimensionStack.push(dim); |
| |
| // save positions |
| int saveIP = currentIPPosition; |
| int saveBP = currentBPPosition; |
| |
| CTM ctm = bv.getCTM(); |
| int borderPaddingStart = bv.getBorderAndPaddingWidthStart(); |
| int borderPaddingBefore = bv.getBorderAndPaddingWidthBefore(); |
| |
| if (bv.getPositioning() == Block.ABSOLUTE |
| || bv.getPositioning() == Block.FIXED) { |
| |
| //For FIXED, we need to break out of the current viewports to the |
| //one established by the page. We save the state stack for restoration |
| //after the block-container has been painted. See below. |
| List breakOutList = null; |
| if (bv.getPositioning() == Block.FIXED) { |
| breakOutList = breakOutOfStateStack(); |
| } |
| |
| AffineTransform positionTransform = new AffineTransform(); |
| positionTransform.translate(bv.getXOffset(), bv.getYOffset()); |
| |
| //"left/"top" (bv.getX/YOffset()) specify the position of the content rectangle |
| positionTransform.translate(-borderPaddingStart, -borderPaddingBefore); |
| |
| //Free transformation for the block-container viewport |
| String transf; |
| transf = bv.getForeignAttributeValue(FOX_TRANSFORM); |
| if (transf != null) { |
| AffineTransform freeTransform = AWTTransformProducer.createAffineTransform(transf); |
| positionTransform.concatenate(freeTransform); |
| } |
| |
| saveGraphicsState(); |
| //Viewport position |
| concatenateTransformationMatrixMpt(positionTransform, false); |
| |
| //Background and borders |
| float bpwidth = (borderPaddingStart + bv.getBorderAndPaddingWidthEnd()); |
| float bpheight = (borderPaddingBefore + bv.getBorderAndPaddingWidthAfter()); |
| drawBackAndBorders(bv, 0, 0, |
| (dim.width + bpwidth) / 1000f, (dim.height + bpheight) / 1000f); |
| |
| //Shift to content rectangle after border painting |
| AffineTransform contentRectTransform = new AffineTransform(); |
| contentRectTransform.translate(borderPaddingStart, borderPaddingBefore); |
| concatenateTransformationMatrixMpt(contentRectTransform, false); |
| |
| //saveGraphicsState(); |
| //Set up coordinate system for content rectangle |
| AffineTransform contentTransform = ctm.toAffineTransform(); |
| //concatenateTransformationMatrixMpt(contentTransform); |
| startViewport(contentTransform, bv.getClipRectangle()); |
| |
| currentIPPosition = 0; |
| currentBPPosition = 0; |
| renderBlocks(bv, children); |
| |
| endViewport(); |
| //restoreGraphicsState(); |
| restoreGraphicsState(); |
| |
| if (breakOutList != null) { |
| restoreStateStackAfterBreakOut(breakOutList); |
| } |
| |
| currentIPPosition = saveIP; |
| currentBPPosition = saveBP; |
| } else { |
| |
| currentBPPosition += bv.getSpaceBefore(); |
| |
| //borders and background in the old coordinate system |
| handleBlockTraits(bv); |
| |
| //Advance to start of content area |
| currentIPPosition += bv.getStartIndent(); |
| |
| CTM tempctm = new CTM(containingIPPosition, currentBPPosition); |
| ctm = tempctm.multiply(ctm); |
| |
| //Now adjust for border/padding |
| currentBPPosition += borderPaddingBefore; |
| |
| startVParea(ctm, bv.getClipRectangle()); |
| currentIPPosition = 0; |
| currentBPPosition = 0; |
| renderBlocks(bv, children); |
| endVParea(); |
| |
| currentIPPosition = saveIP; |
| currentBPPosition = saveBP; |
| |
| currentBPPosition += bv.getAllocBPD(); |
| } |
| viewportDimensionStack.pop(); |
| maybeEndLayer(bv, inNewLayer); |
| } |
| |
| /** {@inheritDoc} */ |
| public void renderInlineViewport(InlineViewport viewport) { |
| StructureTreeElement structElem |
| = (StructureTreeElement) viewport.getTrait(Trait.STRUCTURE_TREE_ELEMENT); |
| establishStructureTreeElement(structElem); |
| pushID(viewport); |
| Dimension dim = new Dimension(viewport.getIPD(), viewport.getBPD()); |
| viewportDimensionStack.push(dim); |
| super.renderInlineViewport(viewport); |
| viewportDimensionStack.pop(); |
| resetStructurePointer(); |
| popID(viewport); |
| } |
| |
| /** {@inheritDoc} */ |
| protected void startVParea(CTM ctm, Rectangle clippingRect) { |
| if (log.isTraceEnabled()) { |
| log.trace("startVParea() ctm=" + ctm + ", clippingRect=" + clippingRect); |
| } |
| AffineTransform at = new AffineTransform(ctm.toArray()); |
| startViewport(at, clippingRect); |
| if (log.isTraceEnabled()) { |
| log.trace("startVPArea: " + at + " --> " + graphicContext.getTransform()); |
| } |
| } |
| |
| private void startViewport(AffineTransform at, Rectangle clipRect) { |
| saveGraphicsState(); |
| try { |
| IFGraphicContext.Viewport viewport = new IFGraphicContext.Viewport( |
| at, (Dimension)viewportDimensionStack.peek(), clipRect); |
| graphicContext.pushGroup(viewport); |
| viewport.start(painter); |
| } catch (IFException e) { |
| handleIFException(e); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| protected void endVParea() { |
| log.trace("endVParea()"); |
| endViewport(); |
| if (log.isTraceEnabled()) { |
| log.trace("endVPArea() --> " + graphicContext.getTransform()); |
| } |
| } |
| |
| private void endViewport() { |
| restoreGraphicsState(); |
| } |
| |
| /** {@inheritDoc} */ |
| protected void startLayer(String layer) { |
| if (log.isTraceEnabled()) { |
| log.trace("startLayer() layer=" + layer); |
| } |
| saveGraphicsState(); |
| pushGroup(new IFGraphicContext.Group(layer)); |
| } |
| |
| /** {@inheritDoc} */ |
| protected void endLayer() { |
| if (log.isTraceEnabled()) { |
| log.trace("endLayer()"); |
| } |
| restoreGraphicsState(); |
| } |
| |
| /** {@inheritDoc} */ |
| protected void renderInlineArea(InlineArea inlineArea) { |
| saveInlinePosIfTargetable(inlineArea); |
| pushID(inlineArea); |
| super.renderInlineArea(inlineArea); |
| popID(inlineArea); |
| } |
| |
| /** {@inheritDoc} */ |
| public void renderInlineParent(InlineParent ip) { |
| // stuff we only need if a link must be created: |
| Rectangle ipRect = null; |
| AbstractAction action = null; |
| // make sure the rect is determined *before* calling super! |
| int ipp = currentIPPosition; |
| int bpp = currentBPPosition + ip.getBlockProgressionOffset(); |
| ipRect = new Rectangle(ipp, bpp, ip.getIPD(), ip.getBPD()); |
| AffineTransform transform = graphicContext.getTransform(); |
| ipRect = transform.createTransformedShape(ipRect).getBounds(); |
| |
| // render contents |
| super.renderInlineParent(ip); |
| |
| boolean linkTraitFound = false; |
| |
| // try INTERNAL_LINK first |
| Trait.InternalLink intLink = (Trait.InternalLink) ip.getTrait(Trait.INTERNAL_LINK); |
| if (intLink != null) { |
| linkTraitFound = true; |
| String pvKey = intLink.getPVKey(); |
| String idRef = intLink.getIDRef(); |
| boolean pvKeyOK = pvKey != null && pvKey.length() > 0; |
| boolean idRefOK = idRef != null && idRef.length() > 0; |
| if (pvKeyOK && idRefOK) { |
| Integer pageIndex = (Integer)pageIndices.get(pvKey); |
| action = getGoToActionForID(idRef, (pageIndex != null ? pageIndex : -1)); |
| } else { |
| //Warnings already issued by AreaTreeHandler |
| } |
| } |
| |
| // no INTERNAL_LINK, look for EXTERNAL_LINK |
| if (!linkTraitFound) { |
| Trait.ExternalLink extLink = (Trait.ExternalLink) ip.getTrait(Trait.EXTERNAL_LINK); |
| if (extLink != null) { |
| String extDest = extLink.getDestination(); |
| if (extDest != null && extDest.length() > 0) { |
| linkTraitFound = true; |
| action = new URIAction(extDest, extLink.newWindow()); |
| action = actionSet.put(action); |
| } |
| } |
| } |
| |
| // warn if link trait found but not allowed, else create link |
| if (linkTraitFound) { |
| StructureTreeElement structElem |
| = (StructureTreeElement) ip.getTrait(Trait.STRUCTURE_TREE_ELEMENT); |
| action.setStructureTreeElement(structElem); |
| Link link = new Link(action, ipRect); |
| this.deferredLinks.add(link); |
| } else if (ip instanceof BasicLinkArea) { |
| BasicLinkArea linkArea = (BasicLinkArea) ip; |
| String id = linkArea.getResolver().getIDRefs()[0]; |
| action = getGoToActionForID(id, -1); |
| Link link = new Link(action, ipRect); |
| this.deferredLinks.add(link); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| protected void renderBlock(Block block) { |
| if (log.isTraceEnabled()) { |
| log.trace("renderBlock() " + block); |
| } |
| saveBlockPosIfTargetable(block); |
| pushID(block); |
| IFContext context = documentHandler.getContext(); |
| Locale oldLocale = context.getLanguage(); |
| context.setLanguage(block.getLocale()); |
| String oldLocation = context.getLocation(); |
| context.setLocation(block.getLocation()); |
| super.renderBlock(block); |
| context.setLocation(oldLocation); |
| context.setLanguage(oldLocale); |
| popID(block); |
| } |
| |
| private void pushID(Area area) { |
| String prodID = (String) area.getTrait(Trait.PROD_ID); |
| if (prodID != null) { |
| ids.push(prodID); |
| documentHandler.getContext().setID(prodID); |
| } |
| } |
| |
| private void popID(Area area) { |
| String prodID = (String) area.getTrait(Trait.PROD_ID); |
| if (prodID != null) { |
| ids.pop(); |
| documentHandler.getContext().setID(ids.empty() ? "" : ids.peek()); |
| } |
| } |
| |
| private Typeface getTypeface(String fontName) { |
| Typeface tf = fontInfo.getFonts().get(fontName); |
| if (tf instanceof LazyFont) { |
| tf = ((LazyFont)tf).getRealFont(); |
| } |
| return tf; |
| } |
| |
| /** {@inheritDoc} */ |
| protected void renderText(TextArea text) { |
| if (log.isTraceEnabled()) { |
| log.trace("renderText() " + text); |
| } |
| renderInlineAreaBackAndBorders(text); |
| Color ct = (Color) text.getTrait(Trait.COLOR); |
| |
| beginTextObject(); |
| |
| String fontName = getInternalFontNameForArea(text); |
| int size = (Integer) text.getTrait(Trait.FONT_SIZE); |
| StructureTreeElement structElem |
| = (StructureTreeElement) text.getTrait(Trait.STRUCTURE_TREE_ELEMENT); |
| establishStructureTreeElement(structElem); |
| |
| // This assumes that *all* CIDFonts use a /ToUnicode mapping |
| Typeface tf = getTypeface(fontName); |
| |
| FontTriplet triplet = (FontTriplet)text.getTrait(Trait.FONT); |
| try { |
| painter.setFont(triplet.getName(), triplet.getStyle(), triplet.getWeight(), |
| "normal", size, ct); |
| } catch (IFException e) { |
| handleIFException(e); |
| } |
| |
| int rx = currentIPPosition + text.getBorderAndPaddingWidthStart(); |
| int bl = currentBPPosition + text.getBlockProgressionOffset() + text.getBaselineOffset(); |
| textUtil.flush(); |
| textUtil.setStartPosition(rx, bl); |
| textUtil.setSpacing(text.getTextLetterSpaceAdjust(), text.getTextWordSpaceAdjust()); |
| documentHandler.getContext().setHyphenated(text.isHyphenated()); |
| super.renderText(text); |
| |
| textUtil.flush(); |
| renderTextDecoration(tf, size, text, bl, rx); |
| documentHandler.getContext().setHyphenated(false); |
| resetStructurePointer(); |
| } |
| |
| /** {@inheritDoc} */ |
| protected void renderWord(WordArea word) { |
| Font font = getFontFromArea(word.getParentArea()); |
| String s = word.getWord(); |
| |
| int[][] dp = word.getGlyphPositionAdjustments(); |
| Area parentArea = word.getParentArea(); |
| assert (parentArea instanceof AbstractTextArea); |
| if (dp == null) { |
| renderTextWithAdjustments(s, word.getLetterAdjustArray(), word.isReversed(), |
| font, (AbstractTextArea) parentArea); |
| } else if (IFUtil.isDPOnlyDX(dp)) { |
| renderTextWithAdjustments(s, IFUtil.convertDPToDX(dp), word.isReversed(), |
| font, (AbstractTextArea) parentArea); |
| } else { |
| renderTextWithAdjustments(s, dp, word.isReversed(), |
| font, (AbstractTextArea) parentArea); |
| } |
| |
| textUtil.nextIsSpace = word.isNextIsSpace(); |
| super.renderWord(word); |
| } |
| |
| /** {@inheritDoc} */ |
| protected void renderSpace(SpaceArea space) { |
| Font font = getFontFromArea(space.getParentArea()); |
| String s = space.getSpace(); |
| |
| Area parentArea = space.getParentArea(); |
| assert (parentArea instanceof AbstractTextArea); |
| AbstractTextArea textArea = (AbstractTextArea) parentArea; |
| renderTextWithAdjustments(s, (int[]) null, false, font, textArea); |
| |
| /* COMBINED is always false |
| if (textUtil.COMBINED && space.isAdjustable()) { |
| //Used for justified text, for example |
| int tws = textArea.getTextWordSpaceAdjust() |
| + 2 * textArea.getTextLetterSpaceAdjust(); |
| if (tws != 0) { |
| textUtil.adjust(tws); |
| } |
| } |
| */ |
| super.renderSpace(space); |
| } |
| |
| /** |
| * Does low-level rendering of text using DX only position adjustments. |
| * @param s text to render |
| * @param dx an array of widths for letter adjustment (may be null) |
| * @param reversed if true then text has been reversed (from logical order) |
| * @param font to font in use |
| * @param parentArea the parent text area to retrieve certain traits from |
| */ |
| private void renderTextWithAdjustments(String s, |
| int[] dx, boolean reversed, |
| Font font, AbstractTextArea parentArea) { |
| int l = s.length(); |
| if (l == 0) { |
| return; |
| } |
| for (int i = 0; i < l; i++) { |
| char ch = s.charAt(i); |
| textUtil.addChar(ch); |
| int glyphAdjust = 0; |
| /* COMBINED is always false |
| if (textUtil.COMBINED && font.hasChar(ch)) { |
| int tls = (i < l - 1 ? parentArea.getTextLetterSpaceAdjust() : 0); |
| glyphAdjust += tls; |
| } |
| */ |
| if (dx != null && i < l) { |
| glyphAdjust += dx[i]; |
| } |
| textUtil.adjust(glyphAdjust); |
| } |
| } |
| |
| /** |
| * Does low-level rendering of text using generalized position adjustments. |
| * @param s text to render |
| * @param dp an array of 4-tuples, expressing [X,Y] placment |
| * adjustments and [X,Y] advancement adjustments, in that order (may be null) |
| * @param reversed if true then text has been reversed (from logical order) |
| * @param font to font in use |
| * @param parentArea the parent text area to retrieve certain traits from |
| */ |
| private void renderTextWithAdjustments(String s, |
| int[][] dp, boolean reversed, |
| Font font, AbstractTextArea parentArea) { |
| // assert !textUtil.COMBINED; |
| for (int i = 0, n = s.length(); i < n; i++) { |
| textUtil.addChar(s.charAt(i)); |
| if (dp != null) { |
| textUtil.adjust(dp[i]); |
| } |
| } |
| } |
| |
| private class TextUtil { |
| private static final int INITIAL_BUFFER_SIZE = 16; |
| private int[][] dp = new int[INITIAL_BUFFER_SIZE][]; |
| private final StringBuffer text = new StringBuffer(); |
| private int startx; |
| private int starty; |
| private int tls; |
| private int tws; |
| private boolean nextIsSpace; |
| |
| void addChar(char ch) { |
| text.append(ch); |
| } |
| |
| void adjust(int dx) { |
| if (dx != 0) { |
| adjust(new int[] { |
| dx, // xPlaAdjust |
| 0, // yPlaAdjust |
| dx, // xAdvAdjust |
| 0 // yAdvAdjust |
| }); |
| } |
| } |
| |
| void adjust(int[] pa) { |
| if (!IFUtil.isPAIdentity(pa)) { |
| int idx = text.length(); |
| if (idx > dp.length - 1) { |
| int newSize = Math.max(dp.length, idx + 1) + INITIAL_BUFFER_SIZE; |
| int[][] newDP = new int[newSize][]; |
| // reuse prior DP[0]...DP[dp.length-1] |
| System.arraycopy(dp, 0, newDP, 0, dp.length); |
| // switch to new DP, leaving DP[dp.length]...DP[newDP.length-1] unpopulated |
| dp = newDP; |
| } |
| if (dp[idx - 1] == null) { |
| dp[idx - 1] = new int[4]; |
| } |
| IFUtil.adjustPA(dp[idx - 1], pa); |
| } |
| } |
| |
| void reset() { |
| if (text.length() > 0) { |
| text.setLength(0); |
| for (int i = 0, n = dp.length; i < n; i++) { |
| dp[i] = null; |
| } |
| } |
| } |
| |
| void setStartPosition(int x, int y) { |
| this.startx = x; |
| this.starty = y; |
| } |
| |
| void setSpacing(int tls, int tws) { |
| this.tls = tls; |
| this.tws = tws; |
| } |
| |
| void flush() { |
| if (text.length() > 0) { |
| try { |
| /* if (COMBINED) { // COMBINED is always false |
| painter.drawText(startx, starty, 0, 0, |
| trimAdjustments(dp, text.length()), text.toString()); |
| } else { */ |
| painter.drawText(startx, starty, tls, tws, |
| trimAdjustments(dp, text.length()), text.toString(), nextIsSpace); |
| /* } */ |
| } catch (IFException e) { |
| handleIFException(e); |
| } |
| reset(); |
| } |
| } |
| |
| void drawText(int x, int y, int letterSpacing, int wordSpacing, int[][] dx, String text, boolean nextIsSpace) |
| throws IFException { |
| painter.drawText(startx, starty, tls, tws, dx, text, nextIsSpace); |
| } |
| |
| /** |
| * Trim adjustments array <code>dp</code> to be no greater length than |
| * text length, and where trailing all-zero entries are removed. |
| * @param dp a position adjustments array (or null) |
| * @param textLength the length of the associated text |
| * @return either the original value of <code>dp</code> or a copy |
| * of its first N significant adjustment entries, such that N is |
| * no greater than text length, and the last entry has a non-zero |
| * adjustment. |
| */ |
| private int[][] trimAdjustments(int[][] dp, int textLength) { |
| if (dp != null) { |
| int tl = textLength; |
| int pl = dp.length; |
| int i = (tl < pl) ? tl : pl; |
| while (i > 0) { |
| int[] pa = dp [ i - 1 ]; |
| if ((pa != null) && !IFUtil.isPAIdentity(pa)) { |
| break; |
| } else { |
| i--; |
| } |
| } |
| if (i == 0) { |
| dp = null; |
| } else if (i < pl) { |
| dp = IFUtil.copyDP(dp, 0, i); |
| } |
| } |
| return dp; |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public void renderImage(Image image, Rectangle2D pos) { |
| drawImage(image.getURL(), pos, image.getForeignAttributes()); |
| } |
| |
| /** {@inheritDoc} */ |
| protected void drawImage(String uri, Rectangle2D pos, Map foreignAttributes) { |
| Rectangle posInt = new Rectangle( |
| currentIPPosition + (int)pos.getX(), |
| currentBPPosition + (int)pos.getY(), |
| (int)pos.getWidth(), |
| (int)pos.getHeight()); |
| uri = URISpecification.getURL(uri); |
| try { |
| establishForeignAttributes(foreignAttributes); |
| painter.drawImage(uri, posInt); |
| resetForeignAttributes(); |
| } catch (IFException ife) { |
| handleIFException(ife); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public void renderForeignObject(ForeignObject fo, Rectangle2D pos) { |
| endTextObject(); |
| Rectangle posInt = new Rectangle( |
| currentIPPosition + (int)pos.getX(), |
| currentBPPosition + (int)pos.getY(), |
| (int)pos.getWidth(), |
| (int)pos.getHeight()); |
| Document doc = fo.getDocument(); |
| try { |
| establishForeignAttributes(fo.getForeignAttributes()); |
| painter.drawImage(doc, posInt); |
| resetForeignAttributes(); |
| } catch (IFException ife) { |
| handleIFException(ife); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public void renderLeader(Leader area) { |
| renderInlineAreaBackAndBorders(area); |
| |
| int style = area.getRuleStyle(); |
| int ruleThickness = area.getRuleThickness(); |
| int startx = currentIPPosition + area.getBorderAndPaddingWidthStart(); |
| int starty = currentBPPosition + area.getBlockProgressionOffset() + (ruleThickness / 2); |
| int endx = currentIPPosition |
| + area.getBorderAndPaddingWidthStart() |
| + area.getIPD(); |
| Color col = (Color)area.getTrait(Trait.COLOR); |
| |
| Point start = new Point(startx, starty); |
| Point end = new Point(endx, starty); |
| try { |
| painter.drawLine(start, end, ruleThickness, col, RuleStyle.valueOf(style)); |
| } catch (IFException ife) { |
| handleIFException(ife); |
| } |
| |
| super.renderLeader(area); |
| } |
| |
| /** {@inheritDoc} */ |
| protected void clip() { |
| throw new IllegalStateException("Not used"); |
| } |
| |
| /** {@inheritDoc} */ |
| protected void clipRect(float x, float y, float width, float height) { |
| pushGroup(new IFGraphicContext.Group()); |
| try { |
| painter.clipRect(toMillipointRectangle(x, y, width, height)); |
| } catch (IFException ife) { |
| handleIFException(ife); |
| } |
| } |
| /** {@inheritDoc} */ |
| protected void clipBackground(float startx, float starty, |
| float width, float height, |
| BorderProps bpsBefore, BorderProps bpsAfter, |
| BorderProps bpsStart, BorderProps bpsEnd) { |
| pushGroup(new IFGraphicContext.Group()); |
| Rectangle rect = toMillipointRectangle(startx, starty, width, height); |
| try { |
| painter.clipBackground(rect, |
| bpsBefore, bpsAfter, bpsStart, bpsEnd); |
| } catch (IFException ife) { |
| handleIFException(ife); |
| } |
| } |
| /** {@inheritDoc} */ |
| protected void closePath() { |
| throw new IllegalStateException("Not used"); |
| } |
| |
| /** {@inheritDoc} */ |
| protected void drawBackground(float startx, float starty, |
| float width, float height, |
| Trait.Background back, |
| BorderProps bpsBefore, BorderProps bpsAfter, |
| BorderProps bpsStart, BorderProps bpsEnd) { |
| if (painter.isBackgroundRequired(bpsBefore, bpsAfter, bpsStart, bpsEnd)) { |
| super.drawBackground(startx, starty, width, height, |
| back, bpsBefore, bpsAfter, |
| bpsStart, bpsEnd); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| protected void drawBorders(float startx, float starty, |
| float width, float height, |
| BorderProps bpsBefore, BorderProps bpsAfter, |
| BorderProps bpsStart, BorderProps bpsEnd, int level, Color innerBackgroundColor) { |
| Rectangle rect = toMillipointRectangle(startx, starty, width, height); |
| try { |
| BorderProps bpsTop = bpsBefore; |
| BorderProps bpsBottom = bpsAfter; |
| BorderProps bpsLeft; |
| BorderProps bpsRight; |
| if ((level == -1) || ((level & 1) == 0)) { |
| bpsLeft = bpsStart; |
| bpsRight = bpsEnd; |
| } else { |
| bpsLeft = bpsEnd; |
| bpsRight = bpsStart; |
| } |
| painter.drawBorderRect(rect, bpsTop, bpsBottom, bpsLeft, bpsRight, innerBackgroundColor); |
| } catch (IFException ife) { |
| handleIFException(ife); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| protected void drawBorderLine(float x1, float y1, float x2, float y2, boolean horz, |
| boolean startOrBefore, int style, Color col) { |
| //Simplified implementation that is only used by renderTextDecoration() |
| //drawBorders() is overridden and uses the Painter's high-level method drawBorderRect() |
| updateColor(col, true); |
| fillRect(x1, y1, x2 - x1, y2 - y1); |
| } |
| |
| private int toMillipoints(float coordinate) { |
| return Math.round(coordinate * 1000); |
| } |
| |
| private Rectangle toMillipointRectangle(float x, float y, float width, float height) { |
| return new Rectangle( |
| toMillipoints(x), |
| toMillipoints(y), |
| toMillipoints(width), |
| toMillipoints(height)); |
| } |
| |
| /** {@inheritDoc} */ |
| protected void fillRect(float x, float y, float width, float height) { |
| try { |
| painter.fillRect( |
| toMillipointRectangle(x, y, width, height), |
| this.graphicContext.getPaint()); |
| } catch (IFException e) { |
| handleIFException(e); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| protected void moveTo(float x, float y) { |
| throw new IllegalStateException("Not used"); |
| } |
| |
| /** {@inheritDoc} */ |
| protected void lineTo(float x, float y) { |
| throw new IllegalStateException("Not used"); |
| } |
| |
| /** {@inheritDoc} */ |
| protected void updateColor(Color col, boolean fill) { |
| if (fill) { |
| this.graphicContext.setPaint(col); |
| } else { |
| this.graphicContext.setColor(col); |
| } |
| |
| } |
| |
| } |