| /* |
| * 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.afp; |
| |
| import java.awt.Color; |
| import java.awt.Dimension; |
| import java.awt.geom.AffineTransform; |
| import java.io.IOException; |
| import java.net.URI; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| |
| import org.apache.fop.afp.AFPDitheredRectanglePainter; |
| import org.apache.fop.afp.AFPPaintingState; |
| import org.apache.fop.afp.AFPRectanglePainter; |
| import org.apache.fop.afp.AFPResourceLevelDefaults; |
| import org.apache.fop.afp.AFPResourceManager; |
| import org.apache.fop.afp.AFPUnitConverter; |
| import org.apache.fop.afp.AbstractAFPPainter; |
| import org.apache.fop.afp.DataStream; |
| import org.apache.fop.afp.fonts.AFPFontCollection; |
| import org.apache.fop.afp.fonts.AFPPageFonts; |
| import org.apache.fop.afp.modca.ResourceObject; |
| import org.apache.fop.afp.util.AFPResourceAccessor; |
| import org.apache.fop.apps.MimeConstants; |
| import org.apache.fop.fonts.FontCollection; |
| import org.apache.fop.fonts.FontEventAdapter; |
| import org.apache.fop.fonts.FontInfo; |
| import org.apache.fop.fonts.FontManager; |
| import org.apache.fop.render.afp.AFPRendererConfig.AFPRendererConfigParser; |
| import org.apache.fop.render.afp.extensions.AFPElementMapping; |
| import org.apache.fop.render.afp.extensions.AFPIncludeFormMap; |
| import org.apache.fop.render.afp.extensions.AFPInvokeMediumMap; |
| import org.apache.fop.render.afp.extensions.AFPPageOverlay; |
| import org.apache.fop.render.afp.extensions.AFPPageSegmentElement; |
| import org.apache.fop.render.afp.extensions.AFPPageSetup; |
| import org.apache.fop.render.afp.extensions.ExtensionPlacement; |
| import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler; |
| import org.apache.fop.render.intermediate.IFContext; |
| import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator; |
| import org.apache.fop.render.intermediate.IFException; |
| import org.apache.fop.render.intermediate.IFPainter; |
| |
| /** |
| * {@link org.apache.fop.render.intermediate.IFDocumentHandler} implementation that produces AFP |
| * (MO:DCA). |
| */ |
| public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler |
| implements AFPCustomizable { |
| |
| //** logging instance */ |
| //private static Log log = LogFactory.getLog(AFPDocumentHandler.class); |
| |
| /** the resource manager */ |
| private AFPResourceManager resourceManager; |
| |
| /** the painting state */ |
| private final AFPPaintingState paintingState; |
| |
| /** unit converter */ |
| private final AFPUnitConverter unitConv; |
| |
| /** the AFP datastream */ |
| private DataStream dataStream; |
| |
| /** the map of page segments */ |
| private Map<String, PageSegmentDescriptor> pageSegmentMap |
| = new java.util.HashMap<String, PageSegmentDescriptor>(); |
| |
| |
| // Rounded corners are cached at the document level |
| private Map<String, String> roundedCornerNameCache |
| = new HashMap<String, String>(); |
| |
| private int roundedCornerCount; |
| |
| private static enum Location { |
| ELSEWHERE, IN_DOCUMENT_HEADER, FOLLOWING_PAGE_SEQUENCE, IN_PAGE_HEADER |
| } |
| |
| private Location location = Location.ELSEWHERE; |
| |
| /** temporary holds extensions that have to be deferred until the end of the page-sequence */ |
| private List<AFPPageSetup> deferredPageSequenceExtensions |
| = new java.util.LinkedList<AFPPageSetup>(); |
| |
| /** the shading mode for filled rectangles */ |
| private AFPShadingMode shadingMode = AFPShadingMode.COLOR; |
| |
| /** |
| * Default constructor. |
| */ |
| public AFPDocumentHandler(IFContext context) { |
| super(context); |
| this.resourceManager = new AFPResourceManager(context.getUserAgent().getResourceResolver()); |
| this.paintingState = new AFPPaintingState(); |
| this.unitConv = paintingState.getUnitConverter(); |
| } |
| |
| /** {@inheritDoc} */ |
| public boolean supportsPagesOutOfOrder() { |
| return false; |
| } |
| |
| /** {@inheritDoc} */ |
| public String getMimeType() { |
| return MimeConstants.MIME_AFP; |
| } |
| |
| /** {@inheritDoc} */ |
| public IFDocumentHandlerConfigurator getConfigurator() { |
| return new AFPRendererConfigurator(getUserAgent(), new AFPRendererConfigParser()); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void setDefaultFontInfo(FontInfo fontInfo) { |
| FontManager fontManager = getUserAgent().getFontManager(); |
| FontCollection[] fontCollections = new FontCollection[] { |
| new AFPFontCollection(getUserAgent().getEventBroadcaster(), null) |
| }; |
| |
| FontInfo fi = (fontInfo != null ? fontInfo : new FontInfo()); |
| fi.setEventListener(new FontEventAdapter(getUserAgent().getEventBroadcaster())); |
| fontManager.setup(fi, fontCollections); |
| setFontInfo(fi); |
| } |
| |
| AFPPaintingState getPaintingState() { |
| return this.paintingState; |
| } |
| |
| DataStream getDataStream() { |
| return this.dataStream; |
| } |
| |
| AFPResourceManager getResourceManager() { |
| return this.resourceManager; |
| } |
| |
| AbstractAFPPainter createRectanglePainter() { |
| if (AFPShadingMode.DITHERED.equals(this.shadingMode)) { |
| return new AFPDitheredRectanglePainter( |
| getPaintingState(), getDataStream(), getResourceManager()); |
| } else { |
| return new AFPRectanglePainter( |
| getPaintingState(), getDataStream()); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void startDocument() throws IFException { |
| super.startDocument(); |
| try { |
| paintingState.setColor(Color.WHITE); |
| |
| this.dataStream = resourceManager.createDataStream(paintingState, outputStream); |
| |
| this.dataStream.startDocument(); |
| } catch (IOException e) { |
| throw new IFException("I/O error in startDocument()", e); |
| } |
| } |
| |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void startDocumentHeader() throws IFException { |
| super.startDocumentHeader(); |
| this.location = Location.IN_DOCUMENT_HEADER; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void endDocumentHeader() throws IFException { |
| super.endDocumentHeader(); |
| this.location = Location.ELSEWHERE; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void endDocument() throws IFException { |
| try { |
| this.dataStream.endDocument(); |
| this.dataStream = null; |
| this.resourceManager.writeToStream(); |
| this.resourceManager = null; |
| } catch (IOException ioe) { |
| throw new IFException("I/O error in endDocument()", ioe); |
| } |
| super.endDocument(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void startPageSequence(String id) throws IFException { |
| try { |
| if (!"false".equals(getContext().getForeignAttribute(AFPElementMapping.PAGE_GROUP))) { |
| dataStream.startPageGroup(); |
| } |
| } catch (IOException ioe) { |
| throw new IFException("I/O error in startPageSequence()", ioe); |
| } |
| this.location = Location.FOLLOWING_PAGE_SEQUENCE; |
| } |
| |
| /** {@inheritDoc} */ |
| public void endPageSequence() throws IFException { |
| try { |
| //Process deferred page-sequence-level extensions |
| Iterator<AFPPageSetup> iter = this.deferredPageSequenceExtensions.iterator(); |
| while (iter.hasNext()) { |
| AFPPageSetup aps = iter.next(); |
| iter.remove(); |
| if (AFPElementMapping.NO_OPERATION.equals(aps.getElementName())) { |
| handleNOP(aps); |
| } else { |
| throw new UnsupportedOperationException("Don't know how to handle " + aps); |
| } |
| } |
| |
| //End page sequence |
| dataStream.endPageGroup(); |
| } catch (IOException ioe) { |
| throw new IFException("I/O error in endPageSequence()", ioe); |
| } |
| this.location = Location.ELSEWHERE; |
| } |
| |
| /** |
| * Returns the base AFP transform |
| * |
| * @return the base AFP transform |
| */ |
| private AffineTransform getBaseTransform() { |
| AffineTransform baseTransform = new AffineTransform(); |
| double scale = unitConv.mpt2units(1); |
| baseTransform.scale(scale, scale); |
| return baseTransform; |
| } |
| |
| /** {@inheritDoc} */ |
| public void startPage(int index, String name, String pageMasterName, Dimension size) |
| throws IFException { |
| this.location = Location.ELSEWHERE; |
| paintingState.clear(); |
| |
| AffineTransform baseTransform = getBaseTransform(); |
| paintingState.concatenate(baseTransform); |
| |
| int pageWidth = Math.round(unitConv.mpt2units(size.width)); |
| paintingState.setPageWidth(pageWidth); |
| |
| int pageHeight = Math.round(unitConv.mpt2units(size.height)); |
| paintingState.setPageHeight(pageHeight); |
| |
| int pageRotation = paintingState.getPageRotation(); |
| int resolution = paintingState.getResolution(); |
| |
| dataStream.startPage(pageWidth, pageHeight, pageRotation, |
| resolution, resolution); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void startPageHeader() throws IFException { |
| super.startPageHeader(); |
| this.location = Location.IN_PAGE_HEADER; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void endPageHeader() throws IFException { |
| this.location = Location.ELSEWHERE; |
| super.endPageHeader(); |
| } |
| |
| /** {@inheritDoc} */ |
| public IFPainter startPageContent() throws IFException { |
| return new AFPPainter(this); |
| } |
| |
| /** {@inheritDoc} */ |
| public void endPageContent() throws IFException { |
| } |
| |
| /** {@inheritDoc} */ |
| public void endPage() throws IFException { |
| try { |
| AFPPageFonts pageFonts = paintingState.getPageFonts(); |
| if (pageFonts != null && !pageFonts.isEmpty()) { |
| dataStream.addFontsToCurrentPage(pageFonts); |
| } |
| |
| dataStream.endPage(); |
| } catch (IOException ioe) { |
| throw new IFException("I/O error in endPage()", ioe); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public void handleExtensionObject(Object extension) throws IFException { |
| if (extension instanceof AFPPageSetup) { |
| AFPPageSetup aps = (AFPPageSetup)extension; |
| String element = aps.getElementName(); |
| if (AFPElementMapping.TAG_LOGICAL_ELEMENT.equals(element)) { |
| switch (this.location) { |
| case FOLLOWING_PAGE_SEQUENCE: |
| case IN_PAGE_HEADER: |
| String name = aps.getName(); |
| String value = aps.getValue(); |
| int encoding = aps.getEncoding(); |
| dataStream.createTagLogicalElement(name, value, encoding); |
| break; |
| default: |
| throw new IFException( |
| "TLE extension must be in the page header or between page-sequence" |
| + " and the first page: " + aps, null); |
| } |
| } else if (AFPElementMapping.NO_OPERATION.equals(element)) { |
| switch (this.location) { |
| case FOLLOWING_PAGE_SEQUENCE: |
| if (aps.getPlacement() == ExtensionPlacement.BEFORE_END) { |
| this.deferredPageSequenceExtensions.add(aps); |
| break; |
| } |
| case IN_DOCUMENT_HEADER: |
| case IN_PAGE_HEADER: |
| handleNOP(aps); |
| break; |
| default: |
| throw new IFException( |
| "NOP extension must be in the document header, the page header" |
| + " or between page-sequence" |
| + " and the first page: " + aps, null); |
| } |
| } else { |
| if (this.location != Location.IN_PAGE_HEADER) { |
| throw new IFException( |
| "AFP page setup extension encountered outside the page header: " + aps, |
| null); |
| } |
| if (AFPElementMapping.INCLUDE_PAGE_SEGMENT.equals(element)) { |
| AFPPageSegmentElement.AFPPageSegmentSetup apse |
| = (AFPPageSegmentElement.AFPPageSegmentSetup)aps; |
| String name = apse.getName(); |
| String source = apse.getValue(); |
| String uri = apse.getResourceSrc(); |
| pageSegmentMap.put(source, new PageSegmentDescriptor(name, uri)); |
| } |
| } |
| } else if (extension instanceof AFPPageOverlay) { |
| AFPPageOverlay ipo = (AFPPageOverlay)extension; |
| if (this.location != Location.IN_PAGE_HEADER) { |
| throw new IFException( |
| "AFP page overlay extension encountered outside the page header: " + ipo, |
| null); |
| } |
| String overlay = ipo.getName(); |
| if (overlay != null) { |
| dataStream.createIncludePageOverlay(overlay, ipo.getX(), ipo.getY()); |
| } |
| } else if (extension instanceof AFPInvokeMediumMap) { |
| if (this.location != Location.FOLLOWING_PAGE_SEQUENCE |
| && this.location != Location.IN_PAGE_HEADER) { |
| |
| throw new IFException( |
| "AFP IMM extension must be between page-sequence" |
| + " and the first page or child of page-header: " |
| + extension, null); |
| } |
| AFPInvokeMediumMap imm = (AFPInvokeMediumMap)extension; |
| String mediumMap = imm.getName(); |
| if (mediumMap != null) { |
| dataStream.createInvokeMediumMap(mediumMap); |
| } |
| } else if (extension instanceof AFPIncludeFormMap) { |
| AFPIncludeFormMap formMap = (AFPIncludeFormMap)extension; |
| AFPResourceAccessor accessor = new AFPResourceAccessor( |
| getUserAgent().getResourceResolver()); |
| try { |
| getResourceManager().createIncludedResource(formMap.getName(), |
| formMap.getSrc(), accessor, |
| ResourceObject.TYPE_FORMDEF, false, null); |
| } catch (IOException ioe) { |
| throw new IFException( |
| "I/O error while embedding form map resource: " + formMap.getName(), ioe); |
| } |
| } |
| } |
| |
| /** |
| * Corner images can be reused by storing at the document level in the AFP |
| * The cache is used to map cahced images to caller generated descriptions of the corner |
| * @param cornerKey caller's identifier for the corner |
| * @return document id of the corner image |
| */ |
| public String cacheRoundedCorner(String cornerKey) { |
| |
| // Make a unique id |
| StringBuffer idBuilder = new StringBuffer("RC"); |
| |
| String tmp = Integer.toHexString(roundedCornerCount).toUpperCase(Locale.ENGLISH); |
| if (tmp.length() > 6) { |
| //Will never happen |
| //log.error("Rounded corners cache capacity exceeded"); |
| //We should get a visual clue |
| roundedCornerCount = 0; |
| tmp = "000000"; |
| } else if (tmp.length() < 6) { |
| for (int i = 0; i < 6 - tmp.length(); i++) { |
| idBuilder.append("0"); |
| } |
| idBuilder.append(tmp); |
| } |
| |
| roundedCornerCount++; |
| |
| String id = idBuilder.toString(); |
| |
| //cache the corner id |
| roundedCornerNameCache.put(cornerKey, id); |
| return id; |
| } |
| /** |
| * This method returns the an id that identifies a cached corner or null if non existent |
| * @param cornerKey caller's identifier for the corner |
| * @return document id of the corner image |
| */ |
| public String getCachedRoundedCorner(String cornerKey) { |
| return roundedCornerNameCache.get(cornerKey); |
| } |
| |
| |
| private void handleNOP(AFPPageSetup nop) { |
| String content = nop.getContent(); |
| if (content != null) { |
| dataStream.createNoOperation(content); |
| } |
| } |
| |
| // ---=== AFPCustomizable ===--- |
| |
| /** {@inheritDoc} */ |
| public void setBitsPerPixel(int bitsPerPixel) { |
| paintingState.setBitsPerPixel(bitsPerPixel); |
| } |
| |
| /** {@inheritDoc} */ |
| public void setColorImages(boolean colorImages) { |
| paintingState.setColorImages(colorImages); |
| } |
| |
| /** {@inheritDoc} */ |
| public void setNativeImagesSupported(boolean nativeImages) { |
| paintingState.setNativeImagesSupported(nativeImages); |
| } |
| |
| /** {@inheritDoc} */ |
| public void setCMYKImagesSupported(boolean value) { |
| paintingState.setCMYKImagesSupported(value); |
| } |
| |
| /** {@inheritDoc} */ |
| public void setDitheringQuality(float quality) { |
| this.paintingState.setDitheringQuality(quality); |
| } |
| |
| /** {@inheritDoc} */ |
| public void setBitmapEncodingQuality(float quality) { |
| this.paintingState.setBitmapEncodingQuality(quality); |
| } |
| |
| /** {@inheritDoc} */ |
| public void setShadingMode(AFPShadingMode shadingMode) { |
| this.shadingMode = shadingMode; |
| } |
| |
| /** {@inheritDoc} */ |
| public void setResolution(int resolution) { |
| paintingState.setResolution(resolution); |
| } |
| |
| /** {@inheritDoc} */ |
| public void setLineWidthCorrection(float correction) { |
| paintingState.setLineWidthCorrection(correction); |
| } |
| |
| /** {@inheritDoc} */ |
| public int getResolution() { |
| return paintingState.getResolution(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void setGOCAEnabled(boolean enabled) { |
| this.paintingState.setGOCAEnabled(enabled); |
| } |
| |
| /** {@inheritDoc} */ |
| public boolean isGOCAEnabled() { |
| return this.paintingState.isGOCAEnabled(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void setStrokeGOCAText(boolean stroke) { |
| this.paintingState.setStrokeGOCAText(stroke); |
| } |
| |
| /** {@inheritDoc} */ |
| public boolean isStrokeGOCAText() { |
| return this.paintingState.isStrokeGOCAText(); |
| } |
| |
| /** {@inheritDoc} */ |
| public void setWrapPSeg(boolean pSeg) { |
| paintingState.setWrapPSeg(pSeg); |
| } |
| |
| public void setWrapGocaPSeg(boolean pSeg) { |
| paintingState.setWrapGocaPSeg(pSeg); |
| } |
| |
| /** {@inheritDoc} */ |
| public void setFS45(boolean fs45) { |
| paintingState.setFS45(fs45); |
| } |
| |
| /** {@inheritDoc} */ |
| public boolean getWrapPSeg() { |
| return paintingState.getWrapPSeg(); |
| } |
| |
| /** {@inheritDoc} */ |
| public boolean getFS45() { |
| return paintingState.getFS45(); |
| } |
| |
| public void setDefaultResourceGroupUri(URI uri) { |
| resourceManager.setDefaultResourceGroupUri(uri); |
| } |
| |
| /** {@inheritDoc} */ |
| public void setResourceLevelDefaults(AFPResourceLevelDefaults defaults) { |
| resourceManager.setResourceLevelDefaults(defaults); |
| } |
| |
| /** |
| * Returns the page segment descriptor for a given URI if it actually represents a page segment. |
| * Otherwise, it just returns null. |
| * @param uri the URI that identifies the page segment |
| * @return the page segment descriptor or null if there's no page segment for the given URI |
| */ |
| PageSegmentDescriptor getPageSegmentNameFor(String uri) { |
| return pageSegmentMap.get(uri); |
| } |
| |
| /** {@inheritDoc} */ |
| public void canEmbedJpeg(boolean canEmbed) { |
| paintingState.setCanEmbedJpeg(canEmbed); |
| } |
| |
| } |