1 /*
2 For general Scribus (>=1.3.2) copyright and licensing information please refer
3 to the COPYING file provided with the program. Following this notice may exist
4 a copyright and/or license notice that predates the release of Scribus 1.3.2
5 for which a new license (GPL+exception) is in place.
6 */
7 
8 #include "fonts/ftface.h"
9 
10 #include FT_CID_H
11 #include FT_OUTLINE_H
12 #include FT_GLYPH_H
13 #include FT_TRUETYPE_IDS_H
14 
15 #include <QObject>
16 #include <QFile>
17 
18 #include "scfonts.h"
19 #include "util_debug.h"
20 #include "fonts/scfontmetrics.h"
21 
22 // static:
23 FT_Library FtFace::m_library = nullptr;
24 
25 /*****
26    ScFace lifecycle:  unchecked -> loaded -> glyphs checked
27                                |         \-> broken glyphs
28 							   \-> broken
29    usable() == ! broken
30    embeddable() == glyphs_checked
31 
32    loadChar/Glyph -> !broken
33    Glyphs:  width    status
34             -1000    unknown
35             -2000    broken
36             >= 0     ok, outline valid
37    CharMap:  unicode -> glyph index
38              gid_type[256][256]
39    unicode ignores: < 32, ...
40    unicode emulate: spaces, hyphen, ligatures?, diacritics?
41  *****/
42 
FtFace(const QString & fam,const QString & sty,const QString & vari,const QString & scname,const QString & psname,const QString & path,int face,const QStringList & features)43 FtFace::FtFace(const QString& fam, const QString& sty, const QString& vari, const QString& scname, const QString& psname, const QString& path, int face, const QStringList& features)
44 	: 	m_face(nullptr),
45 	  m_isBold(false),
46 	  m_isItalic(false),
47 	  m_encoding(0.0),
48 	  m_uniEM(0.0),
49 	  m_ascent(0.0),
50 	  m_descent(0.0),
51 	  m_height(0.0),
52 	  m_xHeight(0.0),
53 	  m_capHeight(0.0),
54 	  m_maxAdvanceWidth(0.0),
55 	  m_underlinePos(0.0),
56 	  m_strikeoutPos(0.0),
57 	  m_strokeWidth(0.0)
58 {
59 	family = fam;
60 	style = sty;
61 	variant = vari;
62 	scName = scname;
63 	psName = psname;
64 	fontFile = path;
65 	faceIndex = face;
66 	fontFeatures = features;
67 	if (!m_library)
68 		if (FT_Init_FreeType( &m_library ))
69 			sDebug(QObject::tr("Freetype2 library not available"));
70 }
71 
72 
~FtFace()73 FtFace::~FtFace() {
74 	unload();
75 }
76 
77 
ftFace() const78 FT_Face FtFace::ftFace() const
79 {
80 	if (!m_face)
81 	{
82 		if (FT_New_Face( m_library, QFile::encodeName(fontFile), faceIndex, & m_face ))
83 		{
84 			status = ScFace::BROKEN;
85 			m_face = nullptr;
86 			sDebug(QObject::tr("Font %1(%2) is broken").arg(fontFile).arg(faceIndex));
87 		}
88 		else
89 			load();
90 	}
91 	return m_face;
92 }
93 
load() const94 void FtFace::load() const
95 {
96 	ScFaceData::load();
97 
98 	if (!m_face) {
99 		if (FT_New_Face( m_library, QFile::encodeName(fontFile), faceIndex, & m_face ))
100 		{
101 			status = ScFace::BROKEN;
102 			m_face = nullptr;
103 			sDebug(QObject::tr("Font %1(%2) is broken").arg(fontFile).arg(faceIndex));
104 			return;
105 		}
106 	}
107 
108 	FT_Bool isCID = false;
109 	FT_Get_CID_Is_Internally_CID_Keyed(m_face, &isCID);
110 
111 	const_cast<FtFace*>(this)->isCIDFont = isCID;
112 	const_cast<FtFace*>(this)->isStroked = false;
113 	const_cast<FtFace*>(this)->hasGlyphNames = FT_HAS_GLYPH_NAMES(m_face);
114 
115 	m_encoding = 0;
116 	m_uniEM = static_cast<qreal>(m_face->units_per_EM);
117 
118 	m_descent = m_face->descender / m_uniEM;
119 	m_ascent = m_face->ascender / m_uniEM;
120 	m_height = m_face->height / m_uniEM;
121 
122 /* Temporary fix for the broken "Dutch Initials" font */
123 	if ((m_ascent == 0) && (m_descent == 0))
124 	{
125 		m_ascent = (m_face->bbox.yMax - m_face->bbox.yMin) / m_uniEM;
126 		m_height = m_ascent;
127 	}
128 
129 	m_xHeight = m_height;
130 	m_capHeight = m_height;
131 	m_maxAdvanceWidth = m_face->max_advance_width / m_uniEM;
132 	m_underlinePos = m_face->underline_position / m_uniEM;
133 	m_strikeoutPos = m_ascent / 3;
134 	m_strokeWidth = m_face->underline_thickness / m_uniEM;
135 	const_cast<FtFace*>(this)->isFixedPitch = m_face->face_flags & 4;
136 	m_pdfAscent    = QString::number(m_face->ascender / m_uniEM * 1000);
137 	m_pdfCapHeight = QString::number(m_face->height / m_uniEM * 1000);
138 	m_pdfDescender = QString::number(m_face->descender / m_uniEM * 1000);
139 	m_pdfFontBBox  = QString::number(m_face->bbox.xMin / m_uniEM * 1000) + " " +
140 	            QString::number(m_face->bbox.yMin / m_uniEM * 1000) + " " +
141 	            QString::number(m_face->bbox.xMax / m_uniEM * 1000) + " " +
142 	            QString::number(m_face->bbox.yMax / m_uniEM * 1000);
143 	m_italicAngle  = "0";
144 
145 	m_isItalic = (m_face->style_flags == 1 || m_face->style_flags == 3);
146 	m_isBold   = (m_face->style_flags == 2 || m_face->style_flags == 3);
147 
148 //FIXME:	FT_Set_Charmap(m_face, m_face->charmaps[m_encoding]);
149 	setBestEncoding(m_face);
150 
151 	FT_UInt gindex = 0;
152 	FT_ULong charcode = FT_Get_First_Char( m_face, &gindex );
153 	int goodGlyph = 0;
154 	int invalidGlyph = 0;
155 	bool error;
156 
157 	while ( gindex != 0 )
158 	{
159 		error = FT_Load_Glyph( m_face, gindex, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP );
160 		if (error)
161 		{
162 			++invalidGlyph;
163 			sDebug(QObject::tr("Font %1 has broken glyph %2 (charcode %3)").arg(fontFile).arg(gindex).arg(charcode));
164 			charcode = FT_Get_Next_Char( m_face, charcode, &gindex );
165 			continue;
166 		}
167 
168 		if (gindex > maxGlyph)
169 			const_cast<FtFace*>(this)->maxGlyph = gindex;
170 
171 		++goodGlyph;
172 		if (m_face->glyph->format == FT_GLYPH_FORMAT_PLOTTER)
173 			const_cast<FtFace*>(this)->isStroked = true;
174 		charcode = FT_Get_Next_Char( m_face, charcode, &gindex );
175 	}
176 	if (invalidGlyph > 0) {
177 		status = ScFace::BROKENGLYPHS;
178 	}
179 }
180 
181 
unload() const182 void FtFace::unload() const
183 {
184 	if (m_face) {
185 		FT_Done_Face( m_face );
186 		m_face = nullptr;
187 	}
188 	// clear caches
189 	ScFaceData::unload();
190 }
191 
192 
char2CMap(uint ch) const193 ScFace::gid_type FtFace::char2CMap(uint ch) const
194 {
195 	// FIXME use cMap cache
196 	FT_Face face = ftFace();
197 	ScFace::gid_type gl = FT_Get_Char_Index(face, ch);
198 	return gl;
199 }
200 
glyphIndexToCID(ScFace::gid_type index) const201 ScFace::cid_type FtFace::glyphIndexToCID(ScFace::gid_type index) const
202 {
203 	FT_Face face = ftFace();
204 
205 	ScFace::cid_type cid = 0;
206 	FT_Error err = FT_Get_CID_From_Glyph_Index(face, index, &cid);
207 	if (err)
208 		cid = index;
209 	return cid;
210 }
211 
loadGlyph(ScFace::gid_type gl) const212 void FtFace::loadGlyph(ScFace::gid_type gl) const
213 {
214 	if (m_glyphWidth.contains(gl))
215 		return;
216 
217 	ScFace::GlyphData GRec;
218 	FT_Face face = ftFace();
219 	if (FT_Load_Glyph( face, gl, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP ))
220 	{
221 		sDebug(QObject::tr("Font %1 has broken glyph %2").arg(fontFile).arg(gl));
222 		m_glyphWidth[gl] = 1;
223 	}
224 	else
225 	{
226 		qreal ww = qreal(face->glyph->metrics.horiAdvance) / m_uniEM;
227 		qreal w  = (face->glyph->metrics.width + qAbs(qreal(face->glyph->metrics.horiBearingX))) / m_uniEM;
228 		GRec.bbox_width = qMax(w, ww);
229 		qreal height = qreal(face->glyph->metrics.height) / m_uniEM;
230 		GRec.bbox_ascent = qreal(face->glyph->metrics.horiBearingY) / m_uniEM;
231 		GRec.bbox_descent = height - GRec.bbox_ascent;
232 //		qDebug() << QString("glyphmetrics %1: EM %2 bearing (%3,%4) size (%5,%6) advance %7 bbox (%8,%9)")
233 //			   .arg(gl).arg(m_uniEM).arg(face->glyph->metrics.horiBearingX).arg(face->glyph->metrics.horiBearingY)
234 //			   .arg(face->glyph->metrics.width).arg(face->glyph->metrics.height).arg(face->glyph->metrics.horiAdvance)
235 //			   .arg(w).arg(height);
236 
237 		qreal x, y;
238 		bool error = false;
239 		error = FT_Set_Char_Size( face, 0, 10, 72, 72 );
240 		if (error)
241 			m_glyphWidth[gl] = 1;
242 		FPointArray outlines = traceGlyph(face, gl, 10, &x, &y, &error);
243 		if (!error)
244 		{
245 			m_glyphWidth[gl] = ww;
246 			GRec.Outlines = outlines;
247 			GRec.x = x;
248 			GRec.y = y;
249 			GRec.broken = false;
250 		}
251 		else {
252 			m_glyphWidth[gl] = 1;
253 		}
254 	}
255 	m_glyphOutline[gl] = GRec;
256 	if (GRec.broken && status < ScFace::BROKENGLYPHS)
257 		status = ScFace::BROKENGLYPHS;
258 }
259 
260 
261 /*
262 GlyphMetrics FtFace::glyphBBox (gid_type gl, qreal sz) const
263 {
264 	FT_Face    face = ftFace();
265 	GlyphMetrics result;
266 	FT_Error error = FT_Load_Glyph( face, gl, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP );
267 	if (!error) {
268 		qreal w  = (face->glyph->metrics.width + QABS((qreal)face->glyph->metrics.horiBearingX)) / m_uniEM * sz;
269 		result.width = qMax(w, face->glyph->metrics.horiAdvance / m_uniEM * sz);
270 		qreal height = face->glyph->metrics.height / m_uniEM * sz;
271 		result.ascent = face->glyph->metrics.horiBearingY / m_uniEM * sz;
272 		result.descent = height - result.ascent;
273 	}
274 	else {
275 		result.width = result.ascent = sz;
276 		result.descent = 0;
277 	}
278 	return result;
279 }
280 */
281 
282 
283 /// copied from Freetype's FT_Stream_ReadAt()
ftIOFunc(FT_Stream stream,unsigned long pos,unsigned char * buffer,unsigned long count)284 FT_Error ftIOFunc( FT_Stream stream, unsigned long pos, unsigned char* buffer, unsigned long count)
285 {
286 	FT_Error  error = FT_Err_Ok;
287 	FT_ULong  read_bytes;
288 
289 	if ( pos >= stream->size )
290 	{
291 		qDebug( "ftIOFunc: invalid i/o; pos = 0x%lx, size = 0x%lx\n", pos, stream->size );
292 
293 		return FT_Err_Invalid_Stream_Operation;
294 	}
295 
296 	if ( stream->read )
297 		read_bytes = stream->read( stream, pos, buffer, count );
298 	else
299 	{
300 		read_bytes = stream->size - pos;
301 		if ( read_bytes > count )
302 			read_bytes = count;
303 
304 		memcpy( buffer, stream->base + pos, read_bytes );
305 	}
306 
307 	stream->pos = pos + read_bytes;
308 
309 	if ( read_bytes < count )
310 	{
311 		qDebug( "ftIOFunc: invalid read; expected %lu bytes, got %lu\n", count, read_bytes );
312 
313 		error = FT_Err_Invalid_Stream_Operation;
314 	}
315 
316 	return error;
317 }
318 
adobeGlyphName(FT_ULong charcode)319 QString FtFace::adobeGlyphName(FT_ULong charcode)
320 {
321 	return ::adobeGlyphName(charcode);
322 }
323 
324 
hasMicrosoftUnicodeCmap(FT_Face face)325 bool FtFace::hasMicrosoftUnicodeCmap(FT_Face face)
326 {
327 	return (face->charmap && face->charmap->encoding == FT_ENCODING_UNICODE && face->charmap->platform_id == TT_PLATFORM_MICROSOFT);
328 }
329 
330 
glyphNames(ScFace::FaceEncoding & glyphList) const331 bool FtFace::glyphNames(ScFace::FaceEncoding& glyphList) const
332 {
333 	char buf[50];
334 	FT_ULong  charcode;
335 	FT_UInt gindex = 0;
336 
337 	FT_Face face = ftFace();
338 	if (!face)
339 		return false;
340 
341 	const bool hasPSNames = FT_HAS_GLYPH_NAMES(face);
342 
343 //	qDebug() << "reading metrics for" << face->family_name << face->style_name;
344 	charcode = FT_Get_First_Char(face, &gindex);
345 	while (gindex != 0)
346 	{
347 		bool notfound = true;
348 		if (hasPSNames)
349 			notfound = FT_Get_Glyph_Name(face, gindex, &buf, 50);
350 
351 		// just in case FT gives empty string or ".notdef"
352 		// no valid glyphname except ".notdef" starts with '.'
353 //		qDebug() << "\t" << gindex << " '" << charcode << "' --> '" << (notfound? "notfound" : buf) << "'";
354 		ScFace::GlyphEncoding glEncoding;
355 		glEncoding.charcode = charcode;
356 		if (notfound || buf[0] == '\0' || buf[0] == '.')
357 			glEncoding.glyphName = adobeGlyphName(charcode);
358 		else
359 			glEncoding.glyphName = QString(reinterpret_cast<char*>(buf));
360 		glEncoding.toUnicode = QString::asprintf("%04lX", charcode);
361 		glyphList.insert(gindex, glEncoding);
362 
363 		charcode = FT_Get_Next_Char(face, charcode, &gindex);
364 	}
365 
366 	if (!hasPSNames)
367 		return true;
368 
369 	// Let's see if we can find some more...
370 	int maxSlot1 = face->num_glyphs;
371 	for (int gindex = 1; gindex < maxSlot1; ++gindex)
372 	{
373 		if (glyphList.contains(gindex))
374 			continue;
375 		if (FT_Get_Glyph_Name(face, gindex, &buf, 50))
376 			continue;
377 		QString glyphName(reinterpret_cast<char*>(buf));
378 
379 		charcode = 0;
380 		for (auto gli = glyphList.cbegin(); gli != glyphList.cend(); ++gli)
381 		{
382 			const ScFace::GlyphEncoding& glEncoding = gli.value();
383 			if (glyphName == glEncoding.glyphName)
384 			{
385 				charcode = glEncoding.charcode;
386 				break;
387 			}
388 		}
389 //		qDebug() << "\tmore: " << gindex << " '" << charcode << "' --> '" << buf << "'";
390 		ScFace::GlyphEncoding glEncoding;
391 		glEncoding.charcode  = static_cast<ScFace::ucs4_type>(charcode);
392 		glEncoding.glyphName = glyphName;
393 		glEncoding.toUnicode = QString::asprintf("%04lX", charcode);
394 		if ((charcode == 0) && glyphName.startsWith("uni"))
395 		{
396 			QString uniHexStr = uniGlyphNameToUnicode(glyphName);
397 			if (uniHexStr.length() > 0)
398 				glEncoding.toUnicode = uniHexStr;
399 		}
400 		glyphList.insert(gindex, glEncoding);
401 	}
402 
403 	return true;
404 }
405 
uniGlyphNameToUnicode(const QString & glyphName) const406 QString FtFace::uniGlyphNameToUnicode(const QString& glyphName) const
407 {
408 	if (!glyphName.startsWith("uni"))
409 		return QString();
410 	if (glyphName.length() < 7)
411 		return QString();
412 
413 	QString uniStr = glyphName.mid(3);
414 	int firstDot = uniStr.indexOf('.');
415 	if (firstDot >= 0)
416 		uniStr = uniStr.left(firstDot);
417 
418 	bool isHexString = true;
419 	if ((uniStr.length() <= 0) || (uniStr.length() % 4 != 0))
420 		return QString();
421 
422 	int len = uniStr.length();
423 	for (int i = 0; i < len; ++i)
424 	{
425 		int uni = uniStr[i].unicode();
426 		isHexString &= (uni >= '0' && uni <= '9') ||
427 			           (uni >= 'a' && uni <= 'f') ||
428 			           (uni >= 'A' && uni <= 'F');
429 		if (!isHexString)
430 			break;
431 	}
432 
433 	if (!isHexString)
434 		return QString();
435 	return uniStr.toUpper();
436 }
437 
rawData(QByteArray & bb) const438 void FtFace::rawData(QByteArray & bb) const
439 {
440 	FT_Stream fts = ftFace()->stream;
441 	bb.resize(fts->size);
442 	bool error = ftIOFunc(fts, 0L, reinterpret_cast<FT_Byte *>(bb.data()), fts->size);
443 	if (error)
444 	{
445 		sDebug(QObject::tr("Font %1 is broken (read stream), no embedding").arg(fontFile));
446 		bb.resize(0);
447 		status = qMax(status, ScFace::BROKENGLYPHS);
448 	}
449 }
450 
451 
452