1 /***************************************************************************/
5 /* Mac FOND support. Written by just@letterror.com. */
7 /* Copyright 1996-2001, 2002 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.
26 Warning: Although the FOND driver sets face->num_faces field to the
27 number of available fonts, but the Type 1 driver sets it to 1 anyway.
28 So this field is currently not reliable, and I don't see a clean way
29 to resolve that. The face_index argument translates to
31 Get1IndResource( 'FOND', face_index + 1 );
33 so clients should figure out the resource index of the FOND.
34 (I'll try to provide some example code for this at some point.)
36 The Mac FOND support works roughly like this:
38 - Check whether the offered stream points to a Mac suitcase file.
39 This is done by checking the file type: it has to be 'FFIL' or 'tfil'.
40 The stream that gets passed to our init_face() routine is a stdio
41 stream, which isn't usable for us, since the FOND resources live
42 in the resource fork. So we just grab the stream->pathname field.
44 - Read the FOND resource into memory, then check whether there is
45 a TrueType font and/or(!) a Type 1 font available.
47 - If there is a Type 1 font available (as a separate 'LWFN' file),
48 read its data into memory, massage it slightly so it becomes
49 PFB data, wrap it into a memory stream, load the Type 1 driver
50 and delegate the rest of the work to it by calling FT_Open_Face().
51 (XXX TODO: after this has been done, the kerning data from the FOND
52 resource should be appended to the face: On the Mac there are usually
53 no AFM files available. However, this is tricky since we need to map
54 Mac char codes to ps glyph names to glyph ID's...)
56 - If there is a TrueType font (an 'sfnt' resource), read it into
57 memory, wrap it into a memory stream, load the TrueType driver
58 and delegate the rest of the work to it, by calling FT_Open_Face().
63 #include FT_FREETYPE_H
64 #include FT_INTERNAL_STREAM_H
65 #include "truetype/ttobjs.h"
66 #include "type1/t1objs.h"
68 #include <Resources.h>
72 #include <TextUtils.h>
78 /* Set PREFER_LWFN to 1 if LWFN (Type 1) is preferred over
79 TrueType in case *both* are available (this is not common,
80 but it *is* possible). */
85 /* Given a pathname, fill in a file spec. */
87 file_spec_from_path( const char* pathname,
90 #if TARGET_API_MAC_CARBON
96 e = FSPathMakeRef( (UInt8 *)pathname, &ref, false /* not a directory */ );
98 e = FSGetCatalogInfo( &ref, kFSCatInfoNone, NULL, NULL, spec, NULL );
100 return ( e == noErr ) ? 0 : (-1);
108 /* convert path to a pascal string */
109 path_len = ft_strlen( pathname );
110 if ( path_len > 255 )
112 p_path[0] = (unsigned char)path_len;
113 ft_strncpy( (char*)p_path + 1, pathname, path_len );
115 if ( FSMakeFSSpec( 0, 0, p_path, spec ) != noErr )
124 /* Return the file type of the file specified by spec. */
126 get_file_type( FSSpec* spec )
131 if ( FSpGetFInfo( spec, &finfo ) != noErr )
132 return 0; /* file might not exist */
138 #if TARGET_API_MAC_CARBON
140 /* is this a Mac OS X .dfont file */
142 is_dfont( FSSpec* spec )
144 int nameLen = spec->name[0];
147 return nameLen >= 6 &&
148 !memcmp( spec->name + nameLen - 5, ".dfont", 6 );
154 /* Given a PostScript font name, create the Macintosh LWFN file name. */
156 create_lwfn_name( char* ps_name,
157 Str255 lwfn_file_name )
159 int max = 5, count = 0;
160 FT_Byte* p = lwfn_file_name;
161 FT_Byte* q = (FT_Byte*)ps_name;
164 lwfn_file_name[0] = 0;
174 if ( count < max && ( ft_isalnum( *q ) || *q == '_' ) )
185 /* Given a file reference, answer its location as a vRefNum
188 get_file_location( short ref_num,
191 unsigned char* file_name )
197 pb.ioNamePtr = file_name;
199 pb.ioRefNum = ref_num;
202 error = PBGetFCBInfoSync( &pb );
203 if ( error == noErr )
205 *v_ref_num = pb.ioFCBVRefNum;
206 *dir_id = pb.ioFCBParID;
212 /* Make a file spec for an LWFN file from a FOND resource and
215 make_lwfn_spec( Handle fond,
216 unsigned char* file_name,
220 short ref_num, v_ref_num;
222 Str255 fond_file_name;
225 ref_num = HomeResFile( fond );
229 error = get_file_location( ref_num, &v_ref_num,
230 &dir_id, fond_file_name );
232 error = FSMakeFSSpec( v_ref_num, dir_id, file_name, spec );
238 /* Look inside the FOND data, answer whether there should be an SFNT
239 resource, and answer the name of a possible LWFN Type 1 file.
241 Thanks to Paul Miller (paulm@profoundeffects.com) for the fix
242 to load a face OTHER than the first one in the FOND!
245 parse_fond( char* fond_data,
248 Str255 lwfn_file_name,
252 AsscEntry* base_assoc;
258 lwfn_file_name[0] = 0;
260 fond = (FamRec*)fond_data;
261 assoc = (AsscEntry*)( fond_data + sizeof ( FamRec ) + 2 );
263 assoc += face_index; /* add on the face_index! */
265 /* if the face at this index is not scalable,
266 fall back to the first one (old behavior) */
267 if ( assoc->fontSize == 0 )
270 *sfnt_id = assoc->fontID;
272 else if ( base_assoc->fontSize == 0 )
275 *sfnt_id = base_assoc->fontID;
278 if ( fond->ffStylOff )
280 unsigned char* p = (unsigned char*)fond_data;
282 unsigned short string_count;
284 unsigned char* names[64];
288 p += fond->ffStylOff;
289 style = (StyleTable*)p;
290 p += sizeof ( StyleTable );
291 string_count = *(unsigned short*)(p);
292 p += sizeof ( short );
294 for ( i = 0 ; i < string_count && i < 64; i++ )
302 size_t ps_name_len = (size_t)names[0][0];
305 if ( ps_name_len != 0 )
307 memcpy(ps_name, names[0] + 1, ps_name_len);
308 ps_name[ps_name_len] = 0;
310 if ( style->indexes[0] > 1 )
312 unsigned char* suffixes = names[style->indexes[0] - 1];
315 for ( i = 1; i < suffixes[0]; i++ )
318 size_t j = suffixes[i] - 1;
321 if ( j < string_count && ( s = names[j] ) != NULL )
323 size_t s_len = (size_t)s[0];
326 if ( s_len != 0 && ps_name_len + s_len < sizeof ( ps_name ) )
328 memcpy( ps_name + ps_name_len, s + 1, s_len );
329 ps_name_len += s_len;
330 ps_name[ps_name_len] = 0;
337 create_lwfn_name( ps_name, lwfn_file_name );
342 /* Read Type 1 data from the POST resources inside the LWFN file,
343 return a PFB buffer. This is somewhat convoluted because the FT2
344 PFB parser wants the ASCII header as one chunk, and the LWFN
345 chunks are often not organized that way, so we'll glue chunks
346 of the same type together. */
348 read_lwfn( FT_Memory memory,
353 FT_Error error = FT_Err_Ok;
354 short res_ref, res_id;
355 unsigned char *buffer, *p, *size_p = NULL;
356 FT_ULong total_size = 0;
357 FT_ULong post_size, pfb_chunk_size;
359 char code, last_code;
362 res_ref = FSpOpenResFile( lwfn_spec, fsRdPerm );
364 return FT_Err_Out_Of_Memory;
365 UseResFile( res_ref );
367 /* First pass: load all POST resources, and determine the size of
368 the output buffer. */
374 post_data = Get1Resource( 'POST', res_id++ );
375 if ( post_data == NULL )
376 break; /* we're done */
378 code = (*post_data)[0];
380 if ( code != last_code )
383 total_size += 2; /* just the end code */
385 total_size += 6; /* code + 4 bytes chunk length */
388 total_size += GetHandleSize( post_data ) - 2;
392 if ( FT_ALLOC( buffer, (FT_Long)total_size ) )
395 /* Second pass: append all POST data to the buffer, add PFB fields.
396 Glue all consecutive chunks of the same type together. */
404 post_data = Get1Resource( 'POST', res_id++ );
405 if ( post_data == NULL )
406 break; /* we're done */
408 post_size = (FT_ULong)GetHandleSize( post_data ) - 2;
409 code = (*post_data)[0];
411 if ( code != last_code )
413 if ( last_code != -1 )
415 /* we're done adding a chunk, fill in the size field */
416 if ( size_p != NULL )
418 *size_p++ = (FT_Byte)( pfb_chunk_size & 0xFF );
419 *size_p++ = (FT_Byte)( ( pfb_chunk_size >> 8 ) & 0xFF );
420 *size_p++ = (FT_Byte)( ( pfb_chunk_size >> 16 ) & 0xFF );
421 *size_p++ = (FT_Byte)( ( pfb_chunk_size >> 24 ) & 0xFF );
428 *p++ = 0x03; /* the end */
429 else if ( code == 2 )
430 *p++ = 0x02; /* binary segment */
432 *p++ = 0x01; /* ASCII segment */
436 size_p = p; /* save for later */
437 p += 4; /* make space for size field */
441 ft_memcpy( p, *post_data + 2, post_size );
442 pfb_chunk_size += post_size;
451 CloseResFile( res_ref );
456 /* Finalizer for a memory stream; gets called by FT_Done_Face().
457 It frees the memory it uses. */
459 memory_stream_close( FT_Stream stream )
461 FT_Memory memory = stream->memory;
464 FT_FREE( stream->base );
472 /* Create a new memory stream from a buffer and a size. */
474 new_memory_stream( FT_Library library,
477 FT_Stream_CloseFunc close,
486 return FT_Err_Invalid_Library_Handle;
489 return FT_Err_Invalid_Argument;
492 memory = library->memory;
493 if ( FT_NEW( stream ) )
496 FT_Stream_OpenMemory( stream, base, size );
498 stream->close = close;
507 /* Create a new FT_Face given a buffer and a driver name. */
509 open_face_from_buffer( FT_Library library,
519 FT_Memory memory = library->memory;
522 error = new_memory_stream( library,
533 args.flags = FT_OPEN_STREAM;
534 args.stream = stream;
537 args.flags = args.flags | FT_OPEN_DRIVER;
538 args.driver = FT_Get_Module( library, driver_name );
541 error = FT_Open_Face( library, &args, face_index, aface );
542 if ( error == FT_Err_Ok )
543 (*aface)->face_flags &= ~FT_FACE_FLAG_EXTERNAL_STREAM;
546 FT_Stream_CloseFunc( stream );
554 /* Create a new FT_Face from a file spec to an LWFN file. */
556 FT_New_Face_From_LWFN( FT_Library library,
566 error = read_lwfn( library->memory, spec, &pfb_data, &pfb_size );
570 return open_face_from_buffer( library,
579 /* Create a new FT_Face from an SFNT resource, specified by res ID. */
581 FT_New_Face_From_SFNT( FT_Library library,
590 FT_Memory memory = library->memory;
593 sfnt = GetResource( 'sfnt', sfnt_id );
595 return FT_Err_Invalid_Handle;
597 sfnt_size = (FT_ULong)GetHandleSize( sfnt );
598 if ( FT_ALLOC( sfnt_data, (FT_Long)sfnt_size ) )
600 ReleaseResource( sfnt );
605 ft_memcpy( sfnt_data, *sfnt, sfnt_size );
607 ReleaseResource( sfnt );
609 return open_face_from_buffer( library,
618 /* Create a new FT_Face from a file spec to a suitcase file. */
620 FT_New_Face_From_Suitcase( FT_Library library,
625 FT_Error error = FT_Err_Ok;
626 short res_ref, res_index;
630 res_ref = FSpOpenResFile( spec, fsRdPerm );
632 return FT_Err_Cannot_Open_Resource;
633 UseResFile( res_ref );
635 /* face_index may be -1, in which case we
636 just need to do a sanity check */
637 if ( face_index < 0 )
641 res_index = (short)( face_index + 1 );
644 fond = Get1IndResource( 'FOND', res_index );
647 error = FT_Err_Cannot_Open_Resource;
651 error = FT_New_Face_From_FOND( library, fond, face_index, aface );
654 CloseResFile( res_ref );
659 #if TARGET_API_MAC_CARBON
661 /* Create a new FT_Face from a file spec to a suitcase file. */
663 FT_New_Face_From_dfont( FT_Library library,
668 FT_Error error = FT_Err_Ok;
669 short res_ref, res_index;
671 FSRef hostContainerRef;
674 error = FSpMakeFSRef( spec, &hostContainerRef );
675 if ( error == noErr )
676 error = FSOpenResourceFile( &hostContainerRef,
677 0, NULL, fsRdPerm, &res_ref );
679 if ( error != noErr )
680 return FT_Err_Cannot_Open_Resource;
682 UseResFile( res_ref );
684 /* face_index may be -1, in which case we
685 just need to do a sanity check */
686 if ( face_index < 0 )
690 res_index = (short)( face_index + 1 );
693 fond = Get1IndResource( 'FOND', res_index );
696 error = FT_Err_Cannot_Open_Resource;
700 error = FT_New_Face_From_FOND( library, fond, face_index, aface );
703 CloseResFile( res_ref );
710 /* documentation is in ftmac.h */
712 FT_EXPORT_DEF( FT_Error )
713 FT_New_Face_From_FOND( FT_Library library,
718 short sfnt_id, have_sfnt, have_lwfn = 0;
719 Str255 lwfn_file_name;
726 GetResInfo( fond, &fond_id, &fond_type, fond_name );
727 if ( ResError() != noErr || fond_type != 'FOND' )
728 return FT_Err_Invalid_File_Format;
731 parse_fond( *fond, &have_sfnt, &sfnt_id, lwfn_file_name, face_index );
734 if ( lwfn_file_name[0] )
736 if ( make_lwfn_spec( fond, lwfn_file_name, &lwfn_spec ) == FT_Err_Ok )
737 have_lwfn = 1; /* yeah, we got one! */
739 have_lwfn = 0; /* no LWFN file found */
742 if ( have_lwfn && ( !have_sfnt || PREFER_LWFN ) )
743 return FT_New_Face_From_LWFN( library,
747 else if ( have_sfnt )
748 return FT_New_Face_From_SFNT( library,
753 return FT_Err_Unknown_File_Format;
757 /* documentation is in ftmac.h */
759 FT_EXPORT_DEF( FT_Error )
760 FT_GetFile_From_Mac_Name( char* fontName,
762 FT_Long* face_index )
764 OptionBits options = kFMUseGlobalScopeOption;
766 FMFontFamilyIterator famIter;
767 OSStatus status = FMCreateFontFamilyIterator( NULL, NULL,
770 FMFont the_font = NULL;
771 FMFontFamily family = NULL;
775 while ( status == 0 && !the_font )
777 status = FMGetNextFontFamily( &famIter, &family );
781 FMFontFamilyInstanceIterator instIter;
786 /* get the family name */
787 FMGetFontFamilyName( family, famNameStr );
788 CopyPascalStringToC( famNameStr, famName );
790 /* iterate through the styles */
791 FMCreateFontFamilyInstanceIterator( family, &instIter );
795 while ( stat2 == 0 && !the_font )
802 stat2 = FMGetNextFontFamilyInstance( &instIter, &font,
804 if ( stat2 == 0 && size == 0 )
809 /* build up a complete face name */
810 ft_strcpy( fullName, famName );
812 strcat( fullName, " Bold" );
813 if ( style & italic )
814 strcat( fullName, " Italic" );
816 /* compare with the name we are looking for */
817 if ( ft_strcmp( fullName, fontName ) == 0 )
827 FMDisposeFontFamilyInstanceIterator( &instIter );
831 FMDisposeFontFamilyIterator( &famIter );
835 FMGetFontContainer( the_font, pathSpec );
839 return FT_Err_Unknown_File_Format;
844 ResourceForkSize(FSSpec* spec)
851 e = FSpOpenRF( spec, fsRdPerm, &refNum ); /* I.M. Files 2-155 */
854 e = GetEOF( refNum, &len );
858 return ( e == noErr ) ? len : 0;
862 /*************************************************************************/
868 /* This is the Mac-specific implementation of FT_New_Face. In */
869 /* addition to the standard FT_New_Face() functionality, it also */
870 /* accepts pathnames to Mac suitcase files. For further */
871 /* documentation see the original FT_New_Face() in freetype.h. */
873 FT_EXPORT_DEF( FT_Error )
874 FT_New_Face( FT_Library library,
875 const char* pathname,
884 /* test for valid `library' and `aface' delayed to FT_Open_Face() */
886 return FT_Err_Invalid_Argument;
888 if ( file_spec_from_path( pathname, &spec ) )
889 return FT_Err_Invalid_Argument;
891 /* Regardless of type, don't try to use the resource fork if it is */
892 /* empty. Some TTF fonts have type `FFIL', for example, but they */
893 /* only have data forks. */
895 if ( ResourceForkSize( &spec ) != 0 )
897 file_type = get_file_type( &spec );
898 if ( file_type == 'FFIL' || file_type == 'tfil' )
899 return FT_New_Face_From_Suitcase( library, &spec, face_index, aface );
901 if ( file_type == 'LWFN' )
902 return FT_New_Face_From_LWFN( library, &spec, face_index, aface );
905 #if TARGET_API_MAC_CARBON
907 if ( is_dfont( &spec ) )
908 return FT_New_Face_From_dfont( library, &spec, face_index, aface );
912 /* let it fall through to normal loader (.ttf, .otf, etc.) */
913 args.flags = FT_OPEN_PATHNAME;
914 args.pathname = (char*)pathname;
915 return FT_Open_Face( library, &args, face_index, aface );