1
/***************************************************************************/
5
/* Mac FOND support. Written by just@letterror.com. */
7
/* Copyright 1996-2001, 2002, 2003, 2004 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
67
#include "../truetype/ttobjs.h"
68
#include "../type1/t1objs.h"
69
/* This is for Mac OS X. Without redefinition, OS_INLINE */
70
/* expands to `static inline' which doesn't survive the */
71
/* -ansi compilation flag of GCC. */
72
#define OS_INLINE static __inline__
73
#include <Carbon/Carbon.h>
75
#include "truetype/ttobjs.h"
76
#include "type1/t1objs.h"
77
#include <Resources.h>
81
#include <TextUtils.h>
84
#if defined( __MWERKS__ ) && !TARGET_RT_MAC_MACHO
85
#include <FSp_fopen.h>
91
/* Set PREFER_LWFN to 1 if LWFN (Type 1) is preferred over
92
TrueType in case *both* are available (this is not common,
93
but it *is* possible). */
99
#if defined( __MWERKS__ ) && !TARGET_RT_MAC_MACHO
101
#define STREAM_FILE( stream ) ( (FILE*)stream->descriptor.pointer )
104
FT_CALLBACK_DEF( void )
105
ft_FSp_stream_close( FT_Stream stream )
107
fclose( STREAM_FILE( stream ) );
109
stream->descriptor.pointer = NULL;
115
FT_CALLBACK_DEF( unsigned long )
116
ft_FSp_stream_io( FT_Stream stream,
117
unsigned long offset,
118
unsigned char* buffer,
119
unsigned long count )
124
file = STREAM_FILE( stream );
126
fseek( file, offset, SEEK_SET );
128
return (unsigned long)fread( buffer, 1, count, file );
131
#endif /* __MWERKS__ && !TARGET_RT_MAC_MACHO */
134
/* Given a pathname, fill in a file spec. */
136
file_spec_from_path( const char* pathname,
140
#if !TARGET_API_MAC_OS8 && \
141
!( defined( __MWERKS__ ) && !TARGET_RT_MAC_MACHO )
147
e = FSPathMakeRef( (UInt8 *)pathname, &ref, false /* not a directory */ );
149
e = FSGetCatalogInfo( &ref, kFSCatInfoNone, NULL, NULL, spec, NULL );
151
return ( e == noErr ) ? 0 : (-1);
159
/* convert path to a pascal string */
160
path_len = ft_strlen( pathname );
161
if ( path_len > 255 )
163
p_path[0] = (unsigned char)path_len;
164
ft_strncpy( (char*)p_path + 1, pathname, path_len );
166
if ( FSMakeFSSpec( 0, 0, p_path, spec ) != noErr )
176
/* Return the file type of the file specified by spec. */
178
get_file_type( const FSSpec* spec )
183
if ( FSpGetFInfo( spec, &finfo ) != noErr )
184
return 0; /* file might not exist */
190
/* Given a PostScript font name, create the Macintosh LWFN file name. */
192
create_lwfn_name( char* ps_name,
193
Str255 lwfn_file_name )
195
int max = 5, count = 0;
196
FT_Byte* p = lwfn_file_name;
197
FT_Byte* q = (FT_Byte*)ps_name;
200
lwfn_file_name[0] = 0;
204
if ( ft_isupper( *q ) )
210
if ( count < max && ( ft_isalnum( *q ) || *q == '_' ) )
221
/* Given a file reference, answer its location as a vRefNum
224
get_file_location( short ref_num,
227
unsigned char* file_name )
233
pb.ioNamePtr = file_name;
235
pb.ioRefNum = ref_num;
238
error = PBGetFCBInfoSync( &pb );
239
if ( error == noErr )
241
*v_ref_num = pb.ioFCBVRefNum;
242
*dir_id = pb.ioFCBParID;
248
/* Make a file spec for an LWFN file from a FOND resource and
251
make_lwfn_spec( Handle fond,
252
const unsigned char* file_name,
256
short ref_num, v_ref_num;
258
Str255 fond_file_name;
261
ref_num = HomeResFile( fond );
265
error = get_file_location( ref_num, &v_ref_num,
266
&dir_id, fond_file_name );
268
error = FSMakeFSSpec( v_ref_num, dir_id, file_name, spec );
275
count_faces_sfnt( char *fond_data )
277
/* The count is 1 greater than the value in the FOND. */
278
/* Isn't that cute? :-) */
280
return 1 + *( (short *)( fond_data + sizeof ( FamRec ) ) );
284
/* Look inside the FOND data, answer whether there should be an SFNT
285
resource, and answer the name of a possible LWFN Type 1 file.
287
Thanks to Paul Miller (paulm@profoundeffects.com) for the fix
288
to load a face OTHER than the first one in the FOND!
293
parse_fond( char* fond_data,
296
Str255 lwfn_file_name,
300
AsscEntry* base_assoc;
306
lwfn_file_name[0] = 0;
308
fond = (FamRec*)fond_data;
309
assoc = (AsscEntry*)( fond_data + sizeof ( FamRec ) + 2 );
312
/* Let's do a little range checking before we get too excited here */
313
if ( face_index < count_faces_sfnt( fond_data ) )
315
assoc += face_index; /* add on the face_index! */
317
/* if the face at this index is not scalable,
318
fall back to the first one (old behavior) */
319
if ( assoc->fontSize == 0 )
322
*sfnt_id = assoc->fontID;
324
else if ( base_assoc->fontSize == 0 )
327
*sfnt_id = base_assoc->fontID;
331
if ( fond->ffStylOff )
333
unsigned char* p = (unsigned char*)fond_data;
335
unsigned short string_count;
337
unsigned char* names[64];
341
p += fond->ffStylOff;
342
style = (StyleTable*)p;
343
p += sizeof ( StyleTable );
344
string_count = *(unsigned short*)(p);
345
p += sizeof ( short );
347
for ( i = 0 ; i < string_count && i < 64; i++ )
355
size_t ps_name_len = (size_t)names[0][0];
358
if ( ps_name_len != 0 )
360
ft_memcpy(ps_name, names[0] + 1, ps_name_len);
361
ps_name[ps_name_len] = 0;
363
if ( style->indexes[0] > 1 )
365
unsigned char* suffixes = names[style->indexes[0] - 1];
368
for ( i = 1; i <= suffixes[0]; i++ )
371
size_t j = suffixes[i] - 1;
374
if ( j < string_count && ( s = names[j] ) != NULL )
376
size_t s_len = (size_t)s[0];
379
if ( s_len != 0 && ps_name_len + s_len < sizeof ( ps_name ) )
381
ft_memcpy( ps_name + ps_name_len, s + 1, s_len );
382
ps_name_len += s_len;
383
ps_name[ps_name_len] = 0;
390
create_lwfn_name( ps_name, lwfn_file_name );
396
count_faces( Handle fond )
398
short sfnt_id, have_sfnt, have_lwfn = 0;
399
Str255 lwfn_file_name;
404
parse_fond( *fond, &have_sfnt, &sfnt_id, lwfn_file_name, 0 );
407
if ( lwfn_file_name[0] )
409
if ( make_lwfn_spec( fond, lwfn_file_name, &lwfn_spec ) == FT_Err_Ok )
410
have_lwfn = 1; /* yeah, we got one! */
412
have_lwfn = 0; /* no LWFN file found */
415
if ( have_lwfn && ( !have_sfnt || PREFER_LWFN ) )
418
return count_faces_sfnt( *fond );
422
/* Read Type 1 data from the POST resources inside the LWFN file,
423
return a PFB buffer. This is somewhat convoluted because the FT2
424
PFB parser wants the ASCII header as one chunk, and the LWFN
425
chunks are often not organized that way, so we'll glue chunks
426
of the same type together. */
428
read_lwfn( FT_Memory memory,
433
FT_Error error = FT_Err_Ok;
435
unsigned char *buffer, *p, *size_p = NULL;
436
FT_ULong total_size = 0;
437
FT_ULong post_size, pfb_chunk_size;
439
char code, last_code;
442
UseResFile( res_ref );
444
/* First pass: load all POST resources, and determine the size of */
445
/* the output buffer. */
451
post_data = Get1Resource( 'POST', res_id++ );
452
if ( post_data == NULL )
453
break; /* we're done */
455
code = (*post_data)[0];
457
if ( code != last_code )
460
total_size += 2; /* just the end code */
462
total_size += 6; /* code + 4 bytes chunk length */
465
total_size += GetHandleSize( post_data ) - 2;
469
if ( FT_ALLOC( buffer, (FT_Long)total_size ) )
472
/* Second pass: append all POST data to the buffer, add PFB fields. */
473
/* Glue all consecutive chunks of the same type together. */
481
post_data = Get1Resource( 'POST', res_id++ );
482
if ( post_data == NULL )
483
break; /* we're done */
485
post_size = (FT_ULong)GetHandleSize( post_data ) - 2;
486
code = (*post_data)[0];
488
if ( code != last_code )
490
if ( last_code != -1 )
492
/* we're done adding a chunk, fill in the size field */
493
if ( size_p != NULL )
495
*size_p++ = (FT_Byte)( pfb_chunk_size & 0xFF );
496
*size_p++ = (FT_Byte)( ( pfb_chunk_size >> 8 ) & 0xFF );
497
*size_p++ = (FT_Byte)( ( pfb_chunk_size >> 16 ) & 0xFF );
498
*size_p++ = (FT_Byte)( ( pfb_chunk_size >> 24 ) & 0xFF );
505
*p++ = 0x03; /* the end */
506
else if ( code == 2 )
507
*p++ = 0x02; /* binary segment */
509
*p++ = 0x01; /* ASCII segment */
513
size_p = p; /* save for later */
514
p += 4; /* make space for size field */
518
ft_memcpy( p, *post_data + 2, post_size );
519
pfb_chunk_size += post_size;
528
CloseResFile( res_ref );
533
/* Finalizer for a memory stream; gets called by FT_Done_Face().
534
It frees the memory it uses. */
536
memory_stream_close( FT_Stream stream )
538
FT_Memory memory = stream->memory;
541
FT_FREE( stream->base );
549
/* Create a new memory stream from a buffer and a size. */
551
new_memory_stream( FT_Library library,
554
FT_Stream_CloseFunc close,
563
return FT_Err_Invalid_Library_Handle;
566
return FT_Err_Invalid_Argument;
569
memory = library->memory;
570
if ( FT_NEW( stream ) )
573
FT_Stream_OpenMemory( stream, base, size );
575
stream->close = close;
584
/* Create a new FT_Face given a buffer and a driver name. */
586
open_face_from_buffer( FT_Library library,
596
FT_Memory memory = library->memory;
599
error = new_memory_stream( library,
610
args.flags = FT_OPEN_STREAM;
611
args.stream = stream;
614
args.flags = args.flags | FT_OPEN_DRIVER;
615
args.driver = FT_Get_Module( library, driver_name );
618
/* At this point, face_index has served its purpose; */
619
/* whoever calls this function has already used it to */
620
/* locate the correct font data. We should not propagate */
621
/* this index to FT_Open_Face() (unless it is negative). */
623
if ( face_index > 0 )
626
error = FT_Open_Face( library, &args, face_index, aface );
627
if ( error == FT_Err_Ok )
628
(*aface)->face_flags &= ~FT_FACE_FLAG_EXTERNAL_STREAM;
635
OpenFileAsResource( const FSSpec* spec,
640
#if !TARGET_API_MAC_OS8
642
FSRef hostContainerRef;
645
error = FSpMakeFSRef( spec, &hostContainerRef );
646
if ( error == noErr )
647
error = FSOpenResourceFile( &hostContainerRef,
648
0, NULL, fsRdPerm, p_res_ref );
650
/* If the above fails, then it is probably not a resource file */
651
/* However, it has been reported that FSOpenResourceFile() sometimes */
652
/* fails on some old resource-fork files, which FSpOpenResFile() can */
653
/* open. So, just try again with FSpOpenResFile() and see what */
656
if ( error != noErr )
658
#endif /* !TARGET_API_MAC_OS8 */
661
*p_res_ref = FSpOpenResFile( spec, fsRdPerm );
665
return error ? FT_Err_Cannot_Open_Resource : FT_Err_Ok;
669
/* Create a new FT_Face from a file spec to an LWFN file. */
671
FT_New_Face_From_LWFN( FT_Library library,
672
const FSSpec* lwfn_spec,
682
error = OpenFileAsResource( lwfn_spec, &res_ref );
686
error = read_lwfn( library->memory, res_ref, &pfb_data, &pfb_size );
690
return open_face_from_buffer( library,
699
/* Create a new FT_Face from an SFNT resource, specified by res ID. */
701
FT_New_Face_From_SFNT( FT_Library library,
710
FT_Memory memory = library->memory;
714
sfnt = GetResource( 'sfnt', sfnt_id );
716
return FT_Err_Invalid_Handle;
718
sfnt_size = (FT_ULong)GetHandleSize( sfnt );
719
if ( FT_ALLOC( sfnt_data, (FT_Long)sfnt_size ) )
721
ReleaseResource( sfnt );
726
ft_memcpy( sfnt_data, *sfnt, sfnt_size );
728
ReleaseResource( sfnt );
730
is_cff = sfnt_size > 4 && sfnt_data[0] == 'O' &&
731
sfnt_data[1] == 'T' &&
732
sfnt_data[2] == 'T' &&
735
return open_face_from_buffer( library,
739
is_cff ? "cff" : "truetype",
744
/* Create a new FT_Face from a file spec to a suitcase file. */
746
FT_New_Face_From_Suitcase( FT_Library library,
751
FT_Error error = FT_Err_Ok;
757
UseResFile( res_ref );
759
for ( res_index = 1; ; ++res_index )
761
fond = Get1IndResource( 'FOND', res_index );
764
error = FT_Err_Cannot_Open_Resource;
767
if ( face_index < 0 )
770
num_faces = count_faces( fond );
771
if ( face_index < num_faces )
774
face_index -= num_faces;
777
error = FT_New_Face_From_FOND( library, fond, face_index, aface );
780
CloseResFile( res_ref );
785
/* documentation is in ftmac.h */
787
FT_EXPORT_DEF( FT_Error )
788
FT_New_Face_From_FOND( FT_Library library,
793
short sfnt_id, have_sfnt, have_lwfn = 0;
794
Str255 lwfn_file_name;
801
GetResInfo( fond, &fond_id, &fond_type, fond_name );
802
if ( ResError() != noErr || fond_type != 'FOND' )
803
return FT_Err_Invalid_File_Format;
806
parse_fond( *fond, &have_sfnt, &sfnt_id, lwfn_file_name, face_index );
809
if ( lwfn_file_name[0] )
811
if ( make_lwfn_spec( fond, lwfn_file_name, &lwfn_spec ) == FT_Err_Ok )
812
have_lwfn = 1; /* yeah, we got one! */
814
have_lwfn = 0; /* no LWFN file found */
817
if ( have_lwfn && ( !have_sfnt || PREFER_LWFN ) )
818
return FT_New_Face_From_LWFN( library,
822
else if ( have_sfnt )
823
return FT_New_Face_From_SFNT( library,
828
return FT_Err_Unknown_File_Format;
832
/* documentation is in ftmac.h */
834
FT_EXPORT_DEF( FT_Error )
835
FT_GetFile_From_Mac_Name( const char* fontName,
837
FT_Long* face_index )
839
OptionBits options = kFMUseGlobalScopeOption;
841
FMFontFamilyIterator famIter;
842
OSStatus status = FMCreateFontFamilyIterator( NULL, NULL,
845
FMFont the_font = NULL;
846
FMFontFamily family = NULL;
850
while ( status == 0 && !the_font )
852
status = FMGetNextFontFamily( &famIter, &family );
856
FMFontFamilyInstanceIterator instIter;
861
/* get the family name */
862
FMGetFontFamilyName( family, famNameStr );
863
CopyPascalStringToC( famNameStr, famName );
865
/* iterate through the styles */
866
FMCreateFontFamilyInstanceIterator( family, &instIter );
870
while ( stat2 == 0 && !the_font )
877
stat2 = FMGetNextFontFamilyInstance( &instIter, &font,
879
if ( stat2 == 0 && size == 0 )
884
/* build up a complete face name */
885
ft_strcpy( fullName, famName );
887
strcat( fullName, " Bold" );
888
if ( style & italic )
889
strcat( fullName, " Italic" );
891
/* compare with the name we are looking for */
892
if ( ft_strcmp( fullName, fontName ) == 0 )
902
FMDisposeFontFamilyInstanceIterator( &instIter );
906
FMDisposeFontFamilyIterator( &famIter );
910
FMGetFontContainer( the_font, pathSpec );
914
return FT_Err_Unknown_File_Format;
917
/* Common function to load a new FT_Face from a resource file. */
920
FT_New_Face_From_Resource( FT_Library library,
930
if ( OpenFileAsResource( spec, &res_ref ) == FT_Err_Ok )
932
/* LWFN is a (very) specific file format, check for it explicitly */
934
file_type = get_file_type( spec );
935
if ( file_type == 'LWFN' )
936
return FT_New_Face_From_LWFN( library, spec, face_index, aface );
938
/* Otherwise the file type doesn't matter (there are more than */
939
/* `FFIL' and `tfil'). Just try opening it as a font suitcase; */
940
/* if it works, fine. */
942
error = FT_New_Face_From_Suitcase( library, res_ref,
947
/* else forget about the resource fork and fall through to */
948
/* data fork formats */
950
CloseResFile( res_ref );
953
/* let it fall through to normal loader (.ttf, .otf, etc.); */
954
/* we signal this by returning no error and no FT_Face */
960
/*************************************************************************/
966
/* This is the Mac-specific implementation of FT_New_Face. In */
967
/* addition to the standard FT_New_Face() functionality, it also */
968
/* accepts pathnames to Mac suitcase files. For further */
969
/* documentation see the original FT_New_Face() in freetype.h. */
971
FT_EXPORT_DEF( FT_Error )
972
FT_New_Face( FT_Library library,
973
const char* pathname,
982
/* test for valid `library' and `aface' delayed to FT_Open_Face() */
984
return FT_Err_Invalid_Argument;
986
if ( file_spec_from_path( pathname, &spec ) )
987
return FT_Err_Invalid_Argument;
989
error = FT_New_Face_From_Resource( library, &spec, face_index, aface );
990
if ( error != 0 || *aface != NULL )
993
/* let it fall through to normal loader (.ttf, .otf, etc.) */
994
args.flags = FT_OPEN_PATHNAME;
995
args.pathname = (char*)pathname;
996
return FT_Open_Face( library, &args, face_index, aface );
1000
/*************************************************************************/
1003
/* FT_New_Face_From_FSSpec */
1006
/* FT_New_Face_From_FSSpec is identical to FT_New_Face except it */
1007
/* accepts an FSSpec instead of a path. */
1009
FT_EXPORT_DEF( FT_Error )
1010
FT_New_Face_From_FSSpec( FT_Library library,
1022
/* test for valid `library' and `aface' delayed to FT_Open_Face() */
1024
return FT_Err_Invalid_Argument;
1026
error = FT_New_Face_From_Resource( library, spec, face_index, aface );
1027
if ( error != 0 || *aface != NULL )
1030
/* let it fall through to normal loader (.ttf, .otf, etc.) */
1032
#if defined( __MWERKS__ ) && !TARGET_RT_MAC_MACHO
1034
/* Codewarrior's C library can open a FILE from a FSSpec */
1035
/* but we must compile with FSp_fopen.c in addition to */
1036
/* runtime libraries. */
1038
memory = library->memory;
1040
if ( FT_NEW( stream ) )
1042
stream->memory = memory;
1044
file = FSp_fopen( spec, "rb" );
1046
return FT_Err_Cannot_Open_Resource;
1048
fseek( file, 0, SEEK_END );
1049
stream->size = ftell( file );
1050
fseek( file, 0, SEEK_SET );
1052
stream->descriptor.pointer = file;
1053
stream->pathname.pointer = NULL;
1056
stream->read = ft_FSp_stream_io;
1057
stream->close = ft_FSp_stream_close;
1059
args.flags = FT_OPEN_STREAM;
1060
args.stream = stream;
1062
error = FT_Open_Face( library, &args, face_index, aface );
1063
if ( error == FT_Err_Ok )
1064
(*aface)->face_flags &= ~FT_FACE_FLAG_EXTERNAL_STREAM;
1066
#else /* !(__MWERKS__ && !TARGET_RT_MAC_MACHO) */
1074
err = FSpMakeFSRef(spec, &ref);
1077
err = FSRefMakePath( &ref, path, sizeof ( path ) );
1079
error = FT_New_Face( library, (const char*)path,
1080
face_index, aface );
1083
error = FT_Err_Cannot_Open_Resource;
1086
#endif /* !(__MWERKS__ && !TARGET_RT_MAC_MACHO) */