/************************************************************************* ** SVGTree.cpp ** ** ** ** This file is part of dvisvgm -- the DVI to SVG converter ** ** Copyright (C) 2005-2013 Martin Gieseking ** ** ** ** This program is free software; you can redistribute it and/or ** ** modify it under the terms of the GNU General Public License as ** ** published by the Free Software Foundation; either version 3 of ** ** the License, or (at your option) any later version. ** ** ** ** This program is distributed in the hope that it will be useful, but ** ** WITHOUT ANY WARRANTY; without even the implied warranty of ** ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** ** GNU General Public License for more details. ** ** ** ** You should have received a copy of the GNU General Public License ** ** along with this program; if not, see . ** *************************************************************************/ #include #include #include "BoundingBox.h" #include "DVIToSVG.h" #include "Font.h" #include "FontManager.h" #include "SVGTree.h" #include "XMLDocument.h" #include "XMLNode.h" #include "XMLString.h" using namespace std; // static class variables bool SVGTree::CREATE_STYLE=true; bool SVGTree::USE_FONTS=true; bool SVGTree::CREATE_USE_ELEMENTS=false; SVGTree::SVGTree () : _font(0), _color(Color::BLACK), _matrix(1) { _xchanged = _ychanged = false; _fontnum = 0; reset(); } /** Clears the SVG tree and initializes the root element. */ void SVGTree::reset () { _doc.clear(); _root = new XMLElementNode("svg"); _root->addAttribute("version", "1.1"); _root->addAttribute("xmlns", "http://www.w3.org/2000/svg"); _root->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); _doc.setRootNode(_root); _page = _text = _span = _defs = 0; } /** Sets the bounding box of the document. * @param[in] bbox bounding box in TeX point units */ void SVGTree::setBBox (const BoundingBox &bbox) { double bp = 72.0/72.27; // pt -> bp ScalingMatrix scale(bp, bp); BoundingBox scaled_box = bbox; scaled_box.transform(scale); _root->addAttribute("width", XMLString(scaled_box.width())+"pt"); _root->addAttribute("height", XMLString(scaled_box.height())+"pt"); _root->addAttribute("viewBox", scaled_box.toSVGViewBox()); } /** Starts a new page. * @param[in] pageno number of new page */ void SVGTree::newPage (int pageno) { _page = new XMLElementNode("g"); if (pageno >= 0) _page->addAttribute("id", string("page")+XMLString(pageno)); _root->append(_page); _text = _span = 0; while (!_pageContainerStack.empty()) _pageContainerStack.pop(); } void SVGTree::appendToDefs (XMLNode *node) { if (!_defs) { _defs = new XMLElementNode("defs"); _root->prepend(_defs); } _defs->append(node); } void SVGTree::appendToPage (XMLNode *node) { if (_pageContainerStack.empty()) _page->append(node); else _pageContainerStack.top()->append(node); } void SVGTree::prependToPage (XMLNode *node) { if (_pageContainerStack.empty()) _page->prepend(node); else _pageContainerStack.top()->prepend(node); } /** Appends a single charater to the current text node. If necessary, and depending on output mode * and further output states, new XML elements (text, tspan, g, ...) are created. * @param[in] c character to be added * @param[in] x x coordinate * @param[in] y y coordinate * @param[in] font font to be used */ void SVGTree::appendChar (int c, double x, double y, const Font &font) { XMLElementNode *node=_span; if (USE_FONTS) { // changes of fonts and transformations require a new text element if (!_text || _font.changed() || _matrix.changed()) { newTextNode(x, y); node = _text; _color.changed(true); } if (_xchanged || _ychanged || (_color.changed() && _color.get() != Color::BLACK)) { // if drawing position was explicitly changed, create a new tspan element _span = new XMLElementNode("tspan"); if (_xchanged) { _span->addAttribute("x", x); _xchanged = false; } if (_ychanged) { _span->addAttribute("y", y); _ychanged = false; } if (_color.get() != Color::BLACK) { _span->addAttribute("fill", _color.get().rgbString()); _color.changed(false); } _text->append(_span); node = _span; } if (!node) { if (!_text) newTextNode(x, y); node = _text; } node->append(XMLString(font.unicode(c), false)); } else { if (_color.changed() || _matrix.changed()) { bool set_color = (_color.changed() && _color.get() != Color::BLACK); bool set_matrix = (_matrix.changed() && !_matrix.get().isIdentity()); if (set_color || set_matrix) { _span = new XMLElementNode("g"); if (_color.get() != Color::BLACK) _span->addAttribute("fill", _color.get().rgbString()); if (!_matrix.get().isIdentity()) _span->addAttribute("transform", _matrix.get().getSVG()); appendToPage(_span); node = _span; _color.changed(false); _matrix.changed(false); } else if (_color.get() == Color::BLACK && _matrix.get().isIdentity()) node = _span = 0; } if (!node) node = _pageContainerStack.empty() ? _page : _pageContainerStack.top(); if (CREATE_USE_ELEMENTS) { ostringstream oss; oss << "#g" << FontManager::instance().fontID(_font) << '-' << c; XMLElementNode *use = new XMLElementNode("use"); use->addAttribute("x", XMLString(x)); use->addAttribute("y", XMLString(y)); use->addAttribute("xlink:href", oss.str()); node->append(use); } else { Glyph glyph; const PhysicalFont *font = dynamic_cast(_font.get()); if (font && font->getGlyph(c, glyph)) { double sx = font->scaledSize()/font->unitsPerEm(); double sy = -sx; ostringstream oss; glyph.writeSVG(oss, sx, sy, x, y); XMLElementNode *glyph_node = new XMLElementNode("path"); glyph_node->addAttribute("d", oss.str()); node->append(glyph_node); } } } } /** Creates a new text element. This is a helper function used by appendChar(). * @param[in] x current x coordinate * @param[in] y current y coordinate */ void SVGTree::newTextNode (double x, double y) { _text = new XMLElementNode("text"); _span = 0; // no tspan in text element yet if (USE_FONTS) { const Font *font = _font.get(); if (CREATE_STYLE || !font) _text->addAttribute("class", string("f")+XMLString(_fontnum)); else { _text->addAttribute("font-family", font->name()); _text->addAttribute("font-size", font->scaledSize()); } } _text->addAttribute("x", x); _text->addAttribute("y", y); if (!_matrix.get().isIdentity()) _text->addAttribute("transform", _matrix.get().getSVG()); appendToPage(_text); _font.changed(false); _matrix.changed(false); _xchanged = false; _ychanged = false; } void SVGTree::setFont (int num, const Font *font) { _font.set(font); _fontnum = num; } void SVGTree::transformPage (const Matrix *usermatrix) { double bp = 72.0/72.27; // pt -> bp ScalingMatrix matrix(bp, bp); if (usermatrix) matrix.rmultiply(*usermatrix); if (!matrix.isIdentity()) _page->addAttribute("transform", matrix.getSVG()); } /** Creates an SVG element for a single glyph. * @param[in] c character number * @param[in] font font to extract the glyph from * @return pointer to element node if glyph exists, 0 otherwise */ static XMLElementNode* createGlyphNode (int c, const PhysicalFont &font, GFGlyphTracer::Callback *cb) { Glyph glyph; if (!font.getGlyph(c, glyph, cb) || (!SVGTree::USE_FONTS && !SVGTree::CREATE_USE_ELEMENTS)) return 0; double sx=1.0, sy=1.0; double upem = font.unitsPerEm(); XMLElementNode *glyph_node=0; if (SVGTree::USE_FONTS) { glyph_node = new XMLElementNode("glyph"); glyph_node->addAttribute("unicode", XMLString(font.unicode(c), false)); glyph_node->addAttribute("horiz-adv-x", XMLString(font.hAdvance(c))); string name = font.glyphName(c); if (!name.empty()) glyph_node->addAttribute("glyph-name", name); } else { ostringstream oss; oss << 'g' << FontManager::instance().fontID(&font) << '-' << c; glyph_node = new XMLElementNode("path"); glyph_node->addAttribute("id", oss.str()); sx = font.scaledSize()/upem; sy = -sx; } ostringstream oss; glyph.writeSVG(oss, sx, sy); glyph_node->addAttribute("d", oss.str()); return glyph_node; } void SVGTree::appendFontStyles (const set &fonts) { if (CREATE_STYLE && USE_FONTS && !fonts.empty() && _defs) { XMLElementNode *styleNode = new XMLElementNode("style"); styleNode->addAttribute("type", "text/css"); _root->insertAfter(styleNode, _defs); typedef map SortMap; SortMap sortmap; FORALL(fonts, set::const_iterator, it) if (!dynamic_cast(*it)) // skip virtual fonts sortmap[FontManager::instance().fontID(*it)] = *it; ostringstream style; // add font style definitions in ascending order FORALL(sortmap, SortMap::const_iterator, it) { style << "text.f" << it->first << ' ' << "{font-family:" << it->second->name() << ";font-size:" << it->second->scaledSize() << "}\n"; } XMLCDataNode *cdata = new XMLCDataNode(style.str()); styleNode->append(cdata); } } /** Appends glyph definitions of a given font to the defs section of the SVG tree. * @param[in] font font to be appended * @param[in] chars codes of the characters whose glyph outlines should be appended */ void SVGTree::append (const PhysicalFont &font, const set &chars, GFGlyphTracer::Callback *cb) { if (chars.empty()) return; if (USE_FONTS) { XMLElementNode *fontNode = new XMLElementNode("font"); string fontname = font.name(); fontNode->addAttribute("id", fontname); fontNode->addAttribute("horiz-adv-x", XMLString(font.hAdvance())); appendToDefs(fontNode); XMLElementNode *faceNode = new XMLElementNode("font-face"); faceNode->addAttribute("font-family", fontname); faceNode->addAttribute("units-per-em", XMLString(font.unitsPerEm())); if (font.type() != PhysicalFont::MF) { faceNode->addAttribute("ascent", XMLString(font.ascent())); faceNode->addAttribute("descent", XMLString(font.descent())); } fontNode->append(faceNode); // add required missing-glyph element XMLElementNode *missing = new XMLElementNode("missing-glyph"); missing->addAttribute("d", ""); fontNode->append(missing); FORALL(chars, set::const_iterator, i) fontNode->append(createGlyphNode(*i, font, cb)); } else if (CREATE_USE_ELEMENTS && &font != font.uniqueFont()) { // If the same character is used in various sizes, we don't want to embed the complete (lengthy) path // descriptions multiple times. Because they would only differ by a scale factor, it's better to // reference the already embedded path together with a transformation attribute and let the SVG renderer // scale the glyphs properly. This is only necessary if we don't want to use font but path elements. FORALL(chars, set::const_iterator, it) { ostringstream oss; XMLElementNode *use = new XMLElementNode("use"); oss << 'g' << FontManager::instance().fontID(&font) << '-' << *it; use->addAttribute("id", oss.str()); oss.str(""); oss << "#g" << FontManager::instance().fontID(font.uniqueFont()) << '-' << *it; use->addAttribute("xlink:href", oss.str()); double scale = font.scaledSize()/font.uniqueFont()->scaledSize(); if (scale != 1.0) { oss.str(""); oss << "scale(" << scale << ')'; use->addAttribute("transform", oss.str()); } appendToDefs(use); } } else { FORALL(chars, set::const_iterator, i) appendToDefs(createGlyphNode(*i, font, cb)); } } /** Pushes a new context element that will take all following nodes added to the page. */ void SVGTree::pushContextElement (XMLElementNode *node) { if (_pageContainerStack.empty()) _page->append(node); else _pageContainerStack.top()->append(node); _pageContainerStack.push(node); _text = _span = 0; // ensure the creation of a new text element for the following characters added } /** Pops the current context element and restored the previous one. */ void SVGTree::popContextElement () { if (!_pageContainerStack.empty()) { _pageContainerStack.pop(); _text = _span = 0; // ensure the creation of a new text element for the following characters added } }