/** Handles the "point" command that adds a point to the point list. */ void EmSpecialHandler::point (InputReader &ir, SpecialActions &actions) { DPair pos(actions.getX(), actions.getY()); int n = ir.getInt(); if (ir.getPunct() == ',') { pos.x(ir.getDouble()); if (ir.getPunct() == ',') pos.y(ir.getDouble()); } _points[n] = pos; }
/** Embeds the virtual rectangle (x, y ,w , h) into the current bounding box, * where (x,y) is the lower left vertex composed of the current DVI position. * @param[in] w width of the rectangle in PS point units * @param[in] h height of the rectangle in PS point units * @param[in] d depth of the rectangle in PS point units * @param[in] transform if true, apply the current transformation matrix to the rectangle * @param[in] actions object providing the actions that can be performed by the SpecialHandler */ static void update_bbox (Length w, Length h, Length d, bool transform, SpecialActions &actions) { double x = actions.getX(); double y = actions.getY(); BoundingBox bbox1(x, y, x+w.bp(), y-h.bp()); BoundingBox bbox2(x, y, x+w.bp(), y+d.bp()); if (transform) { bbox1.transform(actions.getMatrix()); bbox2.transform(actions.getMatrix()); } actions.embed(bbox1); actions.embed(bbox2); }
void BgColorSpecialHandler::dviBeginPage (unsigned pageno, SpecialActions &actions) { // Ensure that the background color of the preceeding page is set as the // default background color of the current page because this special affects // the current and all subsequent pages until the next change. // See the documentation of the color package, section 3.5. if (_pageColors.empty()) return; // find number of page with bg color change not lower than the current one vector<PageColor>::iterator it = lower_bound(_pageColors.begin(), _pageColors.end(), PageColor(pageno, Color::BLACK)); if (it != _pageColors.end() && it->first == pageno) actions.setBgColor(it->second); else if (it != _pageColors.begin()) actions.setBgColor((--it)->second); }
/** Collect all background color changes while preprocessing the DVI file. * We need them in order to apply the correct background colors even if * not all but only selected DVI pages are converted. */ void BgColorSpecialHandler::preprocess (const char*, std::istream &is, SpecialActions &actions) { Color color = ColorSpecialHandler::readColor(is); unsigned pageno = actions.getCurrentPageNumber(); if (_pageColors.empty() || _pageColors.back().second != color) { if (!_pageColors.empty() && _pageColors.back().first == pageno) _pageColors.back().second = color; else _pageColors.push_back(PageColor(pageno, color)); } }
bool ColorSpecialHandler::process (const string&, istream &is, SpecialActions &actions) { string cmd; is >> cmd; if (cmd == "push") // color push <model> <params> _colorStack.push(readColor(is)); else if (cmd == "pop") { if (!_colorStack.empty()) // color pop _colorStack.pop(); } else { // color <model> <params> while (!_colorStack.empty()) _colorStack.pop(); _colorStack.push(readColor(cmd, is)); } if (_colorStack.empty()) actions.setColor(Color::BLACK); else actions.setColor(_colorStack.top()); return true; }
void DvisvgmSpecialHandler::processImg (InputReader &ir, SpecialActions &actions) { try { Length w = read_length(ir); Length h = read_length(ir); string f = ir.getString(); update_bbox(w, h, Length(0), false, actions); auto img = util::make_unique<XMLElement>("image"); img->addAttribute("x", actions.getX()); img->addAttribute("y", actions.getY()); img->addAttribute("width", w.bp()); img->addAttribute("height", h.bp()); img->addAttribute("xlink:href", f); if (!actions.getMatrix().isIdentity()) img->addAttribute("transform", actions.getMatrix().toSVG()); actions.svgTree().appendToPage(std::move(img)); } catch (const UnitException &e) { throw SpecialException(string("dvisvgm:img: ") + e.what()); } }
/** Processes an opening element tag. * @param[in] tag tag without leading and trailing angle brackets */ void DvisvgmSpecialHandler::XMLParser::openElement (const string &tag, SpecialActions &actions) { StringInputBuffer ib(tag); BufferInputReader ir(ib); string name = ir.getString("/ \t\n\r"); ir.skipSpace(); auto elemNode = util::make_unique<XMLElement>(name); map<string, string> attribs; if (ir.parseAttributes(attribs, true, "\"'")) { for (const auto &attrpair : attribs) elemNode->addAttribute(attrpair.first, attrpair.second); } ir.skipSpace(); if (ir.peek() == '/') // end of empty element tag (actions.svgTree().*_append)(std::move(elemNode)); else if (ir.peek() < 0) { // end of opening tag _nameStack.push_back(name); (actions.svgTree().*_pushContext)(std::move(elemNode)); } else throw SpecialException("'>' or '/>' expected at end of opening tag <"+name); }
void DvisvgmSpecialHandler::dviEndPage (unsigned, SpecialActions &actions) { _defsParser.flush(actions); _pageParser.flush(actions); actions.bbox().unlock(); for (auto &strvecpair : _macros) { StringVector &vec = strvecpair.second; for (string &str : vec) { // activate locked parts of a pattern again if (str[0] == 'L') str[0] = 'D'; } } }
/** Replaces constants of the form {?name} by their corresponding value. * @param[in,out] str text to expand * @param[in] actions interfcae to the world outside the special handler */ static void expand_constants (string &str, SpecialActions &actions) { bool repl_bbox = true; while (repl_bbox) { size_t pos = str.find("{?bbox "); if (pos == string::npos) repl_bbox = false; else { size_t endpos = pos+7; while (endpos < str.length() && isalnum(str[endpos])) ++endpos; if (str[endpos] == '}') { BoundingBox &box=actions.bbox(str.substr(pos+7, endpos-pos-7)); str.replace(pos, endpos-pos+1, box.toSVGViewBox()); } else repl_bbox = false; } } struct Constant { const char *name; string val; }; const array<Constant, 5> constants {{ {"x", XMLString(actions.getX())}, {"y", XMLString(actions.getY())}, {"color", actions.getColor().svgColorString()}, {"matrix", actions.getMatrix().toSVG()}, {"nl", "\n"}, }}; for (const Constant &constant : constants) { const string pattern = string("{?")+constant.name+"}"; size_t pos = str.find(pattern); while (pos != string::npos) { str.replace(pos, strlen(constant.name)+3, constant.val); pos = str.find(pattern, pos+constant.val.length()); // look for further matches } } }
/** Processes a closing element tag. * @param[in] tag tag without leading and trailing angle brackets */ void DvisvgmSpecialHandler::XMLParser::closeElement (const string &tag, SpecialActions &actions) { StringInputBuffer ib(tag); BufferInputReader ir(ib); string name = ir.getString(" \t\n\r"); ir.skipSpace(); if (ir.peek() >= 0) throw SpecialException("'>' expected at end of closing tag </"+name); if (_nameStack.empty()) throw SpecialException("spurious closing tag </" + name + ">"); if (_nameStack.back() != name) throw SpecialException("expected </" + name + "> but found </" + _nameStack.back() + ">"); (actions.svgTree().*_popContext)(); _nameStack.pop_back(); }
/** Evaluates the special dvisvgm:bbox. * variant 1: dvisvgm:bbox [r[el]] <width> <height> [<depth>] [transform] * variant 2: dvisvgm:bbox a[bs] <x1> <y1> <x2> <y2> [transform] * variant 3: dvisvgm:bbox f[ix] <x1> <y1> <x2> <y2> [transform] * variant 4: dvisvgm:bbox n[ew] <name> * variant 5: dvisvgm:bbox lock | unlock */ void DvisvgmSpecialHandler::processBBox (InputReader &ir, SpecialActions &actions) { ir.skipSpace(); if (ir.check("lock")) actions.bbox().lock(); else if (ir.check("unlock")) actions.bbox().unlock(); else { int c = ir.peek(); try { if (!isalpha(c)) c = 'r'; // no mode specifier => relative box parameters else { while (!isspace(ir.peek())) // skip trailing characters ir.get(); if (c == 'n') { // "new": create new local bounding box ir.skipSpace(); string name; while (isalnum(ir.peek())) name += char(ir.get()); ir.skipSpace(); if (!name.empty() && ir.eof()) actions.bbox(name, true); // create new user box } else if (c == 'a' || c == 'f') { // "abs" or "fix" Length lengths[4]; for (Length &len : lengths) len = read_length(ir); BoundingBox b(lengths[0], lengths[1], lengths[2], lengths[3]); ir.skipSpace(); if (ir.check("transform")) b.transform(actions.getMatrix()); if (c == 'a') actions.embed(b); else { actions.bbox() = b; actions.bbox().lock(); } } } if (c == 'r') { Length w = read_length(ir); Length h = read_length(ir); Length d = read_length(ir); ir.skipSpace(); update_bbox(w, h, d, ir.check("transform"), actions); } } catch (const UnitException &e) { throw SpecialException(string("dvisvgm:bbox: ") + e.what()); } } }
/** Creates the SVG element that will a the line. * @param[in] p1 first endpoint in PS point units * @param[in] p2 second endpoint in PS point units * @param[in] c1 cut method of first endpoint ('h', 'v' or 'p') * @param[in] c2 cut method of second endpoint ('h', 'v' or 'p') * @param[in] lw line width in PS point units * @param[in] actions object providing the actions that can be performed by the SpecialHandler */ static void create_line (const DPair &p1, const DPair &p2, char c1, char c2, double lw, SpecialActions &actions) { unique_ptr<XMLElementNode> node; DPair dir = p2-p1; if (dir.x() == 0 || dir.y() == 0 || (c1 == 'p' && c2 == 'p')) { // draw regular line node = util::make_unique<XMLElementNode>("line"); node->addAttribute("x1", p1.x()); node->addAttribute("y1", p1.y()); node->addAttribute("x2", p2.x()); node->addAttribute("y2", p2.y()); node->addAttribute("stroke-width", lw); node->addAttribute("stroke", actions.getColor().svgColorString()); // update bounding box DPair cv = cut_vector('p', dir, lw); actions.embed(p1+cv); actions.embed(p1-cv); actions.embed(p2+cv); actions.embed(p2-cv); } else { // draw polygon DPair cv1 = cut_vector(c1, dir, lw); DPair cv2 = cut_vector(c2, dir, lw); DPair q11 = p1+cv1, q12 = p1-cv1; DPair q21 = p2+cv2, q22 = p2-cv2; ostringstream oss; oss << XMLString(q11.x()) << ',' << XMLString(q11.y()) << ' ' << XMLString(q12.x()) << ',' << XMLString(q12.y()) << ' ' << XMLString(q22.x()) << ',' << XMLString(q22.y()) << ' ' << XMLString(q21.x()) << ',' << XMLString(q21.y()); node = util::make_unique<XMLElementNode>("polygon"); node->addAttribute("points", oss.str()); if (actions.getColor() != Color::BLACK) node->addAttribute("fill", actions.getColor().svgColorString()); // update bounding box actions.embed(q11); actions.embed(q12); actions.embed(q21); actions.embed(q22); } actions.appendToPage(std::move(node)); }
void PapersizeSpecialHandler::preprocess (const string&, std::istream &is, SpecialActions &actions) { string params; is >> params; Length w, h; const size_t splitpos = params.find(','); try { if (splitpos == string::npos) { w.set(params); h.set(params); } else { w.set(params.substr(0, splitpos)); h.set(params.substr(splitpos+1)); } storePaperSize(actions.getCurrentPageNumber(), w, h); } catch (UnitException &e) { // ignore invalid length units for now } }
/** Applies the previously recorded size to a given page. */ void PapersizeSpecialHandler::applyPaperSize (unsigned pageno, SpecialActions &actions) { // find page n >= pageno that contains a papersize special auto lb_it = lower_bound(_pageSizes.begin(), _pageSizes.end(), PageSize(pageno, DoublePair()), [](const PageSize &ps1, const PageSize &ps2) { // order PageSize objects by page number return ps1.first < ps2.first; }); auto it = _pageSizes.end(); if (lb_it != _pageSizes.end() && lb_it->first == pageno) it = lb_it; // if current page contains a papersize special, use it else if (lb_it != _pageSizes.begin()) // no papersize special on current page? it = lb_it-1; // => use the one on the nearest preceding page if (it == _pageSizes.end()) Message::wstream(true) << "no valid papersize special found\n"; else { DoublePair size = it->second; const double border = -72; // DVI standard: coordinates of upper left paper corner are (-72bp, -72bp) actions.bbox() = BoundingBox(border, border, size.first+border, size.second+border); } }
void PapersizeSpecialHandler::dviEndPage (unsigned pageno, SpecialActions &actions) { if (actions.getBBoxFormatString() == "papersize") applyPaperSize(pageno, actions); }
/** Handles the "lineto" command that sraws a straight line from the current drawing position * to the current DVI position, and sets the drawing position to the DVI position afterwards. */ void EmSpecialHandler::lineto (InputReader&, SpecialActions &actions) { DPair currpos(actions.getX(), actions.getY()); create_line(_pos, currpos, 'p', 'p', _linewidth, actions); _pos = currpos; }
/** Parses a fragment of XML code, creates corresponding XML nodes and adds them * to the SVG tree. The code may be split and processed by several calls of this * function. Incomplete chunks that can't be processed yet are stored and picked * up again together with the next incoming XML fragment. If no further code should * be appended, parameter 'finish' must be set. * @param[in] xml XML fragment to parse * @param[in] actions object providing the SVG tree functions * @param[in] finish if true, no more XML is expected and parsing is finished */ void DvisvgmSpecialHandler::XMLParser::parse (const string &xml, SpecialActions &actions, bool finish) { // collect/extract an XML fragment that only contains complete tags // incomplete tags are held back _xmlbuf += xml; size_t left=0, right; while (left != string::npos) { right = _xmlbuf.find('<', left); if (left < right && left < _xmlbuf.length()) // plain text found? (actions.svgTree().*_append)(util::make_unique<XMLText>(_xmlbuf.substr(left, right-left))); if (right != string::npos) { left = right; if (_xmlbuf.compare(left, 9, "<![CDATA[") == 0) { right = _xmlbuf.find("]]>", left+9); if (right == string::npos) { if (finish) throw SpecialException("expected ']]>' at end of CDATA block"); break; } (actions.svgTree().*_append)(util::make_unique<XMLCData>(_xmlbuf.substr(left+9, right-left-9))); right += 2; } else if (_xmlbuf.compare(left, 4, "<!--") == 0) { right = _xmlbuf.find("-->", left+4); if (right == string::npos) { if (finish) throw SpecialException("expected '-->' at end of comment"); break; } (actions.svgTree().*_append)(util::make_unique<XMLComment>(_xmlbuf.substr(left+4, right-left-4))); right += 2; } else if (_xmlbuf.compare(left, 2, "<?") == 0) { right = _xmlbuf.find("?>", left+2); if (right == string::npos) { if (finish) throw SpecialException("expected '?>' at end of processing instruction"); break; } (actions.svgTree().*_append)(util::make_unique<XMLText>(_xmlbuf.substr(left, right-left+2))); right++; } else if (_xmlbuf.compare(left, 2, "</") == 0) { right = _xmlbuf.find('>', left+2); if (right == string::npos) { if (finish) throw SpecialException("missing '>' at end of closing XML tag"); break; } closeElement(_xmlbuf.substr(left+2, right-left-2), actions); } else { right = _xmlbuf.find('>', left+1); if (right == string::npos) { if (finish) throw SpecialException("missing '>' or '/>' at end of opening XML tag"); break; } openElement(_xmlbuf.substr(left+1, right-left-1), actions); } } left = right; if (right != string::npos) left++; } if (left == string::npos) _xmlbuf.clear(); else _xmlbuf.erase(0, left); }
/** Handles the "moveto" command that sets the drawing position to the current DVI position. */ void EmSpecialHandler::moveto (InputReader&, SpecialActions &actions) { _pos.x(actions.getX()); _pos.y(actions.getY()); }