/************************************************************************* ** EmSpecialHandler.cpp ** ** ** ** This file is part of dvisvgm -- a fast DVI to SVG converter ** ** Copyright (C) 2005-2024 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 "EmSpecialHandler.hpp" #include "InputBuffer.hpp" #include "InputReader.hpp" #include "Length.hpp" #include "SpecialActions.hpp" #include "SVGElement.hpp" #include "SVGTree.hpp" using namespace std; /** Computes the "cut vector" that is used to compute the line shape. * Because each line has a width > 0 the actual shape of the line is a tetragon. * The 4 vertices can be influenced by the cut parameter c that specifies * a horizontal, vertical or orthogonal cut of a line end. Depending on c and the * line's slope a cut vector v can be computed that, relatively to endpoint p, denotes * the 2 vertices of that line end: v1=p+v and v2=p-v. * @param[in] cuttype character identifying the cut direction ('h', 'v' or 'p') * @param[in] linedir direction vector of line to be drawn * @param[in] lw width of line to be drawn * @return the "cut vector" */ static DPair cut_vector (char cuttype, const DPair &linedir, double linewidth) { DPair cut; switch (cuttype) { case 'v': // vertical if (linedir.x() != 0) { double slope = linedir.y()/linedir.x(); double h = sqrt(linewidth*linewidth*(1+slope*slope)); cut.y(h/2); } break; case 'h': // horizontal if (linedir.y() != 0) { double slope = linedir.x()/linedir.y(); double h = sqrt(linewidth*linewidth*(1+slope*slope)); double sgn = slope < 0 ? 1.0 : -1.0; cut.x(h*sgn/2); } break; default: // c == 'p': perpendicular to the line vector if (linedir.x() != 0 && linedir.y() != 0) return linedir.ortho()/linedir.length() * (linewidth/2); } return cut; } /** 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) { if (actions.outputLocked()) return; unique_ptr node; DPair dir = p2-p1; if (dir.x() == 0 || dir.y() == 0 || (c1 == 'p' && c2 == 'p')) { // draw regular line node = util::make_unique("line"); node->addAttribute("x1", p1.x()); node->addAttribute("y1", p1.y()); node->addAttribute("x2", p2.x()); node->addAttribute("y2", p2.y()); node->setStrokeWidth(lw); node->setStrokeColor(actions.getColor()); node->setStrokeOpacity(actions.getOpacity()); // 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 vector points; DPair cv1 = cut_vector(c1, dir, lw); DPair cv2 = cut_vector(c2, dir, lw); points.push_back(p1+cv1); points.push_back(p1-cv1); points.push_back(p2-cv2); points.push_back(p2+cv2); node = util::make_unique("polygon"); node->setPoints(points); node->setFillColor(actions.getColor()); node->setFillOpacity(actions.getOpacity()); // update bounding box actions.embed(points[0]); actions.embed(points[1]); actions.embed(points[2]); actions.embed(points[3]); } actions.svgTree().appendToPage(std::move(node)); } /** Reads a length (value + unit) and returns its value in PS points (bp). * If no unit is specified, TeX points are assumed. */ static double read_length (InputReader &in) { double val = in.getDouble(); string unitstr; if (isalpha(in.peek())) unitstr += char(in.get()); if (isalpha(in.peek())) unitstr += char(in.get()); Length::Unit unit = Length::Unit::PT; try { unit = Length::stringToUnit(unitstr); } catch (UnitException &e) { } return Length(val, unit).bp(); } bool EmSpecialHandler::process (const string &prefix, istream &is, SpecialActions &actions) { // em:moveto => move graphic cursor to dvi position // em:lineto => draw line from graphic cursor to dvi cursor, then move graphic cursor to dvi position // em:linewidth => set line width to // em:point [,[,]] => defines point as (,); if and/or is missing, // the corresponding dvi cursor coordinate is inserted // and are lengths // em:line [h|v|p], [h|v|p] [,] => draws line of width from point # to point # // point number suffixes: // h: cut line horizontally // v: cut line vertically // p: cut line orthogonally to line direction (default) // if is omitted, the global line width is used // // supported length units: pt, pc, in, bp, cm, mm, dd, cc, sp // default line width: 0.4pt struct Command { const char *name; void (EmSpecialHandler::*handler)(InputReader&, SpecialActions&); } commands[] = { {"point", &EmSpecialHandler::point}, {"line", &EmSpecialHandler::line}, {"moveto", &EmSpecialHandler::moveto}, {"lineto", &EmSpecialHandler::lineto}, {"linewidth", &EmSpecialHandler::linewidth}, {nullptr, nullptr} }; StreamInputReader ir(is); const string cmdstr = ir.getWord(); for (Command *cmd=commands; cmd->name; cmd++) { if (cmdstr == cmd->name) { (this->*cmd->handler)(ir, actions); break; } } return true; } /** 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()); } /** 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; } /** Handles the "linewidth" command that changes the line width which affects all following * drawing commands. */ void EmSpecialHandler::linewidth (InputReader &ir, SpecialActions&) { _linewidth = read_length(ir); } /** 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; } /** Handles the "line" command that draws a straight line between two points * from the point list. */ void EmSpecialHandler::line (InputReader &ir, SpecialActions& actions) { int pointnum1 = ir.getInt(); int cut1 = 'p'; if (isalpha(ir.peek())) cut1 = ir.get(); ir.getPunct(); int pointnum2 = ir.getInt(); int cut2 = 'p'; if (isalpha(ir.peek())) cut2 = ir.get(); double linewidth = _linewidth; if (ir.getPunct() == ',') linewidth = read_length(ir); auto it1=_points.find(pointnum1); auto it2=_points.find(pointnum2); if (it1 != _points.end() && it2 != _points.end()) create_line(it1->second, it2->second, char(cut1), char(cut2), linewidth, actions); else { // Line endpoints don't necessarily have to be defined before // a line definition. If a point isn't defined yet, we put the line // in a wait list and process the lines at the end of the page. _lines.emplace_back(pointnum1, pointnum2, char(cut1), char(cut2), linewidth); } } /** This method is called at the end of a DVI page. Here we have to draw all pending * lines that are still in the line list. All line endpoints must be defined until here. */ void EmSpecialHandler::dviEndPage (unsigned pageno, SpecialActions &actions) { for (const Line &line : _lines) { auto pit1=_points.find(line.p1); auto pit2=_points.find(line.p2); if (pit1 != _points.end() && pit2 != _points.end()) create_line(pit1->second, pit2->second, line.c1, line.c2, line.width, actions); // all lines with still undefined points are ignored } // line and point definitions are local to a page _lines.clear(); _points.clear(); } vector EmSpecialHandler::prefixes () const { vector pfx {"em:"}; return pfx; }