1 /*
2  * Copyright 2012 Chris Young <chris@unsatisfactorysoftware.co.uk>
3  *
4  * This file is part of NetSurf, http://www.netsurf-browser.org/
5  *
6  * NetSurf is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; version 2 of the License.
9  *
10  * NetSurf is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 /** \file
20  * Font glyph scanner for Unicode substitutions.
21 */
22 
23 #include "amiga/os3support.h"
24 
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 
29 #ifndef __amigaos4__
30 #include <proto/bullet.h>
31 #endif
32 #include <proto/diskfont.h>
33 #include <proto/dos.h>
34 #include <proto/exec.h>
35 #include <proto/intuition.h>
36 #include <diskfont/diskfonttag.h>
37 #include <diskfont/oterrors.h>
38 
39 #include <proto/window.h>
40 #include <proto/layout.h>
41 #include <proto/fuelgauge.h>
42 #include <classes/window.h>
43 #include <gadgets/fuelgauge.h>
44 #include <gadgets/layout.h>
45 
46 #include <reaction/reaction_macros.h>
47 
48 #include "utils/nsoption.h"
49 #include "utils/log.h"
50 #include "utils/messages.h"
51 #include "netsurf/mouse.h"
52 #include "netsurf/window.h"
53 
54 #include "amiga/font_scan.h"
55 #include "amiga/gui.h"
56 #include "amiga/libs.h"
57 #include "amiga/object.h"
58 #include "amiga/utf8.h"
59 
60 enum {
61 	FS_OID_MAIN = 0,
62 	FS_GID_MAIN,
63 	FS_GID_FONTS,
64 	FS_GID_GLYPHS,
65 	FS_GID_LAST
66 };
67 
68 struct ami_font_scan_window {
69 	struct Window *win;
70 	Object *objects[FS_GID_LAST];
71 	char *title;
72 	char *glyphtext;
73 };
74 
75 /**
76  * Lookup a font that contains a UTF-16 codepoint
77  *
78  * \param  code           UTF-16 codepoint to lookup
79  * \param  glypharray     an array of 0xffff lwc_string pointers
80  * \return font name or NULL
81  */
ami_font_scan_lookup(const uint16 * code,lwc_string ** glypharray)82 const char *ami_font_scan_lookup(const uint16 *code, lwc_string **glypharray)
83 {
84 	if(*code >= 0xd800 && *code <= 0xdbff) {
85 		/* This is a multi-byte character, we don't support fallback for these yet. */
86 		return NULL;
87 	}
88 
89 	if(glypharray[*code] == NULL) return NULL;
90 		else return lwc_string_data(glypharray[*code]);
91 }
92 
93 /**
94  * Open GUI to show font scanning progress
95  *
96  * \param fonts  number of fonts that are being scanned
97  * \return pointer to a struct ami_font_scan_window
98  */
ami_font_scan_gui_open(int32 fonts)99 static struct ami_font_scan_window *ami_font_scan_gui_open(int32 fonts)
100 {
101 	struct ami_font_scan_window *fsw =
102 		malloc(sizeof(struct ami_font_scan_window));
103 
104 	if(fsw == NULL) return NULL;
105 
106 	fsw->title = ami_utf8_easy(messages_get("FontScanning"));
107 	fsw->glyphtext = ami_utf8_easy(messages_get("FontGlyphs"));
108 
109 	fsw->objects[FS_OID_MAIN] = WindowObj,
110       	    WA_ScreenTitle, ami_gui_get_screen_title(),
111            	WA_Title, fsw->title,
112            	WA_Activate, TRUE,
113            	WA_DepthGadget, TRUE,
114            	WA_DragBar, TRUE,
115            	WA_CloseGadget, FALSE,
116            	WA_SizeGadget, TRUE,
117 			WA_PubScreen, ami_gui_get_screen(),
118 			WA_BusyPointer, TRUE,
119 			WA_Width, 400,
120 			WINDOW_UserData, fsw,
121 			WINDOW_IconifyGadget, FALSE,
122          	WINDOW_Position, WPOS_CENTERSCREEN,
123 			WINDOW_LockHeight, TRUE,
124            	WINDOW_ParentGroup, fsw->objects[FS_GID_MAIN] = LayoutVObj,
125 				LAYOUT_AddChild, fsw->objects[FS_GID_FONTS] = FuelGaugeObj,
126 					GA_ID, FS_GID_FONTS,
127 					GA_Text, fsw->title,
128 					FUELGAUGE_Min, 0,
129 					FUELGAUGE_Max, fonts,
130 					FUELGAUGE_Level, 0,
131 					FUELGAUGE_Ticks, 11,
132 					FUELGAUGE_ShortTicks, TRUE,
133 					FUELGAUGE_Percent, FALSE,
134 					FUELGAUGE_Justification, FGJ_CENTER,
135 				FuelGaugeEnd,
136 				CHILD_NominalSize, TRUE,
137 				CHILD_WeightedHeight, 0,
138 				LAYOUT_AddChild, fsw->objects[FS_GID_GLYPHS] = FuelGaugeObj,
139 					GA_ID, FS_GID_GLYPHS,
140 					//GA_Text, "Glyphs",
141 					FUELGAUGE_Min, 0x0000,
142 					FUELGAUGE_Max, 0xffff,
143 					FUELGAUGE_Level, 0,
144 					FUELGAUGE_Ticks,11,
145 					FUELGAUGE_ShortTicks, TRUE,
146 					FUELGAUGE_Percent, FALSE,
147 					FUELGAUGE_Justification, FGJ_CENTER,
148 				FuelGaugeEnd,
149 				CHILD_NominalSize, TRUE,
150 				CHILD_WeightedHeight, 0,
151 			EndGroup,
152 		EndWindow;
153 
154 	fsw->win = (struct Window *)RA_OpenWindow(fsw->objects[FS_OID_MAIN]);
155 
156 	return fsw;
157 }
158 
159 /**
160  * Update GUI showing font scanning progress
161  *
162  * \param fsw       pointer to a struct ami_font_scan_window
163  * \param font      current font being scanned
164  * \param font_num  font number being scanned
165  * \param glyphs    number of unique glyphs found
166  */
ami_font_scan_gui_update(struct ami_font_scan_window * fsw,const char * font,ULONG font_num,ULONG glyphs)167 static void ami_font_scan_gui_update(struct ami_font_scan_window *fsw, const char *font,
168 			ULONG font_num, ULONG glyphs)
169 {
170 	ULONG va[2];
171 
172 	if(fsw) {
173 		RefreshSetGadgetAttrs((struct Gadget *)fsw->objects[FS_GID_FONTS],
174 						fsw->win, NULL,
175 						FUELGAUGE_Level,   font_num,
176 						GA_Text,           font,
177 						TAG_DONE);
178 
179 		va[0] = glyphs;
180 		va[1] = 0;
181 
182 		RefreshSetGadgetAttrs((struct Gadget *)fsw->objects[FS_GID_GLYPHS],
183 						fsw->win, NULL,
184 						GA_Text,           fsw->glyphtext,
185 						FUELGAUGE_VarArgs, va,
186 						FUELGAUGE_Level,   glyphs,
187 						TAG_DONE);
188 	} else {
189 		printf("Found %ld glyphs\n", glyphs);
190 		printf("Scanning font #%ld (%s)...\n", font_num, font);
191 	}
192 }
193 
194 /**
195  * Close GUI showing font scanning progress
196  *
197  * \param fsw pointer to a struct ami_font_scan_window
198  */
ami_font_scan_gui_close(struct ami_font_scan_window * fsw)199 static void ami_font_scan_gui_close(struct ami_font_scan_window *fsw)
200 {
201 	if(fsw) {
202 		DisposeObject(fsw->objects[FS_OID_MAIN]);
203 		ami_utf8_free(fsw->title);
204 		free(fsw);
205 	}
206 }
207 
208 /**
209  * Scan a font for glyphs not present in glypharray.
210  *
211  * \param  fontname       font to scan
212  * \param  glypharray     an array of 0xffff lwc_string pointers
213  * \return number of new glyphs found
214  */
ami_font_scan_font(const char * fontname,lwc_string ** glypharray)215 static ULONG ami_font_scan_font(const char *fontname, lwc_string **glypharray)
216 {
217 	struct OutlineFont *ofont;
218 	struct MinList *widthlist = NULL;
219 	struct GlyphWidthEntry *gwnode;
220 	ULONG foundglyphs = 0;
221 	lwc_error lerror;
222 	ULONG unicoderanges = 0;
223 
224 	ofont = OpenOutlineFont(fontname, NULL, OFF_OPEN);
225 
226 	if(!ofont) return 0;
227 
228 #ifndef __amigaos4__
229 	struct BulletBase *BulletBase = ofont->BulletBase;
230 #endif
231 
232 	if(ESetInfo(AMI_OFONT_ENGINE,
233 		OT_PointHeight, 10 * (1 << 16),
234 		OT_GlyphCode, 0x0000,
235 		OT_GlyphCode2, 0xffff,
236 		TAG_END) == OTERR_Success)
237 	{
238 		if(EObtainInfo(AMI_OFONT_ENGINE,
239 			OT_WidthList, &widthlist,
240 			TAG_END) == 0)
241 		{
242 			gwnode = (struct GlyphWidthEntry *)GetHead((struct List *)widthlist);
243 			do {
244 				if(gwnode && (glypharray[gwnode->gwe_Code] == NULL)) {
245 					lerror = lwc_intern_string(fontname, strlen(fontname), &glypharray[gwnode->gwe_Code]);
246 					if(lerror != lwc_error_ok) continue;
247 					foundglyphs++;
248 				}
249 			} while((gwnode = (struct GlyphWidthEntry *)GetSucc((struct Node *)gwnode)));
250 			EReleaseInfo(AMI_OFONT_ENGINE,
251 				OT_WidthList, widthlist,
252 				TAG_END);
253 		}
254 	}
255 #ifdef __amigaos4__
256 	if(EObtainInfo(AMI_OFONT_ENGINE, OT_UnicodeRanges, &unicoderanges, TAG_END) == 0) {
257 		if(unicoderanges & UCR_SURROGATES) {
258 			NSLOG(netsurf, INFO, "%s supports UTF-16 surrogates",
259 			      fontname);
260 			if (nsoption_charp(font_surrogate) == NULL) {
261 				nsoption_set_charp(font_surrogate, (char *)strdup(fontname));
262 			}
263 		}
264 		EReleaseInfo(AMI_OFONT_ENGINE,
265 			OT_UnicodeRanges, unicoderanges,
266 			TAG_END);
267 	}
268 #endif
269 	CloseOutlineFont(ofont, NULL);
270 
271 	return foundglyphs;
272 }
273 
274 /**
275  * Scan all fonts for glyphs.
276  *
277  * \param list min list
278  * \param win scan window
279  * \param glypharray an array of 0xffff lwc_string pointers
280  * \return number of glyphs found
281  */
ami_font_scan_fonts(struct MinList * list,struct ami_font_scan_window * win,lwc_string ** glypharray)282 static ULONG ami_font_scan_fonts(struct MinList *list,
283 		struct ami_font_scan_window *win, lwc_string **glypharray)
284 {
285 	ULONG found, total = 0, font_num = 0;
286 	struct nsObject *node;
287 	struct nsObject *nnode;
288 
289 	if(IsMinListEmpty(list)) return 0;
290 
291 	node = (struct nsObject *)GetHead((struct List *)list);
292 
293 	do {
294 		nnode = (struct nsObject *)GetSucc((struct Node *)node);
295 		ami_font_scan_gui_update(win, node->dtz_Node.ln_Name, font_num, total);
296 		NSLOG(netsurf, INFO, "Scanning %s", node->dtz_Node.ln_Name);
297 		found = ami_font_scan_font(node->dtz_Node.ln_Name, glypharray);
298 		total += found;
299 		NSLOG(netsurf, INFO, "Found %ld new glyphs (total = %ld)",
300 		      found, total);
301 		font_num++;
302 	} while((node = nnode));
303 
304 	return total;
305 }
306 
307 /**
308  * Add OS fonts to a list.
309  *
310  * \param  list   list to add font names to
311  * \return number of fonts found
312  */
ami_font_scan_list(struct MinList * list)313 static ULONG ami_font_scan_list(struct MinList *list)
314 {
315 	int afShortage, afSize = 100;
316 	struct AvailFontsHeader *afh;
317 	struct AvailFonts *af;
318 	ULONG found = 0;
319 	struct nsObject *node;
320 
321 	do {
322 		if((afh = (struct AvailFontsHeader *)malloc(afSize))) {
323 			if(((afShortage = AvailFonts((STRPTR)afh, afSize,
324 					AFF_DISK | AFF_OTAG | AFF_SCALED)))) {
325 				free(afh);
326 				afSize += afShortage;
327 			}
328 		} else {
329 			/* out of memory, bail out */
330 			return 0;
331 		}
332 	} while (afShortage);
333 
334 	if(afh) {
335 		af = (struct AvailFonts *)&(afh[1]);
336 
337 		for(int i = 0; i < afh->afh_NumEntries; i++) {
338 			if(af[i].af_Attr.ta_Style == FS_NORMAL) {
339 				if(af[i].af_Attr.ta_Name != NULL) {
340 					char *p = 0;
341 					if((p = strrchr(af[i].af_Attr.ta_Name, '.'))) *p = '\0';
342 					node = (struct nsObject *)FindIName((struct List *)list,
343 								af[i].af_Attr.ta_Name);
344 					if(node == NULL) {
345 						node = AddObject(list, AMINS_UNKNOWN);
346 						if(node) {
347 							node->dtz_Node.ln_Name = strdup(af[i].af_Attr.ta_Name);
348 							found++;
349 							NSLOG(netsurf, INFO,
350 							      "Added %s",
351 							      af[i].af_Attr.ta_Name);
352 						}
353 					}
354 				}
355 			}
356 		}
357 		free(afh);
358 	} else {
359 		return 0;
360 	}
361 	return found;
362 }
363 
364 /**
365  * Load a font glyph cache
366  *
367  * \param  filename       name of cache file to load
368  * \param  glypharray     an array of 0xffff lwc_string pointers
369  * \return number of glyphs loaded
370  */
ami_font_scan_load(const char * filename,lwc_string ** glypharray)371 static ULONG ami_font_scan_load(const char *filename, lwc_string **glypharray)
372 {
373 	ULONG found = 0;
374 	BPTR fh = 0;
375 	lwc_error lerror;
376 	char buffer[256];
377 	struct RDArgs *rargs = NULL;
378 	CONST_STRPTR template = "CODE/A,FONT/A";
379 	long rarray[] = {0,0};
380 
381 	enum {
382 		A_CODE = 0,
383 		A_FONT
384 	};
385 
386 	rargs = AllocDosObjectTags(DOS_RDARGS, TAG_DONE);
387 
388 	if((fh = FOpen(filename, MODE_OLDFILE, 0))) {
389 		NSLOG(netsurf, INFO, "Loading font glyph cache from %s",
390 		      filename);
391 
392 		while(FGets(fh, (STRPTR)&buffer, 256) != 0)
393 		{
394 			rargs->RDA_Source.CS_Buffer = (char *)&buffer;
395 			rargs->RDA_Source.CS_Length = 256;
396 			rargs->RDA_Source.CS_CurChr = 0;
397 
398 			rargs->RDA_DAList = NULL;
399 			rargs->RDA_Buffer = NULL;
400 			rargs->RDA_BufSiz = 0;
401 			rargs->RDA_ExtHelp = NULL;
402 			rargs->RDA_Flags = 0;
403 
404 			if(ReadArgs(template, rarray, rargs))
405 			{
406 				lerror = lwc_intern_string((const char *)rarray[A_FONT],
407 							strlen((const char *)rarray[A_FONT]),
408 							&glypharray[strtoul((const char *)rarray[A_CODE], NULL, 0)]);
409 				if(lerror != lwc_error_ok) continue;
410 				found++;
411 			}
412 		}
413 		FClose(fh);
414 	}
415 
416 	return found;
417 }
418 
419 /**
420  * Save a font glyph cache
421  *
422  * \param  filename       name of cache file to save
423  * \param  glypharray     an array of 0xffff lwc_string pointers
424  */
ami_font_scan_save(const char * filename,lwc_string ** glypharray)425 void ami_font_scan_save(const char *filename, lwc_string **glypharray)
426 {
427 	ULONG i;
428 	BPTR fh = 0;
429 
430 	if((fh = FOpen(filename, MODE_NEWFILE, 0))) {
431 		NSLOG(netsurf, INFO, "Writing font glyph cache to %s",
432 		      filename);
433 		FPrintf(fh, "; This file is auto-generated. To re-create the cache, delete this file.\n");
434 		FPrintf(fh, "; This file is parsed using ReadArgs() with the following template:\n");
435 		FPrintf(fh, "; CODE/A,FONT/A\n;\n");
436 
437 		for(i=0x0000; i<=0xffff; i++)
438 		{
439 			if(glypharray[i]) {
440 				FPrintf(fh, "0x%04lx \"%s\"\n", i, lwc_string_data(glypharray[i]));
441 			}
442 		}
443 		FClose(fh);
444 	}
445 }
446 
447 /**
448  * Finalise the font glyph cache.
449  *
450  * \param  glypharray     an array of 0xffff lwc_string pointers to free
451  */
ami_font_scan_fini(lwc_string ** glypharray)452 void ami_font_scan_fini(lwc_string **glypharray)
453 {
454 	ULONG i;
455 
456 	for(i=0x0000; i<=0xffff; i++)
457 	{
458 		if(glypharray[i]) {
459 			lwc_string_unref(glypharray[i]);
460 			glypharray[i] = NULL;
461 		}
462 	}
463 }
464 
465 /**
466  * Initialise the font glyph cache.
467  * Reads an existing file or, if not present, generates a new cache.
468  *
469  * \param  filename   cache file to attempt to read
470  * \param  force_scan force re-creation of cache
471  * \param  save       save the cache
472  * \param  glypharray an array of 0xffff lwc_string pointers
473  */
ami_font_scan_init(const char * filename,bool force_scan,bool save,lwc_string ** glypharray)474 void ami_font_scan_init(const char *filename, bool force_scan, bool save,
475 		lwc_string **glypharray)
476 {
477 	ULONG i, found = 0, entries = 0;
478 	struct MinList *list;
479 	struct nsObject *node;
480 	char *csv;
481 	struct ami_font_scan_window *win = NULL;
482 
483 	/* Ensure array zeroed */
484 	for(i=0x0000; i<=0xffff; i++)
485 		glypharray[i] = NULL;
486 
487 	if(force_scan == false)
488 		found = ami_font_scan_load(filename, glypharray);
489 
490 	if(found == 0) {
491 		NSLOG(netsurf, INFO, "Creating new font glyph cache");
492 		if((list = NewObjList())) {
493 
494 			/* add preferred fonts list */
495 			if(nsoption_charp(font_unicode) &&
496 					(csv = strdup(nsoption_charp(font_unicode))))
497 			{
498 				char *p;
499 
500 				while((p = strsep(&csv, ","))) {
501 					if(p != NULL) {
502 						node = AddObject(list, AMINS_UNKNOWN);
503 						if(node) node->dtz_Node.ln_Name = strdup(p);
504 						entries++;
505 					}
506 				}
507 				free(csv);
508 			}
509 
510 			if(nsoption_bool(font_unicode_only) == false)
511 				entries += ami_font_scan_list(list);
512 
513 			NSLOG(netsurf, INFO, "Found %ld fonts", entries);
514 
515 			win = ami_font_scan_gui_open(entries);
516 			found = ami_font_scan_fonts(list, win, glypharray);
517 			ami_font_scan_gui_close(win);
518 
519 			FreeObjList(list);
520 
521 			if(save == true)
522 				ami_font_scan_save(filename, glypharray);
523 		}
524 	}
525 
526 	NSLOG(netsurf, INFO, "Initialised with %ld glyphs", found);
527 }
528 
529