1 /***************************************************************************/
5 /* Mac FOND support. Written by just@letterror.com. */
7 /* Copyright 1996-2001 by */
8 /* Just van Rossum, David Turner, Robert Wilhelm, and Werner Lemberg. */
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. */
16 /***************************************************************************/
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().
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>
70 #include <ctype.h> /* for isupper() and isalnum() */
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). */
85 /* Quick'n'dirty Pascal string to C string converter.
86 Warning: this call is not thread safe! Use with caution. */
88 p2c_str( unsigned char* pstr )
90 static char cstr[256];
93 strncpy( cstr, (char*)pstr + 1, pstr[0] );
99 /* Given a pathname, fill in a file spec. */
101 file_spec_from_path( const char* pathname,
108 /* convert path to a pascal string */
109 path_len = strlen( pathname );
110 if ( path_len > 255 )
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 )
122 /* Return the file type of the file specified by spec. */
124 get_file_type( FSSpec* spec )
129 if ( FSpGetFInfo( spec, &finfo ) != noErr )
130 return 0; /* file might not exist */
136 /* Given a PostScript font name, create the Macintosh LWFN file name. */
138 create_lwfn_name( char* ps_name,
139 Str255 lwfn_file_name )
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;
156 if ( count < max && ( isalnum( *q ) || *q == '_' ) )
167 /* Given a file reference, answer its location as a vRefNum
170 get_file_location( short ref_num,
173 unsigned char* file_name )
178 pb.ioNamePtr = file_name;
180 pb.ioRefNum = ref_num;
184 error = PBGetFCBInfoSync( &pb );
185 if ( error == noErr )
187 *v_ref_num = pb.ioFCBVRefNum;
188 *dir_id = pb.ioFCBParID;
194 /* Make a file spec for an LWFN file from a FOND resource and
197 make_lwfn_spec( Handle fond,
198 unsigned char* file_name,
202 short ref_num, v_ref_num;
204 Str255 fond_file_name;
207 ref_num = HomeResFile( fond );
211 error = get_file_location( ref_num, &v_ref_num,
212 &dir_id, fond_file_name );
214 error = FSMakeFSSpec( v_ref_num, dir_id, file_name, spec );
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. */
223 parse_fond( char* fond_data,
226 Str255 lwfn_file_name )
234 lwfn_file_name[0] = 0;
236 fond = (FamRec*)fond_data;
237 assoc = (AsscEntry*)( fond_data + sizeof ( FamRec ) + 2 );
239 if ( assoc->fontSize == 0 )
242 *sfnt_id = assoc->fontID;
245 if ( fond->ffStylOff )
247 unsigned char* p = (unsigned char*)fond_data;
249 unsigned short string_count;
250 unsigned char* name_table = 0;
252 unsigned char* names[64];
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++ )
268 strcpy( ps_name, p2c_str( names[0] ) ); /* Family name */
270 if ( style->indexes[0] > 1 )
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 ] ) );
278 create_lwfn_name( ps_name, lwfn_file_name );
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. */
289 read_lwfn( FT_Memory memory,
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;
300 char code, last_code;
303 res_ref = FSpOpenResFile( lwfn_spec, fsRdPerm );
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. */
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 )
324 total_size += 2; /* just the end code */
326 total_size += 6; /* code + 4 bytes chunk length */
329 total_size += GetHandleSize( post_data ) - 2;
333 if ( ALLOC( buffer, (FT_Long)total_size ) )
336 /* Second pass: append all POST data to the buffer, add PFB fields.
337 Glue all consecutive chunks of the same type together. */
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 )
355 if ( last_code != -1 )
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 );
367 *p++ = 0x03; /* the end */
368 else if ( code == 2 )
369 *p++ = 0x02; /* binary segment */
371 *p++ = 0x01; /* ASCII segment */
375 size_p = p; /* save for later */
376 p += 4; /* make space for size field */
380 memcpy( p, *post_data + 2, post_size );
381 pfb_chunk_size += post_size;
390 CloseResFile( res_ref );
395 /* Finalizer for a memory stream; gets called by FT_Done_Face().
396 It frees the memory it uses. */
398 memory_stream_close( FT_Stream stream )
400 FT_Memory memory = stream->memory;
403 FREE( stream->base );
411 /* Create a new memory stream from a buffer and a size. */
413 new_memory_stream( FT_Library library,
416 FT_Stream_Close close,
425 return FT_Err_Invalid_Library_Handle;
428 return FT_Err_Invalid_Argument;
431 memory = library->memory;
432 if ( ALLOC( stream, sizeof ( *stream ) ) )
435 FT_New_Memory_Stream( library,
440 stream->close = close;
449 /* Create a new FT_Face given a buffer and a driver name. */
451 open_face_from_buffer( FT_Library library,
461 FT_Memory memory = library->memory;
464 error = new_memory_stream( library,
475 args.flags = ft_open_stream;
476 args.stream = stream;
479 args.flags = args.flags | ft_open_driver;
480 args.driver = FT_Get_Module( library, driver_name );
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;
488 FT_Done_Stream( stream );
495 /* Create a new FT_Face from a file spec to an LWFN file. */
497 FT_New_Face_From_LWFN( FT_Library library,
505 FT_Memory memory = library->memory;
508 error = read_lwfn( library->memory, spec, &pfb_data, &pfb_size );
518 path = p2c_str( spec->name );
519 strcat( path, ".PFB" );
520 f = fopen( path, "wb" );
523 fwrite( pfb_data, 1, pfb_size, f );
529 return open_face_from_buffer( library,
538 /* Create a new FT_Face from an SFNT resource, specified by res ID. */
540 FT_New_Face_From_SFNT( FT_Library library,
548 FT_Stream stream = NULL;
550 FT_Memory memory = library->memory;
553 sfnt = GetResource( 'sfnt', sfnt_id );
555 return FT_Err_Invalid_Handle;
557 sfnt_size = (FT_ULong)GetHandleSize( sfnt );
558 if ( ALLOC( sfnt_data, (FT_Long)sfnt_size ) )
560 ReleaseResource( sfnt );
565 memcpy( sfnt_data, *sfnt, sfnt_size );
567 ReleaseResource( sfnt );
569 return open_face_from_buffer( library,
578 /* Create a new FT_Face from a file spec to a suitcase file. */
580 FT_New_Face_From_Suitcase( FT_Library library,
585 FT_Error error = FT_Err_Ok;
586 short res_ref, res_index;
590 res_ref = FSpOpenResFile( spec, fsRdPerm );
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 )
601 res_index = (short)( face_index + 1 );
604 fond = Get1IndResource( 'FOND', res_index );
607 error = FT_Err_Cannot_Open_Resource;
611 error = FT_New_Face_From_FOND( library, fond, face_index, aface );
614 CloseResFile( res_ref );
619 /* documentation in ftmac.h */
621 FT_EXPORT_DEF( FT_Error )
622 FT_New_Face_From_FOND( FT_Library library,
627 short sfnt_id, have_sfnt, have_lwfn = 0;
628 Str255 lwfn_file_name;
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;
641 parse_fond( *fond, &have_sfnt, &sfnt_id, lwfn_file_name );
644 if ( lwfn_file_name[0] )
646 if ( make_lwfn_spec( fond, lwfn_file_name, &lwfn_spec ) == FT_Err_Ok )
647 have_lwfn = 1; /* yeah, we got one! */
649 have_lwfn = 0; /* no LWFN file found */
652 if ( have_lwfn && ( !have_sfnt || PREFER_LWFN ) )
653 return FT_New_Face_From_LWFN( library,
657 else if ( have_sfnt )
658 return FT_New_Face_From_SFNT( library,
663 return FT_Err_Unknown_File_Format;
667 /*************************************************************************/
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. */
678 FT_EXPORT_DEF( FT_Error )
679 FT_New_Face( FT_Library library,
680 const char* pathname,
689 /* test for valid `library' and `aface' delayed to FT_Open_Face() */
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 );
703 args.flags = ft_open_pathname;
704 args.pathname = (char*)pathname;
706 return FT_Open_Face( library, &args, face_index, aface );