summaryrefslogtreecommitdiff
path: root/dviware/dvisvgm/src/DVIToSVGActions.cpp
blob: 74e0f97aa5bb4b793d42568903defdc809356459 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
/*************************************************************************
** DVIToSVGActions.cpp                                                  **
**                                                                      **
** This file is part of dvisvgm -- a fast DVI to SVG converter          **
** Copyright (C) 2005-2022 Martin Gieseking <martin.gieseking@uos.de>   **
**                                                                      **
** 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 <http://www.gnu.org/licenses/>. **
*************************************************************************/

#include <cstring>
#include <ctime>
#include "BoundingBox.hpp"
#include "DVIToSVG.hpp"
#include "DVIToSVGActions.hpp"
#include "Font.hpp"
#include "FontManager.hpp"
#include "GlyphTracerMessages.hpp"
#include "System.hpp"
#include "utility.hpp"

using namespace std;


void DVIToSVGActions::reset() {
	_usedChars.clear();
	_usedFonts.clear();
	_bbox = BoundingBox();
	_currentFontNum = -1;
	_bgcolor = Color::TRANSPARENT;
}


/** Change current horizontal position.
 *  @param[in] x new horizontal position
 *  @param[in] forceSVGMove if true, creates an explicit position change in the SVG tree */
void DVIToSVGActions::moveToX (double x, bool forceSVGMove) {
	SpecialManager::instance().notifyPositionChange(getX(), getY(), *this);
	if (forceSVGMove)
		_svg.setX(x);
}


/** Change current vertical position.
 *  @param[in] y new vertical position
 *  @param[in] forceSVGMove if true, creates an explicit position change in the SVG tree */
void DVIToSVGActions::moveToY (double y, bool forceSVGMove) {
	SpecialManager::instance().notifyPositionChange(getX(), getY(), *this);
	if (forceSVGMove)
		_svg.setY(y);
}


FilePath DVIToSVGActions::getSVGFilePath (unsigned pageno) const {
	FilePath path;
	if (auto dvi2svg = dynamic_cast<DVIToSVG*>(_dvireader))
		path = dvi2svg->getSVGFilePath(pageno);
	return path;
}


string DVIToSVGActions::getBBoxFormatString () const {
	string boxstr;
	if (auto dvi2svg = dynamic_cast<DVIToSVG*>(_dvireader))
		boxstr = dvi2svg->getUserBBoxString();
	return boxstr;
}


/** This method is called when a "set char" command was found in the DVI file.
 *  It draws a character of the current font.
 *  @param[in] x horizontal position of left bounding box edge
 *  @param[in] y vertical position of the character's baseline
 *  @param[in] c character code relative to the current font
 *  @param[in] vertical true if we're in vertical mode
 *  @param[in] font font to be used */
void DVIToSVGActions::setChar (double x, double y, unsigned c, bool vertical, const Font &font) {
	if (_outputLocked)
		return;

	// If we use SVG fonts there is no need to record all font name/char/size combinations
	// because the SVG font mechanism handles this automatically. It's sufficient to
	// record font names and chars. The various font sizes can be ignored here.
	// For a given font object, Font::uniqueFont() returns the same unique font object for
	// all fonts with the same name.
	_usedChars[SVGTree::USE_FONTS ? font.uniqueFont() : &font].insert(c);

	// However, we record all required fonts
	_usedFonts.insert(&font);
	_svg.appendChar(c, x, y);

	static string fontname;
	GlyphTracerMessages callback(fontname != font.name(), false);
	fontname = font.name();

	GlyphMetrics metrics;
	font.getGlyphMetrics(c, vertical, metrics);
	auto pf = font_cast<const PhysicalFont*>(&font);
	if (PhysicalFont::EXACT_BBOX && pf) {
		GlyphMetrics exact_metrics;
		pf->getExactGlyphBox(c, exact_metrics, vertical, &callback);
		if (vertical) {
			// move top of bbox to upper margin of glyph (just an approximation yet)
			y += (metrics.d-exact_metrics.h-exact_metrics.d)/2;
		}
		metrics = exact_metrics;
	}
	BoundingBox bbox(x-metrics.wl, y-metrics.h, x+metrics.wr, y+metrics.d);

	// update bounding box
	if (!getMatrix().isIdentity())
		bbox.transform(getMatrix());
	embed(bbox);
#if 0
	auto rect = util::make_unique<XMLElement>("rect");
	rect->addAttribute("x", x-metrics.wl);
	rect->addAttribute("y", y-metrics.h);
	rect->addAttribute("width", metrics.wl+metrics.wr);
	rect->addAttribute("height", metrics.h+metrics.d);
	rect->addAttribute("fill", "none");
	rect->addAttribute("stroke", "red");
	rect->addAttribute("stroke-width", "0.5");
	_svg.appendToPage(std::move(rect));
	if (metrics.d > 0) {
		auto line = util::make_unique<XMLElement>("line");
		line->addAttribute("x1", x-metrics.wl);
		line->addAttribute("y1", y);
		line->addAttribute("x2", x+metrics.wr);
		line->addAttribute("y2", y);
		line->addAttribute("stroke", "blue");
		line->addAttribute("stroke-width", "0.5");
		_svg.appendToPage(std::move(line));
	}
	if (metrics.wl > 0) {
		auto line = util::make_unique<XMLElement>("line");
		line->addAttribute("x1", x);
		line->addAttribute("y1", y-metrics.h);
		line->addAttribute("x2", x);
		line->addAttribute("y2", y+metrics.d);
		line->addAttribute("stroke", "blue");
		line->addAttribute("stroke-width", "0.5");
		_svg.appendToPage(std::move(line));
	}
#endif
}


/** This method is called when a "set rule" or "put rule" command was found in the
 *  DVI file. It draws a solid unrotated rectangle.
 *  @param[in] x horizontal position of left edge
 *  @param[in] y vertical position of bottom(!) edge
 *  @param[in] height length of the vertical edges
 *  @param[in] width length of the horizontal edges */
void DVIToSVGActions::setRule (double x, double y, double height, double width) {
	if (_outputLocked)
		return;

	// (x,y) is the lower left corner of the rectangle
	auto rect = util::make_unique<SVGElement>("rect");
	rect->addAttribute("x", x);
	rect->addAttribute("y", y-height);
	rect->addAttribute("height", height);
	rect->addAttribute("width", width);
	rect->setTransform(getMatrix());
	rect->setFillColor(_svg.getColor());
	_svg.appendToPage(std::move(rect));

	// update bounding box
	BoundingBox bb(x, y-height, x+width, y);
	if (!getMatrix().isIdentity())
		bb.transform(getMatrix());
	embed(bb);
}


/** This method is called when a "set font" command was found in the DVI file. The
 *  font must be previously defined.
 *  @param[in] num unique number of the font in the DVI file (not necessarily equal to the DVI font number)
 *  @param[in] font pointer to the font object (always represents a physical font and never a virtual font) */
void DVIToSVGActions::setFont (int num, const Font &font) {
	_currentFontNum = num;
	_svg.setFont(num, font);
}


/** This method is called when a "special" command was found in the DVI file.
 *  @param[in] spc the special expression
 *  @param[in] dvi2bp factor to scale DVI units to PS points
 *  @param[in] preprocessing if true, the DVI file is being pre-processed */
void DVIToSVGActions::special (const string &spc, double dvi2bp, bool preprocessing) {
	try {
		if (preprocessing)
			SpecialManager::instance().preprocess(spc, *this);
		else
			SpecialManager::instance().process(spc, dvi2bp, *this);
		// @@ output message in case of unsupported specials?
	}
	catch (const SpecialException &e) {
		Message::estream(true) << "error in special: " << e.what() << '\n';
	}
}


/** This method is called when a "begin of page (bop)" command was found in the DVI file.
 *  @param[in] pageno physical page number
 *  @param[in] c array with 10 components representing \\count0 ... \\count9. c[0] contains the
 *               current (printed) page number (may differ from page count) */
void DVIToSVGActions::beginPage (unsigned pageno, const vector<int32_t>&) {
	SpecialManager::instance().notifyBeginPage(pageno, *this);
	_svg.newPage(++_pageCount);
	_bbox = BoundingBox();  // clear bounding box
	_boxes.clear();
}


/** This method is called when an "end of page (eop)" command was found in the DVI file. */
void DVIToSVGActions::endPage (unsigned pageno) {
	try {
		SpecialManager::instance().notifyEndPage(pageno, *this);
	}
	catch (const SpecialException &e) {
		Message::estream(true) << "error in special: " << e.what() << '\n';
	}
	Matrix matrix = _dvireader->getPageTransformation();
	_svg.transformPage(matrix);
	if (_bgcolor != Color::TRANSPARENT) {
		// create a rectangle filled with the background color
		auto rect = util::make_unique<SVGElement>("rect");
		rect->addAttribute("x", _bbox.minX());
		rect->addAttribute("y", _bbox.minY());
		rect->addAttribute("width", _bbox.width());
		rect->addAttribute("height", _bbox.height());
		rect->setFillColor(_bgcolor);
		_svg.prependToPage(std::move(rect));
	}
}


void DVIToSVGActions::setBgColor (const Color &color) {
	_bgcolor = color;
}


void DVIToSVGActions::embed(const BoundingBox &bbox) {
	_bbox.embed(bbox);
	for (auto &strboxpair : _boxes)
		strboxpair.second.embed(bbox);
}


void DVIToSVGActions::embed(const DPair& p, double r) {
	if (r == 0)
		_bbox.embed(p);
	else
		_bbox.embed(p, r);
	for (auto &strboxpair : _boxes)
		strboxpair.second.embed(p, r);
}


BoundingBox& DVIToSVGActions::bbox(const string& name, bool reset) {
	BoundingBox &box = _boxes[name];
	if (reset)
		box = BoundingBox();
	return box;
}


/** This method is called by subprocesses like the PS handler when
 *  a computation step has finished. Since the total number of steps
 *  can't be determined in advance, we don't show a percent value but
 *  a rotating dash. */
void DVIToSVGActions::progress (const char *id) {
	if (PROGRESSBAR_DELAY < 1000) {
		static double time=System::time();
		// slow down updating the progress indicator to prevent flickering
		if (System::time() - time > 0.1) {
			progress(0, 0, id);
			time = System::time();
		}
	}
}


/** Returns the number of digits of a given integer. */
static int digits (int n) {
	if (n == 0)
		return 1;
	if (n > 0)
		return util::ilog10(n)+1;
	return util::ilog10(-n)+2;
}


/** Draws a simple progress indicator.
 *  @param[in] current current iteration step (of 'total' steps)
 *  @param[in] total total number of iteration steps
 *  @param[in] id ID of the subprocess providing the information */
void DVIToSVGActions::progress (size_t current, size_t total, const char *id) {
	static double time=0;
	static bool draw=false; // show progress indicator?
	static const char *prev_id=nullptr;
	if (current == 0 && total > 0) {
		time = System::time();
		draw = false;
		Message::mstream() << '\n';
	}
	// don't show the progress indicator before the given time has elapsed
	if (!draw && System::time()-time > PROGRESSBAR_DELAY) {
		draw = true;
		Terminal::cursor(false);
	}
	if (draw && (System::time() - time > 0.1 || (total > 0 && current == total) || prev_id != id)) {
		static int step = -1;   // >=0: rotating dash
		static size_t prev_current=0, prev_total=1;
		const char *tips = "-\\|/";
		if (total == 0) {
			current = prev_current;
			total = prev_total;
			step = (step+1) % 4;
		}
		else {
			prev_current = current;
			prev_total = total;
			step = -1;
		}
		// adapt length of progress indicator to terminal width
		int cols = Terminal::columns();
		int width = (cols == 0 || cols > 60) ? 50 : 49-60+cols;
		double factor = double(current)/double(total);
		int length = int(width*factor);
		Message::mstream(false, Message::MC_PROGRESS)
			<< '[' << string(length, '=')
			<< (factor < 1.0 ? (step < 0 ? ' ' : tips[step]) : '=')
			<< string(width-length, ' ')
			<< "] " << string(3-digits(int(100*factor)), ' ') << int(100*factor)
			<< "%\r";
		// overprint indicator when finished
		if (factor == 1.0) {
			Message::estream().clearline();
			Terminal::cursor(true);
		}
		time = System::time();
		prev_id = id;
	}
}