001    /*
002     * Copyright 2007 - 2007 JEuclid, http://jeuclid.sf.net
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *      http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    /* $Id: Processor.java 375 2007-07-05 11:51:58Z maxberger $ */
018    
019    package net.sourceforge.jeuclid.app.foprep;
020    
021    import java.awt.Dimension;
022    import java.io.IOException;
023    
024    import javax.xml.parsers.ParserConfigurationException;
025    import javax.xml.transform.Result;
026    import javax.xml.transform.Source;
027    import javax.xml.transform.Transformer;
028    import javax.xml.transform.TransformerException;
029    import javax.xml.transform.TransformerFactory;
030    import javax.xml.transform.dom.DOMResult;
031    import javax.xml.transform.dom.DOMSource;
032    
033    import net.sourceforge.jeuclid.DOMBuilder;
034    import net.sourceforge.jeuclid.MathBase;
035    import net.sourceforge.jeuclid.elements.AbstractJEuclidElement;
036    import net.sourceforge.jeuclid.elements.generic.MathImpl;
037    import net.sourceforge.jeuclid.parser.Parser;
038    
039    import org.apache.batik.dom.GenericDOMImplementation;
040    import org.apache.batik.svggen.SVGGeneratorContext;
041    import org.apache.batik.svggen.SVGGraphics2D;
042    import org.apache.commons.logging.Log;
043    import org.apache.commons.logging.LogFactory;
044    import org.w3c.dom.DOMImplementation;
045    import org.w3c.dom.Document;
046    import org.w3c.dom.Element;
047    import org.w3c.dom.Node;
048    import org.w3c.dom.NodeList;
049    import org.xml.sax.SAXException;
050    
051    /**
052     * Contains the actual processing routines.
053     * <p>
054     * To use this class obtain an instance of the Processor singleton instance.
055     * Then use the {@link #process(Source, Result)} function to process your
056     * Document.
057     * <p>
058     * This will replace all occurrences of MathML within fo:instream tags by the
059     * equivalent SVG code. It will also add a baseline-shift attribute so that
060     * the formula is in line with the rest of the text.
061     * 
062     * @author Max Berger
063     * @version $Revision: 375 $
064     */
065    public final class Processor {
066    
067        private static Processor processor;
068    
069        /**
070         * Logger for this class
071         */
072        private static final Log LOGGER = LogFactory.getLog(Processor.class);
073    
074        private final Transformer transformer;
075    
076        private Processor() throws TransformerException {
077            this.transformer = TransformerFactory.newInstance().newTransformer();
078        }
079    
080        /**
081         * Retrieve the processor singleton object.
082         * 
083         * @return the Processor.
084         * @throws TransformerException
085         *             an error occurred creating the necessary Transformer
086         *             instance.
087         */
088        public static synchronized Processor getProcessor()
089                throws TransformerException {
090            if (Processor.processor == null) {
091                Processor.processor = new Processor();
092            }
093            return Processor.processor;
094        }
095    
096        /**
097         * Pre-process a .fo file.
098         * 
099         * @param inputSource
100         *            Input File
101         * @param result
102         *            Output File
103         * @throws TransformerException
104         *             an error occurred during the processing.
105         */
106        public void process(final Source inputSource, final Result result)
107                throws TransformerException {
108            Processor.LOGGER.info("Processing " + inputSource + " to " + result);
109            try {
110                final Node doc = Parser.getParser().parse(inputSource);
111    
112                this.processSubtree(doc);
113    
114                final DOMSource source = new DOMSource(doc);
115    
116                this.transformer.transform(source, result);
117    
118            } catch (final IOException e) {
119                throw new TransformerException("IOException", e);
120            } catch (final SAXException e) {
121                throw new TransformerException("SAXException", e);
122            } catch (final ParserConfigurationException e) {
123                throw new TransformerException("ParserConfigurationException", e);
124            }
125        }
126    
127        private void processSubtree(final Node node) {
128            if (AbstractJEuclidElement.URI.equals(node.getNamespaceURI())
129                    && MathImpl.ELEMENT.equals(node.getLocalName())) {
130    
131                final MathBase mathBase = new MathBase(MathBase
132                        .getDefaultParameters());
133                new DOMBuilder(node, mathBase);
134    
135                final SVGGraphics2D svgGenerator = this
136                        .createSVGGenerator(mathBase);
137                mathBase.paint(svgGenerator);
138                final float descender = mathBase.getDescender(svgGenerator);
139    
140                final Node parent = node.getParentNode();
141                if ("http://www.w3.org/1999/XSL/Format".equals(parent
142                        .getNamespaceURI())
143                        && "instream-foreign-object"
144                                .equals(parent.getLocalName())) {
145                    final Element pElement = (Element) parent;
146                    pElement.setAttribute("baseline-shift", -descender + "pt");
147                }
148                this.safeReplaceChild(parent, node, svgGenerator.getRoot());
149            } else {
150                this.processChildren(node);
151            }
152        }
153    
154        private SVGGraphics2D createSVGGenerator(final MathBase mathBase) {
155            final DOMImplementation domImpl = GenericDOMImplementation
156                    .getDOMImplementation();
157    
158            final Document document = domImpl.createDocument(null,
159                    net.sourceforge.jeuclid.Converter.EXTENSION_SVG, null);
160            final SVGGeneratorContext svgContext = SVGGeneratorContext
161                    .createDefault(document);
162            svgContext.setComment("Converted from MathML using JEuclid");
163            final SVGGraphics2D svgGenerator = new SVGGraphics2D(svgContext, true);
164    
165            final Dimension size = new Dimension((int) Math.ceil(mathBase
166                    .getWidth(svgGenerator)), (int) Math.ceil(mathBase
167                    .getHeight(svgGenerator)));
168            svgGenerator.setSVGCanvasSize(size);
169            return svgGenerator;
170        }
171    
172        private void safeReplaceChild(final Node parent, final Node oldChild,
173                final Node newChild) {
174            try {
175                final DOMSource source = new DOMSource(newChild);
176                final DOMResult result = new DOMResult(parent);
177    
178                this.transformer.transform(source, result);
179            } catch (final TransformerException e) {
180                Processor.LOGGER.warn("TranformerException: " + e.getMessage());
181            }
182            parent.removeChild(oldChild);
183        }
184    
185        private void processChildren(final Node node) {
186            final NodeList childList = node.getChildNodes();
187            if (childList != null) {
188                for (int i = 0; i < childList.getLength(); i++) {
189                    final Node child = childList.item(i);
190                    this.processSubtree(child);
191                }
192            }
193        }
194    
195    }