/* * 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.area; import java.awt.Color; import java.awt.geom.Rectangle2D; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import java.util.StringTokenizer; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.sax.SAXResult; import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.sax.TransformerHandler; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.xmlgraphics.image.loader.ImageInfo; import org.apache.xmlgraphics.image.loader.ImageManager; import org.apache.xmlgraphics.image.loader.ImageSessionContext; import org.apache.xmlgraphics.util.QName; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.area.Trait.Background; import org.apache.fop.area.Trait.InternalLink; import org.apache.fop.area.inline.AbstractTextArea; import org.apache.fop.area.inline.Character; 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.InlineBlockParent; import org.apache.fop.area.inline.InlineParent; import org.apache.fop.area.inline.Leader; import org.apache.fop.area.inline.Space; import org.apache.fop.area.inline.SpaceArea; import org.apache.fop.area.inline.TextArea; import org.apache.fop.area.inline.Viewport; import org.apache.fop.area.inline.WordArea; import org.apache.fop.fo.Constants; import org.apache.fop.fo.ElementMappingRegistry; import org.apache.fop.fo.expr.PropertyException; import org.apache.fop.fo.extensions.ExtensionAttachment; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; import org.apache.fop.traits.BorderProps; import org.apache.fop.util.ColorUtil; import org.apache.fop.util.ContentHandlerFactory; import org.apache.fop.util.ContentHandlerFactoryRegistry; import org.apache.fop.util.DefaultErrorListener; /** * This is a parser for the area tree XML (intermediate format) which is used to reread an area * tree (or part of it) into memory again for rendering to the final output format. */ public class AreaTreeParser { /** Logger instance */ protected static Log log = LogFactory.getLog(AreaTreeParser.class); private static SAXTransformerFactory tFactory = (SAXTransformerFactory)SAXTransformerFactory.newInstance(); /** * Parses an intermediate file (area tree XML) into an AreaTreeModel instance by adding * pages to it. * @param src the Source instance pointing to the intermediate file * @param treeModel the AreaTreeModel that the parsed pages are added to * @param userAgent the user agent * @throws TransformerException if an error occurs while parsing the area tree XML */ public void parse(Source src, AreaTreeModel treeModel, FOUserAgent userAgent) throws TransformerException { Transformer transformer = tFactory.newTransformer(); transformer.setErrorListener(new DefaultErrorListener(log)); SAXResult res = new SAXResult(getContentHandler(treeModel, userAgent)); transformer.transform(src, res); } /** * Creates a new ContentHandler instance that you can send the area tree XML to. The parsed * pages are added to the AreaTreeModel instance you pass in as a parameter. * @param treeModel the AreaTreeModel that the parsed pages are added to * @param userAgent the user agent * @return the ContentHandler instance to receive the SAX stream from the area tree XML */ public ContentHandler getContentHandler(AreaTreeModel treeModel, FOUserAgent userAgent) { ElementMappingRegistry elementMappingRegistry = userAgent.getFactory().getElementMappingRegistry(); return new Handler(treeModel, userAgent, elementMappingRegistry); } private static class Handler extends DefaultHandler { private Map makers = new java.util.HashMap(); private AreaTreeModel treeModel; private FOUserAgent userAgent; private ElementMappingRegistry elementMappingRegistry; private Attributes lastAttributes; private StringBuffer content = new StringBuffer(); private PageViewport currentPageViewport; private Map pageViewportsByKey = new java.util.HashMap(); // set of "ID firsts" that have already been assigned to a PV: private Set idFirstsAssigned = new java.util.HashSet(); private Stack areaStack = new Stack(); private boolean firstFlow; private Stack delegateStack = new Stack(); private ContentHandler delegate; private DOMImplementation domImplementation; public Handler(AreaTreeModel treeModel, FOUserAgent userAgent, ElementMappingRegistry elementMappingRegistry) { this.treeModel = treeModel; this.userAgent = userAgent; this.elementMappingRegistry = elementMappingRegistry; makers.put("areaTree", new AreaTreeMaker()); makers.put("page", new PageMaker()); makers.put("pageSequence", new PageSequenceMaker()); makers.put("title", new TitleMaker()); makers.put("pageViewport", new PageViewportMaker()); makers.put("regionViewport", new RegionViewportMaker()); makers.put("regionBefore", new RegionBeforeMaker()); makers.put("regionAfter", new RegionAfterMaker()); makers.put("regionStart", new RegionStartMaker()); makers.put("regionEnd", new RegionEndMaker()); makers.put("regionBody", new RegionBodyMaker()); makers.put("flow", new FlowMaker()); makers.put("mainReference", new MainReferenceMaker()); makers.put("span", new SpanMaker()); makers.put("footnote", new FootnoteMaker()); makers.put("beforeFloat", new BeforeFloatMaker()); makers.put("block", new BlockMaker()); makers.put("lineArea", new LineAreaMaker()); makers.put("inline", new InlineMaker()); makers.put("inlineparent", new InlineParentMaker()); makers.put("inlineblockparent", new InlineBlockParentMaker()); makers.put("text", new TextMaker()); makers.put("word", new WordMaker()); makers.put("space", new SpaceMaker()); makers.put("char", new CharMaker()); makers.put("leader", new LeaderMaker()); makers.put("viewport", new ViewportMaker()); makers.put("image", new ImageMaker()); makers.put("foreignObject", new ForeignObjectMaker()); makers.put("bookmarkTree", new BookmarkTreeMaker()); makers.put("bookmark", new BookmarkMaker()); makers.put("destination", new DestinationMaker()); } private static Rectangle2D parseRect(String rect) { StringTokenizer tokenizer = new StringTokenizer(rect, " "); return new Rectangle2D.Double( Double.parseDouble(tokenizer.nextToken()), Double.parseDouble(tokenizer.nextToken()), Double.parseDouble(tokenizer.nextToken()), Double.parseDouble(tokenizer.nextToken())); } private Area findAreaType(Class clazz) { if (areaStack.size() > 0) { int pos = areaStack.size() - 1; Object obj = null; while (pos >= 0 && !(clazz.isInstance(obj = areaStack.get(pos)))) { pos--; } if (pos >= 0) { return (Area)obj; } } return null; } private RegionViewport getCurrentRegionViewport() { return (RegionViewport)findAreaType(RegionViewport.class); } private BodyRegion getCurrentBodyRegion() { return (BodyRegion)findAreaType(BodyRegion.class); } private BlockParent getCurrentBlockParent() { return (BlockParent)findAreaType(BlockParent.class); } private AbstractTextArea getCurrentText() { return (AbstractTextArea)findAreaType(AbstractTextArea.class); } private Viewport getCurrentViewport() { return (Viewport)findAreaType(Viewport.class); } /** {@inheritDoc} */ public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (delegate != null) { delegateStack.push(qName); delegate.startElement(uri, localName, qName, attributes); } else if (domImplementation != null) { //domImplementation is set so we need to start a new DOM building sub-process TransformerHandler handler; try { handler = tFactory.newTransformerHandler(); } catch (TransformerConfigurationException e) { throw new SAXException("Error creating a new TransformerHandler", e); } Document doc = domImplementation.createDocument(uri, qName, null); //It's easier to work with an empty document, so remove the root element doc.removeChild(doc.getDocumentElement()); handler.setResult(new DOMResult(doc)); Area parent = (Area)areaStack.peek(); ((ForeignObject)parent).setDocument(doc); //activate delegate for nested foreign document domImplementation = null; //Not needed anymore now this.delegate = handler; delegateStack.push(qName); delegate.startDocument(); delegate.startElement(uri, localName, qName, attributes); } else { lastAttributes = attributes; boolean handled = true; if ("".equals(uri)) { Maker maker = (Maker)makers.get(localName); if (maker != null) { maker.startElement(attributes); } else if ("extension-attachments".equals(localName)) { //TODO implement me } else { handled = false; } } else { ContentHandlerFactoryRegistry registry = userAgent.getFactory().getContentHandlerFactoryRegistry(); ContentHandlerFactory factory = registry.getFactory(uri); if (factory != null) { delegate = factory.createContentHandler(); delegateStack.push(qName); delegate.startDocument(); delegate.startElement(uri, localName, qName, attributes); } else { handled = false; } } if (!handled) { if (uri == null || uri.length() == 0) { throw new SAXException("Unhandled element " + localName + " in namespace: " + uri); } else { log.warn("Unhandled element " + localName + " in namespace: " + uri); } } } } /** {@inheritDoc} */ public void endElement(String uri, String localName, String qName) throws SAXException { if (delegate != null) { delegate.endElement(uri, localName, qName); delegateStack.pop(); if (delegateStack.size() == 0) { delegate.endDocument(); if (delegate instanceof ContentHandlerFactory.ObjectSource) { Object obj = ((ContentHandlerFactory.ObjectSource)delegate).getObject(); handleExternallyGeneratedObject(obj); } delegate = null; //Sub-document is processed, return to normal processing } } else { if ("".equals(uri)) { Maker maker = (Maker)makers.get(localName); if (maker != null) { maker.endElement(); } } else { //log.debug("Ignoring " + localName + " in namespace: " + uri); } content.setLength(0); //Reset text buffer (see characters()) } } // ============== Maker classes for the area tree objects ============= private static interface Maker { void startElement(Attributes attributes) throws SAXException; void endElement(); } private abstract class AbstractMaker implements Maker { public void startElement(Attributes attributes) throws SAXException { //nop } public void endElement() { //nop } } private class AreaTreeMaker extends AbstractMaker { public void startElement(Attributes attributes) { // In case the Handler is reused: idFirstsAssigned.clear(); } } private class PageSequenceMaker extends AbstractMaker { public void startElement(Attributes attributes) { PageSequence pageSequence = new PageSequence(null); String lang = attributes.getValue("language"); pageSequence.setLanguage(lang); String country = attributes.getValue("country"); pageSequence.setCountry(country); areaStack.push(pageSequence); } } private class TitleMaker extends AbstractMaker { public void startElement(Attributes attributes) { LineArea line = new LineArea(); transferForeignObjects(attributes, line); areaStack.push(line); } public void endElement() { LineArea line = (LineArea)areaStack.pop(); PageSequence pageSequence = (PageSequence)areaStack.peek(); pageSequence.setTitle(line); } } private class PageViewportMaker extends AbstractMaker { public void startElement(Attributes attributes) { if (!areaStack.isEmpty()) { PageSequence pageSequence = (PageSequence)areaStack.peek(); treeModel.startPageSequence(pageSequence); areaStack.pop(); } if (currentPageViewport != null) { throw new IllegalStateException("currentPageViewport must be null"); } Rectangle2D viewArea = parseRect(attributes.getValue("bounds")); int pageNumber = getAttributeAsInteger(attributes, "nr", -1); String key = attributes.getValue("key"); String pageNumberString = attributes.getValue("formatted-nr"); String pageMaster = attributes.getValue("simple-page-master-name"); boolean blank = getAttributeAsBoolean(attributes, "blank", false); currentPageViewport = new PageViewport(viewArea, pageNumber, pageNumberString, pageMaster, blank); transferForeignObjects(attributes, currentPageViewport); currentPageViewport.setKey(key); pageViewportsByKey.put(key, currentPageViewport); } } private class PageMaker extends AbstractMaker { public void startElement(Attributes attributes) { Page p = new Page(); currentPageViewport.setPage(p); } public void endElement() { treeModel.addPage(currentPageViewport); currentPageViewport = null; } } private class RegionViewportMaker extends AbstractMaker { public void startElement(Attributes attributes) { RegionViewport rv = getCurrentRegionViewport(); if (rv != null) { throw new IllegalStateException("Current RegionViewport must be null"); } Rectangle2D viewArea = parseRect(attributes.getValue("rect")); rv = new RegionViewport(viewArea); transferForeignObjects(attributes, rv); rv.setClip(getAttributeAsBoolean(attributes, "clipped", false)); setAreaAttributes(attributes, rv); setTraits(attributes, rv, SUBSET_COMMON); setTraits(attributes, rv, SUBSET_BOX); setTraits(attributes, rv, SUBSET_COLOR); areaStack.push(rv); } public void endElement() { assertObjectOfClass(areaStack.pop(), RegionViewport.class); } } private class RegionBeforeMaker extends AbstractMaker { public void startElement(Attributes attributes) { pushNewRegionReference(attributes, Constants.FO_REGION_BEFORE); } public void endElement() { assertObjectOfClass(areaStack.pop(), RegionReference.class); } } private class RegionAfterMaker extends AbstractMaker { public void startElement(Attributes attributes) { pushNewRegionReference(attributes, Constants.FO_REGION_AFTER); } public void endElement() { assertObjectOfClass(areaStack.pop(), RegionReference.class); } } private class RegionStartMaker extends AbstractMaker { public void startElement(Attributes attributes) { pushNewRegionReference(attributes, Constants.FO_REGION_START); } public void endElement() { assertObjectOfClass(areaStack.pop(), RegionReference.class); } } private class RegionEndMaker extends AbstractMaker { public void startElement(Attributes attributes) { pushNewRegionReference(attributes, Constants.FO_REGION_END); } public void endElement() { assertObjectOfClass(areaStack.pop(), RegionReference.class); } } private class RegionBodyMaker extends AbstractMaker { public void startElement(Attributes attributes) { BodyRegion body = getCurrentBodyRegion(); if (body != null) { throw new IllegalStateException("Current BodyRegion must be null"); } String regionName = attributes.getValue("name"); int columnCount = getAttributeAsInteger(attributes, "columnCount", 1); int columnGap = getAttributeAsInteger(attributes, "columnGap", 0); RegionViewport rv = getCurrentRegionViewport(); body = new BodyRegion(Constants.FO_REGION_BODY, regionName, rv, columnCount, columnGap); transferForeignObjects(attributes, body); body.setCTM(getAttributeAsCTM(attributes, "ctm")); setAreaAttributes(attributes, body); rv.setRegionReference(body); currentPageViewport.getPage().setRegionViewport( Constants.FO_REGION_BODY, rv); areaStack.push(body); } public void endElement() { assertObjectOfClass(areaStack.pop(), BodyRegion.class); } } private class FlowMaker extends AbstractMaker { public void startElement(Attributes attributes) { BodyRegion body = getCurrentBodyRegion(); if (!firstFlow) { body.getMainReference().getCurrentSpan().moveToNextFlow(); } else { firstFlow = false; } NormalFlow flow = body.getMainReference().getCurrentSpan().getCurrentFlow(); transferForeignObjects(attributes, flow); setAreaAttributes(attributes, flow); areaStack.push(flow); } public void endElement() { assertObjectOfClass(areaStack.pop(), NormalFlow.class); } } private class MainReferenceMaker extends AbstractMaker { public void startElement(Attributes attributes) { //mainReference is created by the BodyRegion MainReference mr = getCurrentBodyRegion().getMainReference(); transferForeignObjects(attributes, mr); setAreaAttributes(attributes, mr); } } private class SpanMaker extends AbstractMaker { public void startElement(Attributes attributes) { int ipd = getAttributeAsInteger(attributes, "ipd", 0); int columnCount = getAttributeAsInteger(attributes, "columnCount", 1); BodyRegion body = getCurrentBodyRegion(); Span span = new Span(columnCount, body.getColumnGap(), ipd); transferForeignObjects(attributes, span); setAreaAttributes(attributes, span); body.getMainReference().getSpans().add(span); firstFlow = true; } } private class FootnoteMaker extends AbstractMaker { public void startElement(Attributes attributes) { Footnote fn = getCurrentBodyRegion().getFootnote(); transferForeignObjects(attributes, fn); areaStack.push(fn); } public void endElement() { assertObjectOfClass(areaStack.pop(), Footnote.class); } } private class BeforeFloatMaker extends AbstractMaker { public void startElement(Attributes attributes) { BeforeFloat bf = getCurrentBodyRegion().getBeforeFloat(); transferForeignObjects(attributes, bf); areaStack.push(bf); } public void endElement() { assertObjectOfClass(areaStack.pop(), BeforeFloat.class); } } private class BlockMaker extends AbstractMaker { public void startElement(Attributes attributes) { boolean isViewport = getAttributeAsBoolean(attributes, "is-viewport-area", false); Block block; if (isViewport) { BlockViewport bv = new BlockViewport(); bv.setClip(getAttributeAsBoolean(attributes, "clipped", false)); bv.setCTM(getAttributeAsCTM(attributes, "ctm")); if (bv.getPositioning() != BlockViewport.RELATIVE) { bv.setXOffset( getAttributeAsInteger(attributes, "left-position", 0)); bv.setYOffset( getAttributeAsInteger(attributes, "top-position", 0)); } block = bv; } else { block = new Block(); } String positioning = attributes.getValue("positioning"); if ("absolute".equalsIgnoreCase(positioning)) { block.setPositioning(Block.ABSOLUTE); } else if ("fixed".equalsIgnoreCase(positioning)) { block.setPositioning(Block.FIXED); } else if ("relative".equalsIgnoreCase(positioning)) { block.setPositioning(Block.RELATIVE); } else { block.setPositioning(Block.STACK); } if (attributes.getValue("left-offset") != null) { block.setXOffset(getAttributeAsInteger(attributes, "left-offset", 0)); } if (attributes.getValue("top-offset") != null) { block.setYOffset(getAttributeAsInteger(attributes, "top-offset", 0)); } transferForeignObjects(attributes, block); setAreaAttributes(attributes, block); setTraits(attributes, block, SUBSET_COMMON); setTraits(attributes, block, SUBSET_BOX); setTraits(attributes, block, SUBSET_COLOR); Area parent = (Area)areaStack.peek(); //BlockParent parent = getCurrentBlockParent(); parent.addChildArea(block); areaStack.push(block); } public void endElement() { assertObjectOfClass(areaStack.pop(), Block.class); } } private class LineAreaMaker extends AbstractMaker { public void startElement(Attributes attributes) { LineArea line = new LineArea(); setAreaAttributes(attributes, line); setTraits(attributes, line, SUBSET_COMMON); setTraits(attributes, line, SUBSET_BOX); setTraits(attributes, line, SUBSET_COLOR); BlockParent parent = getCurrentBlockParent(); parent.addChildArea(line); areaStack.push(line); } public void endElement() { assertObjectOfClass(areaStack.pop(), LineArea.class); } } // Maker for "generic" inline areas private class InlineMaker extends AbstractMaker { public void startElement(Attributes attributes) { InlineArea inl = new InlineArea(); transferForeignObjects(attributes, inl); inl.setOffset(getAttributeAsInteger(attributes, "offset", 0)); setAreaAttributes(attributes, inl); setTraits(attributes, inl, SUBSET_COMMON); setTraits(attributes, inl, SUBSET_BOX); setTraits(attributes, inl, SUBSET_COLOR); Area parent = (Area)areaStack.peek(); parent.addChildArea(inl); areaStack.push(inl); } public void endElement() { assertObjectOfClass(areaStack.pop(), InlineArea.class); } } private class InlineParentMaker extends AbstractMaker { public void startElement(Attributes attributes) { InlineParent ip = new InlineParent(); transferForeignObjects(attributes, ip); ip.setOffset(getAttributeAsInteger(attributes, "offset", 0)); setAreaAttributes(attributes, ip); setTraits(attributes, ip, SUBSET_COMMON); setTraits(attributes, ip, SUBSET_BOX); setTraits(attributes, ip, SUBSET_COLOR); setTraits(attributes, ip, SUBSET_LINK); Area parent = (Area)areaStack.peek(); parent.addChildArea(ip); areaStack.push(ip); } public void endElement() { assertObjectOfClass(areaStack.pop(), InlineParent.class); } } private class InlineBlockParentMaker extends AbstractMaker { public void startElement(Attributes attributes) { InlineBlockParent ibp = new InlineBlockParent(); transferForeignObjects(attributes, ibp); ibp.setOffset(getAttributeAsInteger(attributes, "offset", 0)); setAreaAttributes(attributes, ibp); setTraits(attributes, ibp, SUBSET_COMMON); setTraits(attributes, ibp, SUBSET_BOX); setTraits(attributes, ibp, SUBSET_COLOR); Area parent = (Area)areaStack.peek(); parent.addChildArea(ibp); areaStack.push(ibp); } public void endElement() { assertObjectOfClass(areaStack.pop(), InlineBlockParent.class); } } private class TextMaker extends AbstractMaker { public void startElement(Attributes attributes) { if (getCurrentText() != null) { throw new IllegalStateException("Current Text must be null"); } TextArea text = new TextArea(); setAreaAttributes(attributes, text); setTraits(attributes, text, SUBSET_COMMON); setTraits(attributes, text, SUBSET_BOX); setTraits(attributes, text, SUBSET_COLOR); setTraits(attributes, text, SUBSET_FONT); text.setBaselineOffset(getAttributeAsInteger(attributes, "baseline", 0)); text.setOffset(getAttributeAsInteger(attributes, "offset", 0)); text.setTextLetterSpaceAdjust(getAttributeAsInteger(attributes, "tlsadjust", 0)); text.setTextWordSpaceAdjust(getAttributeAsInteger(attributes, "twsadjust", 0)); Area parent = (Area)areaStack.peek(); parent.addChildArea(text); areaStack.push(text); } public void endElement() { assertObjectOfClass(areaStack.pop(), TextArea.class); } } private class WordMaker extends AbstractMaker { private int[] toIntArray(String s) { if (s == null || s.length() == 0) { return null; } StringTokenizer tokenizer = new StringTokenizer(s, " "); List values = new java.util.ArrayList(); while (tokenizer.hasMoreTokens()) { values.add(new Integer(tokenizer.nextToken())); } int[] res = new int[values.size()]; for (int i = 0, c = res.length; i < c; i++) { res[i] = ((Integer)values.get(i)).intValue(); } return res; } public void endElement() { int offset = getAttributeAsInteger(lastAttributes, "offset", 0); int[] letterAdjust = toIntArray(lastAttributes.getValue("letter-adjust")); String txt = content.toString(); WordArea word = new WordArea(txt, offset, letterAdjust); AbstractTextArea text = getCurrentText(); word.setParentArea(text); text.addChildArea(word); } } private class SpaceMaker extends AbstractMaker { public void endElement() { int offset = getAttributeAsInteger(lastAttributes, "offset", 0); String txt = content.toString(); //TODO the isAdjustable parameter is currently not used/implemented if (txt.length() > 0) { boolean adjustable = getAttributeAsBoolean(lastAttributes, "adj", true); SpaceArea space = new SpaceArea(txt.charAt(0), offset, adjustable); AbstractTextArea text = getCurrentText(); space.setParentArea(text); text.addChildArea(space); } else { Space space = new Space(); setAreaAttributes(lastAttributes, space); setTraits(lastAttributes, space, SUBSET_COMMON); setTraits(lastAttributes, space, SUBSET_BOX); setTraits(lastAttributes, space, SUBSET_COLOR); space.setOffset(offset); Area parent = (Area)areaStack.peek(); parent.addChildArea(space); } } } private class CharMaker extends AbstractMaker { public void endElement() { String txt = content.toString(); Character ch = new Character(txt.charAt(0)); transferForeignObjects(lastAttributes, ch); setAreaAttributes(lastAttributes, ch); setTraits(lastAttributes, ch, SUBSET_COMMON); setTraits(lastAttributes, ch, SUBSET_BOX); setTraits(lastAttributes, ch, SUBSET_COLOR); setTraits(lastAttributes, ch, SUBSET_FONT); ch.setOffset(getAttributeAsInteger(lastAttributes, "offset", 0)); ch.setBaselineOffset(getAttributeAsInteger(lastAttributes, "baseline", 0)); Area parent = (Area)areaStack.peek(); parent.addChildArea(ch); } } private class LeaderMaker extends AbstractMaker { public void startElement(Attributes attributes) { Leader leader = new Leader(); transferForeignObjects(attributes, leader); setAreaAttributes(attributes, leader); setTraits(attributes, leader, SUBSET_COMMON); setTraits(attributes, leader, SUBSET_BOX); setTraits(attributes, leader, SUBSET_COLOR); setTraits(attributes, leader, SUBSET_FONT); leader.setOffset(getAttributeAsInteger(attributes, "offset", 0)); String ruleStyle = attributes.getValue("ruleStyle"); if (ruleStyle != null) { leader.setRuleStyle(ruleStyle); } leader.setRuleThickness( getAttributeAsInteger(attributes, "ruleThickness", 0)); Area parent = (Area)areaStack.peek(); parent.addChildArea(leader); } public void endElement() { } } private class ViewportMaker extends AbstractMaker { public void startElement(Attributes attributes) { Viewport viewport = new Viewport(null); transferForeignObjects(attributes, viewport); setAreaAttributes(attributes, viewport); setTraits(attributes, viewport, SUBSET_COMMON); setTraits(attributes, viewport, SUBSET_BOX); setTraits(attributes, viewport, SUBSET_COLOR); viewport.setContentPosition(getAttributeAsRectangle2D(attributes, "pos")); viewport.setClip(getAttributeAsBoolean(attributes, "clip", false)); viewport.setOffset(getAttributeAsInteger(attributes, "offset", 0)); Area parent = (Area)areaStack.peek(); parent.addChildArea(viewport); areaStack.push(viewport); } public void endElement() { assertObjectOfClass(areaStack.pop(), Viewport.class); } } private class ImageMaker extends AbstractMaker { public void startElement(Attributes attributes) { String url = attributes.getValue("url"); Image image = new Image(url); transferForeignObjects(attributes, image); setAreaAttributes(attributes, image); setTraits(attributes, image, SUBSET_COMMON); getCurrentViewport().setContent(image); } } private class ForeignObjectMaker extends AbstractMaker { public void startElement(Attributes attributes) throws SAXException { String ns = attributes.getValue("ns"); domImplementation = elementMappingRegistry.getDOMImplementationForNamespace(ns); if (domImplementation == null) { throw new SAXException("No DOMImplementation could be" + " identified to handle namespace: " + ns); } ForeignObject foreign = new ForeignObject(ns); transferForeignObjects(attributes, foreign); setAreaAttributes(attributes, foreign); setTraits(attributes, foreign, SUBSET_COMMON); getCurrentViewport().setContent(foreign); areaStack.push(foreign); } public void endElement() { assertObjectOfClass(areaStack.pop(), ForeignObject.class); } } private class BookmarkTreeMaker extends AbstractMaker { public void startElement(Attributes attributes) { BookmarkData bm = new BookmarkData(); areaStack.push(bm); } public void endElement() { Object tos = areaStack.pop(); assertObjectOfClass(tos, BookmarkData.class); treeModel.handleOffDocumentItem((BookmarkData) tos); // as long as the bookmark tree comes after the last PageViewport in the // area tree XML, we don't have to worry about resolved/unresolved. The // only resolution needed is the mapping of the pvKey to the PV instance. } } private class BookmarkMaker extends AbstractMaker { public void startElement(Attributes attributes) { String title = attributes.getValue("title"); boolean showChildren = getAttributeAsBoolean(attributes, "show-children", false); String[] linkdata = InternalLink.parseXMLAttribute(attributes.getValue("internal-link")); PageViewport pv = (PageViewport) pageViewportsByKey.get(linkdata[0]); BookmarkData bm = new BookmarkData(title, showChildren, pv, linkdata[1]); Object tos = areaStack.peek(); if (tos instanceof BookmarkData) { BookmarkData parent = (BookmarkData) tos; parent.addSubData(bm); } areaStack.push(bm); } public void endElement() { assertObjectOfClass(areaStack.pop(), BookmarkData.class); } } private class DestinationMaker extends AbstractMaker { public void startElement(Attributes attributes) { String[] linkdata = InternalLink.parseXMLAttribute(lastAttributes.getValue("internal-link")); PageViewport pv = (PageViewport) pageViewportsByKey.get(linkdata[0]); DestinationData dest = new DestinationData(linkdata[1]); List pages = new java.util.ArrayList(); pages.add(pv); dest.resolveIDRef(linkdata[1], pages); areaStack.push(dest); } public void endElement() { Object tos = areaStack.pop(); assertObjectOfClass(tos, DestinationData.class); treeModel.handleOffDocumentItem((DestinationData) tos); } } // ==================================================================== private void pushNewRegionReference(Attributes attributes, int side) { String regionName = attributes.getValue("name"); RegionViewport rv = getCurrentRegionViewport(); RegionReference reg = new RegionReference(side, regionName, rv); transferForeignObjects(attributes, reg); reg.setCTM(getAttributeAsCTM(attributes, "ctm")); setAreaAttributes(attributes, reg); rv.setRegionReference(reg); currentPageViewport.getPage().setRegionViewport( side, rv); areaStack.push(reg); } private void assertObjectOfClass(Object obj, Class clazz) { if (!clazz.isInstance(obj)) { throw new IllegalStateException("Object is not an instance of " + clazz.getName() + " but of " + obj.getClass().getName()); } } /** * Handles objects created by "sub-parsers" that implement the ObjectSource interface. * An example of object handled here are ExtensionAttachments. * @param obj the Object to be handled. */ protected void handleExternallyGeneratedObject(Object obj) { if (areaStack.size() == 0 && obj instanceof ExtensionAttachment) { ExtensionAttachment attachment = (ExtensionAttachment)obj; if (this.currentPageViewport == null) { this.treeModel.handleOffDocumentItem( new OffDocumentExtensionAttachment(attachment)); } else { this.currentPageViewport.addExtensionAttachment(attachment); } } else { log.warn("Don't know how to handle externally generated object: " + obj); } } private void setAreaAttributes(Attributes attributes, Area area) { area.setIPD(Integer.parseInt(attributes.getValue("ipd"))); area.setBPD(Integer.parseInt(attributes.getValue("bpd"))); } private static final Object[] SUBSET_COMMON = new Object[] { Trait.PROD_ID}; private static final Object[] SUBSET_LINK = new Object[] { Trait.INTERNAL_LINK, Trait.EXTERNAL_LINK}; private static final Object[] SUBSET_COLOR = new Object[] { Trait.BACKGROUND, Trait.COLOR}; private static final Object[] SUBSET_FONT = new Object[] { Trait.FONT, Trait.FONT_SIZE, Trait.BLINK, Trait.OVERLINE, Trait.OVERLINE_COLOR, Trait.LINETHROUGH, Trait.LINETHROUGH_COLOR, Trait.UNDERLINE, Trait.UNDERLINE_COLOR}; private static final Object[] SUBSET_BOX = new Object[] { Trait.BORDER_BEFORE, Trait.BORDER_AFTER, Trait.BORDER_START, Trait.BORDER_END, Trait.SPACE_BEFORE, Trait.SPACE_AFTER, Trait.SPACE_START, Trait.SPACE_END, Trait.PADDING_BEFORE, Trait.PADDING_AFTER, Trait.PADDING_START, Trait.PADDING_END, Trait.START_INDENT, Trait.END_INDENT, Trait.IS_REFERENCE_AREA, Trait.IS_VIEWPORT_AREA}; private void setTraits(Attributes attributes, Area area, Object[] traitSubset) { for (int i = 0, c = traitSubset.length; i < c; i++) { Object trait = traitSubset[i]; String traitName = Trait.getTraitName(trait); String value = attributes.getValue(traitName); if (value != null) { Class cl = Trait.getTraitClass(trait); if (cl == Integer.class) { area.addTrait(trait, new Integer(value)); } else if (cl == Boolean.class) { area.addTrait(trait, Boolean.valueOf(value)); } else if (cl == String.class) { area.addTrait(trait, value); if (trait == Trait.PROD_ID && !idFirstsAssigned.contains(value) && currentPageViewport != null) { currentPageViewport.setFirstWithID(value); idFirstsAssigned.add(value); } } else if (cl == Color.class) { try { area.addTrait(trait, ColorUtil.parseColorString(this.userAgent, value)); } catch (PropertyException e) { throw new IllegalArgumentException(e.getMessage()); } } else if (cl == InternalLink.class) { area.addTrait(trait, new InternalLink(value)); } else if (cl == Background.class) { Background bkg = new Background(); try { Color col = ColorUtil.parseColorString( this.userAgent, attributes.getValue("bkg-color")); bkg.setColor(col); } catch (PropertyException e) { throw new IllegalArgumentException(e.getMessage()); } String uri = attributes.getValue("bkg-img"); if (uri != null) { bkg.setURL(uri); try { ImageManager manager = userAgent.getFactory().getImageManager(); ImageSessionContext sessionContext = userAgent.getImageSessionContext(); ImageInfo info = manager.getImageInfo(uri, sessionContext); bkg.setImageInfo(info); } catch (Exception e) { log.error("Background image not available: " + uri, e); } String repeat = attributes.getValue("bkg-repeat"); if (repeat != null) { bkg.setRepeat(repeat); } bkg.setHoriz(getAttributeAsInteger(attributes, "bkg-horz-offset", 0)); bkg.setVertical(getAttributeAsInteger(attributes, "bkg-vert-offset", 0)); } area.addTrait(trait, bkg); } else if (cl == BorderProps.class) { area.addTrait(trait, BorderProps.valueOf(this.userAgent, value)); } } else { if (trait == Trait.FONT) { String fontName = attributes.getValue("font-name"); if (fontName != null) { String fontStyle = attributes.getValue("font-style"); int fontWeight = getAttributeAsInteger( attributes, "font-weight", Font.WEIGHT_NORMAL); area.addTrait(trait, FontInfo.createFontKey(fontName, fontStyle, fontWeight)); } } } } } private boolean getAttributeAsBoolean(Attributes attributes, String name, boolean defaultValue) { String s = attributes.getValue(name); if (s == null) { return defaultValue; } else { return Boolean.valueOf(s).booleanValue(); } } private int getAttributeAsInteger(Attributes attributes, String name, int defaultValue) { String s = attributes.getValue(name); if (s == null) { return defaultValue; } else { return Integer.parseInt(s); } } private CTM getAttributeAsCTM(Attributes attributes, String name) { String s = attributes.getValue(name).trim(); if (s.startsWith("[") && s.endsWith("]")) { s = s.substring(1, s.length() - 1); StringTokenizer tokenizer = new StringTokenizer(s, " "); double[] values = new double[] { Double.parseDouble(tokenizer.nextToken()), Double.parseDouble(tokenizer.nextToken()), Double.parseDouble(tokenizer.nextToken()), Double.parseDouble(tokenizer.nextToken()), Double.parseDouble(tokenizer.nextToken()), Double.parseDouble(tokenizer.nextToken())}; return new CTM(values[0], values[1], values[2], values[3], values[4], values[5]); } else { throw new IllegalArgumentException("CTM must be surrounded by square brackets"); } } private Rectangle2D getAttributeAsRectangle2D(Attributes attributes, String name) { String s = attributes.getValue(name).trim(); StringTokenizer tokenizer = new StringTokenizer(s, " "); double[] values = new double[] { Double.parseDouble(tokenizer.nextToken()), Double.parseDouble(tokenizer.nextToken()), Double.parseDouble(tokenizer.nextToken()), Double.parseDouble(tokenizer.nextToken())}; return new Rectangle2D.Double(values[0], values[1], values[2], values[3]); } private void transferForeignObjects(Attributes atts, AreaTreeObject ato) { for (int i = 0, c = atts.getLength(); i < c; i++) { String ns = atts.getURI(i); if (ns.length() > 0) { if ("http://www.w3.org/2000/xmlns/".equals(ns)) { continue; } QName qname = new QName(ns, atts.getQName(i)); ato.setForeignAttribute(qname, atts.getValue(i)); } } } /** {@inheritDoc} */ public void characters(char[] ch, int start, int length) throws SAXException { if (delegate != null) { delegate.characters(ch, start, length); } else { content.append(ch, start, length); } } } }