1 /***************************************************************************/
2 /* */
3 /* ftmac.c */
4 /* */
5 /* Mac FOND support. Written by just@letterror.com. */
6 /* */
7 /* Copyright 1996-2001 by */
8 /* Just van Rossum, David Turner, Robert Wilhelm, and Werner Lemberg. */
9 /* */
10 /* This file is part of the FreeType project, and may only be used, */
11 /* modified, and distributed under the terms of the FreeType project */
12 /* license, LICENSE.TXT. By continuing to use, modify, or distribute */
13 /* this file you indicate that you have read the license and */
14 /* understand and accept it fully. */
15 /* */
16 /***************************************************************************/
19 /*
20 Notes
22 Mac suitcase files can (and often do!) contain multiple fonts. To
23 support this I use the face_index argument of FT_(Open|New)_Face()
24 functions, and pretend the suitcase file is a collection.
25 Warning: although the FOND driver sets face->num_faces field to the
26 number of available fonts, but the Type 1 driver sets it to 1 anyway.
27 So this field is currently not reliable, and I don't see a clean way
28 to resolve that. The face_index argument translates to
29 Get1IndResource( 'FOND', face_index + 1 );
30 so clients should figure out the resource index of the FOND.
31 (I'll try to provide some example code for this at some point.)
34 The Mac FOND support works roughly like this:
36 - Check whether the offered stream points to a Mac suitcase file.
37 This is done by checking the file type: it has to be 'FFIL' or 'tfil'.
38 The stream that gets passed to our init_face() routine is a stdio
39 stream, which isn't usable for us, since the FOND resources live
40 in the resource fork. So we just grab the stream->pathname field.
42 - Read the FOND resource into memory, then check whether there is
43 a TrueType font and/or (!) a Type 1 font available.
45 - If there is a Type 1 font available (as a separate 'LWFN' file),
46 read its data into memory, massage it slightly so it becomes
47 PFB data, wrap it into a memory stream, load the Type 1 driver
48 and delegate the rest of the work to it by calling FT_Open_Face().
49 (XXX TODO: after this has been done, the kerning data from the FOND
50 resource should be appended to the face: on the Mac there are usually
51 no AFM files available. However, this is tricky since we need to map
52 Mac char codes to ps glyph names to glyph ID's...)
54 - If there is a TrueType font (an 'sfnt' resource), read it into
55 memory, wrap it into a memory stream, load the TrueType driver
56 and delegate the rest of the work to it, by calling FT_Open_Face().
57 */
60 #include <ft2build.h>
61 #include FT_FREETYPE_H
62 #include FT_INTERNAL_STREAM_H
63 #include "truetype/ttobjs.h"
64 #include "type1/t1objs.h"
66 #include <Resources.h>
67 #include <Fonts.h>
68 #include <Errors.h>
70 #include <ctype.h> /* for isupper() and isalnum() */
72 #include FT_MAC_H
76 /* Set PREFER_LWFN to 1 if LWFN (Type 1) is preferred over
77 TrueType in case *both* are available (this is not common,
78 but it *is* possible). */
79 #ifndef PREFER_LWFN
80 #define PREFER_LWFN 1
81 #endif
85 /* Quick'n'dirty Pascal string to C string converter.
86 Warning: this call is not thread safe! Use with caution. */
87 static char*
88 p2c_str( unsigned char* pstr )
89 {
90 static char cstr[256];
93 strncpy( cstr, (char*)pstr + 1, pstr[0] );
94 cstr[pstr[0]] = '\0';
95 return cstr;
96 }
99 /* Given a pathname, fill in a file spec. */
100 static int
101 file_spec_from_path( const char* pathname,
102 FSSpec* spec )
103 {
104 Str255 p_path;
105 FT_ULong path_len;
108 /* convert path to a pascal string */
109 path_len = strlen( pathname );
110 if ( path_len > 255 )
111 return -1;
112 p_path[0] = (unsigned char)path_len;
113 strncpy( (char*)p_path + 1, pathname, path_len );
115 if ( FSMakeFSSpec( 0, 0, p_path, spec ) != noErr )
116 return -1;
117 else
118 return 0;
119 }
122 /* Return the file type of the file specified by spec. */
123 static OSType
124 get_file_type( FSSpec* spec )
125 {
126 FInfo finfo;
129 if ( FSpGetFInfo( spec, &finfo ) != noErr )
130 return 0; /* file might not exist */
132 return finfo.fdType;
133 }
136 /* Given a PostScript font name, create the Macintosh LWFN file name. */
137 static void
138 create_lwfn_name( char* ps_name,
139 Str255 lwfn_file_name )
140 {
141 int max = 5, count = 0;
142 FT_Byte* p = lwfn_file_name;
143 FT_Byte* q = (FT_Byte*)ps_name;
146 lwfn_file_name[0] = 0;
148 while ( *q )
149 {
150 if ( isupper( *q ) )
151 {
152 if ( count )
153 max = 3;
154 count = 0;
155 }
156 if ( count < max && ( isalnum( *q ) || *q == '_' ) )
157 {
158 *++p = *q;
159 lwfn_file_name[0]++;
160 count++;
161 }
162 q++;
163 }
164 }
167 /* Given a file reference, answer its location as a vRefNum
168 and a dirID. */
169 static FT_Error
170 get_file_location( short ref_num,
171 short* v_ref_num,
172 long* dir_id,
173 unsigned char* file_name )
174 {
175 FCBPBRec pb;
176 OSErr error;
178 pb.ioNamePtr = file_name;
179 pb.ioVRefNum = 0;
180 pb.ioRefNum = ref_num;
181 pb.ioFCBIndx = 0;
184 error = PBGetFCBInfoSync( &pb );
185 if ( error == noErr )
186 {
187 *v_ref_num = pb.ioFCBVRefNum;
188 *dir_id = pb.ioFCBParID;
189 }
190 return error;
191 }
194 /* Make a file spec for an LWFN file from a FOND resource and
195 a file name. */
196 static FT_Error
197 make_lwfn_spec( Handle fond,
198 unsigned char* file_name,
199 FSSpec* spec )
200 {
201 FT_Error error;
202 short ref_num, v_ref_num;
203 long dir_id;
204 Str255 fond_file_name;
207 ref_num = HomeResFile( fond );
209 error = ResError();
210 if ( !error )
211 error = get_file_location( ref_num, &v_ref_num,
212 &dir_id, fond_file_name );
213 if ( !error )
214 error = FSMakeFSSpec( v_ref_num, dir_id, file_name, spec );
216 return error;
217 }
220 /* Look inside the FOND data, answer whether there should be an SFNT
221 resource, and answer the name of a possible LWFN Type 1 file. */
222 static void
223 parse_fond( char* fond_data,
224 short* have_sfnt,
225 short* sfnt_id,
226 Str255 lwfn_file_name )
227 {
228 AsscEntry* assoc;
229 FamRec* fond;
232 *sfnt_id = 0;
233 *have_sfnt = 0;
234 lwfn_file_name[0] = 0;
236 fond = (FamRec*)fond_data;
237 assoc = (AsscEntry*)( fond_data + sizeof ( FamRec ) + 2 );
239 if ( assoc->fontSize == 0 )
240 {
241 *have_sfnt = 1;
242 *sfnt_id = assoc->fontID;
243 }
245 if ( fond->ffStylOff )
246 {
247 unsigned char* p = (unsigned char*)fond_data;
248 StyleTable* style;
249 unsigned short string_count;
250 unsigned char* name_table = 0;
251 char ps_name[256];
252 unsigned char* names[64];
253 int i;
256 p += fond->ffStylOff;
257 style = (StyleTable*)p;
258 p += sizeof ( StyleTable );
259 string_count = *(unsigned short*)(p);
260 p += sizeof ( short );
262 for ( i = 0 ; i < string_count && i < 64; i++ )
263 {
264 names[i] = p;
265 p += names[i][0];
266 p++;
267 }
268 strcpy( ps_name, p2c_str( names[0] ) ); /* Family name */
270 if ( style->indexes[0] > 1 )
271 {
272 unsigned char* suffixes = names[style->indexes[0] - 1];
275 for ( i=1; i<=suffixes[0]; i++ )
276 strcat( ps_name, p2c_str( names[suffixes[i] - 1 ] ) );
277 }
278 create_lwfn_name( ps_name, lwfn_file_name );
279 }
280 }
283 /* Read Type 1 data from the POST resources inside the LWFN file,
284 return a PFB buffer. This is somewhat convoluted because the FT2
285 PFB parser wants the ASCII header as one chunk, and the LWFN
286 chunks are often not organized that way, so we'll glue chunks
287 of the same type together. */
288 static FT_Error
289 read_lwfn( FT_Memory memory,
290 FSSpec* lwfn_spec,
291 FT_Byte** pfb_data,
292 FT_ULong* size )
293 {
294 FT_Error error = FT_Err_Ok;
295 short res_ref, res_id;
296 unsigned char *buffer, *p, *size_p;
297 FT_ULong total_size = 0;
298 FT_ULong post_size, pfb_chunk_size;
299 Handle post_data;
300 char code, last_code;
303 res_ref = FSpOpenResFile( lwfn_spec, fsRdPerm );
304 if ( ResError() )
305 return FT_Err_Out_Of_Memory;
306 UseResFile( res_ref );
308 /* First pass: load all POST resources, and determine the size of
309 the output buffer. */
310 res_id = 501;
311 last_code = -1;
313 for (;;)
314 {
315 post_data = Get1Resource( 'POST', res_id++ );
316 if ( post_data == NULL )
317 break; /* we're done */
319 code = (*post_data)[0];
321 if ( code != last_code )
322 {
323 if ( code == 5 )
324 total_size += 2; /* just the end code */
325 else
326 total_size += 6; /* code + 4 bytes chunk length */
327 }
329 total_size += GetHandleSize( post_data ) - 2;
330 last_code = code;
331 }
333 if ( ALLOC( buffer, (FT_Long)total_size ) )
334 goto Error;
336 /* Second pass: append all POST data to the buffer, add PFB fields.
337 Glue all consecutive chunks of the same type together. */
338 p = buffer;
339 res_id = 501;
340 last_code = -1;
341 pfb_chunk_size = 0;
343 for (;;)
344 {
345 post_data = Get1Resource( 'POST', res_id++ );
346 if ( post_data == NULL )
347 break; /* we're done */
349 post_size = (FT_ULong)GetHandleSize( post_data ) - 2;
350 code = (*post_data)[0];
352 if ( code != last_code )
353 {
355 if ( last_code != -1 )
356 {
357 /* we're done adding a chunk, fill in the size field */
358 *size_p++ = (FT_Byte)( pfb_chunk_size & 0xFF );
359 *size_p++ = (FT_Byte)( ( pfb_chunk_size >> 8 ) & 0xFF );
360 *size_p++ = (FT_Byte)( ( pfb_chunk_size >> 16 ) & 0xFF );
361 *size_p++ = (FT_Byte)( ( pfb_chunk_size >> 24 ) & 0xFF );
362 pfb_chunk_size = 0;
363 }
365 *p++ = 0x80;
366 if ( code == 5 )
367 *p++ = 0x03; /* the end */
368 else if ( code == 2 )
369 *p++ = 0x02; /* binary segment */
370 else
371 *p++ = 0x01; /* ASCII segment */
373 if ( code != 5 )
374 {
375 size_p = p; /* save for later */
376 p += 4; /* make space for size field */
377 }
378 }
380 memcpy( p, *post_data + 2, post_size );
381 pfb_chunk_size += post_size;
382 p += post_size;
383 last_code = code;
384 }
386 *pfb_data = buffer;
387 *size = total_size;
389 Error:
390 CloseResFile( res_ref );
391 return error;
392 }
395 /* Finalizer for a memory stream; gets called by FT_Done_Face().
396 It frees the memory it uses. */
397 static void
398 memory_stream_close( FT_Stream stream )
399 {
400 FT_Memory memory = stream->memory;
403 FREE( stream->base );
405 stream->size = 0;
406 stream->base = 0;
407 stream->close = 0;
408 }
411 /* Create a new memory stream from a buffer and a size. */
412 static FT_Error
413 new_memory_stream( FT_Library library,
414 FT_Byte* base,
415 FT_ULong size,
416 FT_Stream_Close close,
417 FT_Stream* astream )
418 {
419 FT_Error error;
420 FT_Memory memory;
421 FT_Stream stream;
424 if ( !library )
425 return FT_Err_Invalid_Library_Handle;
427 if ( !base )
428 return FT_Err_Invalid_Argument;
430 *astream = 0;
431 memory = library->memory;
432 if ( ALLOC( stream, sizeof ( *stream ) ) )
433 goto Exit;
435 FT_New_Memory_Stream( library,
436 base,
437 size,
438 stream );
440 stream->close = close;
442 *astream = stream;
444 Exit:
445 return error;
446 }
449 /* Create a new FT_Face given a buffer and a driver name. */
450 static FT_Error
451 open_face_from_buffer( FT_Library library,
452 FT_Byte* base,
453 FT_ULong size,
454 FT_Long face_index,
455 char* driver_name,
456 FT_Face* aface )
457 {
458 FT_Open_Args args;
459 FT_Error error;
460 FT_Stream stream;
461 FT_Memory memory = library->memory;
464 error = new_memory_stream( library,
465 base,
466 size,
467 memory_stream_close,
468 &stream );
469 if ( error )
470 {
471 FREE( base );
472 return error;
473 }
475 args.flags = ft_open_stream;
476 args.stream = stream;
477 if ( driver_name )
478 {
479 args.flags = args.flags | ft_open_driver;
480 args.driver = FT_Get_Module( library, driver_name );
481 }
483 error = FT_Open_Face( library, &args, face_index, aface );
484 if ( error == FT_Err_Ok )
485 (*aface)->face_flags &= ~FT_FACE_FLAG_EXTERNAL_STREAM;
486 else
487 {
488 FT_Done_Stream( stream );
489 FREE( stream );
490 }
491 return error;
492 }
495 /* Create a new FT_Face from a file spec to an LWFN file. */
496 static FT_Error
497 FT_New_Face_From_LWFN( FT_Library library,
498 FSSpec* spec,
499 FT_Long face_index,
500 FT_Face* aface )
501 {
502 FT_Byte* pfb_data;
503 FT_ULong pfb_size;
504 FT_Error error;
505 FT_Memory memory = library->memory;
508 error = read_lwfn( library->memory, spec, &pfb_data, &pfb_size );
509 if ( error )
510 return error;
512 #if 0
513 {
514 FILE* f;
515 char* path;
518 path = p2c_str( spec->name );
519 strcat( path, ".PFB" );
520 f = fopen( path, "wb" );
521 if ( f )
522 {
523 fwrite( pfb_data, 1, pfb_size, f );
524 fclose( f );
525 }
526 }
527 #endif
529 return open_face_from_buffer( library,
530 pfb_data,
531 pfb_size,
532 face_index,
533 "type1",
534 aface );
535 }
538 /* Create a new FT_Face from an SFNT resource, specified by res ID. */
539 static FT_Error
540 FT_New_Face_From_SFNT( FT_Library library,
541 short sfnt_id,
542 FT_Long face_index,
543 FT_Face* aface )
544 {
545 Handle sfnt = NULL;
546 FT_Byte* sfnt_data;
547 size_t sfnt_size;
548 FT_Stream stream = NULL;
549 FT_Error error = 0;
550 FT_Memory memory = library->memory;
553 sfnt = GetResource( 'sfnt', sfnt_id );
554 if ( ResError() )
555 return FT_Err_Invalid_Handle;
557 sfnt_size = (FT_ULong)GetHandleSize( sfnt );
558 if ( ALLOC( sfnt_data, (FT_Long)sfnt_size ) )
559 {
560 ReleaseResource( sfnt );
561 return error;
562 }
564 HLock( sfnt );
565 memcpy( sfnt_data, *sfnt, sfnt_size );
566 HUnlock( sfnt );
567 ReleaseResource( sfnt );
569 return open_face_from_buffer( library,
570 sfnt_data,
571 sfnt_size,
572 face_index,
573 "truetype",
574 aface );
575 }
578 /* Create a new FT_Face from a file spec to a suitcase file. */
579 static FT_Error
580 FT_New_Face_From_Suitcase( FT_Library library,
581 FSSpec* spec,
582 FT_Long face_index,
583 FT_Face* aface )
584 {
585 FT_Error error = FT_Err_Ok;
586 short res_ref, res_index;
587 Handle fond;
590 res_ref = FSpOpenResFile( spec, fsRdPerm );
591 if ( ResError() )
592 return FT_Err_Cannot_Open_Resource;
593 UseResFile( res_ref );
595 /* face_index may be -1, in which case we
596 just need to do a sanity check */
597 if ( face_index < 0 )
598 res_index = 1;
599 else
600 {
601 res_index = (short)( face_index + 1 );
602 face_index = 0;
603 }
604 fond = Get1IndResource( 'FOND', res_index );
605 if ( ResError() )
606 {
607 error = FT_Err_Cannot_Open_Resource;
608 goto Error;
609 }
611 error = FT_New_Face_From_FOND( library, fond, face_index, aface );
613 Error:
614 CloseResFile( res_ref );
615 return error;
616 }
619 /* documentation in ftmac.h */
621 FT_EXPORT_DEF( FT_Error )
622 FT_New_Face_From_FOND( FT_Library library,
623 Handle fond,
624 FT_Long face_index,
625 FT_Face *aface )
626 {
627 short sfnt_id, have_sfnt, have_lwfn = 0;
628 Str255 lwfn_file_name;
629 short fond_id;
630 OSType fond_type;
631 Str255 fond_name;
632 FSSpec lwfn_spec;
633 FT_Error error = FT_Err_Unknown_File_Format;
636 GetResInfo(fond, &fond_id, &fond_type, fond_name);
637 if ( ResError() != noErr || fond_type != 'FOND' )
638 return FT_Err_Invalid_File_Format;
640 HLock( fond );
641 parse_fond( *fond, &have_sfnt, &sfnt_id, lwfn_file_name );
642 HUnlock( fond );
644 if ( lwfn_file_name[0] )
645 {
646 if ( make_lwfn_spec( fond, lwfn_file_name, &lwfn_spec ) == FT_Err_Ok )
647 have_lwfn = 1; /* yeah, we got one! */
648 else
649 have_lwfn = 0; /* no LWFN file found */
650 }
652 if ( have_lwfn && ( !have_sfnt || PREFER_LWFN ) )
653 return FT_New_Face_From_LWFN( library,
654 &lwfn_spec,
655 face_index,
656 aface );
657 else if ( have_sfnt )
658 return FT_New_Face_From_SFNT( library,
659 sfnt_id,
660 face_index,
661 aface );
663 return FT_Err_Unknown_File_Format;
664 }
667 /*************************************************************************/
668 /* */
669 /* <Function> */
670 /* FT_New_Face */
671 /* */
672 /* <Description> */
673 /* This is the Mac-specific implementation of FT_New_Face. In */
674 /* addition to the standard FT_New_Face() functionality, it also */
675 /* accepts pathnames to Mac suitcase files. For further */
676 /* documentation see the original FT_New_Face() in freetype.h. */
677 /* */
678 FT_EXPORT_DEF( FT_Error )
679 FT_New_Face( FT_Library library,
680 const char* pathname,
681 FT_Long face_index,
682 FT_Face *aface )
683 {
684 FT_Open_Args args;
685 FSSpec spec;
686 OSType file_type;
689 /* test for valid `library' and `aface' delayed to FT_Open_Face() */
690 if ( !pathname )
691 return FT_Err_Invalid_Argument;
693 if ( file_spec_from_path( pathname, &spec ) )
694 return FT_Err_Invalid_Argument;
696 file_type = get_file_type( &spec );
697 if ( file_type == 'FFIL' || file_type == 'tfil' )
698 return FT_New_Face_From_Suitcase( library, &spec, face_index, aface );
699 else if ( file_type == 'LWFN' )
700 return FT_New_Face_From_LWFN( library, &spec, face_index, aface );
701 else
702 {
703 args.flags = ft_open_pathname;
704 args.pathname = (char*)pathname;
706 return FT_Open_Face( library, &args, face_index, aface );
707 }
708 }
711 /* END */