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