summaryrefslogtreecommitdiff
path: root/dviware/dvisvgm/src/DVIReader.cpp
blob: 893e6a818beb09e7fb55f4eff68ae310ec57547f (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
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
/*************************************************************************
** DVIReader.cpp                                                        **
**                                                                      **
** This file is part of dvisvgm -- a fast DVI to SVG converter          **
** Copyright (C) 2005-2020 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 <algorithm>
#include <sstream>
#include "Color.hpp"
#include "DVIActions.hpp"
#include "DVIReader.hpp"
#include "Font.hpp"
#include "FontManager.hpp"
#include "HashFunction.hpp"
#include "JFM.hpp"
#include "utility.hpp"
#include "VectorStream.hpp"

using namespace std;

DVIReader::DVIReader (istream &is) : BasicDVIReader(is)
{
	executePreamble();
	_bopOffsets = collectBopOffsets();
	// read data from postamble but don't process font definitions
	goToPostamble();
	executeCommand();
	executePostPost();
}


/** Executes all DVI commands read from the input stream. */
void DVIReader::executeAll () {
	int opcode = 0;
	while (!eof() && opcode >= 0) {
		try {
			opcode = executeCommand();
		}
		catch (const InvalidDVIFileException &e) {
			// end of stream reached
			opcode = -1;
		}
	}
}


/** Reads and executes the commands of a single page.
 *  This methods stops reading after the page's eop command has been executed.
 *  @param[in] n number of page to be executed (1-based)
 *  @returns true if page was read successfully */
bool DVIReader::executePage (unsigned n) {
	clearStream();    // reset all status bits
	if (!isStreamValid())
		throw DVIException("invalid DVI file");
	if (n < 1 || n > numberOfPages())
		return false;

	seek(_bopOffsets[n-1]); // goto bop of n-th page
	_currPageNum = n;
	while (executeCommand() != OP_EOP);
	return true;
}


/** Computes a hash value for a given page. The hash algorithm is selected by
 *  a HashFunction object which will also contain the resulting hash value if
 *  this function returns true.
 *  @param[in] pageno number of page to process (1-based)
 *  @param[in,out] hashFunc hash function to use
 *  @return true on success, hashFunc contains the resulting hash value */
bool DVIReader::computePageHash (size_t pageno, HashFunction &hashFunc) {
	if (pageno == 0 || pageno > numberOfPages())
		return false;

	hashFunc.reset();
	clearStream();
	seek(_bopOffsets[pageno-1]+45);  // now on first command after bop of selected page
	const size_t BUFSIZE = 4096;
	char buf[BUFSIZE];
	size_t numBytes = numberOfPageBytes(pageno-1)-46;  // number of bytes excluding bop and eop
	while (numBytes > 0) {
		getInputStream().read(buf, min(numBytes, BUFSIZE));
		hashFunc.update(buf, getInputStream().gcount());
		numBytes -= getInputStream().gcount();
	}
	return true;
}


/////////////////////////////////////

/** Reads and executes DVI preamble command.
 *  Format: pre ver[1] num[4] den[4] mag[4] cmtlen[1] cmt[cmtlen] */
void DVIReader::cmdPre (int) {
	uint8_t id = readUnsigned(1);
	setDVIVersion(DVIVersion(id));     // identification number
	uint32_t numer = readUnsigned(4);  // numerator units of measurement
	uint32_t denom = readUnsigned(4);  // denominator units of measurement
	if (denom == 0)
		throw DVIException("denominator of measurement unit is zero");
	_mag = readUnsigned(4);        // magnification
	uint32_t k = readUnsigned(1);  // length of following comment
	string comment = readString(k);
	// 1 dviunit * num/den == multiples of 0.0000001m
	// 1 dviunit * _dvibp: length of 1 dviunit in PS points * _mag/1000
	_dvi2bp = numer/254000.0*72.0/denom*_mag/1000.0;
	dviPre(id, numer, denom, _mag, comment);
}


/** Reads and executes DVI postamble command.
 *  Format: post p[4] num[4] den[4] mag[4] ph[4] pw[4] sd[2] np[2] */
void DVIReader::cmdPost (int) {
	uint32_t prevBopOffset = readUnsigned(4);
	uint32_t numer = readUnsigned(4);
	uint32_t denom = readUnsigned(4);
	if (denom == 0)
		throw DVIException("denominator of measurement unit is zero");
	_mag = readUnsigned(4);
	uint32_t pageHeight = readUnsigned(4); // height of tallest page in dvi units
	uint32_t pageWidth  = readUnsigned(4); // width of widest page in dvi units
	uint16_t stackDepth = readUnsigned(2); // max. stack depth required
	uint16_t numPages = readUnsigned(2);
	if (numPages != (numberOfPages() & 0xffff))
		throw DVIException("page count in postamble doesn't match actual number of pages");

	// 1 dviunit * num/den == multiples of 0.0000001m
	// 1 dviunit * _dvi2bp: length of 1 dviunit in PS points * _mag/1000
	_dvi2bp = numer/254000.0*72.0/denom*_mag/1000.0;
	dviPost(stackDepth, numPages, pageWidth*_dvi2bp, pageHeight*_dvi2bp, _mag, numer, denom, prevBopOffset);
}


/** Reads and executes DVI post_post command.
 *  Format: post_post q[4] i[1] 223[>=4] */
void DVIReader::cmdPostPost (int) {
	uint32_t postOffset = readUnsigned(4);   // pointer to begin of postamble
	uint8_t id = readUnsigned(1);
	setDVIVersion(DVIVersion(id));   // identification byte
	while (readUnsigned(1) == DVI_FILL);  // skip fill bytes (223), eof bit should be set now
	dviPostPost(DVIVersion(id), postOffset);
}


/** Reads and executes Begin-Of-Page command.
 *  Format: bop c0[+4] ... c9[+4] p[+4] */
void DVIReader::cmdBop (int) {
	vector<int32_t> c(10);
	for (int i=0; i < 10; i++)
		c[i] = readSigned(4);
	int32_t prevBopOffset = readSigned(4);  // pointer to peceeding bop (-1 in case of first page)
	_dviState.reset();    // set all DVI registers to 0
	while (!_stateStack.empty())
		_stateStack.pop();
	_currFontNum = 0;
	_inPage = true;
	dviBop(c, prevBopOffset);
}


/** Reads and executes End-Of-Page command. */
void DVIReader::cmdEop (int) {
	if (!_stateStack.empty())
		throw DVIException("stack not empty at end of page");
	_inPage = false;
	dviEop();
}


/** Reads and executes push command. */
void DVIReader::cmdPush (int) {
	_stateStack.push(_dviState);
	dviPush();
}


/** Reads and executes pop command (restores pushed position information). */
void DVIReader::cmdPop (int) {
	if (_stateStack.empty())
		throw DVIException("stack empty at pop command");
	_dviState = _stateStack.top();
	_stateStack.pop();
	dviPop();
}


/** Helper function that handles charaters from virtual fonts (VF).
 *  It is called by the cmdSetChar and cmdPutChar methods.
 *  @param[in] font current font (corresponding to _currFontNum)
 *  @param[in] c character to typeset */
void DVIReader::putVFChar (Font *font, uint32_t c) {
	if (auto vf = dynamic_cast<VirtualFont*>(font)) { // is current font a virtual font?
		FontManager &fm = FontManager::instance();
		const vector<uint8_t> *dvi = vf->getDVI(c);    // try to get DVI snippet that represents character c
		Font *firstFont = fm.vfFirstFont(vf);
		if (!dvi && (!firstFont || !dynamic_cast<const JFM*>(firstFont->getMetrics())))
			return;
		fm.enterVF(vf);                              // enter VF font number context
		int savedFontNum = _currFontNum;             // save current font number
		setFont(fm.vfFirstFontNum(vf), SetFontMode::VF_ENTER);
		if (!dvi)                                    // no definition present for current (Japanese) char?
			dviPutChar(c, firstFont);                 // fallback for JFM-based virtual fonts
		else {
			// DVI units in virtual fonts are multiples of 1^(-20) times the scaled size of the VF
			double savedScale = _dvi2bp;
			_dvi2bp = vf->scaledSize()/(1 << 20);
			DVIState savedState = _dviState;  // save current cursor position
			_dviState.x = _dviState.y = _dviState.w = _dviState.z = 0;
			VectorInputStream<uint8_t> vis(*dvi);
			istream &is = replaceStream(vis);
			try {
				executeAll();  // execute DVI fragment
			}
			catch (const DVIException &e) {
				// Message::estream(true) << "invalid dvi in vf: " << e.getMessage() << endl; // @@
			}
			replaceStream(is);       // restore previous input stream
			_dviState = savedState;  // restore previous cursor position
			_dvi2bp = savedScale;    // restore previous scale factor
		}
		fm.leaveVF();          // restore previous font number context
		setFont(savedFontNum, SetFontMode::VF_LEAVE);  // restore previous font number
	}
}


/** Reads and executes set_char_x command. Puts a character at the current
 *  position and advances the cursor.
 *  @param[in] c character to set
 *  @throw DVIException if method is called ouside a bop/eop pair */
void DVIReader::cmdSetChar0 (int c) {
	if (!_inPage)
		throw DVIException("setchar outside of page");
	Font *font = FontManager::instance().getFont(_currFontNum);
	dviSetChar0(c, font); // template method that may trigger further actions
	putVFChar(font, c);   // further character processing if current font is a virtual font
	moveRight(font->charWidth(c)*font->scaleFactor()*_mag/1000.0, MoveMode::SETCHAR);
}


/** Reads and executes setx command. Puts a character at the current
 *  position and advances the cursor.
 *  @param[in] len number of parameter bytes (possible values: 1-4)
 *  @throw DVIException if method is called ouside a bop/eop pair */
void DVIReader::cmdSetChar (int len) {
	if (!_inPage)
		throw DVIException("setchar outside of page");
	// According to the dvi specification all character codes are unsigned
	// except len == 4. At the moment all char codes are treated as unsigned...
	uint32_t c = readUnsigned(len); // if len == 4 c may be signed
	Font *font = FontManager::instance().getFont(_currFontNum);
	dviSetChar(c, font); // template method that may trigger further actions
	putVFChar(font, c);  // further character processing if current font is a virtual font
	moveRight(font->charWidth(c)*font->scaleFactor()*_mag/1000.0, MoveMode::SETCHAR);
}


/** Reads and executes putx command. Puts a character at the current
 *  position but doesn't change the cursor position.
 *  @param[in] len number of parameter bytes (possible values: 1-4)
 *  @throw DVIException if method is called ouside a bop/eop pair */
void DVIReader::cmdPutChar (int len) {
	if (!_inPage)
		throw DVIException("putchar outside of page");
	// According to the dvi specification all character codes are unsigned
	// except len == 4. At the moment all char codes are treated as unsigned...
	int32_t c = readUnsigned(len);
	Font *font = FontManager::instance().getFont(_currFontNum);
	dviPutChar(c, font);
	putVFChar(font, c);
}


/** Reads and executes set_rule command. Puts a solid rectangle at the current
 *  position and updates the cursor position.
 *  Format: set_rule h[+4] w[+4]
 *  @throw DVIException if method is called ouside a bop/eop pair */
void DVIReader::cmdSetRule (int) {
	if (!_inPage)
		throw DVIException("set_rule outside of page");
	double height = _dvi2bp*readSigned(4);
	double width  = _dvi2bp*readSigned(4);
	dviSetRule(height, width);
	moveRight(width, MoveMode::CHANGEPOS);
}


/** Reads and executes set_rule command. Puts a solid rectangle at the current
 *  position but leaves the cursor position unchanged.
 *  Format: put_rule h[+4] w[+4]
 *  @throw DVIException if method is called ouside a bop/eop pair */
void DVIReader::cmdPutRule (int) {
	if (!_inPage)
		throw DVIException("put_rule outside of page");
	double height = _dvi2bp*readSigned(4);
	double width  = _dvi2bp*readSigned(4);
	dviPutRule(height, width);
}


void DVIReader::moveRight (double dx, MoveMode) {
	switch (_dviState.d) {
		case WritingMode::LR: _dviState.h += dx; break;
		case WritingMode::TB: _dviState.v += dx; break;
		case WritingMode::BT: _dviState.v -= dx; break;
	}
}


void DVIReader::moveDown (double dy, MoveMode) {
	switch (_dviState.d) {
		case WritingMode::LR: _dviState.v += dy; break;
		case WritingMode::TB: _dviState.h -= dy; break;
		case WritingMode::BT: _dviState.h += dy; break;
	}
}


void DVIReader::cmdRight (int len) {
	double dx = _dvi2bp*readSigned(len);
	moveRight(dx, MoveMode::CHANGEPOS);
	dviRight(dx);
}


void DVIReader::cmdDown (int len) {
	double dy = _dvi2bp*readSigned(len);
	moveDown(dy, MoveMode::CHANGEPOS);
	dviDown(dy);
}


void DVIReader::cmdNop (int) {dviNop();}
void DVIReader::cmdX0 (int)  {moveRight(_dviState.x, MoveMode::CHANGEPOS); dviX0();}
void DVIReader::cmdY0 (int)  {moveDown(_dviState.y, MoveMode::CHANGEPOS); dviY0();}
void DVIReader::cmdW0 (int)  {moveRight(_dviState.w, MoveMode::CHANGEPOS); dviW0();}
void DVIReader::cmdZ0 (int)  {moveDown(_dviState.z, MoveMode::CHANGEPOS); dviZ0();}


void DVIReader::cmdX (int len) {
	double dx = _dvi2bp*readSigned(len);
	_dviState.x = dx;
	moveRight(dx, MoveMode::CHANGEPOS);
	dviX(dx);
}


void DVIReader::cmdY (int len) {
	double dy = _dvi2bp*readSigned(len);
	_dviState.y = dy;
	moveDown(dy, MoveMode::CHANGEPOS);
	dviY(dy);
}


void DVIReader::cmdW (int len) {
	double dx = _dvi2bp*readSigned(len);
	_dviState.w = dx;
	moveRight(dx, MoveMode::CHANGEPOS);
	dviW(dx);
}


void DVIReader::cmdZ (int len) {
	double dy = _dvi2bp*readSigned(len);
	_dviState.z = dy;
	moveDown(dy, MoveMode::CHANGEPOS);
	dviZ(dy);
}


/** Sets the text orientation (horizontal, vertical).
 *  This command is only available in DVI version 3 (created by pTeX) */
void DVIReader::cmdDir (int) {
	uint8_t wmode = readUnsigned(1);
	if (wmode == 4)  // yoko mode (4) equals default LR mode (0)
		wmode = 0;
	if (wmode == 2 || wmode > 3)
		throw DVIException("invalid writing mode value " + std::to_string(wmode) + " (0, 1, 3, or 4 expected)");
	_dviState.d = (WritingMode)wmode;
	dviDir(_dviState.d);
}


void DVIReader::cmdXXX (int len) {
	if (!_inPage)
		throw DVIException("special outside of page");
	uint32_t numBytes = readUnsigned(len);
	string str = readString(numBytes);
	dviXXX(str);
}


/** Change the current font.
 *  @param[in] fontnum local number of font to select
 *  @param[in] mode info on command that triggered the font change
 *  @throw DVIException if font number is undefined */
void DVIReader::setFont (int fontnum, SetFontMode mode) {
	if (const Font *font = FontManager::instance().getFont(fontnum)) {
		_currFontNum = fontnum;
		dviFontNum(uint32_t(fontnum), mode, font);
	}
	else
		throw DVIException("undefined font number " + std::to_string(fontnum));
}


/** Selects a previously defined font by its number.
 *  @param[in] fontnum font number
 *  @throw DVIException if font number is undefined */
void DVIReader::cmdFontNum0 (int fontnum) {
	setFont(fontnum, SetFontMode::SF_SHORT);
}


/** Selects a previously defined font.
 *  @param[in] len size of font number variable (in bytes)
 *  @throw DVIException if font number is undefined */
void DVIReader::cmdFontNum (int len) {
	uint32_t fontnum = readUnsigned(len);
	setFont(fontnum, SetFontMode::SF_LONG);
}


/** Parses a sequence of font attributes given as key1=val1;key2=val2;...;keyn=valn */
static map<string,string> parse_font_attribs (const string &str) {
	map<string,string> attribs;
	if (!str.empty()) {
		for (const string &attr : util::split(str, ";")) {
			vector<string> keyval = util::split(attr, "=");
			if (keyval.size() == 2)
				attribs[keyval[0]] = keyval[1];
		}
	}
	return attribs;
}


/** Helper function to handle a font definition.
 *  @param[in] fontnum local font number
 *  @param[in] name font name (or file path if enclosed in square brackets)
 *  @param[in] cs checksum to be compared with TFM checksum
 *  @param[in] dsize design size of font in PS point units
 *  @param[in] ssize scaled size of font in PS point units */
const Font* DVIReader::defineFont (uint32_t fontnum, const string &name, uint32_t cs, double dsize, double ssize) {
	FontManager &fm = FontManager::instance();
	Font *font = fm.getFont(fontnum);
	if (!font && !name.empty()) {  // font not registered yet?
		if (name[0] == '[') {       // LuaTeX native font reference?
			size_t last = name.rfind(']');
			if (last != string::npos) {
				string path = name.substr(1, last-1);
				FontStyle style;
				int fontIndex=0;
				if (name.size() > last && name[last+1] == ':') {  // look for font attributes?
					auto attribs = parse_font_attribs(name.substr(last+2));
					auto it = attribs.begin();
					if ((it = attribs.find("index")) != attribs.end())
						fontIndex = stoi(it->second);
					if ((it = attribs.find("embolden")) != attribs.end())
						style.bold = stoi(it->second)/65536.0;
					if ((it = attribs.find("extend")) != attribs.end())
						style.extend = stoi(it->second)/65536.0;
					if ((it = attribs.find("slant")) != attribs.end())
						style.slant = stoi(it->second)/65536.0;
				}
				int id = fm.registerFont(fontnum, path, fontIndex, ssize, style, Color::BLACK);
				font = fm.getFontById(id);
			}
		}
		else {  // TFM-based font specified by name
			int id = fm.registerFont(fontnum, name, cs, dsize, ssize);
			font = fm.getFontById(id);
			if (auto vf = dynamic_cast<VirtualFont*>(font)) {
				// read vf file, register its font and character definitions
				fm.enterVF(vf);
				ifstream ifs(vf->path(), ios::binary);
				VFReader vfReader(ifs);
				vfReader.replaceActions(this);
				vfReader.executeAll();
				fm.leaveVF();
			}
		}
	}
	return font;
}


/** Defines a new font.
 *  @param[in] len size of font number variable (in bytes) */
void DVIReader::cmdFontDef (int len) {
	uint32_t fontnum  = readUnsigned(len);    // font number
	uint32_t checksum = readUnsigned(4);      // font checksum (to be compared with corresponding TFM checksum)
	uint32_t ssize    = readUnsigned(4);      // scaled size of font in DVI units
	uint32_t dsize    = readUnsigned(4);      // design size of font in DVI units
	uint32_t pathlen  = readUnsigned(1);      // length of font path
	uint32_t namelen  = readUnsigned(1);      // length of font name
	string path       = readString(pathlen);  // path to font file
	string fontname   = readString(namelen);
	const Font *font  = defineFont(fontnum, fontname, checksum, dsize*_dvi2bp, ssize*_dvi2bp);
	dviFontDef(fontnum, checksum, font);
}


/** This template method is called by the VFReader after reading a font definition from a VF file.
 *  @param[in] fontnum local font number
 *  @param[in] path path to font file
 *  @param[in] name font name
 *  @param[in] checksum checksum to be compared with TFM checksum
 *  @param[in] dsize design size in PS point units
 *  @param[in] ssize scaled size in PS point units */
void DVIReader::defineVFFont (uint32_t fontnum, const string &path, const string &name, uint32_t checksum, double dsize, double ssize) {
	if (const VirtualFont *vf = FontManager::instance().getVF())
		defineFont(fontnum, name, checksum, dsize, ssize * vf->scaleFactor());
}


/** This template method is called by the VFReader after reading a character definition from a VF file.
 *  @param[in] c character number
 *  @param[in] dvi DVI fragment describing the character */
void DVIReader::defineVFChar (uint32_t c, vector<uint8_t> &&dvi) {
	FontManager::instance().assignVFChar(c, std::move(dvi));
}


/** XDV extension: includes image or pdf file.
 *  parameters: box[1] matrix[4][6] p[2] len[2] path[l] */
void DVIReader::cmdXPic (int) {
	uint8_t box = readUnsigned(1);    // box
	vector<int32_t> matrix(6);
	for (int i=0; i < 6; i++)       // matrix
		matrix[i] = readSigned(4);
	int16_t page = readSigned(2);     // page number
	uint16_t len = readUnsigned(2);
	string path = readString(len);  // path to image/pdf file
	dviXPic(box, matrix, page, path);
}


/** XDV extension: defines a native font */
void DVIReader::cmdXFontDef (int) {
	int32_t fontnum = readSigned(4);
	double ptsize = _dvi2bp*readUnsigned(4);
	uint16_t flags = readUnsigned(2);
	uint8_t psname_len = readUnsigned(1);
	uint8_t fmname_len = getDVIVersion() == DVI_XDV5 ? readUnsigned(1) : 0;  // length of family name
	uint8_t stname_len = getDVIVersion() == DVI_XDV5 ? readUnsigned(1) : 0;  // length of style name
	string fontname = readString(psname_len);
	uint32_t fontIndex=0;
	if (getDVIVersion() == DVI_XDV5)
		seek(fmname_len+stname_len, ios::cur);
	else
		fontIndex = readUnsigned(4);
	FontStyle style;
	Color color;
	if (flags & 0x0100) { // vertical?
	}
	if (flags & 0x0200) { // colored?
		// The font color must not interfere with color specials. If the font color is not black,
		// all color specials should be ignored, i.e. glyphs of a non-black fonts have a fixed color
		// that can't be changed by color specials.
		uint32_t rgba = readUnsigned(4);
		color.setRGB(uint8_t(rgba >> 24), uint8_t((rgba >> 16) & 0xff), uint8_t((rgba >> 8) & 0xff));
	}
	if (flags & 0x1000)   // extend?
		style.extend = _dvi2bp*readSigned(4);
	if (flags & 0x2000)   // slant?
		style.slant = _dvi2bp*readSigned(4);
	if (flags & 0x4000)   // embolden?
		style.bold = _dvi2bp*readSigned(4);
	if ((flags & 0x0800) && (getDVIVersion() == DVI_XDV5)) { // variations?
		uint16_t num_variations = readSigned(2);
		for (int i=0; i < num_variations; i++)
			readUnsigned(4);
	}
	const Font *font = FontManager::instance().getFont(fontnum);
	if (!font) {
		FontManager::instance().registerFont(fontnum, fontname, fontIndex, ptsize, style, color);
		font = FontManager::instance().getFont(fontnum);
	}
	dviXFontDef(fontnum, dynamic_cast<const NativeFont*>(font));
}


/** XDV extension: prints an array of characters where each character
 *  can take independent x and y coordinates.
 *  parameters: w[4] n[2] (dx,dy)[(4+4)n] glyphs[2n] */
void DVIReader::cmdXGlyphArray (int) {
	vector<double> dx, dy;
	vector<uint16_t> glyphs;
	double width = putGlyphArray(false, dx, dy, glyphs);
	if (Font *font = FontManager::instance().getFont(_currFontNum))
		dviXGlyphArray(dx, dy, glyphs, *font);
	else
		throw DVIException("missing setfont prior to xglypharray");
	moveRight(width, MoveMode::SETCHAR);
}


/** XDV extension: prints an array/string of characters where each character
 *  can take independent x coordinates whereas all share a single y coordinate.
 *  parameters: w[4] n[2] dx[4n] dy[4] glyphs[2n] */
void DVIReader::cmdXGlyphString (int) {
	vector<double> dx, dy;
	vector<uint16_t> glyphs;
	double width = putGlyphArray(true, dx, dy, glyphs);
	if (Font *font = FontManager::instance().getFont(_currFontNum))
		dviXGlyphString(dx, glyphs, *font);
	else
		throw DVIException("missing setfont prior to xglyphstring");
	moveRight(width, MoveMode::SETCHAR);
}


/** XDV extension: Same as cmdXGlyphArray plus a leading array of UTF-16 characters
 *  that specify the "actual text" represented by the glyphs to be printed. It usually
 *  contains the text with special characters (like ligatures) expanded so that it
 *  can be used for text search, plain text copy & paste etc. This XDV command was
 *  introduced with XeTeX 0.99995 and can be triggered by <tt>\\XeTeXgenerateactualtext1</tt>.
 *  parameters: l[2] chars[2l] w[4] n[2] (dx,dy)[8n] glyphs[2n] */
void DVIReader::cmdXTextAndGlyphs (int) {
	uint16_t l = readUnsigned(2);  // number of chars
	vector<uint16_t> chars(l);
	for (int i=0; i < l; i++)
		chars[i] = readUnsigned(2);
	vector<double> x, y;
	vector<uint16_t> glyphs;
	double width = putGlyphArray(false, x, y, glyphs);
	if (Font *font = FontManager::instance().getFont(_currFontNum))
		dviXTextAndGlyphs(x, y, chars, glyphs, *font);
	else
		throw DVIException("missing setfont prior to xtextandglyphs");
	moveRight(width, MoveMode::SETCHAR);
}


/** Implements the common functionality of cmdXGlyphArray, cmdXGlyphString, and cmdXTextAndGlyphs.
 *  @param[in] xonly indicates if the characters share a single y coordinate (xonly==true)
 *  @param[out] dx relative horizontal positions of each glyph
 *  @param[out] dy relative vertical positions of each glyph
 *  @param[out] glyphs FreeType indices of the glyphs to typeset
 *  @return total width of the glyph array */
double DVIReader::putGlyphArray (bool xonly, vector<double> &dx, vector<double> &dy, vector<uint16_t> &glyphs) {
	double strwidth = _dvi2bp*readSigned(4);
	uint16_t num_glyphs = readUnsigned(2);
	dx.resize(num_glyphs);
	dy.resize(num_glyphs);
	glyphs.resize(num_glyphs);
	for (int i=0; i < num_glyphs; i++) {
		dx[i] = readSigned(4)*_dvi2bp;
		dy[i] = xonly ? 0 : readSigned(4)*_dvi2bp;
	}
	for (int i=0; i < num_glyphs; i++)
		glyphs[i] = readUnsigned(2);
	return strwidth;
}