1 /*************************************************************************
2 ** MapLine.cpp                                                          **
3 **                                                                      **
4 ** This file is part of dvisvgm -- the DVI to SVG converter             **
5 ** Copyright (C) 2005-2015 Martin Gieseking <martin.gieseking@uos.de>   **
6 **                                                                      **
7 ** This program is free software; you can redistribute it and/or        **
8 ** modify it under the terms of the GNU General Public License as       **
9 ** published by the Free Software Foundation; either version 3 of       **
10 ** the License, or (at your option) any later version.                  **
11 **                                                                      **
12 ** This program is distributed in the hope that it will be useful, but  **
13 ** WITHOUT ANY WARRANTY; without even the implied warranty of           **
14 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the         **
15 ** GNU General Public License for more details.                         **
16 **                                                                      **
17 ** You should have received a copy of the GNU General Public License    **
18 ** along with this program; if not, see <http://www.gnu.org/licenses/>. **
19 *************************************************************************/
20 
21 #include <config.h>
22 #include <cstring>
23 #include <sstream>
24 #include "InputBuffer.h"
25 #include "InputReader.h"
26 #include "MapLine.h"
27 #include "Subfont.h"
28 
29 using namespace std;
30 
31 
32 /** Constructs a MapLine object by parsing a single mapline from the given stream. */
MapLine(istream & is)33 MapLine::MapLine (istream &is)
34 	: _sfd(0), _fontindex(0), _slant(0), _bold(0), _extend(1)
35 {
36 	char buf[256];
37 	is.getline(buf, 256);
38 	parse(buf);
39 }
40 
41 
42 // Some of the following functions have been derived from the dvipdfmx source file fontmap.c:
43 // http://cvs.ktug.or.kr/viewcvs/dvipdfmx/src/fontmap.c?revision=1.43&view=markup
44 
45 
46 /** Returns true if the given string is in dvips mapline format, and false if it's in dvipdfm format.
47 	 @param[in] line string to check */
isDVIPSFormat(const char * line) const48 bool MapLine::isDVIPSFormat (const char *line) const {
49 	if (strchr(line, '"') || strchr(line, '<'))  // these chars are only present in dvips maps
50 		return true;
51 	char prevchar = ' ';
52 	int entry_count=0;
53 	for (const char *p=line; *p; ++p) {
54 		if (isspace(prevchar)) {
55 			if (*p == '-') // options starting with '-' are only present in dvipdfm map files
56 				return false;
57 			if (!isspace(*p))
58 				entry_count++;
59 		}
60 		prevchar = *p;
61 	}
62 	// tfm_name and ps_name only => dvips map
63 	return entry_count == 2;
64 }
65 
66 
67 /** Separates main font name and subfont definition name from a given combined name.
68  *  Example: "basename@sfdname@10" => {"basename10", "sfdname"}
69  *  @param[in,out] fontname complete fontname; after separation: main fontname only
70  *  @param[out] sfdname name of subfont definition
71  *  @return true on success */
split_fontname(string & fontname,string & sfdname)72 static bool split_fontname (string &fontname, string &sfdname) {
73 	size_t pos1;    // index of first '@'
74 	if ((pos1 = fontname.find('@')) != string::npos && pos1 > 0) {
75 		size_t pos2; // index of second '@'
76 		if ((pos2 = fontname.find('@', pos1+1)) != string::npos && pos2 > pos1+1) {
77 			sfdname = fontname.substr(pos1+1, pos2-pos1-1);
78 			fontname = fontname.substr(0, pos1) + fontname.substr(pos2+1);
79 			return true;
80 		}
81 	}
82 	return false;
83 }
84 
85 
86 /** Parses a single mapline and stores the scanned data in member variables.
87  *  The line may either be given in dvips or dvipdfmx mapfile format.
88  *  @param[in] line the mapline */
parse(const char * line)89 void MapLine::parse (const char *line) {
90 	CharInputBuffer ib(line, strlen(line));
91 	BufferInputReader ir(ib);
92 	_texname = ir.getString();
93 	string sfdname;
94 	split_fontname(_texname, sfdname);
95 	if (!sfdname.empty())
96 		_sfd = SubfontDefinition::lookup(sfdname);
97 	if (isDVIPSFormat(line))
98 		parseDVIPSLine(ir);
99 	else
100 		parseDVIPDFMLine(ir);
101 }
102 
103 
104 /** Parses a single line in dvips mapfile format.
105  *  @param[in] ir the input stream must be assigned to this reader */
parseDVIPSLine(InputReader & ir)106 void MapLine::parseDVIPSLine (InputReader &ir) {
107 	ir.skipSpace();
108 	if (ir.peek() != '<' && ir.peek() != '"')
109 		_psname = ir.getString();
110 	ir.skipSpace();
111 	while (ir.peek() == '<' || ir.peek() == '"') {
112 		if (ir.peek() == '<') {
113 			ir.get();
114 			if (ir.peek() == '[')
115 				ir.get();
116 			string name = ir.getString();
117 			if (name.length() > 4 && name.substr(name.length()-4) == ".enc")
118 				_encname = name.substr(0, name.length()-4);
119 			else
120 				_fontfname = name;
121 		}
122 		else {  // ir.peek() == '"' => list of PS font operators
123 			string options = ir.getQuotedString('"');
124 			StringInputBuffer sib(options);
125 			BufferInputReader sir(sib);
126 			while (!sir.eof()) {
127 				double number;
128 				if (sir.parseDouble(number)) {
129 					// operator with preceding numeric parameter (value opstr)
130 					string opstr = sir.getString();
131 					if (opstr == "SlantFont")
132 						_slant = number;
133 					else if (opstr == "ExtendFont")
134 						_extend = number;
135 				}
136 				else {
137 					// operator without parameter => skip for now
138 					sir.getString();
139 				}
140 			}
141 		}
142 		ir.skipSpace();
143 	}
144 }
145 
146 
throw_number_expected(char opt,bool integer_only=false)147 static void throw_number_expected (char opt, bool integer_only=false) {
148 	ostringstream oss;
149 	oss << "option -" << opt << ": " << (integer_only ? "integer" : "floating point") << " value expected";
150 	throw MapLineException(oss.str());
151 }
152 
153 
154 /** Parses a single line in dvipdfmx mapfile format.
155  *  @param[in] ir the input stream must be assigned to this reader */
parseDVIPDFMLine(InputReader & ir)156 void MapLine::parseDVIPDFMLine (InputReader &ir) {
157 	ir.skipSpace();
158 	if (ir.peek() != '-') {
159 		_encname = ir.getString();
160 		if (_encname == "default" || _encname == "none")
161 			_encname.clear();
162 	}
163 	ir.skipSpace();
164 		if (ir.peek() != '-')
165 		_fontfname = ir.getString();
166 	if (!_fontfname.empty()) {
167 		parseFilenameOptions(_fontfname);
168 	}
169 	ir.skipSpace();
170 	while (ir.peek() == '-') {
171 		ir.get();
172 		char option = ir.get();
173 		if (!isprint(option))
174 			throw MapLineException("option character expected");
175 		ir.skipSpace();
176 		switch (option) {
177 			case 's': // slant
178 				if (!ir.parseDouble(_slant))
179 					throw_number_expected('s');
180 				break;
181 			case 'e': // extend
182 				if (!ir.parseDouble(_extend))
183 					throw_number_expected('e');
184 				break;
185 			case 'b': // bold
186 				if (!ir.parseDouble(_bold))
187 					throw_number_expected('b');
188 				break;
189 			case 'r': //remap (deprecated)
190 				break;
191 			case 'i': // ttc index
192 				if (!ir.parseInt(_fontindex, false))
193 					throw_number_expected('i', true);
194 				break;
195 			case 'p': // UCS plane
196 				int dummy;
197 				if (!ir.parseInt(dummy, false))
198 					throw_number_expected('p', true);
199 				break;
200 			case 'u': // to unicode
201 				ir.getString();
202 				break;
203 			case 'v': // stemV
204 				int stemv;
205 				if (!ir.parseInt(stemv, true))
206 					throw_number_expected('v', true);
207 				break;
208 			case 'm': // map single chars
209 				ir.skipUntil("-");
210 				break;
211 			case 'w': // writing mode (horizontal=0, vertical=1)
212 				int vertical;
213 				if (!ir.parseInt(vertical, false))
214 					throw_number_expected('w', true);
215 				break;
216 			default:
217 				ostringstream oss;
218 				oss << "invalid option: -" << option;
219 				throw MapLineException(oss.str());
220 		}
221 		ir.skipSpace();
222 	}
223 }
224 
225 
226 /** [:INDEX:][!]FONTNAME[/CSI][,VARIANT] */
parseFilenameOptions(string fname)227 void MapLine::parseFilenameOptions (string fname) {
228 	_fontfname = fname;
229 	StringInputBuffer ib(fname);
230 	BufferInputReader ir(ib);
231 	if (ir.peek() == ':' && isdigit(ir.peek(1))) {  // index given?
232 		ir.get();
233 		_fontindex = ir.getInt();  // font index of file with multiple fonts
234 		if (ir.peek() == ':')
235 			ir.get();
236 		else
237 			_fontindex = 0;
238 	}
239 	if (ir.peek() == '!')  // no embedding
240 		ir.get();
241 
242 	bool csi_given=false, style_given=false;
243 	int pos;
244 	if ((pos = ir.find('/')) >= 0) {  // csi delimiter
245 		csi_given = true;
246 		_fontfname = ir.getString(pos);
247 	}
248 	else if ((pos = ir.find(',')) >= 0) {
249 		style_given = true;
250 		_fontfname = ir.getString(pos);
251 	}
252 	else
253 		_fontfname = ir.getString();
254 
255 	if (csi_given) {
256 		if ((pos = ir.find(',')) >= 0) {
257 			style_given = true;
258 			ir.getString(pos);  // charcoll
259 		}
260 		else if (ir.eof())
261 			throw MapLineException("CSI specifier expected");
262 		else
263 			ir.getString();  // charcoll
264 	}
265 	if (style_given) {
266 		ir.get();  // skip ','
267 		if (ir.check("BoldItalic")) {
268 		}
269 		else if (ir.check("Bold")) {
270 		}
271 		else if (ir.check("Italic")) {
272 		}
273 		if (!ir.eof())
274 			throw MapLineException("invalid style given");
275 	}
276 }
277