/* * 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.Paint; import java.awt.Point; import java.awt.Rectangle; import java.awt.geom.AffineTransform; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; import org.apache.xmlgraphics.util.QName; import org.apache.xmlgraphics.util.XMLizable; import org.apache.fop.accessibility.StructureTree; import org.apache.fop.fonts.FontInfo; import org.apache.fop.render.PrintRendererConfigurator; import org.apache.fop.render.RenderingContext; import org.apache.fop.render.intermediate.extensions.AbstractAction; import org.apache.fop.render.intermediate.extensions.Bookmark; import org.apache.fop.render.intermediate.extensions.BookmarkTree; import org.apache.fop.render.intermediate.extensions.DocumentNavigationExtensionConstants; import org.apache.fop.render.intermediate.extensions.Link; import org.apache.fop.render.intermediate.extensions.NamedDestination; import org.apache.fop.traits.BorderProps; import org.apache.fop.traits.RuleStyle; import org.apache.fop.util.ColorUtil; import org.apache.fop.util.DOM2SAX; import org.apache.fop.util.XMLConstants; import org.apache.fop.util.XMLUtil; /** * IFPainter implementation that serializes the intermediate format to XML. */ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler implements IFConstants, IFPainter, IFDocumentNavigationHandler { private IFDocumentHandler mimicHandler; private int pageSequenceIndex; // used for accessibility /** Holds the intermediate format state */ private IFState state; /** * Default constructor. */ public IFSerializer() { } /** {@inheritDoc} */ protected String getMainNamespace() { return NAMESPACE; } /** {@inheritDoc} */ public boolean supportsPagesOutOfOrder() { return false; //Theoretically supported but disabled to improve performance when //rendering the IF to the final format later on } /** {@inheritDoc} */ public String getMimeType() { return MIME_TYPE; } /** {@inheritDoc} */ public IFDocumentHandlerConfigurator getConfigurator() { if (this.mimicHandler != null) { return getMimickedDocumentHandler().getConfigurator(); } else { return new PrintRendererConfigurator(getUserAgent()); } } /** {@inheritDoc} */ public IFDocumentNavigationHandler getDocumentNavigationHandler() { return this; } /** * Tells this serializer to mimic the given document handler (mostly applies to the font set * that is used during layout). * @param targetHandler the document handler to mimic */ public void mimicDocumentHandler(IFDocumentHandler targetHandler) { this.mimicHandler = targetHandler; } /** * Returns the document handler that is being mimicked by this serializer. * @return the mimicked document handler or null if no such document handler has been set */ public IFDocumentHandler getMimickedDocumentHandler() { return this.mimicHandler; } /** {@inheritDoc} */ public FontInfo getFontInfo() { if (this.mimicHandler != null) { return this.mimicHandler.getFontInfo(); } else { return null; } } /** {@inheritDoc} */ public void setFontInfo(FontInfo fontInfo) { if (this.mimicHandler != null) { this.mimicHandler.setFontInfo(fontInfo); } } /** {@inheritDoc} */ public void setDefaultFontInfo(FontInfo fontInfo) { if (this.mimicHandler != null) { this.mimicHandler.setDefaultFontInfo(fontInfo); } } /** {@inheritDoc} */ public void startDocument() throws IFException { super.startDocument(); try { handler.startDocument(); handler.startPrefixMapping("", NAMESPACE); handler.startPrefixMapping(XLINK_PREFIX, XLINK_NAMESPACE); handler.startPrefixMapping(DocumentNavigationExtensionConstants.PREFIX, DocumentNavigationExtensionConstants.NAMESPACE); handler.startElement(EL_DOCUMENT); } catch (SAXException e) { throw new IFException("SAX error in startDocument()", e); } } /** {@inheritDoc} */ public void startDocumentHeader() throws IFException { try { handler.startElement(EL_HEADER); } catch (SAXException e) { throw new IFException("SAX error in startDocumentHeader()", e); } } /** {@inheritDoc} */ public void endDocumentHeader() throws IFException { try { handler.endElement(EL_HEADER); } catch (SAXException e) { throw new IFException("SAX error in startDocumentHeader()", e); } } /** {@inheritDoc} */ public void startDocumentTrailer() throws IFException { try { handler.startElement(EL_TRAILER); } catch (SAXException e) { throw new IFException("SAX error in startDocumentTrailer()", e); } } /** {@inheritDoc} */ public void endDocumentTrailer() throws IFException { try { handler.endElement(EL_TRAILER); } catch (SAXException e) { throw new IFException("SAX error in endDocumentTrailer()", e); } } /** {@inheritDoc} */ public void endDocument() throws IFException { try { handler.endElement(EL_DOCUMENT); handler.endDocument(); finishDocumentNavigation(); } catch (SAXException e) { throw new IFException("SAX error in endDocument()", e); } } /** {@inheritDoc} */ public void startPageSequence(String id) throws IFException { try { AttributesImpl atts = new AttributesImpl(); if (id != null) { atts.addAttribute(XML_NAMESPACE, "id", "xml:id", XMLUtil.CDATA, id); } Locale lang = getContext().getLanguage(); if (lang != null) { atts.addAttribute(XML_NAMESPACE, "lang", "xml:lang", XMLUtil.CDATA, XMLUtil.toRFC3066(lang)); } XMLUtil.addAttribute(atts, XMLConstants.XML_SPACE, "preserve"); addForeignAttributes(atts); handler.startElement(EL_PAGE_SEQUENCE, atts); if (this.getUserAgent().isAccessibilityEnabled()) { StructureTree structureTree = getUserAgent().getStructureTree(); handler.startElement(EL_STRUCTURE_TREE); // add structure tree NodeList nodes = structureTree.getPageSequence(pageSequenceIndex++); for (int i = 0, n = nodes.getLength(); i < n; i++) { Node node = nodes.item(i); new DOM2SAX(handler).writeFragment(node); } handler.endElement(EL_STRUCTURE_TREE); } } catch (SAXException e) { throw new IFException("SAX error in startPageSequence()", e); } } /** {@inheritDoc} */ public void endPageSequence() throws IFException { try { handler.endElement(EL_PAGE_SEQUENCE); } catch (SAXException e) { throw new IFException("SAX error in endPageSequence()", e); } } /** {@inheritDoc} */ public void startPage(int index, String name, String pageMasterName, Dimension size) throws IFException { try { AttributesImpl atts = new AttributesImpl(); addAttribute(atts, "index", Integer.toString(index)); addAttribute(atts, "name", name); addAttribute(atts, "page-master-name", pageMasterName); addAttribute(atts, "width", Integer.toString(size.width)); addAttribute(atts, "height", Integer.toString(size.height)); addForeignAttributes(atts); handler.startElement(EL_PAGE, atts); } catch (SAXException e) { throw new IFException("SAX error in startPage()", e); } } /** {@inheritDoc} */ public void startPageHeader() throws IFException { try { handler.startElement(EL_PAGE_HEADER); } catch (SAXException e) { throw new IFException("SAX error in startPageHeader()", e); } } /** {@inheritDoc} */ public void endPageHeader() throws IFException { try { handler.endElement(EL_PAGE_HEADER); } catch (SAXException e) { throw new IFException("SAX error in endPageHeader()", e); } } /** {@inheritDoc} */ public IFPainter startPageContent() throws IFException { try { handler.startElement(EL_PAGE_CONTENT); this.state = IFState.create(); return this; } catch (SAXException e) { throw new IFException("SAX error in startPageContent()", e); } } /** {@inheritDoc} */ public void endPageContent() throws IFException { try { this.state = null; handler.endElement(EL_PAGE_CONTENT); } catch (SAXException e) { throw new IFException("SAX error in endPageContent()", e); } } /** {@inheritDoc} */ public void startPageTrailer() throws IFException { try { handler.startElement(EL_PAGE_TRAILER); } catch (SAXException e) { throw new IFException("SAX error in startPageTrailer()", e); } } /** {@inheritDoc} */ public void endPageTrailer() throws IFException { try { commitNavigation(); handler.endElement(EL_PAGE_TRAILER); } catch (SAXException e) { throw new IFException("SAX error in endPageTrailer()", e); } } /** {@inheritDoc} */ public void endPage() throws IFException { try { handler.endElement(EL_PAGE); } catch (SAXException e) { throw new IFException("SAX error in endPage()", e); } } //---=== IFPainter ===--- /** {@inheritDoc} */ public void startViewport(AffineTransform transform, Dimension size, Rectangle clipRect) throws IFException { startViewport(IFUtil.toString(transform), size, clipRect); } /** {@inheritDoc} */ public void startViewport(AffineTransform[] transforms, Dimension size, Rectangle clipRect) throws IFException { startViewport(IFUtil.toString(transforms), size, clipRect); } private void startViewport(String transform, Dimension size, Rectangle clipRect) throws IFException { try { AttributesImpl atts = new AttributesImpl(); if (transform != null && transform.length() > 0) { addAttribute(atts, "transform", transform); } addAttribute(atts, "width", Integer.toString(size.width)); addAttribute(atts, "height", Integer.toString(size.height)); if (clipRect != null) { addAttribute(atts, "clip-rect", IFUtil.toString(clipRect)); } handler.startElement(EL_VIEWPORT, atts); } catch (SAXException e) { throw new IFException("SAX error in startViewport()", e); } } /** {@inheritDoc} */ public void endViewport() throws IFException { try { handler.endElement(EL_VIEWPORT); } catch (SAXException e) { throw new IFException("SAX error in endViewport()", e); } } /** {@inheritDoc} */ public void startGroup(AffineTransform[] transforms) throws IFException { startGroup(IFUtil.toString(transforms)); } /** {@inheritDoc} */ public void startGroup(AffineTransform transform) throws IFException { startGroup(IFUtil.toString(transform)); } private void startGroup(String transform) throws IFException { try { AttributesImpl atts = new AttributesImpl(); if (transform != null && transform.length() > 0) { addAttribute(atts, "transform", transform); } handler.startElement(EL_GROUP, atts); } catch (SAXException e) { throw new IFException("SAX error in startGroup()", e); } } /** {@inheritDoc} */ public void endGroup() throws IFException { try { handler.endElement(EL_GROUP); } catch (SAXException e) { throw new IFException("SAX error in endGroup()", e); } } /** {@inheritDoc} */ public void drawImage(String uri, Rectangle rect) throws IFException { try { AttributesImpl atts = new AttributesImpl(); addAttribute(atts, XLINK_HREF, uri); addAttribute(atts, "x", Integer.toString(rect.x)); addAttribute(atts, "y", Integer.toString(rect.y)); addAttribute(atts, "width", Integer.toString(rect.width)); addAttribute(atts, "height", Integer.toString(rect.height)); addForeignAttributes(atts); addStructurePointerAttribute(atts); handler.element(EL_IMAGE, atts); } catch (SAXException e) { throw new IFException("SAX error in startGroup()", e); } } private void addForeignAttributes(AttributesImpl atts) throws SAXException { Map foreignAttributes = getContext().getForeignAttributes(); if (!foreignAttributes.isEmpty()) { Iterator iter = foreignAttributes.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry)iter.next(); addAttribute(atts, (QName)entry.getKey(), entry.getValue().toString()); } } } /** {@inheritDoc} */ public void drawImage(Document doc, Rectangle rect) throws IFException { try { AttributesImpl atts = new AttributesImpl(); addAttribute(atts, "x", Integer.toString(rect.x)); addAttribute(atts, "y", Integer.toString(rect.y)); addAttribute(atts, "width", Integer.toString(rect.width)); addAttribute(atts, "height", Integer.toString(rect.height)); addForeignAttributes(atts); addStructurePointerAttribute(atts); handler.startElement(EL_IMAGE, atts); new DOM2SAX(handler).writeDocument(doc, true); handler.endElement(EL_IMAGE); } catch (SAXException e) { throw new IFException("SAX error in startGroup()", e); } } private static String toString(Paint paint) { if (paint instanceof Color) { return ColorUtil.colorToString((Color)paint); } else { throw new UnsupportedOperationException("Paint not supported: " + paint); } } /** {@inheritDoc} */ public void clipRect(Rectangle rect) throws IFException { try { AttributesImpl atts = new AttributesImpl(); addAttribute(atts, "x", Integer.toString(rect.x)); addAttribute(atts, "y", Integer.toString(rect.y)); addAttribute(atts, "width", Integer.toString(rect.width)); addAttribute(atts, "height", Integer.toString(rect.height)); handler.element(EL_CLIP_RECT, atts); } catch (SAXException e) { throw new IFException("SAX error in clipRect()", e); } } /** {@inheritDoc} */ public void fillRect(Rectangle rect, Paint fill) throws IFException { if (fill == null) { return; } try { AttributesImpl atts = new AttributesImpl(); addAttribute(atts, "x", Integer.toString(rect.x)); addAttribute(atts, "y", Integer.toString(rect.y)); addAttribute(atts, "width", Integer.toString(rect.width)); addAttribute(atts, "height", Integer.toString(rect.height)); addAttribute(atts, "fill", toString(fill)); handler.element(EL_RECT, atts); } catch (SAXException e) { throw new IFException("SAX error in fillRect()", e); } } /** {@inheritDoc} */ public void drawBorderRect(Rectangle rect, BorderProps before, BorderProps after, BorderProps start, BorderProps end) throws IFException { if (before == null && after == null && start == null && end == null) { return; } try { AttributesImpl atts = new AttributesImpl(); addAttribute(atts, "x", Integer.toString(rect.x)); addAttribute(atts, "y", Integer.toString(rect.y)); addAttribute(atts, "width", Integer.toString(rect.width)); addAttribute(atts, "height", Integer.toString(rect.height)); if (before != null) { addAttribute(atts, "before", before.toString()); } if (after != null) { addAttribute(atts, "after", after.toString()); } if (start != null) { addAttribute(atts, "start", start.toString()); } if (end != null) { addAttribute(atts, "end", end.toString()); } handler.element(EL_BORDER_RECT, atts); } catch (SAXException e) { throw new IFException("SAX error in drawBorderRect()", e); } } /** {@inheritDoc} */ public void drawLine(Point start, Point end, int width, Color color, RuleStyle style) throws IFException { try { AttributesImpl atts = new AttributesImpl(); addAttribute(atts, "x1", Integer.toString(start.x)); addAttribute(atts, "y1", Integer.toString(start.y)); addAttribute(atts, "x2", Integer.toString(end.x)); addAttribute(atts, "y2", Integer.toString(end.y)); addAttribute(atts, "stroke-width", Integer.toString(width)); addAttribute(atts, "color", ColorUtil.colorToString(color)); addAttribute(atts, "style", style.getName()); handler.element(EL_LINE, atts); } catch (SAXException e) { throw new IFException("SAX error in drawLine()", e); } } /** {@inheritDoc} */ public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[] dx, String text) throws IFException { try { AttributesImpl atts = new AttributesImpl(); addAttribute(atts, "x", Integer.toString(x)); addAttribute(atts, "y", Integer.toString(y)); if (letterSpacing != 0) { addAttribute(atts, "letter-spacing", Integer.toString(letterSpacing)); } if (wordSpacing != 0) { addAttribute(atts, "word-spacing", Integer.toString(wordSpacing)); } if (dx != null) { addAttribute(atts, "dx", IFUtil.toString(dx)); } addStructurePointerAttribute(atts); handler.startElement(EL_TEXT, atts); char[] chars = text.toCharArray(); handler.characters(chars, 0, chars.length); handler.endElement(EL_TEXT); } catch (SAXException e) { throw new IFException("SAX error in setFont()", e); } } /** {@inheritDoc} */ public void setFont(String family, String style, Integer weight, String variant, Integer size, Color color) throws IFException { try { AttributesImpl atts = new AttributesImpl(); boolean changed; if (family != null) { changed = !family.equals(state.getFontFamily()); if (changed) { state.setFontFamily(family); addAttribute(atts, "family", family); } } if (style != null) { changed = !style.equals(state.getFontStyle()); if (changed) { state.setFontStyle(style); addAttribute(atts, "style", style); } } if (weight != null) { changed = (weight.intValue() != state.getFontWeight()); if (changed) { state.setFontWeight(weight.intValue()); addAttribute(atts, "weight", weight.toString()); } } if (variant != null) { changed = !variant.equals(state.getFontVariant()); if (changed) { state.setFontVariant(variant); addAttribute(atts, "variant", variant); } } if (size != null) { changed = (size.intValue() != state.getFontSize()); if (changed) { state.setFontSize(size.intValue()); addAttribute(atts, "size", size.toString()); } } if (color != null) { changed = !color.equals(state.getTextColor()); if (changed) { state.setTextColor(color); addAttribute(atts, "color", toString(color)); } } if (atts.getLength() > 0) { handler.element(EL_FONT, atts); } } catch (SAXException e) { throw new IFException("SAX error in setFont()", e); } } /** {@inheritDoc} */ public void handleExtensionObject(Object extension) throws IFException { if (extension instanceof XMLizable) { try { ((XMLizable)extension).toSAX(this.handler); } catch (SAXException e) { throw new IFException("SAX error while handling extension object", e); } } else { throw new UnsupportedOperationException( "Extension must implement XMLizable: " + extension + " (" + extension.getClass().getName() + ")"); } } /** * @return a new rendering context * @throws IllegalStateException unless overridden */ protected RenderingContext createRenderingContext() throws IllegalStateException { throw new IllegalStateException("Should never be called!"); } private void addAttribute(AttributesImpl atts, org.apache.xmlgraphics.util.QName attribute, String value) throws SAXException { handler.startPrefixMapping(attribute.getPrefix(), attribute.getNamespaceURI()); XMLUtil.addAttribute(atts, attribute, value); } private void addAttribute(AttributesImpl atts, String localName, String value) { XMLUtil.addAttribute(atts, localName, value); } private void addStructurePointerAttribute(AttributesImpl atts) { String ptr = getContext().getStructurePointer(); if (ptr != null) { addAttribute(atts, "ptr", ptr); } } // ---=== IFDocumentNavigationHandler ===--- private Map incompleteActions = new java.util.HashMap(); private List completeActions = new java.util.LinkedList(); private void noteAction(AbstractAction action) { if (action == null) { throw new NullPointerException("action must not be null"); } if (!action.isComplete()) { assert action.hasId(); incompleteActions.put(action.getId(), action); } } /** {@inheritDoc} */ public void renderNamedDestination(NamedDestination destination) throws IFException { noteAction(destination.getAction()); AttributesImpl atts = new AttributesImpl(); atts.addAttribute(null, "name", "name", XMLConstants.CDATA, destination.getName()); try { handler.startElement(DocumentNavigationExtensionConstants.NAMED_DESTINATION, atts); serializeXMLizable(destination.getAction()); handler.endElement(DocumentNavigationExtensionConstants.NAMED_DESTINATION); } catch (SAXException e) { throw new IFException("SAX error serializing named destination", e); } } /** {@inheritDoc} */ public void renderBookmarkTree(BookmarkTree tree) throws IFException { AttributesImpl atts = new AttributesImpl(); try { handler.startElement(DocumentNavigationExtensionConstants.BOOKMARK_TREE, atts); Iterator iter = tree.getBookmarks().iterator(); while (iter.hasNext()) { Bookmark b = (Bookmark)iter.next(); serializeBookmark(b); } handler.endElement(DocumentNavigationExtensionConstants.BOOKMARK_TREE); } catch (SAXException e) { throw new IFException("SAX error serializing bookmark tree", e); } } private void serializeBookmark(Bookmark bookmark) throws SAXException, IFException { noteAction(bookmark.getAction()); AttributesImpl atts = new AttributesImpl(); atts.addAttribute(null, "title", "title", XMLUtil.CDATA, bookmark.getTitle()); atts.addAttribute(null, "starting-state", "starting-state", XMLUtil.CDATA, bookmark.isShown() ? "show" : "hide"); handler.startElement(DocumentNavigationExtensionConstants.BOOKMARK, atts); serializeXMLizable(bookmark.getAction()); Iterator iter = bookmark.getChildBookmarks().iterator(); while (iter.hasNext()) { Bookmark b = (Bookmark)iter.next(); serializeBookmark(b); } handler.endElement(DocumentNavigationExtensionConstants.BOOKMARK); } /** {@inheritDoc} */ public void renderLink(Link link) throws IFException { noteAction(link.getAction()); AttributesImpl atts = new AttributesImpl(); atts.addAttribute(null, "rect", "rect", XMLConstants.CDATA, IFUtil.toString(link.getTargetRect())); if (getUserAgent().isAccessibilityEnabled()) { addAttribute(atts, "ptr", link.getAction().getStructurePointer()); } try { handler.startElement(DocumentNavigationExtensionConstants.LINK, atts); serializeXMLizable(link.getAction()); handler.endElement(DocumentNavigationExtensionConstants.LINK); } catch (SAXException e) { throw new IFException("SAX error serializing link", e); } } /** {@inheritDoc} */ public void addResolvedAction(AbstractAction action) throws IFException { assert action.isComplete(); assert action.hasId(); AbstractAction noted = (AbstractAction)incompleteActions.remove(action.getId()); if (noted != null) { completeActions.add(action); } else { //ignore as it was already complete when it was first used. } } private void commitNavigation() throws IFException { Iterator iter = this.completeActions.iterator(); while (iter.hasNext()) { AbstractAction action = (AbstractAction)iter.next(); iter.remove(); serializeXMLizable(action); } assert this.completeActions.size() == 0; } private void finishDocumentNavigation() { assert this.incompleteActions.size() == 0 : "Still holding incomplete actions!"; } private void serializeXMLizable(XMLizable object) throws IFException { try { object.toSAX(handler); } catch (SAXException e) { throw new IFException("SAX error serializing object", e); } } }