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 	Scribus font handling code - copyright (C) 2001 by
9 	Alastair M. Robinson
10 
11 	Based on code by Christian Tpp and Franz Schmid.
12 
13 	Contributed to the Scribus project under the terms of
14 	the GNU General Public License version 2, or any later
15 	version, at your discretion.
16 */
17 
18 #include <QApplication>
19 #include <QDir>
20 #include <QDomDocument>
21 #include <QFile>
22 #include <QFileInfo>
23 #include <QFont>
24 #include <QGlobalStatic>
25 #include <QHash>
26 #include <QMap>
27 #include <QRegExp>
28 #include <QRawFont>
29 #ifdef Q_OS_WIN32
30 #include <QSet>
31 #include <QSettings>
32 #include <QStandardPaths>
33 #endif
34 #include <QString>
35 #include <QTextCodec>
36 
37 #include <cstdlib>
38 #include <vector>
39 
40 #include "scfonts.h"
41 #include "fonts/ftface.h"
42 #include "fonts/scface_ps.h"
43 #include "fonts/scface_ttf.h"
44 #include "fonts/scfontmetrics.h"
45 
46 #include "prefsmanager.h"
47 #include "prefsfile.h"
48 #include "prefscontext.h"
49 #include "prefstable.h"
50 
51 #include "scribuscore.h"
52 #include "scribusdoc.h"
53 #ifdef Q_OS_LINUX
54 #include <X11/X.h>
55 #include <X11/Xlib.h>
56 #endif
57 
58 #ifdef HAVE_FONTCONFIG
59 #include <fontconfig/fontconfig.h>
60 #endif
61 
62 #include <ft2build.h>
63 #include FT_FREETYPE_H
64 #include FT_OUTLINE_H
65 #include FT_GLYPH_H
66 #include FT_SFNT_NAMES_H
67 #include FT_TRUETYPE_IDS_H
68 #include FT_TRUETYPE_TAGS_H
69 #include FT_TRUETYPE_TABLES_H
70 
71 #include <harfbuzz/hb.h>
72 #include <harfbuzz/hb-ot.h>
73 #include <harfbuzz/hb-ft.h>
74 
75 #include "scpaths.h"
76 #include "util_debug.h"
77 
78 
79 
80 /***************************************************************************/
81 
SCFonts()82 SCFonts::SCFonts()
83 {
84 //	insert("", ScFace::none()); // Wtf why inserting an empty entry here ????
85 	m_showFontInfo = false;
86 	m_checkedFonts.clear();
87 }
88 
~SCFonts()89 SCFonts::~SCFonts()
90 {
91 }
92 
updateFontMap()93 void SCFonts::updateFontMap()
94 {
95 	fontMap.clear();
96 	SCFontsIterator it( *this );
97 	for ( ; it.hasNext(); it.next())
98 	{
99 		if (it.current().usable())
100 		{
101 			if (fontMap.contains(it.current().family()))
102 			{
103 				if (!fontMap[it.current().family()].contains(it.current().style()))
104 				{
105 					fontMap[it.current().family()].append(it.current().style());
106 				}
107 			}
108 			else
109 			{
110 				QStringList styles;
111 				styles.append(it.current().style());
112 				fontMap.insert(it.current().family(), styles);
113 			}
114 		}
115 	}
116 }
117 
118 /* Add a path to the list of fontpaths, ensuring that
119    a trailing slash is present.
120    Checks to make sure this path is not already present
121    before adding.
122 */
addPath(QString p)123 void SCFonts::addPath(QString p)
124 {
125 	if (p.right(1) != "/")
126 		p += "/";
127 	if (!m_fontPaths.contains(p))
128 		m_fontPaths.insert(m_fontPaths.count(), p);
129 }
130 
addScalableFonts(const QString & path,const QString & DocName)131 void SCFonts::addScalableFonts(const QString &path, const QString& DocName)
132 {
133 	//Make sure this is not empty or we will scan the whole drive on *nix
134 	//QString()+/ is / of course.
135 	if (path.isEmpty())
136 		return;
137 	FT_Library library = nullptr;
138 	QString pathfile, fullpath;
139 //	bool error;
140 //	error =
141 	FT_Init_FreeType( &library );
142 	QString pathname(path);
143 	if ( !pathname.endsWith("/") )
144 		pathname += "/";
145 	pathname = QDir::toNativeSeparators(pathname);
146 	QDir d(pathname, "*", QDir::Name, QDir::Dirs | QDir::Files | QDir::Readable);
147 	if ((d.exists()) && (d.count() != 0))
148 	{
149 		for (uint i = 0; i < d.count(); ++i)
150 		{
151 			// readdir may return . or .., which we don't want to recurse
152 			// over. Skip 'em.
153 			if (d[i] == "." || d[i] == "..")
154 				continue;
155 			fullpath = pathname + d[i];
156 			QFileInfo fi(fullpath);
157 			if (!fi.exists())      // Sanity check for broken Symlinks
158 				continue;
159 
160 			qApp->processEvents();
161 
162 			bool symlink = fi.isSymLink();
163 			if (symlink)
164 			{
165 				QFileInfo fi3(fi.symLinkTarget());
166 				if (fi3.isRelative())
167 					pathfile = pathname + fi.symLinkTarget();
168 				else
169 					pathfile = fi3.absoluteFilePath();
170 			}
171 			else
172 				pathfile = fullpath;
173 			QFileInfo fi2(pathfile);
174 			if (fi2.isDir())
175 			{
176 				if (symlink)
177 				{
178 					// Check if symlink points to a parent directory
179 					// in order to avoid infinite recursion
180 					QString fullpath2 = fullpath, pathfile2 = pathfile;
181 					if (ScCore->isWinGUI())
182 					{
183 						// Ensure both path use same separators on Windows
184 						fullpath2 = QDir::toNativeSeparators(fullpath2.toLower());
185 						pathfile2 = QDir::toNativeSeparators(pathfile2.toLower());
186 					}
187 					if (fullpath2.startsWith(pathfile2))
188 						continue;
189 				}
190 				if (DocName.isEmpty())
191 					addScalableFonts(pathfile);
192 				continue;
193 			}
194 			QString ext = fi.suffix().toLower();
195 			QString ext2 = fi2.suffix().toLower();
196 			if ((ext != ext2) && (ext.isEmpty()))
197 				ext = ext2;
198 			if ((ext == "ttc") || (ext == "dfont") || (ext == "pfa") || (ext == "pfb") || (ext == "ttf") || (ext == "otf"))
199 			{
200 				addScalableFont(pathfile, library, DocName);
201 			}
202 #ifdef Q_OS_MAC
203 			else if (ext.isEmpty() && DocName.isEmpty())
204 			{
205 				bool error = addScalableFont(pathfile, library, DocName);
206 				if (error)
207 					error = addScalableFont(pathfile + "/..namedfork/rsrc",library, DocName);
208 			}
209 #endif
210 		}
211 	}
212 	FT_Done_FreeType(library);
213 }
214 
215 
216 /*****
217    What to do with font files:
218    - note mod. date
219    - in FontCache?  => load faces from cache
220    - load via FT    => or broken
221    - for all faces:
222        load face
223        get fontinfo (type, names, styles, global metrics)
224        check encoding(s)
225        (calc. hash sum)
226        create cache entry
227  *****/
228 
229 
230 /**
231  * tests magic words to determine the fontformat and preliminary fonttype
232  */
getFontFormat(FT_Face face,ScFace::FontFormat & fmt,ScFace::FontType & type)233 void getFontFormat(FT_Face face, ScFace::FontFormat & fmt, ScFace::FontType & type)
234 {
235 	static const char* T42_HEAD      = "%!PS-TrueTypeFont";
236 	static const char* T1_HEAD      = "%!FontType1";
237 	static const char* T1_ADOBE_HEAD = "%!PS-AdobeFont-1";
238 	static const char* PSFONT_ADOBE2_HEAD  = "%!PS-Adobe-2.0 Font";
239 	static const char* PSFONT_ADOBE21_HEAD  = "%!PS-Adobe-2.1 Font";
240 	static const char* PSFONT_ADOBE3_HEAD  = "%!PS-Adobe-3.0 Resource-Font";
241 	static const int   FONT_NO_ERROR = 0;
242 
243 	const FT_Stream fts = face->stream;
244 	char buf[128];
245 
246 	fmt = ScFace::UNKNOWN_FORMAT;
247 	type = ScFace::UNKNOWN_TYPE;
248 	if (ftIOFunc(fts, 0L, reinterpret_cast<FT_Byte *>(buf), 128) == FONT_NO_ERROR)
249 	{
250 		if (strncmp(buf, T42_HEAD, strlen(T42_HEAD)) == 0)
251 		{
252 			fmt = ScFace::TYPE42;
253 			type = ScFace::TTF;
254 		}
255 		else if (strncmp(buf, T1_HEAD, strlen(T1_HEAD)) == 0 ||
256 			     strncmp(buf, T1_ADOBE_HEAD, strlen(T1_ADOBE_HEAD)) == 0)
257 		{
258 			fmt = ScFace::PFA;
259 			type = ScFace::TYPE1;
260 		}
261 		else if (strncmp(buf, PSFONT_ADOBE2_HEAD, strlen(PSFONT_ADOBE2_HEAD)) == 0 ||
262 			     strncmp(buf, PSFONT_ADOBE21_HEAD, strlen(PSFONT_ADOBE21_HEAD)) == 0 ||
263 			     strncmp(buf, PSFONT_ADOBE3_HEAD, strlen(PSFONT_ADOBE3_HEAD)) == 0)
264 		{
265 			// Type2(CFF), Type0(Composite/CID), Type 3, Type 14 etc would end here
266 			fmt = ScFace::PFA;
267 			type = ScFace::UNKNOWN_TYPE;
268 		}
269 		else if (buf[0] == '\200' && buf[1] == '\1')
270 		{
271 			fmt = ScFace::PFB;
272 			type = ScFace::TYPE1;
273 		}
274 		else if (buf[0] == '\0' && buf[1] == '\1'
275 				&& buf[2] == '\0' && buf[3] == '\0')
276 		{
277 			fmt = ScFace::SFNT;
278 			type = ScFace::TTF;
279 		}
280 		else if (strncmp(buf, "true", 4) == 0)
281 		{
282 			fmt = ScFace::SFNT;
283 			type = ScFace::TTF;
284 		}
285 		else if (strncmp(buf, "ttcf", 4) == 0)
286 		{
287 			fmt = ScFace::TTCF;
288 			type = ScFace::OTF;
289 		}
290 		else if (strncmp(buf, "OTTO", 4) == 0)
291 		{
292 			fmt = ScFace::SFNT;
293 			type = ScFace::OTF;
294 		}
295 		// missing: raw CFF
296 	}
297 }
298 
299 /**
300  * Checks face, which must be sfnt based, for the subtype.
301  * Possible values: TTF, CFF, OTF
302  */
getSubFontType(FT_Face face,ScFace::FontType & type)303 void getSubFontType(FT_Face face, ScFace::FontType & type)
304 {
305 	if (FT_IS_SFNT(face))
306 	{
307 		FT_ULong ret = 0;
308 		bool hasGlyph = ! FT_Load_Sfnt_Table(face, TTAG_glyf, 0, nullptr,  &ret);
309 		hasGlyph &= ret > 0;
310 		bool hasCFF = ! FT_Load_Sfnt_Table(face, TTAG_CFF, 0, nullptr,  &ret);
311 		hasCFF &= ret > 0;
312 		if (hasGlyph)
313 			type = ScFace::TTF;
314 		else if (hasCFF)
315 			type = ScFace::OTF;
316 		// TODO: CID or no
317 	}
318 }
319 
320 // sort name records so the preferred ones come first
nameComp(const FT_SfntName a,const FT_SfntName b)321 static bool nameComp(const FT_SfntName a, const FT_SfntName b)
322 {
323 	// sort preferred family first
324 	if (a.name_id != b.name_id)
325 	{
326 		if (a.name_id == TT_NAME_ID_PREFERRED_FAMILY)
327 			return true;
328 		if (b.name_id == TT_NAME_ID_PREFERRED_FAMILY)
329 			return false;
330 	}
331 
332 	// sort Unicode platforms first
333 	// preferring MS platform as it is more likely to
334 	// not have bogus entries, being the more tested one.
335 	if (a.platform_id != b.platform_id)
336 	{
337 		if (a.platform_id == TT_PLATFORM_MICROSOFT)
338 			return true;
339 		if (b.platform_id == TT_PLATFORM_MICROSOFT)
340 			return false;
341 		if (a.platform_id == TT_PLATFORM_APPLE_UNICODE)
342 			return true;
343 		if (b.platform_id == TT_PLATFORM_APPLE_UNICODE)
344 			return false;
345 	}
346 
347 	// sort Unicode encodings first
348 	if (a.encoding_id != b.encoding_id)
349 	{
350 		if (a.platform_id == TT_PLATFORM_MICROSOFT)
351 		{
352 			if (a.encoding_id == TT_MS_ID_UCS_4)
353 				return true;
354 			if (b.encoding_id == TT_MS_ID_UCS_4)
355 				return false;
356 			if (a.encoding_id == TT_MS_ID_UNICODE_CS)
357 				return true;
358 			if (b.encoding_id == TT_MS_ID_UNICODE_CS)
359 				return false;
360 		}
361 	}
362 
363 	// sort English names first
364 	if (a.language_id != b.language_id)
365 	{
366 		if (a.platform_id == TT_PLATFORM_MICROSOFT)
367 		{
368 			if (a.language_id == TT_MS_LANGID_ENGLISH_UNITED_STATES)
369 				return true;
370 			if (b.language_id == TT_MS_LANGID_ENGLISH_UNITED_STATES)
371 				return false;
372 		}
373 	}
374 
375 	// the rest is all the same for us
376 	return false;
377 }
378 
decodeNameRecord(FT_SfntName name)379 static QString decodeNameRecord(FT_SfntName name)
380 {
381 	if (!name.string || name.string_len == 0)
382 		return QString();
383 
384 	QByteArray encoding;
385 	if (name.platform_id == TT_PLATFORM_APPLE_UNICODE)
386 	{
387 		encoding = "UTF-16BE";
388 	}
389 	else if (name.platform_id == TT_PLATFORM_MICROSOFT)
390 	{
391 		switch (name.encoding_id) {
392 		case TT_MS_ID_SYMBOL_CS:
393 		case TT_MS_ID_UNICODE_CS:
394 		case TT_MS_ID_UCS_4:
395 			encoding = "UTF-16BE";
396 			break;
397 		case TT_MS_ID_SJIS:
398 			encoding = "Shift-JIS";
399 			break;
400 		case TT_MS_ID_GB2312:
401 			encoding = "GB18030";
402 			break;
403 		case TT_MS_ID_BIG_5:
404 			encoding = "Big5";
405 			break;
406 		default:
407 			break;
408 		}
409 	}
410 
411 	if (encoding.isEmpty())
412 		return QString();
413 
414 	QTextCodec* codec = QTextCodec::codecForName(encoding);
415 	if (!codec)
416 		return QString();
417 
418 	QByteArray bytes((const char*) name.string, name.string_len);
419 	if (name.encoding_id == TT_MS_ID_GB2312 && bytes.startsWith('\0'))
420 	{
421 		// 16 bytes chars appears to be used as transport type for 8 bytes values.
422 		// We have to fix this so that Qt GB18030 codec process strings correctly
423 		for (int i = 0; i < (int) name.string_len / 2; ++i)
424 			bytes[i] = bytes[2 * i + 1];
425 		bytes.resize(name.string_len / 2);
426 	}
427 
428 	QString string = codec->toUnicode(bytes);
429 	return string;
430 }
431 
getFamilyName(const FT_Face face)432 static QString getFamilyName(const FT_Face face)
433 {
434 	QString familyName(face->family_name);
435 
436 	QVector<FT_SfntName> names;
437 
438 	for (FT_UInt i = 0; i < FT_Get_Sfnt_Name_Count(face); i++)
439 	{
440 		FT_SfntName name;
441 		if (!FT_Get_Sfnt_Name(face, i, &name))
442 		{
443 			switch (name.name_id) {
444 			case TT_NAME_ID_FONT_FAMILY:
445 			case TT_NAME_ID_PREFERRED_FAMILY:
446 				if (name.platform_id != TT_PLATFORM_MACINTOSH)
447 					names.append(name);
448 				break;
449 			default:
450 				break;
451 			}
452 		}
453 	}
454 
455 	if (!names.isEmpty())
456 	{
457 		std::sort(names.begin(), names.end(), nameComp);
458 		for (const FT_SfntName& name : qAsConst(names))
459 		{
460 			QString string(decodeNameRecord(name));
461 			if (!string.isEmpty())
462 			{
463 				familyName = string;
464 				break;
465 			}
466 		}
467 	}
468 
469 	return familyName;
470 }
471 
getFontFeaturesFromTable(hb_tag_t table,hb_face_t * hb_face)472 static QStringList getFontFeaturesFromTable(hb_tag_t table, hb_face_t *hb_face)
473 {
474 	QStringList fontFeaturesList;
475 	//get all supported Opentype Features
476 	unsigned count = hb_ot_layout_table_get_feature_tags(hb_face, table, 0, nullptr, nullptr);
477 	std::vector<hb_tag_t> features(count);
478 	hb_ot_layout_table_get_feature_tags(hb_face, table, 0,  &count, features.data());
479 	for (unsigned i = 0; i < count; ++i)
480 	{
481 		char feature[4] = {0};
482 		hb_tag_to_string(features[i], feature);
483 		std::string strFeature(feature, 4);
484 		fontFeaturesList.append(QString::fromStdString(strFeature));
485 	}
486 	fontFeaturesList.removeDuplicates();
487 	fontFeaturesList.sort();
488 	return fontFeaturesList;
489 }
490 
getFontFeatures(const FT_Face face)491 static QStringList getFontFeatures(const FT_Face face)
492 {
493 	// Create hb-ft font and get hb_face from it
494 	hb_font_t *hb_font;
495 	hb_font = hb_ft_font_create(face, nullptr);
496 	hb_face_t *hb_face = hb_font_get_face(hb_font);
497 	//find Opentype Font Features in GSUB table
498 	QStringList featuresGSUB = getFontFeaturesFromTable(HB_OT_TAG_GSUB, hb_face);
499 	// find Opentype Font Features in GPOS table
500 	QStringList featuresGPOS = getFontFeaturesFromTable(HB_OT_TAG_GPOS, hb_face);
501 
502 	hb_font_destroy(hb_font);
503 
504 	QStringList fontFeatures = featuresGSUB + featuresGPOS;
505 	fontFeatures.removeDuplicates();
506 	fontFeatures.sort();
507 	return fontFeatures;
508 }
509 
loadScalableFont(const QString & filename)510 ScFace SCFonts::loadScalableFont(const QString &filename)
511 {
512 	ScFace t;
513 	if (filename.isEmpty())
514 		return t;
515 	FT_Library library = nullptr;
516 	bool error;
517 	error = FT_Init_FreeType( &library );
518 	QFileInfo fi(filename);
519 	if (!fi.exists())
520 		return t;
521 	bool Subset = false;
522 	char *buf[50];
523 	QString glyName = "";
524 	ScFace::FontFormat format;
525 	ScFace::FontType   type;
526 	FT_Face         face = nullptr;
527 	error = FT_New_Face(library, QFile::encodeName(filename), 0, &face);
528 	if (error || (face == nullptr))
529 	{
530 		if (face != nullptr)
531 			FT_Done_Face(face);
532 		FT_Done_FreeType(library);
533 		return t;
534 	}
535 	getFontFormat(face, format, type);
536 	if (format == ScFace::UNKNOWN_FORMAT)
537 	{
538 		FT_Done_Face(face);
539 		FT_Done_FreeType(library);
540 		return t;
541 	}
542 	bool HasNames = FT_HAS_GLYPH_NAMES(face);
543 
544 	FT_UInt gindex = 0;
545 	FT_ULong charcode = FT_Get_First_Char(face, &gindex);
546 	while (gindex != 0)
547 	{
548 		error = FT_Load_Glyph(face, gindex, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP);
549 		if (error)
550 		{
551 			FT_Done_Face(face);
552 			FT_Done_FreeType(library);
553 			return t;
554 		}
555 		FT_Get_Glyph_Name(face, gindex, buf, 50);
556 		QString newName = QString(reinterpret_cast<char*>(buf));
557 		if (newName == glyName)
558 		{
559 			HasNames = false;
560 			Subset = true;
561 		}
562 		glyName = newName;
563 		charcode = FT_Get_Next_Char(face, charcode, &gindex);
564 	}
565 
566 	// Warning: code below is also present in addScalableFont, so if you do
567 	// any modification here, think also about modifying code in addScalableFont
568 	int faceIndex = 0;
569 	QString fam(getFamilyName(face));
570 	QStringList features(getFontFeatures(face));
571 	QString sty(face->style_name);
572 	if ((sty == "Regular" && face->style_flags != 0) || sty.isEmpty())
573 	{
574 		switch (face->style_flags)
575 		{
576 			case 0:
577 				sty = "Regular";
578 				break;
579 			case 1:
580 				sty = "Italic";
581 				break;
582 			case 2:
583 				sty = "Bold";
584 				break;
585 			case 3:
586 				sty = "Bold Italic";
587 				break;
588 			default:
589 				break;
590 		}
591 	}
592 	QString fullName(fam);
593 	if (!sty.isEmpty())
594 		fullName += " " + sty;
595 	const char* psName = FT_Get_Postscript_Name(face);
596 	QString qpsName;
597 	if (psName)
598 		qpsName = QString(psName);
599 	else
600 		qpsName = fullName;
601 	if (t.isNone())
602 	{
603 		switch (format)
604 		{
605 			case ScFace::PFA:
606 				t = ScFace(new ScFace_PFA(fam, sty, "", fullName, qpsName, filename, faceIndex, features));
607 				t.subset(Subset);
608 				break;
609 			case ScFace::PFB:
610 				t = ScFace(new ScFace_PFB(fam, sty, "", fullName, qpsName, filename, faceIndex, features));
611 				t.subset(Subset);
612 				break;
613 			case ScFace::SFNT:
614 			case ScFace::TTCF:
615 			case ScFace::TYPE42:
616 				t = ScFace(new ScFace_ttf(fam, sty, "", fullName, qpsName, filename, faceIndex, features));
617 				getSubFontType(face, t.m_m->typeCode);
618 				t.subset(Subset);
619 				break;
620 			default:
621 				/* catching any types not handled above to silence compiler */
622 				break;
623 		}
624 		t.m_m->hasGlyphNames = HasNames;
625 		t.embedPs(true);
626 		t.usable(true);
627 		t.m_m->status = ScFace::UNKNOWN;
628 		if (face->num_glyphs > 2048)
629 			t.subset(true);
630 	}
631 	setBestEncoding(face);
632 	FT_Done_Face(face);
633 	FT_Done_FreeType(library);
634 	return t;
635 }
636 
getFtError(int code)637 static QString getFtError(int code)
638 {
639 #undef FTERRORS_H_
640 #define FT_ERRORDEF(e, v, s) ftErrors[e] = s;
641 	static QHash<int, QString> ftErrors;
642 #include FT_ERRORS_H
643 #undef FT_ERRORDEF
644 
645 	if (ftErrors.contains(code))
646 		return ftErrors.value(code);
647 	return QString();
648 }
649 
650 // Load a single font into the library from the passed filename. Returns true on error.
addScalableFont(const QString & filename,FT_Library & library,const QString & DocName)651 bool SCFonts::addScalableFont(const QString& filename, FT_Library &library, const QString& DocName)
652 {
653 	static bool firstRun;
654 	bool Subset = false;
655 	char *buf[50];
656 	QString glyName = "";
657 	ScFace::FontFormat format;
658 	ScFace::FontType   type;
659 	FT_Face         face = nullptr;
660 	struct testCache foCache;
661 	QFileInfo fic(filename);
662 	QDateTime lastMod = fic.lastModified();
663 	QTime lastModTime = lastMod.time();
664 	if (lastModTime.msec() != 0)  //Sometime file time is stored with precision up to msecs
665 	{
666 		lastModTime.setHMS(lastModTime.hour(), lastModTime.minute(), lastModTime.second());
667 		lastMod.setTime(lastModTime);
668 	}
669 	foCache.isOK = false;
670 	foCache.isChecked = true;
671 	foCache.lastMod = lastMod;
672 	if (m_checkedFonts.count() == 0)
673 	{
674 		firstRun = true;
675 		ScCore->setSplashStatus( QObject::tr("Creating Font Cache") );
676 	}
677 	FT_Error error = FT_New_Face( library, QFile::encodeName(filename), 0, &face );
678 	if (error || (face == nullptr))
679 	{
680 		if (face != nullptr)
681 			FT_Done_Face(face);
682 		m_checkedFonts.insert(filename, foCache);
683 		addRejectedFont(filename, QObject::tr("Font is broken: \"%1\"").arg(getFtError(error)));
684 		if (m_showFontInfo)
685 			sDebug(QObject::tr("Font %1 is broken, discarding it. Error message: \"%2\"").arg(filename, getFtError(error)));
686 		return true;
687 	}
688 	if (face->family_name == nullptr)
689 	{
690 		addRejectedFont(filename, QObject::tr("Failed to load font: font family unspecified"));
691 		if (m_showFontInfo)
692 			sDebug(QObject::tr("Failed to load font %1 - font family unspecified").arg(filename));
693 		FT_Done_Face(face);
694 		m_checkedFonts.insert(filename, foCache);
695 		return true;
696 	}
697 	getFontFormat(face, format, type);
698 	if (format == ScFace::UNKNOWN_FORMAT)
699 	{
700 		addRejectedFont(filename, QObject::tr("Failed to load font: font type unknown"));
701 		if (m_showFontInfo)
702 			sDebug(QObject::tr("Failed to load font %1 - font type unknown").arg(filename));
703 		FT_Done_Face(face);
704 		m_checkedFonts.insert(filename, foCache);
705 		return true;
706 	}
707 	// Some fonts such as Noto ColorEmoji are in fact bitmap fonts
708 	// and do not provide a valid value for units_per_EM
709 	if (face->units_per_EM == 0)
710 	{
711 		addRejectedFont(filename, QObject::tr("Failed to load font: font is not scalable"));
712 		if (m_showFontInfo)
713 			sDebug(QObject::tr("Failed to load font %1 - font is not scalable").arg(filename));
714 		FT_Done_Face(face);
715 		m_checkedFonts.insert(filename, foCache);
716 		return true;
717 	}
718 	bool HasNames = FT_HAS_GLYPH_NAMES(face);
719 
720 	if (!m_checkedFonts.contains(filename))
721 	{
722 		if (!firstRun)
723 			ScCore->setSplashStatus( QObject::tr("New Font found, checking...") );
724 		FT_UInt gindex = 0;
725 		FT_ULong charcode = FT_Get_First_Char( face, &gindex );
726 		while ( gindex != 0 )
727 		{
728 			error = FT_Load_Glyph(face, gindex, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP);
729 			if (error)
730 			{
731 				auto errorMessage = QObject::tr("Font %1 has broken glyph %2 (charcode U+%3). Error message: \"%4\"")
732 							   .arg(filename)
733 							   .arg(gindex)
734 							   .arg(charcode, 4, 16, QChar('0'))
735 							   .arg(getFtError(error));
736 				addRejectedFont(filename, errorMessage);
737 				if (m_showFontInfo)
738 					sDebug(errorMessage);
739 				FT_Done_Face(face);
740 				m_checkedFonts.insert(filename, foCache);
741 				return true;
742 			}
743 			FT_Get_Glyph_Name(face, gindex, buf, 50);
744 			QString newName = QString(reinterpret_cast<char*>(buf));
745 			if (newName == glyName)
746 			{
747 				HasNames = false;
748 				Subset = true;
749 			}
750 			glyName = newName;
751 			charcode = FT_Get_Next_Char( face, charcode, &gindex );
752 		}
753 		foCache.isOK = true;
754 		m_checkedFonts.insert(filename, foCache);
755 	}
756 	else
757 	{
758 		auto& checkedFont = m_checkedFonts[filename];
759 		if (!checkedFont.isOK)
760 		{
761 			checkedFont.isChecked = true;
762 			FT_Done_Face(face);
763 			return true;
764 		}
765 		if (checkedFont.lastMod != foCache.lastMod)
766 		{
767 			ScCore->setSplashStatus( QObject::tr("Modified Font found, checking...") );
768 			FT_UInt gindex = 0;
769 			FT_ULong charcode = FT_Get_First_Char( face, &gindex );
770 			while ( gindex != 0 )
771 			{
772 				error = FT_Load_Glyph(face, gindex, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP);
773 				if (error)
774 				{
775 					auto errorMessage = QObject::tr("Font %1 has broken glyph %2 (charcode U+%3). Error message: \"%4\"")
776 								   .arg(filename)
777 								   .arg(gindex)
778 								   .arg(charcode, 4, 16, QChar('0'))
779 								   .arg(getFtError(error));
780 					addRejectedFont(filename, errorMessage);
781 					if (m_showFontInfo)
782 						sDebug(errorMessage);
783 					FT_Done_Face(face);
784 					m_checkedFonts.insert(filename, foCache);
785 					return true;
786 				}
787 				FT_Get_Glyph_Name(face, gindex, buf, 50);
788 				QString newName = QString(reinterpret_cast<char*>(buf));
789 				if (newName == glyName)
790 				{
791 					HasNames = false;
792 					Subset = true;
793 				}
794 				glyName = newName;
795 				charcode = FT_Get_Next_Char( face, charcode, &gindex );
796 			}
797 			foCache.isOK = true;
798 			checkedFont = foCache;
799 		}
800 		else
801 		{
802 			checkedFont.isOK = true;
803 			checkedFont.isChecked = true;
804 		}
805 	}
806 
807 	// Warning: code below is also present in loadScalableFont, so if you do
808 	// any modification here, think also about modifying code in loadScalableFont
809 	int faceIndex = 0;
810 	while (!error)
811 	{
812 		QString fam(getFamilyName(face));
813 		QStringList features(getFontFeatures(face));
814 		QString sty(face->style_name);
815 		if ((sty == "Regular" && face->style_flags != 0) || sty.isEmpty())
816 		{
817 			switch (face->style_flags)
818 			{
819 				case 0:
820 					sty = "Regular";
821 					break;
822 				case 1:
823 					sty = "Italic";
824 					break;
825 				case 2:
826 					sty = "Bold";
827 					break;
828 				case 3:
829 					sty = "Bold Italic";
830 					break;
831 				default:
832 					break;
833 			}
834 		}
835 		QString fullName(fam);
836 		if (!sty.isEmpty())
837 			fullName += " " + sty;
838 		const char* psName = FT_Get_Postscript_Name(face);
839 		QString qpsName;
840 		if (psName)
841 			qpsName = QString(psName);
842 		else
843 			qpsName = fullName;
844 		ScFace t;
845 		if (contains(fullName))
846 		{
847 			t = (*this)[fullName];
848 			if (t.psName() != qpsName)
849 			{
850 				QString alt = " (" + qpsName + ")";
851 				fullName += alt;
852 				sty += alt;
853 			}
854 		}
855 		t = (*this)[fullName];
856 		if (t.isNone())
857 		{
858 			switch (format)
859 			{
860 				case ScFace::PFA:
861 					t = ScFace(new ScFace_PFA(fam, sty, "", fullName, qpsName, filename, faceIndex, features));
862 					t.subset(Subset);
863 					break;
864 				case ScFace::PFB:
865 					t = ScFace(new ScFace_PFB(fam, sty, "", fullName, qpsName, filename, faceIndex, features));
866 					t.subset(Subset);
867 					break;
868 				case ScFace::SFNT:
869 					t = ScFace(new ScFace_ttf(fam, sty, "", fullName, qpsName, filename, faceIndex, features));
870 					getSubFontType(face, t.m_m->typeCode);
871 					t.subset(Subset);
872 					break;
873 				case ScFace::TTCF:
874 					t = ScFace(new ScFace_ttf(fam, sty, "", fullName, qpsName, filename, faceIndex, features));
875 					t.m_m->formatCode = ScFace::TTCF;
876 					t.m_m->typeCode = ScFace::TTF;
877 					getSubFontType(face, t.m_m->typeCode);
878 					t.subset(Subset);
879 					break;
880 				case ScFace::TYPE42:
881 					t = ScFace(new ScFace_ttf(fam, sty, "", fullName, qpsName, filename, faceIndex, features));
882 					getSubFontType(face, t.m_m->typeCode);
883 					t.subset(Subset);
884 					break;
885 				default:
886 				/* catching any types not handled above to silence compiler */
887 					break;
888 			}
889 			insert(fullName, t);
890 			t.m_m->hasGlyphNames = HasNames;
891 			t.embedPs(true);
892 			t.usable(true);
893 			t.m_m->status = ScFace::UNKNOWN;
894 			if (face->num_glyphs > 2048)
895 				t.subset(true);
896 			t.m_m->forDocument = DocName;
897 			//setBestEncoding(face); //AV
898 			if (m_showFontInfo)
899 				sDebug(QObject::tr("Font %1 loaded from %2(%3)").arg(t.psName(), filename).arg(faceIndex + 1));
900 
901 /*
902 //debug
903 			QByteArray bb;
904 			t->rawData(bb);
905 			QFile dump(QString("/tmp/fonts/%1-%2").arg(fullName, psName));
906 			dump.open(IO_WriteOnly);
907 			QDataStream os(&dump);
908 			os.writeRawBytes(bb.data(), bb.size());
909 			dump.close();
910 */
911 		}
912 		else
913 		{
914 			if (m_showFontInfo)
915 				sDebug(QObject::tr("Font %1(%2) is duplicate of %3").arg(filename).arg(faceIndex + 1).arg(t.fontPath()));
916 			// this is needed since eg. AppleSymbols will happily return a face for *any* face_index
917 			if (faceIndex > 0) {
918 				break;
919 			}
920 		}
921 		if ((++faceIndex) >= face->num_faces)
922 			break;
923 		FT_Done_Face(face);
924 		face = nullptr;
925 		error = FT_New_Face(library, QFile::encodeName(filename), faceIndex, &face);
926 	} //while
927 
928 	if (face != nullptr)
929 		FT_Done_Face(face);
930 	return error && faceIndex == 0;
931 }
932 
removeFont(const QString & name)933 void SCFonts::removeFont(const QString& name)
934 {
935 	remove(name);
936 	updateFontMap();
937 }
938 
findFont(const QString & fontname,ScribusDoc * doc)939 const ScFace& SCFonts::findFont(const QString& fontname, ScribusDoc *doc)
940 {
941 	if (fontname.isEmpty())
942 		return ScFace::none();
943 
944 	PrefsManager& prefsManager = PrefsManager::instance();
945 
946 	if (!contains(fontname) || !(*this)[fontname].usable())
947 	{
948 		QString replFont;
949 		if ((!prefsManager.appPrefs.fontPrefs.GFontSub.contains(fontname)) || (!(*this)[prefsManager.appPrefs.fontPrefs.GFontSub[fontname]].usable()))
950 		{
951 			replFont = doc ? doc->itemToolPrefs().textFont : prefsManager.appPrefs.itemToolPrefs.textFont;
952 		}
953 		else
954 			replFont = prefsManager.appPrefs.fontPrefs.GFontSub[fontname];
955 		ScFace repl = (*this)[replFont].mkReplacementFor(fontname, doc ? doc->documentFileName() : QString());
956 		insert(fontname, repl);
957 	}
958 	else if ( doc && !doc->UsedFonts.contains(fontname) )
959 	{
960 		doc->AddFont(fontname, qRound(doc->itemToolPrefs().textSize / 10.0));
961 	}
962 	return (*this)[fontname];
963 }
964 
965 
findFont(const QString & fontFamily,const QString & fontStyle,ScribusDoc * doc)966 const ScFace& SCFonts::findFont(const QString& fontFamily, const QString& fontStyle, ScribusDoc* doc)
967 {
968 	if (fontStyle.isEmpty())
969 		return findFont(fontFamily + " Regular", doc);
970 	return findFont(fontFamily + " " + fontStyle, doc);
971 }
972 
getSubstitutions(const QList<QString> & skip) const973 QMap<QString,QString> SCFonts::getSubstitutions(const QList<QString>& skip) const
974 {
975 	QMap<QString,QString> result;
976 	QMap<QString,ScFace>::ConstIterator it;
977 	for (it = begin(); it != end(); ++it)
978 	{
979 		if (it.value().isReplacement() && !skip.contains(it.key()))
980 			result[it.key()] = it.value().replacementName();
981 	}
982 	return result;
983 }
984 
985 
setSubstitutions(const QMap<QString,QString> & substitutes,ScribusDoc * doc)986 void SCFonts::setSubstitutions(const QMap<QString,QString>& substitutes, ScribusDoc* doc)
987 {
988 	QMap<QString,QString>::ConstIterator it;
989 	for (it = substitutes.begin(); it != substitutes.end(); ++it)
990 	{
991 		if (it.key() == it.value())
992 			continue;
993 		ScFace& font(const_cast<ScFace&>(findFont(it.key(), doc)));
994 		if (font.isReplacement())
995 		{
996 			font.chReplacementTo(const_cast<ScFace&>(findFont(it.value(), doc)), doc->documentFileName());
997 		}
998 	}
999 }
1000 
1001 
1002 #ifdef HAVE_FONTCONFIG
1003 // Use Fontconfig to locate and load fonts.
addFontconfigFonts()1004 void SCFonts::addFontconfigFonts()
1005 {
1006 	// All-in-one library setup. Perhaps this should be in
1007 	// the SCFonts constructor.
1008 	FcConfig* config = FcInitLoadConfigAndFonts();
1009 	// The pattern controls what fonts to match. In this case we want to
1010 	// match all scalable fonts, but ignore bitmap fonts.
1011 	FcPattern* pat = FcPatternBuild(nullptr,
1012 									FC_SCALABLE, FcTypeBool, true,
1013 									nullptr);
1014 	// The ObjectSet tells FontConfig what information about each match to return.
1015 	// We currently just need FC_FILE, but other info like font family and style
1016 	// is available - see "man fontconfig".
1017 	FcObjectSet* os = FcObjectSetBuild (FC_FILE, (char *) nullptr);
1018 	if (!os)
1019 	{
1020 		qFatal("SCFonts::addFontconfigFonts() FcObjectSet* os failed to build object set");
1021 		return;
1022 	}
1023 	// Now ask fontconfig to retrieve info as specified in 'os' about fonts
1024 	// matching pattern 'pat'.
1025 	FcFontSet* fs = FcFontList(config, pat, os);
1026 	if (!fs)
1027 	{
1028 		qFatal("SCFonts::addFontconfigFonts() FcFontSet* fs failed to create font list");
1029 		return;
1030 	}
1031 	FcConfigDestroy(config);
1032 	FcObjectSetDestroy(os);
1033 	FcPatternDestroy(pat);
1034 	// Create the Freetype library
1035 //	bool error;
1036 	FT_Library library = nullptr;
1037 	FT_Init_FreeType( &library );
1038 	// Now iterate over the font files and load them
1039 	for (int i = 0; i < fs->nfont; i++)
1040 	{
1041 		FcChar8 *file = nullptr;
1042 		if (FcPatternGetString (fs->fonts[i], FC_FILE, 0, &file) == FcResultMatch)
1043 		{
1044 			if (m_showFontInfo)
1045 				sDebug(QObject::tr("Loading font %1 (found using fontconfig)").arg(QString((char*)file)));
1046 			addScalableFont(QString((char*)file), library, "");
1047 		}
1048 		else
1049 			if (m_showFontInfo)
1050 			{
1051 				auto errorMessage = QObject::tr("Failed to load a font - freetype2 couldn't find the font file");
1052 				addRejectedFont(QString((char*)file), errorMessage);
1053 				sDebug(errorMessage);
1054 			}
1055 	}
1056 	FT_Done_FreeType(library);
1057 	FcFontSetDestroy(fs);
1058 }
1059 
1060 #elif defined(Q_OS_WIN32)
1061 
addRegistryFonts()1062 void SCFonts::addRegistryFonts()
1063 {
1064 	const QStringList keys = { QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"),
1065 		                       QStringLiteral("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"),
1066 #if defined(Q_OS_WIN64)
1067 	                           QStringLiteral("HKEY_CURRENT_USER\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"),
1068 		                       QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"),
1069 #endif
1070 	                         };
1071 	QSet<QString> foundFonts;
1072 
1073 	FT_Library library = nullptr;
1074 	FT_Init_FreeType( &library );
1075 
1076 	for (const auto key : keys)
1077 	{
1078 		const QSettings fontRegistry(key, QSettings::NativeFormat);
1079 		const QStringList allKeys = fontRegistry.allKeys();
1080 
1081 		int size = allKeys.size();
1082 		for (int i = 0; i < size; ++i)
1083 		{
1084 			const QString &fontKey = allKeys.at(i);
1085 			const QString fileName = fontRegistry.value(fontKey).toString();
1086 
1087 			QFileInfo fontInfo(fileName);
1088 			if (fontInfo.isRelative())
1089 				continue; // Font located in system font directory, nothing to do
1090 			if (!fontInfo.exists())
1091 				continue; // Sanity check for broken Symlinks
1092 			QString fontPath = fontInfo.absoluteFilePath();
1093 			QString pathName = fontInfo.absolutePath();
1094 
1095 			qApp->processEvents();
1096 
1097 			bool isSymlink = fontInfo.isSymLink();
1098 			if (isSymlink)
1099 			{
1100 				QFileInfo symlinkInfo(fontInfo.symLinkTarget());
1101 				if (symlinkInfo.isRelative())
1102 					fontPath = pathName + fontInfo.symLinkTarget();
1103 				else
1104 					fontPath = symlinkInfo.absoluteFilePath();
1105 			}
1106 
1107 			fontPath = QDir::toNativeSeparators(fontPath);
1108 			if (foundFonts.contains(fontPath))
1109 				continue;
1110 
1111 			QFileInfo fontInfo2(fontPath);
1112 			QString ext = fontInfo.suffix().toLower();
1113 			QString ext2 = fontInfo2.suffix().toLower();
1114 			if ((ext != ext2) && (ext.isEmpty()))
1115 				ext = ext2;
1116 			if ((ext == "ttc") || (ext == "dfont") || (ext == "pfa") || (ext == "pfb") || (ext == "ttf") || (ext == "otf"))
1117 			{
1118 				foundFonts.insert(fontPath);
1119 				addScalableFont(fontPath, library, QString());
1120 			}
1121 		}
1122 	}
1123 
1124 	FT_Done_FreeType(library);
1125 }
1126 
addType1RegistryFonts()1127 void SCFonts::addType1RegistryFonts()
1128 {
1129 	const QStringList keys = { QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Type 1 Installer\\Type 1 Fonts"),
1130 		                       QStringLiteral("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Type 1 Installer\\Type 1 Fonts"),
1131 #if defined(Q_OS_WIN64)
1132 	                           QStringLiteral("HKEY_CURRENT_USER\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Type 1 Installer\\Type 1 Fonts"),
1133 		                       QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Type 1 Installer\\Type 1 Fonts"),
1134 #endif
1135 	                         };
1136 	QSet<QString> foundFonts;
1137 
1138 	FT_Library library = nullptr;
1139 	FT_Init_FreeType( &library );
1140 
1141 	for (const auto key : keys)
1142 	{
1143 		const QSettings fontRegistry(key, QSettings::NativeFormat);
1144 		const QStringList allKeys = fontRegistry.allKeys();
1145 
1146 		int size = allKeys.size();
1147 		for (int i = 0; i < size; ++i)
1148 		{
1149 			const QString &fontKey = allKeys.at(i);
1150 			const QStringList fileNames = fontRegistry.value(fontKey).toStringList();
1151 			if (fileNames.size() <= 0)
1152 				continue;
1153 			if (fileNames.at(0) != "T")
1154 				continue;
1155 
1156 			for (int j = 1; j < fileNames.count(); ++j)
1157 			{
1158 				QString fileName = fileNames.at(j);
1159 
1160 				QFileInfo fontInfo(fileName);
1161 				if (fontInfo.isRelative())
1162 					continue; // Font located in system font directory, nothing to do
1163 				if (!fontInfo.exists())
1164 					continue; // Sanity check for broken Symlinks
1165 
1166 				QString ext = fontInfo.suffix().toLower();
1167 				if ((ext != "pfa") && (ext != "pfb"))
1168 					continue;
1169 
1170 				QString fontPath = fontInfo.absoluteFilePath();
1171 				QString pathName = fontInfo.absolutePath();
1172 
1173 				qApp->processEvents();
1174 
1175 				bool isSymlink = fontInfo.isSymLink();
1176 				if (isSymlink)
1177 				{
1178 					QFileInfo symlinkInfo(fontInfo.symLinkTarget());
1179 					if (symlinkInfo.isRelative())
1180 						fontPath = pathName + fontInfo.symLinkTarget();
1181 					else
1182 						fontPath = symlinkInfo.absoluteFilePath();
1183 				}
1184 
1185 				fontPath = QDir::toNativeSeparators(fontPath);
1186 				if (foundFonts.contains(fontPath))
1187 					continue;
1188 
1189 				QFileInfo fontInfo2(fontPath);
1190 				QString ext2 = fontInfo2.suffix().toLower();
1191 				if ((ext != ext2) && (ext.isEmpty()))
1192 					ext = ext2;
1193 				if ((ext == "pfa") || (ext == "pfb"))
1194 				{
1195 					foundFonts.insert(fontPath);
1196 					addScalableFont(fontPath, library, QString());
1197 					break;
1198 				}
1199 			}
1200 		}
1201 	}
1202 
1203 	FT_Done_FreeType(library);
1204 }
1205 
1206 #endif
1207 
1208 /* Add an extra path to our font search path,
1209  * allowing a user to have extra fonts installed
1210  * only for this user. Can also be used also as an emergency
1211  * fallback if no suitable fonts are found elsewere */
addUserPath(const QString & pf)1212 void SCFonts::addUserPath(const QString& pf)
1213 {
1214 	PrefsContext *pc = PrefsManager::instance().prefsFile->getContext("Fonts");
1215 	PrefsTable *extraDirs = pc->getTable("ExtraFontDirs");
1216 	for (int i = 0; i < extraDirs->getRowCount(); ++i)
1217 		addPath(extraDirs->get(i, 0));
1218 }
1219 
readFontCache(const QString & pf)1220 void SCFonts::readFontCache(const QString& pf)
1221 {
1222 	QFile fr(pf + "/cfonts.xml");
1223 	QFileInfo fir(fr);
1224 	if (fir.exists())
1225 		fr.remove();
1226 	m_checkedFonts.clear();
1227 	struct testCache foCache;
1228 	QDomDocument docu("fontcacherc");
1229 	QFile f(pf + "/checkfonts150.xml");
1230 	if (!f.open(QIODevice::ReadOnly))
1231 		return;
1232 	ScCore->setSplashStatus( QObject::tr("Reading Font Cache") );
1233 	QTextStream ts(&f);
1234 	ts.setCodec("UTF-8");
1235 	QString errorMsg;
1236 	int errorLine = 0, errorColumn = 0;
1237 	if ( !docu.setContent(ts.readAll(), &errorMsg, &errorLine, &errorColumn) )
1238 	{
1239 		f.close();
1240 		return;
1241 	}
1242 	f.close();
1243 	QDomElement elem = docu.documentElement();
1244 	if (elem.tagName() != "CachedFonts")
1245 		return;
1246 	QDomNode DOC = elem.firstChild();
1247 	while (!DOC.isNull())
1248 	{
1249 		QDomElement dc = DOC.toElement();
1250 		if (dc.tagName()=="Font")
1251 		{
1252 			foCache.isChecked = false;
1253 			foCache.isOK = static_cast<bool>(dc.attribute("Status", "1").toInt());
1254 			foCache.lastMod = QDateTime::fromString(dc.attribute("Modified"), Qt::ISODate);
1255 			m_checkedFonts.insert(dc.attribute("File"), foCache);
1256 		}
1257 		DOC = DOC.nextSibling();
1258 	}
1259 }
1260 
writeFontCache()1261 void SCFonts::writeFontCache()
1262 {
1263 	QString prefsLocation = PrefsManager::instance().preferencesLocation();
1264 	writeFontCache(prefsLocation);
1265 }
1266 
writeFontCache(const QString & pf)1267 void SCFonts::writeFontCache(const QString& pf)
1268 {
1269 	QDomDocument docu("fontcacherc");
1270 	QString st="<CachedFonts></CachedFonts>";
1271 
1272 	docu.setContent(st);
1273 	QDomElement elem = docu.documentElement();
1274 	for (auto it = m_checkedFonts.cbegin(); it != m_checkedFonts.cend(); ++it)
1275 	{
1276 		const auto& checkedFont = it.value();
1277 
1278 		bool saveItem = checkedFont.isChecked;
1279 		if (!checkedFont.isChecked) // Font might be located in another local Scribus font folder
1280 			saveItem = QFile::exists(it.key());
1281 		if (!saveItem)
1282 			continue;
1283 
1284 		QDomElement fosu = docu.createElement("Font");
1285 		fosu.setAttribute("File", it.key());
1286 		fosu.setAttribute("Status", static_cast<int>(checkedFont.isOK));
1287 		fosu.setAttribute("Modified", checkedFont.lastMod.toString(Qt::ISODate));
1288 		elem.appendChild(fosu);
1289 	}
1290 
1291 	ScCore->setSplashStatus( QObject::tr("Writing updated Font Cache") );
1292 
1293 	QFile file(pf + "/checkfonts150.xml");
1294 	if (!file.open(QIODevice::WriteOnly))
1295 		return;
1296 	QTextStream s(&file);
1297 	s.setCodec("UTF-8");
1298 	s << docu.toString();
1299 	file.close();
1300 }
1301 
getFonts(const QString & pf,bool showFontInfo)1302 void SCFonts::getFonts(const QString& pf, bool showFontInfo)
1303 {
1304 	m_showFontInfo=showFontInfo;
1305 	m_fontPaths.clear();
1306 	readFontCache(pf);
1307 	ScCore->setSplashStatus( QObject::tr("Searching for Fonts") );
1308 	addUserPath(pf);
1309 
1310 	// Search the system paths
1311 	QStringList ftDirs = ScPaths::systemFontDirs();
1312 	for (int i = 0; i < ftDirs.count(); i++)
1313 		addScalableFonts( ftDirs[i] );
1314 
1315 #ifdef Q_OS_WIN32
1316 	// Search fonts outside system paths using Windows Registry
1317 	addRegistryFonts();
1318 	addType1RegistryFonts();
1319 #endif
1320 
1321 	// Search Scribus font path
1322 	if (!ScPaths::instance().fontDir().isEmpty() && QDir(ScPaths::instance().fontDir()).exists())
1323 		addScalableFonts( ScPaths::instance().fontDir() );
1324 
1325 	//Add downloaded user fonts
1326 	QString userFontDir(ScPaths::instance().userFontDir(false));
1327 	if (QDir(userFontDir).exists())
1328 		addScalableFonts( userFontDir );
1329 
1330 // if fontconfig is there, it does all the work
1331 #if HAVE_FONTCONFIG
1332 	// Search fontconfig paths
1333 	QStringList::iterator fpi, fpend = m_fontPaths.end();
1334 	for (fpi = m_fontPaths.begin() ; fpi != fpend; ++fpi)
1335 		addScalableFonts(*fpi);
1336 	addFontconfigFonts();
1337 #else
1338 	// add user and X11 fonts:
1339 	QStringList::iterator fpi, fpend = m_fontPaths.end();
1340 	for (fpi = m_fontPaths.begin() ; fpi != fpend; ++fpi)
1341 		addScalableFonts(*fpi);
1342 #endif
1343 	updateFontMap();
1344 	writeFontCache(pf);
1345 }
1346 
addRejectedFont(const QString & fontPath,const QString & message)1347 void SCFonts::addRejectedFont(const QString& fontPath, const QString& message)
1348 {
1349 	rejectedFonts.insert(fontPath, message);
1350 }
1351