blob: 695514776b09cb8bc16fe29fb3e862cbf10e9a3b [file] [log] [blame]
/*
* 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.Map;
import org.w3c.dom.Document;
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.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;
/** 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);
}
addForeignAttributes(atts);
handler.startElement(EL_PAGE_SEQUENCE, atts);
} 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);
handler.element(EL_IMAGE, atts);
} catch (SAXException e) {
throw new IFException("SAX error in startGroup()", e);
}
}
private void addForeignAttributes(AttributesImpl atts) {
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);
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();
XMLUtil.addAttribute(atts, XMLConstants.XML_SPACE, "preserve");
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));
}
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() + ")");
}
}
/** {@inheritDoc} */
protected RenderingContext createRenderingContext() {
throw new IllegalStateException("Should never be called!");
}
private void addAttribute(AttributesImpl atts,
org.apache.xmlgraphics.util.QName attribute, String value) {
XMLUtil.addAttribute(atts, attribute, value);
}
private void addAttribute(AttributesImpl atts, String localName, String value) {
XMLUtil.addAttribute(atts, localName, value);
}
// ---=== 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()));
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);
}
}
}