1/*
2
3Copyright (c) 2001, 2002 OpenBeOS.
4
5Authors:
6	Philippe Houdoin
7	Simon Gauvin
8	Michael Pfeiffer
9
10Permission is hereby granted, free of charge, to any person obtaining a copy of
11this software and associated documentation files (the "Software"), to deal in
12the Software without restriction, including without limitation the rights to
13use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
14of the Software, and to permit persons to whom the Software is furnished to do
15so, subject to the following conditions:
16
17The above copyright notice and this permission notice shall be included in all
18copies or substantial portions of the Software.
19
20THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26THE SOFTWARE.
27
28*/
29
30#include <stdio.h>
31#include <malloc.h>
32#include <StorageKit.h>
33#include "Fonts.h"
34#include "Report.h"
35
36// FontFile
37
38// ---------------------------------------
39// Implementation of BArchivable interface
40// ---------------------------------------
41
42
43// --------------------------------------------------
44FontFile::FontFile(BMessage *archive)
45{
46	int8 type;
47	archive->FindString("name", &fName);
48	archive->FindString("path", &fPath);
49	archive->FindInt64 ("size", &fSize);
50	if (B_OK == archive->FindInt8  ("type", &type))
51		fType = (font_type)type;
52	else
53		fType = unknown_type;
54	archive->FindBool  ("embed", &fEmbed);
55	archive->FindString("subst", &fSubst);
56}
57
58// --------------------------------------------------
59BArchivable *FontFile::Instantiate(BMessage *archive)
60{
61	if (!validate_instantiation(archive, "FontFile")) {
62		return NULL;
63	}
64	return new FontFile(archive);
65}
66
67
68// --------------------------------------------------
69status_t FontFile::Archive(BMessage *archive, bool deep) const
70{
71	archive->AddString("class", "FontFile");
72	archive->AddString("name", fName);
73	archive->AddString("path", fPath);
74	archive->AddInt64 ("size", fSize);
75	archive->AddInt8  ("type", (int8)fType);
76	archive->AddBool  ("embed", fEmbed);
77	archive->AddString("subst", fSubst);
78	return B_OK;
79}
80
81
82// Fonts
83
84#define fmin(x, y) ( (x < y) ? x : y);
85
86#define TRUETYPE_VERSION		0x00010000
87#define OPENTYPE_CFF_VERSION	'OTTO'
88
89#define TRUETTYPE_TABLE_NAME_TAG	'name'
90
91static uint16 ttf_get_uint16(FILE * ttf);
92static uint32 ttf_get_uint32(FILE *ttf);
93static status_t ttf_get_fontname(const char * path, char * fontname, size_t fn_size);
94static status_t psf_get_fontname(const char * path, char * fontname, size_t fn_size);
95
96
97// --------------------------------------------------
98Fonts::Fonts()
99{
100	SetDefaultCJKOrder();
101}
102
103
104void
105Fonts::SetDefaultCJKOrder()
106{
107	SetCJKOrder(0, japanese_encoding,     true);
108	SetCJKOrder(1, chinese_gb1_encoding,  true);
109	SetCJKOrder(2, chinese_cns1_encoding, true);
110	SetCJKOrder(3, korean_encoding,       true);
111}
112
113// --------------------------------------------------
114bool
115Fonts::SetCJKOrder(int i, font_encoding  enc, bool  active)
116{
117	if (0 <= i && i < no_of_cjk_encodings) {
118		fCJKOrder[i].encoding = enc;
119		fCJKOrder[i].active   = active;
120		return true;
121	}
122	return false;
123}
124
125
126// --------------------------------------------------
127bool
128Fonts::GetCJKOrder(int i, font_encoding& enc, bool& active) const
129{
130	if (0 <= i && i < no_of_cjk_encodings) {
131		enc    = fCJKOrder[i].encoding;
132		active = fCJKOrder[i].active;
133		return true;
134	}
135	return false;
136}
137
138
139// ---------------------------------------
140// Implementation of BArchivable interface
141// ---------------------------------------
142
143
144// --------------------------------------------------
145Fonts::Fonts(BMessage *archive)
146{
147	BMessage m;
148	for (int i = 0; archive->FindMessage("fontfile", i, &m) == B_OK; i++) {
149		BArchivable* base = instantiate_object(&m);
150		if (base) {
151			FontFile* f = dynamic_cast<FontFile*>(base);
152			if (f) fFontFiles.AddItem(f);
153			else delete f;
154		}
155	}
156	if (archive->FindMessage("cjk_order", &m) == B_OK) {
157		for (int i = 0; i < no_of_cjk_encodings; i++) {
158			bool active; int32 encoding;
159			if (m.FindInt32("encoding", i, &encoding) == B_OK &&
160				m.FindBool("active", i, &active) == B_OK &&
161				first_cjk_encoding <= encoding &&
162				encoding < first_cjk_encoding + no_of_cjk_encodings) {
163				SetCJKOrder(i, (font_encoding)encoding, active);
164			} else {
165				SetDefaultCJKOrder(); return;
166			}
167		}
168		return;
169	}
170	SetDefaultCJKOrder();
171}
172
173
174// --------------------------------------------------
175BArchivable *Fonts::Instantiate(BMessage *archive)
176{
177	if (!validate_instantiation(archive, "Fonts")) {
178		return NULL;
179	}
180	return new Fonts(archive);
181}
182
183
184// --------------------------------------------------
185status_t Fonts::Archive(BMessage *archive, bool deep) const
186{
187	archive->AddString("class", "Fonts");
188	const int n = Length();
189	for (int i = 0; i < n; i++) {
190		FontFile* f = At(i);
191		BMessage m;
192		if (f->Archive(&m) == B_OK) {
193			archive->AddMessage("fontfile", &m);
194		}
195	}
196	BMessage m;
197	font_encoding enc;
198	bool active;
199	for (int i = 0; GetCJKOrder(i, enc, active); i++) {
200		m.AddInt32("encoding", enc);
201		m.AddBool("active", active);
202	}
203	archive->AddMessage("cjk_order", &m);
204	return B_OK;
205}
206
207
208// --------------------------------------------------
209status_t
210Fonts::CollectFonts()
211{
212	BPath				path;
213	directory_which	*	which_dir;
214	directory_which		lookup_dirs[] = {
215		B_BEOS_FONTS_DIRECTORY,
216		// B_COMMON_FONTS_DIRECTORY,	// seem to be the same directory than B_USER_FONTS_DIRECTORY!!!
217		B_USER_FONTS_DIRECTORY,
218		(directory_which) -1
219	};
220
221	which_dir = lookup_dirs;
222	while (*which_dir >= 0) {
223		if ( find_directory(*which_dir, &path) == B_OK )
224			LookupFontFiles(path);
225
226		which_dir++;
227	};
228
229	return B_OK;
230}
231
232
233// --------------------------------------------------
234status_t
235Fonts::LookupFontFiles(BPath path)
236{
237	BDirectory 	dir(path.Path());
238	BEntry 		entry;
239
240	if (dir.InitCheck() != B_OK)
241		return B_ERROR;
242
243	dir.Rewind();
244	while (dir.GetNextEntry(&entry) >= 0) {
245		BPath 		name;
246		char 		fn[512];
247		font_type	ft = unknown_type; // to keep the compiler silent.
248		off_t 		size;
249		status_t	status;
250
251		entry.GetPath(&name);
252		if (entry.IsDirectory())
253			// recursivly lookup in sub-directories...
254			LookupFontFiles(name);
255
256		if (! entry.IsFile())
257			continue;
258
259		fn[0] = 0;
260		ft = unknown_type;
261
262		// is it a truetype file?
263		status = ttf_get_fontname(name.Path(), fn, sizeof(fn));
264		if (status == B_OK ) {
265			ft = true_type_type;
266		} else {
267			// okay, maybe it's a postscript type file?
268			status = psf_get_fontname(name.Path(), fn, sizeof(fn));
269			if (status == B_OK) {
270				ft = type1_type;
271			}
272		}
273
274		if (ft == unknown_type)
275			// not a font file...
276			continue;
277
278		if (entry.GetSize(&size) != B_OK)
279			size = 1024*1024*1024;
280
281		REPORT(kDebug, -1, "Installed font %s -> %s", fn, name.Path());
282		fFontFiles.AddItem(new FontFile(fn, name.Path(), size, ft, size < 100*1024));
283	}	// while dir.GetNextEntry()...
284
285	return B_OK;
286}
287
288// --------------------------------------------------
289void
290Fonts::SetTo(BMessage *archive)
291{
292	BArchivable *a = Instantiate(archive);
293	Fonts *f = a ? dynamic_cast<Fonts*>(a) : NULL;
294	if (f) {
295		const int n = Length();
296		const int m = f->Length();
297		for (int i = 0; i < m; i ++) {
298			FontFile *font = f->At(i);
299			for (int j = 0; j < n; j ++) {
300				if (strcmp(font->Path(), At(j)->Path()) == 0) {
301					At(j)->SetEmbed(font->Embed());
302					At(j)->SetSubst(font->Subst());
303					break;
304				}
305			}
306		}
307
308		font_encoding enc;
309		bool          active;
310		for (int i = 0; f->GetCJKOrder(i, enc, active); i++) {
311			SetCJKOrder(i, enc, active);
312		}
313		delete f;
314	}
315}
316
317
318// --------------------------------------------------
319static uint16 ttf_get_uint16(FILE * ttf)
320{
321    uint16 v;
322
323	if (fread(&v, 1, 2, ttf) != 2)
324		return 0;
325
326	return B_BENDIAN_TO_HOST_INT16(v);
327}
328
329
330// --------------------------------------------------
331static uint32 ttf_get_uint32(FILE *ttf)
332{
333    uint32 buf;
334
335    if (fread(&buf, 1, 4, ttf) != 4)
336		return 0;
337
338	return B_BENDIAN_TO_HOST_INT32(buf);
339}
340
341
342// --------------------------------------------------
343static status_t ttf_get_fontname(const char * path, char * fontname, size_t fn_size)
344{
345	FILE *		ttf;
346	status_t	status;
347	uint16		nb_tables, nb_records;
348	uint16		i;
349	uint32		tag;
350	uint32		table_offset;
351	uint32		strings_offset;
352	char		family_name[256];
353	char		face_name[256];
354	int			names_found;
355
356	status = B_ERROR;
357
358	ttf = fopen(path, "rb");
359	if (! ttf)
360		return status;
361
362    tag = ttf_get_uint32(ttf);		/* version */
363	switch(tag) {
364		case TRUETYPE_VERSION:
365		case OPENTYPE_CFF_VERSION:
366			break;
367
368		default:
369			goto exit;
370	}
371
372    /* set up table directory */
373    nb_tables = ttf_get_uint16(ttf);
374
375	fseek(ttf, 12, SEEK_SET);
376
377	table_offset = 0;	// quiet the compiler...
378
379    for (i = 0; i < nb_tables; ++i) {
380		tag				= ttf_get_uint32(ttf);
381		ttf_get_uint32(ttf);	// checksum
382		table_offset	= ttf_get_uint32(ttf);
383
384		if (tag == TRUETTYPE_TABLE_NAME_TAG)
385			break;
386	}
387
388	if (tag != TRUETTYPE_TABLE_NAME_TAG)
389		// Mandatory name table not found!
390		goto exit;
391
392	// move to name table start
393	fseek(ttf, table_offset, SEEK_SET);
394
395	ttf_get_uint16(ttf);	// name table format (must be 0!)
396    nb_records		= ttf_get_uint16(ttf);
397	strings_offset	= table_offset + ttf_get_uint16(ttf); // string storage offset is from table offset
398
399	//    offs = ttf->dir[idx].offset + tp->offsetStrings;
400
401	// printf("  pid   eid   lid   nid   len offset value\n");
402        //  65536 65536 65536 65536 65536 65536  ......
403
404	family_name[0] = 0;
405	face_name[0] = 0;
406	names_found = 0;
407
408	for (i = 0; i < nb_records; ++i) {
409		uint16	platform_id, encoding_id, name_id;
410		uint16	string_len, string_offset;
411
412		platform_id		= ttf_get_uint16(ttf);
413		encoding_id		= ttf_get_uint16(ttf);
414		ttf_get_uint16(ttf);	// language_id
415		name_id			= ttf_get_uint16(ttf);
416		string_len		= ttf_get_uint16(ttf);
417		string_offset	= ttf_get_uint16(ttf);
418
419		if ( name_id != 1 && name_id != 2 )
420			continue;
421
422		// printf("%5d %5d %5d %5d %5d %5d ",
423		// 	platform_id, encoding_id, language_id, name_id, string_len, string_offset);
424
425		if (string_len != 0) {
426			long	pos;
427			char *	buffer;
428
429			pos = ftell(ttf);
430			fseek(ttf, strings_offset + string_offset, SEEK_SET);
431
432			buffer = (char *) malloc(string_len + 16);
433
434			fread(buffer, 1, string_len, ttf);
435			buffer[string_len] = '\0';
436
437			fseek(ttf, pos, SEEK_SET);
438
439			if ( (platform_id == 3 && encoding_id == 1) || // Windows Unicode
440				 (platform_id == 0) ) { // Unicode
441				// dirty unicode -> ascii conversion
442				int k;
443
444				for (k=0; k < string_len/2; k++)
445					buffer[k] = buffer[2*k + 1];
446				buffer[k] = '\0';
447			}
448
449			// printf("%s\n", buffer);
450
451			if (name_id == 1)
452				strncpy(family_name, buffer, sizeof(family_name));
453			else if (name_id == 2)
454				strncpy(face_name, buffer, sizeof(face_name));
455
456			names_found += name_id;
457
458			free(buffer);
459		}
460		// else
461			// printf("<null>\n");
462
463		if (names_found == 3)
464			break;
465	}
466
467	if (names_found == 3) {
468#ifndef __POWERPC__
469		snprintf(fontname, fn_size, "%s-%s", family_name, face_name);
470#else
471		sprintf(fontname, "%s-%s", family_name, face_name);
472#endif
473		status = B_OK;
474	}
475
476exit:
477	fclose(ttf);
478	return status;
479}
480
481
482// --------------------------------------------------
483static status_t psf_get_fontname(const char * path, char * fontname, size_t fn_size)
484{
485	FILE *		psf;
486	status_t	status;
487	int			i;
488	char		line[1024];
489	char * 		token;
490	char *		name;
491
492	// *.afm	search for "FontName <font_name_without_blank>" line
493	// *.pfa 	search for "/FontName /<font_name_without_blank> def" line
494
495	status = B_ERROR;
496
497	psf = fopen(path, "r");
498	if (! psf)
499		return status;
500
501	name = NULL;
502
503	i = 0;
504	while ( fgets(line, sizeof(line), psf) != NULL ) {
505		i++;
506		if ( i > 64 )
507			// only check the first 64 lines of files...
508			break;
509
510		token = strtok(line, " \r\n");
511		if (! token)
512			continue;
513
514		if (strcmp(token, "FontName") == 0)
515			name = strtok(NULL, " \r\n");
516		else if (strcmp(token, "/FontName") == 0) {
517			name = strtok(NULL, " \r\n");
518			if (name)
519				name++;	// skip the '/'
520		}
521
522		if (name)
523			break;
524	}
525
526	if (name) {
527		strncpy(fontname, name, fn_size);
528		status = B_OK;
529	}
530
531	fclose(psf);
532	return status;
533}
534
535// Implementation of UserDefinedEncodings
536
537UserDefinedEncodings::UserDefinedEncodings()
538	: fCurrentEncoding(0)
539	, fCurrentIndex(0)
540{
541	memset(fUsedMask, 0, sizeof(fUsedMask));
542	memset(fEncoding, 0, sizeof(fEncoding));
543	memset(fIndex, 0, sizeof(fIndex));
544}
545
546bool UserDefinedEncodings::Get(uint16 unicode, uint8 &encoding, uint8 &index) {
547	bool missing = !IsUsed(unicode);
548	if (missing) {
549		SetUsed(unicode);
550		fEncoding[unicode] = fCurrentEncoding; fIndex[unicode] = fCurrentIndex;
551		if (fCurrentIndex == 255) {
552			fCurrentIndex = 0; fCurrentEncoding ++;
553		} else {
554			fCurrentIndex ++;
555		}
556	}
557	encoding = fEncoding[unicode]; index = fIndex[unicode];
558	return missing;
559}
560