1 /***************************************************************************/
2 /* */
3 /* ftmac.c */
4 /* */
5 /* Mac FOND support. Written by just@letterror.com. */
6 /* Heavily Fixed by mpsuzuki, George Williams and Sean McBride */
7 /* */
8 /* Copyright (C) 1996-2023 by */
9 /* Just van Rossum, David Turner, Robert Wilhelm, and Werner Lemberg. */
10 /* */
11 /* This file is part of the FreeType project, and may only be used, */
12 /* modified, and distributed under the terms of the FreeType project */
13 /* license, LICENSE.TXT. By continuing to use, modify, or distribute */
14 /* this file you indicate that you have read the license and */
15 /* understand and accept it fully. */
16 /* */
17 /***************************************************************************/
18
19
20 /*
21 Notes
22
23 Mac suitcase files can (and often do!) contain multiple fonts. To
24 support this I use the face_index argument of FT_(Open|New)_Face()
25 functions, and pretend the suitcase file is a collection.
26
27 Warning: fbit and NFNT bitmap resources are not supported yet. In old
28 sfnt fonts, bitmap glyph data for each size is stored in each `NFNT'
29 resources instead of the `bdat' table in the sfnt resource. Therefore,
30 face->num_fixed_sizes is set to 0, because bitmap data in `NFNT'
31 resource is unavailable at present.
32
33 The Mac FOND support works roughly like this:
34
35 - Check whether the offered stream points to a Mac suitcase file. This
36 is done by checking the file type: it has to be 'FFIL' or 'tfil'. The
37 stream that gets passed to our init_face() routine is a stdio stream,
38 which isn't usable for us, since the FOND resources live in the
39 resource fork. So we just grab the stream->pathname field.
40
41 - Read the FOND resource into memory, then check whether there is a
42 TrueType font and/or(!) a Type 1 font available.
43
44 - If there is a Type 1 font available (as a separate `LWFN' file), read
45 its data into memory, massage it slightly so it becomes PFB data, wrap
46 it into a memory stream, load the Type 1 driver and delegate the rest
47 of the work to it by calling FT_Open_Face(). (XXX TODO: after this
48 has been done, the kerning data from the FOND resource should be
49 appended to the face: On the Mac there are usually no AFM files
50 available. However, this is tricky since we need to map Mac char
51 codes to ps glyph names to glyph ID's...)
52
53 - If there is a TrueType font (an `sfnt' resource), read it into memory,
54 wrap it into a memory stream, load the TrueType driver and delegate
55 the rest of the work to it, by calling FT_Open_Face().
56
57 - Some suitcase fonts (notably Onyx) might point the `LWFN' file to
58 itself, even though it doesn't contains `POST' resources. To handle
59 this special case without opening the file an extra time, we just
60 ignore errors from the `LWFN' and fallback to the `sfnt' if both are
61 available.
62 */
63
64
65 #include <freetype/freetype.h>
66 #include <freetype/tttags.h>
67 #include <freetype/internal/ftstream.h>
68 #include "ftbase.h"
69
70 #if defined( __GNUC__ ) || defined( __IBMC__ )
71 /* This is for Mac OS X. Without redefinition, OS_INLINE */
72 /* expands to `static inline' which doesn't survive the */
73 /* -ansi compilation flag of GCC. */
74 #if !HAVE_ANSI_OS_INLINE
75 #undef OS_INLINE
76 #define OS_INLINE static __inline__
77 #endif
78 #include <CoreServices/CoreServices.h>
79 #include <ApplicationServices/ApplicationServices.h>
80 #include <sys/syslimits.h> /* PATH_MAX */
81 #else
82 #include <Resources.h>
83 #include <Fonts.h>
84 #include <Endian.h>
85 #include <Errors.h>
86 #include <Files.h>
87 #include <TextUtils.h>
88 #endif
89
90 #ifndef PATH_MAX
91 #define PATH_MAX 1024 /* same with Mac OS X's syslimits.h */
92 #endif
93
94 #if defined( __MWERKS__ ) && !TARGET_RT_MAC_MACHO
95 #include <FSp_fopen.h>
96 #endif
97
98 #define FT_DEPRECATED_ATTRIBUTE
99
100 #include <freetype/ftmac.h>
101
102 /* undefine blocking-macros in ftmac.h */
103 #undef FT_GetFile_From_Mac_Name
104 #undef FT_GetFile_From_Mac_ATS_Name
105 #undef FT_New_Face_From_FOND
106 #undef FT_New_Face_From_FSSpec
107 #undef FT_New_Face_From_FSRef
108
109
110 /* FSSpec functions are deprecated since Mac OS X 10.4 */
111 #ifndef HAVE_FSSPEC
112 #if TARGET_API_MAC_OS8 || TARGET_API_MAC_CARBON
113 #define HAVE_FSSPEC 1
114 #else
115 #define HAVE_FSSPEC 0
116 #endif
117 #endif
118
119 /* most FSRef functions were introduced since Mac OS 9 */
120 #ifndef HAVE_FSREF
121 #if TARGET_API_MAC_OSX
122 #define HAVE_FSREF 1
123 #else
124 #define HAVE_FSREF 0
125 #endif
126 #endif
127
128 /* QuickDraw is deprecated since Mac OS X 10.4 */
129 #ifndef HAVE_QUICKDRAW_CARBON
130 #if TARGET_API_MAC_OS8 || TARGET_API_MAC_CARBON
131 #define HAVE_QUICKDRAW_CARBON 1
132 #else
133 #define HAVE_QUICKDRAW_CARBON 0
134 #endif
135 #endif
136
137 /* AppleTypeService is available since Mac OS X */
138 #ifndef HAVE_ATS
139 #if TARGET_API_MAC_OSX
140 #define HAVE_ATS 1
141 #ifndef kATSOptionFlagsUnRestrictedScope /* since Mac OS X 10.1 */
142 #define kATSOptionFlagsUnRestrictedScope kATSOptionFlagsDefault
143 #endif
144 #else
145 #define HAVE_ATS 0
146 #endif
147 #endif
148
149 /* `configure' checks the availability of `ResourceIndex' strictly */
150 /* and sets HAVE_TYPE_RESOURCE_INDEX to 1 or 0 always. If it is */
151 /* not set (e.g., a build without `configure'), the availability */
152 /* is guessed from the SDK version. */
153 #ifndef HAVE_TYPE_RESOURCE_INDEX
154 #if !defined( MAC_OS_X_VERSION_10_5 ) || \
155 ( MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5 )
156 #define HAVE_TYPE_RESOURCE_INDEX 0
157 #else
158 #define HAVE_TYPE_RESOURCE_INDEX 1
159 #endif
160 #endif /* !HAVE_TYPE_RESOURCE_INDEX */
161
162 #if ( HAVE_TYPE_RESOURCE_INDEX == 0 )
163 typedef short ResourceIndex;
164 #endif
165
166 /* Set PREFER_LWFN to 1 if LWFN (Type 1) is preferred over
167 TrueType in case *both* are available (this is not common,
168 but it *is* possible). */
169 #ifndef PREFER_LWFN
170 #define PREFER_LWFN 1
171 #endif
172
173 #ifdef FT_MACINTOSH
174
175 #if !HAVE_QUICKDRAW_CARBON /* QuickDraw is deprecated since Mac OS X 10.4 */
176
177 FT_EXPORT_DEF( FT_Error )
178 FT_GetFile_From_Mac_Name( const char* fontName,
179 FSSpec* pathSpec,
180 FT_Long* face_index )
181 {
182 FT_UNUSED( fontName );
183 FT_UNUSED( pathSpec );
184 FT_UNUSED( face_index );
185
186 return FT_THROW( Unimplemented_Feature );
187 }
188
189 #else
190
191 FT_EXPORT_DEF( FT_Error )
192 FT_GetFile_From_Mac_Name( const char* fontName,
193 FSSpec* pathSpec,
194 FT_Long* face_index )
195 {
196 OptionBits options = kFMUseGlobalScopeOption;
197
198 FMFontFamilyIterator famIter;
199 OSStatus status = FMCreateFontFamilyIterator( NULL, NULL,
200 options,
201 &famIter );
202 FMFont the_font = 0;
203 FMFontFamily family = 0;
204
205
206 if ( !fontName || !face_index )
207 return FT_THROW( Invalid_Argument );
208
209 *face_index = 0;
210 while ( status == 0 && !the_font )
211 {
212 status = FMGetNextFontFamily( &famIter, &family );
213 if ( status == 0 )
214 {
215 int stat2;
216 FMFontFamilyInstanceIterator instIter;
217 Str255 famNameStr;
218 char famName[256];
219
220
221 /* get the family name */
222 FMGetFontFamilyName( family, famNameStr );
223 CopyPascalStringToC( famNameStr, famName );
224
225 /* iterate through the styles */
226 FMCreateFontFamilyInstanceIterator( family, &instIter );
227
228 *face_index = 0;
229 stat2 = 0;
230
231 while ( stat2 == 0 && !the_font )
232 {
233 FMFontStyle style;
234 FMFontSize size;
235 FMFont font;
236
237
238 stat2 = FMGetNextFontFamilyInstance( &instIter, &font,
239 &style, &size );
240 if ( stat2 == 0 && size == 0 )
241 {
242 char fullName[256];
243
244
245 /* build up a complete face name */
246 ft_strcpy( fullName, famName );
247 if ( style & bold )
248 ft_strcat( fullName, " Bold" );
249 if ( style & italic )
250 ft_strcat( fullName, " Italic" );
251
252 /* compare with the name we are looking for */
253 if ( ft_strcmp( fullName, fontName ) == 0 )
254 {
255 /* found it! */
256 the_font = font;
257 }
258 else
259 ++(*face_index);
260 }
261 }
262
263 FMDisposeFontFamilyInstanceIterator( &instIter );
264 }
265 }
266
267 FMDisposeFontFamilyIterator( &famIter );
268
269 if ( the_font )
270 {
271 FMGetFontContainer( the_font, pathSpec );
272 return FT_Err_Ok;
273 }
274 else
275 return FT_THROW( Unknown_File_Format );
276 }
277
278 #endif /* HAVE_QUICKDRAW_CARBON */
279
280
281 #if HAVE_ATS
282
283 /* Private function. */
284 /* The FSSpec type has been discouraged for a long time, */
285 /* unfortunately an FSRef replacement API for */
286 /* ATSFontGetFileSpecification() is only available in */
287 /* Mac OS X 10.5 and later. */
288 static OSStatus
289 FT_ATSFontGetFileReference( ATSFontRef ats_font_id,
290 FSRef* ats_font_ref )
291 {
292 OSStatus err;
293
294 #if !defined( MAC_OS_X_VERSION_10_5 ) || \
295 MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
296 FSSpec spec;
297
298
299 err = ATSFontGetFileSpecification( ats_font_id, &spec );
300 if ( noErr == err )
301 err = FSpMakeFSRef( &spec, ats_font_ref );
302 #else
303 err = ATSFontGetFileReference( ats_font_id, ats_font_ref );
304 #endif
305
306 return err;
307 }
308
309
310 static FT_Error
311 FT_GetFileRef_From_Mac_ATS_Name( const char* fontName,
312 FSRef* ats_font_ref,
313 FT_Long* face_index )
314 {
315 CFStringRef cf_fontName;
316 ATSFontRef ats_font_id;
317
318
319 *face_index = 0;
320
321 cf_fontName = CFStringCreateWithCString( NULL, fontName,
322 kCFStringEncodingMacRoman );
323 ats_font_id = ATSFontFindFromName( cf_fontName,
324 kATSOptionFlagsUnRestrictedScope );
325 CFRelease( cf_fontName );
326
327 if ( ats_font_id == 0 || ats_font_id == 0xFFFFFFFFUL )
328 return FT_THROW( Unknown_File_Format );
329
330 if ( noErr != FT_ATSFontGetFileReference( ats_font_id, ats_font_ref ) )
331 return FT_THROW( Unknown_File_Format );
332
333 /* face_index calculation by searching preceding fontIDs */
334 /* with same FSRef */
335 {
336 ATSFontRef id2 = ats_font_id - 1;
337 FSRef ref2;
338
339
340 while ( id2 > 0 )
341 {
342 if ( noErr != FT_ATSFontGetFileReference( id2, &ref2 ) )
343 break;
344 if ( noErr != FSCompareFSRefs( ats_font_ref, &ref2 ) )
345 break;
346
347 id2--;
348 }
349 *face_index = ats_font_id - ( id2 + 1 );
350 }
351
352 return FT_Err_Ok;
353 }
354
355 #endif
356
357 #if !HAVE_ATS
358
359 FT_EXPORT_DEF( FT_Error )
360 FT_GetFilePath_From_Mac_ATS_Name( const char* fontName,
361 UInt8* path,
362 UInt32 maxPathSize,
363 FT_Long* face_index )
364 {
365 FT_UNUSED( fontName );
366 FT_UNUSED( path );
367 FT_UNUSED( maxPathSize );
368 FT_UNUSED( face_index );
369
370 return FT_THROW( Unimplemented_Feature );
371 }
372
373 #else
374
375 FT_EXPORT_DEF( FT_Error )
376 FT_GetFilePath_From_Mac_ATS_Name( const char* fontName,
377 UInt8* path,
378 UInt32 maxPathSize,
379 FT_Long* face_index )
380 {
381 FSRef ref;
382 FT_Error err;
383
384
385 err = FT_GetFileRef_From_Mac_ATS_Name( fontName, &ref, face_index );
386 if ( err )
387 return err;
388
389 if ( noErr != FSRefMakePath( &ref, path, maxPathSize ) )
390 return FT_THROW( Unknown_File_Format );
391
392 return FT_Err_Ok;
393 }
394
395 #endif /* HAVE_ATS */
396
397
398 #if !HAVE_FSSPEC || !HAVE_ATS
399
400 FT_EXPORT_DEF( FT_Error )
401 FT_GetFile_From_Mac_ATS_Name( const char* fontName,
402 FSSpec* pathSpec,
403 FT_Long* face_index )
404 {
405 FT_UNUSED( fontName );
406 FT_UNUSED( pathSpec );
407 FT_UNUSED( face_index );
408
409 return FT_THROW( Unimplemented_Feature );
410 }
411
412 #else
413
414 /* This function is deprecated because FSSpec is deprecated in Mac OS X. */
415 FT_EXPORT_DEF( FT_Error )
416 FT_GetFile_From_Mac_ATS_Name( const char* fontName,
417 FSSpec* pathSpec,
418 FT_Long* face_index )
419 {
420 FSRef ref;
421 FT_Error err;
422
423
424 err = FT_GetFileRef_From_Mac_ATS_Name( fontName, &ref, face_index );
425 if ( err )
426 return err;
427
428 if ( noErr != FSGetCatalogInfo( &ref, kFSCatInfoNone, NULL, NULL,
429 pathSpec, NULL ) )
430 return FT_THROW( Unknown_File_Format );
431
432 return FT_Err_Ok;
433 }
434
435 #endif
436
437
438 #if defined( __MWERKS__ ) && !TARGET_RT_MAC_MACHO
439
440 #define STREAM_FILE( stream ) ( (FT_FILE*)stream->descriptor.pointer )
441
442
443 FT_CALLBACK_DEF( void )
444 ft_FSp_stream_close( FT_Stream stream )
445 {
446 ft_fclose( STREAM_FILE( stream ) );
447
448 stream->descriptor.pointer = NULL;
449 stream->size = 0;
450 stream->base = NULL;
451 }
452
453
454 FT_CALLBACK_DEF( unsigned long )
455 ft_FSp_stream_io( FT_Stream stream,
456 unsigned long offset,
457 unsigned char* buffer,
458 unsigned long count )
459 {
460 FT_FILE* file;
461
462
463 file = STREAM_FILE( stream );
464
465 ft_fseek( file, offset, SEEK_SET );
466
467 return (unsigned long)ft_fread( buffer, 1, count, file );
468 }
469
470 #endif /* __MWERKS__ && !TARGET_RT_MAC_MACHO */
471
472
473 #if HAVE_FSSPEC && !HAVE_FSREF
474
475 /* isDirectory is a dummy to synchronize API with FSPathMakeRef() */
476 static OSErr
477 FT_FSPathMakeSpec( const UInt8* pathname,
478 FSSpec* spec_p,
479 Boolean isDirectory )
480 {
481 const char *p, *q;
482 short vRefNum;
483 long dirID;
484 Str255 nodeName;
485 OSErr err;
486 FT_UNUSED( isDirectory );
487
488
489 p = q = (const char *)pathname;
490 dirID = 0;
491 vRefNum = 0;
492
493 while ( 1 )
494 {
495 int len = ft_strlen( p );
496
497
498 if ( len > 255 )
499 len = 255;
500
501 q = p + len;
502
503 if ( q == p )
504 return 0;
505
506 if ( 255 < ft_strlen( (char *)pathname ) )
507 {
508 while ( p < q && *q != ':' )
509 q--;
510 }
511
512 if ( p < q )
513 *(char *)nodeName = q - p;
514 else if ( ft_strlen( p ) < 256 )
515 *(char *)nodeName = ft_strlen( p );
516 else
517 return errFSNameTooLong;
518
519 ft_strncpy( (char *)nodeName + 1, (char *)p, *(char *)nodeName );
520 err = FSMakeFSSpec( vRefNum, dirID, nodeName, spec_p );
521 if ( err || '\0' == *q )
522 return err;
523
524 vRefNum = spec_p->vRefNum;
525 dirID = spec_p->parID;
526
527 p = q;
528 }
529 }
530
531
532 static OSErr
533 FT_FSpMakePath( const FSSpec* spec_p,
534 UInt8* path,
535 UInt32 maxPathSize )
536 {
537 OSErr err;
538 FSSpec spec = *spec_p;
539 short vRefNum;
540 long dirID;
541 Str255 parDir_name;
542
543
544 FT_MEM_SET( path, 0, maxPathSize );
545 while ( 1 )
546 {
547 int child_namelen = ft_strlen( (char *)path );
548 unsigned char node_namelen = spec.name[0];
549 unsigned char* node_name = spec.name + 1;
550
551
552 if ( node_namelen + child_namelen > maxPathSize )
553 return errFSNameTooLong;
554
555 FT_MEM_MOVE( path + node_namelen + 1, path, child_namelen );
556 FT_MEM_COPY( path, node_name, node_namelen );
557 if ( child_namelen > 0 )
558 path[node_namelen] = ':';
559
560 vRefNum = spec.vRefNum;
561 dirID = spec.parID;
562 parDir_name[0] = '\0';
563 err = FSMakeFSSpec( vRefNum, dirID, parDir_name, &spec );
564 if ( noErr != err || dirID == spec.parID )
565 break;
566 }
567 return noErr;
568 }
569
570 #endif /* HAVE_FSSPEC && !HAVE_FSREF */
571
572
573 static OSErr
574 FT_FSPathMakeRes( const UInt8* pathname,
575 ResFileRefNum* res )
576 {
577
578 #if HAVE_FSREF
579
580 OSErr err;
581 FSRef ref;
582
583
584 if ( noErr != FSPathMakeRef( pathname, &ref, FALSE ) )
585 return FT_THROW( Cannot_Open_Resource );
586
587 /* at present, no support for dfont format */
588 err = FSOpenResourceFile( &ref, 0, NULL, fsRdPerm, res );
589 if ( noErr == err )
590 return err;
591
592 /* fallback to original resource-fork font */
593 *res = FSOpenResFile( &ref, fsRdPerm );
594 err = ResError();
595
596 #else
597
598 OSErr err;
599 FSSpec spec;
600
601
602 if ( noErr != FT_FSPathMakeSpec( pathname, &spec, FALSE ) )
603 return FT_THROW( Cannot_Open_Resource );
604
605 /* at present, no support for dfont format without FSRef */
606 /* (see above), try original resource-fork font */
607 *res = FSpOpenResFile( &spec, fsRdPerm );
608 err = ResError();
609
610 #endif /* HAVE_FSREF */
611
612 return err;
613 }
614
615
616 /* Return the file type for given pathname */
617 static OSType
618 get_file_type_from_path( const UInt8* pathname )
619 {
620
621 #if HAVE_FSREF
622
623 FSRef ref;
624 FSCatalogInfo info;
625
626
627 if ( noErr != FSPathMakeRef( pathname, &ref, FALSE ) )
628 return ( OSType ) 0;
629
630 if ( noErr != FSGetCatalogInfo( &ref, kFSCatInfoFinderInfo, &info,
631 NULL, NULL, NULL ) )
632 return ( OSType ) 0;
633
634 return ((FInfo *)(info.finderInfo))->fdType;
635
636 #else
637
638 FSSpec spec;
639 FInfo finfo;
640
641
642 if ( noErr != FT_FSPathMakeSpec( pathname, &spec, FALSE ) )
643 return ( OSType ) 0;
644
645 if ( noErr != FSpGetFInfo( &spec, &finfo ) )
646 return ( OSType ) 0;
647
648 return finfo.fdType;
649
650 #endif /* HAVE_FSREF */
651
652 }
653
654
655 /* Given a PostScript font name, create the Macintosh LWFN file name. */
656 static void
657 create_lwfn_name( char* ps_name,
658 Str255 lwfn_file_name )
659 {
660 int max = 5, count = 0;
661 FT_Byte* p = lwfn_file_name;
662 FT_Byte* q = (FT_Byte*)ps_name;
663
664
665 lwfn_file_name[0] = 0;
666
667 while ( *q )
668 {
669 if ( ft_isupper( *q ) )
670 {
671 if ( count )
672 max = 3;
673 count = 0;
674 }
675 if ( count < max && ( ft_isalnum( *q ) || *q == '_' ) )
676 {
677 *++p = *q;
678 lwfn_file_name[0]++;
679 count++;
680 }
681 q++;
682 }
683 }
684
685
686 static short
687 count_faces_sfnt( char* fond_data )
688 {
689 /* The count is 1 greater than the value in the FOND. */
690 /* Isn't that cute? :-) */
691
692 return EndianS16_BtoN( *( (short*)( fond_data +
693 sizeof ( FamRec ) ) ) ) + 1;
694 }
695
696
697 static short
698 count_faces_scalable( char* fond_data )
699 {
700 AsscEntry* assoc;
701 short i, face, face_all;
702
703
704 face_all = EndianS16_BtoN( *( (short *)( fond_data +
705 sizeof ( FamRec ) ) ) ) + 1;
706 assoc = (AsscEntry*)( fond_data + sizeof ( FamRec ) + 2 );
707 face = 0;
708
709 for ( i = 0; i < face_all; i++ )
710 {
711 if ( 0 == EndianS16_BtoN( assoc[i].fontSize ) )
712 face++;
713 }
714 return face;
715 }
716
717
718 /* Look inside the FOND data, answer whether there should be an SFNT
719 resource, and answer the name of a possible LWFN Type 1 file.
720
721 Thanks to Paul Miller (paulm@profoundeffects.com) for the fix
722 to load a face OTHER than the first one in the FOND!
723 */
724
725 static void
726 parse_fond( char* fond_data,
727 short* have_sfnt,
728 ResID* sfnt_id,
729 Str255 lwfn_file_name,
730 short face_index )
731 {
732 AsscEntry* assoc;
733 AsscEntry* base_assoc;
734 FamRec* fond;
735
736
737 *sfnt_id = 0;
738 *have_sfnt = 0;
739 lwfn_file_name[0] = 0;
740
741 fond = (FamRec*)fond_data;
742 assoc = (AsscEntry*)( fond_data + sizeof ( FamRec ) + 2 );
743 base_assoc = assoc;
744
745 /* the maximum faces in a FOND is 48, size of StyleTable.indexes[] */
746 if ( 47 < face_index )
747 return;
748
749 /* Let's do a little range checking before we get too excited here */
750 if ( face_index < count_faces_sfnt( fond_data ) )
751 {
752 assoc += face_index; /* add on the face_index! */
753
754 /* if the face at this index is not scalable,
755 fall back to the first one (old behavior) */
756 if ( EndianS16_BtoN( assoc->fontSize ) == 0 )
757 {
758 *have_sfnt = 1;
759 *sfnt_id = EndianS16_BtoN( assoc->fontID );
760 }
761 else if ( base_assoc->fontSize == 0 )
762 {
763 *have_sfnt = 1;
764 *sfnt_id = EndianS16_BtoN( base_assoc->fontID );
765 }
766 }
767
768 if ( EndianS32_BtoN( fond->ffStylOff ) )
769 {
770 unsigned char* p = (unsigned char*)fond_data;
771 StyleTable* style;
772 unsigned short string_count;
773 char ps_name[256];
774 unsigned char* names[64];
775 int i;
776
777
778 p += EndianS32_BtoN( fond->ffStylOff );
779 style = (StyleTable*)p;
780 p += sizeof ( StyleTable );
781 string_count = EndianS16_BtoN( *(short*)(p) );
782 string_count = FT_MIN( 64, string_count );
783 p += sizeof ( short );
784
785 for ( i = 0; i < string_count; i++ )
786 {
787 names[i] = p;
788 p += names[i][0];
789 p++;
790 }
791
792 {
793 size_t ps_name_len = (size_t)names[0][0];
794
795
796 if ( ps_name_len != 0 )
797 {
798 ft_memcpy(ps_name, names[0] + 1, ps_name_len);
799 ps_name[ps_name_len] = 0;
800 }
801 if ( style->indexes[face_index] > 1 &&
802 style->indexes[face_index] <= string_count )
803 {
804 unsigned char* suffixes = names[style->indexes[face_index] - 1];
805
806
807 for ( i = 1; i <= suffixes[0]; i++ )
808 {
809 unsigned char* s;
810 size_t j = suffixes[i] - 1;
811
812
813 if ( j < string_count && ( s = names[j] ) != NULL )
814 {
815 size_t s_len = (size_t)s[0];
816
817
818 if ( s_len != 0 && ps_name_len + s_len < sizeof ( ps_name ) )
819 {
820 ft_memcpy( ps_name + ps_name_len, s + 1, s_len );
821 ps_name_len += s_len;
822 ps_name[ps_name_len] = 0;
823 }
824 }
825 }
826 }
827 }
828
829 create_lwfn_name( ps_name, lwfn_file_name );
830 }
831 }
832
833
834 static FT_Error
835 lookup_lwfn_by_fond( const UInt8* path_fond,
836 ConstStr255Param base_lwfn,
837 UInt8* path_lwfn,
838 int path_size )
839 {
840
841 #if HAVE_FSREF
842
843 FSRef ref, par_ref;
844 int dirname_len;
845
846
847 /* Pathname for FSRef can be in various formats: HFS, HFS+, and POSIX. */
848 /* We should not extract parent directory by string manipulation. */
849
850 if ( noErr != FSPathMakeRef( path_fond, &ref, FALSE ) )
851 return FT_THROW( Invalid_Argument );
852
853 if ( noErr != FSGetCatalogInfo( &ref, kFSCatInfoNone,
854 NULL, NULL, NULL, &par_ref ) )
855 return FT_THROW( Invalid_Argument );
856
857 if ( noErr != FSRefMakePath( &par_ref, path_lwfn, path_size ) )
858 return FT_THROW( Invalid_Argument );
859
860 if ( ft_strlen( (char *)path_lwfn ) + 1 + base_lwfn[0] > path_size )
861 return FT_THROW( Invalid_Argument );
862
863 /* now we have absolute dirname in path_lwfn */
864 if ( path_lwfn[0] == '/' )
865 ft_strcat( (char *)path_lwfn, "/" );
866 else
867 ft_strcat( (char *)path_lwfn, ":" );
868
869 dirname_len = ft_strlen( (char *)path_lwfn );
870 ft_strcat( (char *)path_lwfn, (char *)base_lwfn + 1 );
871 path_lwfn[dirname_len + base_lwfn[0]] = '\0';
872
873 if ( noErr != FSPathMakeRef( path_lwfn, &ref, FALSE ) )
874 return FT_THROW( Cannot_Open_Resource );
875
876 if ( noErr != FSGetCatalogInfo( &ref, kFSCatInfoNone,
877 NULL, NULL, NULL, NULL ) )
878 return FT_THROW( Cannot_Open_Resource );
879
880 return FT_Err_Ok;
881
882 #else
883
884 int i;
885 FSSpec spec;
886
887
888 /* pathname for FSSpec is always HFS format */
889 if ( ft_strlen( (char *)path_fond ) > path_size )
890 return FT_THROW( Invalid_Argument );
891
892 ft_strcpy( (char *)path_lwfn, (char *)path_fond );
893
894 i = ft_strlen( (char *)path_lwfn ) - 1;
895 while ( i > 0 && ':' != path_lwfn[i] )
896 i--;
897
898 if ( i + 1 + base_lwfn[0] > path_size )
899 return FT_THROW( Invalid_Argument );
900
901 if ( ':' == path_lwfn[i] )
902 {
903 ft_strcpy( (char *)path_lwfn + i + 1, (char *)base_lwfn + 1 );
904 path_lwfn[i + 1 + base_lwfn[0]] = '\0';
905 }
906 else
907 {
908 ft_strcpy( (char *)path_lwfn, (char *)base_lwfn + 1 );
909 path_lwfn[base_lwfn[0]] = '\0';
910 }
911
912 if ( noErr != FT_FSPathMakeSpec( path_lwfn, &spec, FALSE ) )
913 return FT_THROW( Cannot_Open_Resource );
914
915 return FT_Err_Ok;
916
917 #endif /* HAVE_FSREF */
918
919 }
920
921
922 static short
923 count_faces( Handle fond,
924 const UInt8* pathname )
925 {
926 ResID sfnt_id;
927 short have_sfnt, have_lwfn;
928 Str255 lwfn_file_name;
929 UInt8 buff[PATH_MAX];
930 FT_Error err;
931 short num_faces;
932
933
934 have_sfnt = have_lwfn = 0;
935
936 HLock( fond );
937 parse_fond( *fond, &have_sfnt, &sfnt_id, lwfn_file_name, 0 );
938
939 if ( lwfn_file_name[0] )
940 {
941 err = lookup_lwfn_by_fond( pathname, lwfn_file_name,
942 buff, sizeof ( buff ) );
943 if ( !err )
944 have_lwfn = 1;
945 }
946
947 if ( have_lwfn && ( !have_sfnt || PREFER_LWFN ) )
948 num_faces = 1;
949 else
950 num_faces = count_faces_scalable( *fond );
951
952 HUnlock( fond );
953 return num_faces;
954 }
955
956
957 /* Read Type 1 data from the POST resources inside the LWFN file,
958 return a PFB buffer. This is somewhat convoluted because the FT2
959 PFB parser wants the ASCII header as one chunk, and the LWFN
960 chunks are often not organized that way, so we glue chunks
961 of the same type together. */
962 static FT_Error
963 read_lwfn( FT_Memory memory,
964 ResFileRefNum res,
965 FT_Byte** pfb_data,
966 FT_ULong* size )
967 {
968 FT_Error error = FT_Err_Ok;
969 ResID res_id;
970 unsigned char *buffer, *p, *size_p = NULL;
971 FT_ULong total_size = 0;
972 FT_ULong old_total_size = 0;
973 FT_ULong post_size, pfb_chunk_size;
974 Handle post_data;
975 char code, last_code;
976
977
978 UseResFile( res );
979
980 /* First pass: load all POST resources, and determine the size of */
981 /* the output buffer. */
982 res_id = 501;
983 last_code = -1;
984
985 for (;;)
986 {
987 post_data = Get1Resource( TTAG_POST, res_id++ );
988 if ( post_data == NULL )
989 break; /* we are done */
990
991 code = (*post_data)[0];
992
993 if ( code != last_code )
994 {
995 if ( code == 5 )
996 total_size += 2; /* just the end code */
997 else
998 total_size += 6; /* code + 4 bytes chunk length */
999 }
1000
1001 total_size += GetHandleSize( post_data ) - 2;
1002 last_code = code;
1003
1004 /* detect integer overflows */
1005 if ( total_size < old_total_size )
1006 {
1007 error = FT_ERR( Array_Too_Large );
1008 goto Error;
1009 }
1010
1011 old_total_size = total_size;
1012 }
1013
1014 if ( FT_QALLOC( buffer, (FT_Long)total_size ) )
1015 goto Error;
1016
1017 /* Second pass: append all POST data to the buffer, add PFB fields. */
1018 /* Glue all consecutive chunks of the same type together. */
1019 p = buffer;
1020 res_id = 501;
1021 last_code = -1;
1022 pfb_chunk_size = 0;
1023
1024 for (;;)
1025 {
1026 post_data = Get1Resource( TTAG_POST, res_id++ );
1027 if ( post_data == NULL )
1028 break; /* we are done */
1029
1030 post_size = (FT_ULong)GetHandleSize( post_data ) - 2;
1031 code = (*post_data)[0];
1032
1033 if ( code != last_code )
1034 {
1035 if ( last_code != -1 )
1036 {
1037 /* we are done adding a chunk, fill in the size field */
1038 if ( size_p != NULL )
1039 {
1040 *size_p++ = (FT_Byte)( pfb_chunk_size & 0xFF );
1041 *size_p++ = (FT_Byte)( ( pfb_chunk_size >> 8 ) & 0xFF );
1042 *size_p++ = (FT_Byte)( ( pfb_chunk_size >> 16 ) & 0xFF );
1043 *size_p++ = (FT_Byte)( ( pfb_chunk_size >> 24 ) & 0xFF );
1044 }
1045 pfb_chunk_size = 0;
1046 }
1047
1048 *p++ = 0x80;
1049 if ( code == 5 )
1050 *p++ = 0x03; /* the end */
1051 else if ( code == 2 )
1052 *p++ = 0x02; /* binary segment */
1053 else
1054 *p++ = 0x01; /* ASCII segment */
1055
1056 if ( code != 5 )
1057 {
1058 size_p = p; /* save for later */
1059 p += 4; /* make space for size field */
1060 }
1061 }
1062
1063 ft_memcpy( p, *post_data + 2, post_size );
1064 pfb_chunk_size += post_size;
1065 p += post_size;
1066 last_code = code;
1067 }
1068
1069 *pfb_data = buffer;
1070 *size = total_size;
1071
1072 Error:
1073 CloseResFile( res );
1074 return error;
1075 }
1076
1077
1078 /* Create a new FT_Face from a file spec to an LWFN file. */
1079 static FT_Error
1080 FT_New_Face_From_LWFN( FT_Library library,
1081 const UInt8* pathname,
1082 FT_Long face_index,
1083 FT_Face* aface )
1084 {
1085 FT_Byte* pfb_data;
1086 FT_ULong pfb_size;
1087 FT_Error error;
1088 ResFileRefNum res;
1089
1090
1091 if ( noErr != FT_FSPathMakeRes( pathname, &res ) )
1092 return FT_THROW( Cannot_Open_Resource );
1093
1094 pfb_data = NULL;
1095 pfb_size = 0;
1096 error = read_lwfn( library->memory, res, &pfb_data, &pfb_size );
1097 CloseResFile( res ); /* PFB is already loaded, useless anymore */
1098 if ( error )
1099 return error;
1100
1101 return open_face_from_buffer( library,
1102 pfb_data,
1103 pfb_size,
1104 face_index,
1105 "type1",
1106 aface );
1107 }
1108
1109
1110 /* Create a new FT_Face from an SFNT resource, specified by res ID. */
1111 static FT_Error
1112 FT_New_Face_From_SFNT( FT_Library library,
1113 ResID sfnt_id,
1114 FT_Long face_index,
1115 FT_Face* aface )
1116 {
1117 Handle sfnt = NULL;
1118 FT_Byte* sfnt_data;
1119 size_t sfnt_size;
1120 FT_Error error = FT_Err_Ok;
1121 FT_Memory memory = library->memory;
1122 int is_cff, is_sfnt_ps;
1123
1124
1125 sfnt = GetResource( TTAG_sfnt, sfnt_id );
1126 if ( sfnt == NULL )
1127 return FT_THROW( Invalid_Handle );
1128
1129 sfnt_size = (FT_ULong)GetHandleSize( sfnt );
1130 if ( FT_QALLOC( sfnt_data, (FT_Long)sfnt_size ) )
1131 {
1132 ReleaseResource( sfnt );
1133 return error;
1134 }
1135
1136 HLock( sfnt );
1137 ft_memcpy( sfnt_data, *sfnt, sfnt_size );
1138 HUnlock( sfnt );
1139 ReleaseResource( sfnt );
1140
1141 is_cff = sfnt_size > 4 && !ft_memcmp( sfnt_data, "OTTO", 4 );
1142 is_sfnt_ps = sfnt_size > 4 && !ft_memcmp( sfnt_data, "typ1", 4 );
1143
1144 if ( is_sfnt_ps )
1145 {
1146 FT_Stream stream;
1147
1148
1149 if ( FT_NEW( stream ) )
1150 goto Try_OpenType;
1151
1152 FT_Stream_OpenMemory( stream, sfnt_data, sfnt_size );
1153 if ( !open_face_PS_from_sfnt_stream( library,
1154 stream,
1155 face_index,
1156 0, NULL,
1157 aface ) )
1158 {
1159 FT_Stream_Close( stream );
1160 FT_FREE( stream );
1161 FT_FREE( sfnt_data );
1162 goto Exit;
1163 }
1164
1165 FT_FREE( stream );
1166 }
1167 Try_OpenType:
1168 error = open_face_from_buffer( library,
1169 sfnt_data,
1170 sfnt_size,
1171 face_index,
1172 is_cff ? "cff" : "truetype",
1173 aface );
1174 Exit:
1175 return error;
1176 }
1177
1178
1179 /* Create a new FT_Face from a file spec to a suitcase file. */
1180 static FT_Error
1181 FT_New_Face_From_Suitcase( FT_Library library,
1182 const UInt8* pathname,
1183 FT_Long face_index,
1184 FT_Face* aface )
1185 {
1186 FT_Error error = FT_ERR( Cannot_Open_Resource );
1187 ResFileRefNum res_ref;
1188 ResourceIndex res_index;
1189 Handle fond;
1190 short num_faces_in_res;
1191
1192
1193 if ( noErr != FT_FSPathMakeRes( pathname, &res_ref ) )
1194 return FT_THROW( Cannot_Open_Resource );
1195
1196 UseResFile( res_ref );
1197 if ( ResError() )
1198 return FT_THROW( Cannot_Open_Resource );
1199
1200 num_faces_in_res = 0;
1201 for ( res_index = 1; ; ++res_index )
1202 {
1203 short num_faces_in_fond;
1204
1205
1206 fond = Get1IndResource( TTAG_FOND, res_index );
1207 if ( ResError() )
1208 break;
1209
1210 num_faces_in_fond = count_faces( fond, pathname );
1211 num_faces_in_res += num_faces_in_fond;
1212
1213 if ( 0 <= face_index && face_index < num_faces_in_fond && error )
1214 error = FT_New_Face_From_FOND( library, fond, face_index, aface );
1215
1216 face_index -= num_faces_in_fond;
1217 }
1218
1219 CloseResFile( res_ref );
1220 if ( !error && aface )
1221 (*aface)->num_faces = num_faces_in_res;
1222 return error;
1223 }
1224
1225
1226 /* documentation is in ftmac.h */
1227
1228 FT_EXPORT_DEF( FT_Error )
1229 FT_New_Face_From_FOND( FT_Library library,
1230 Handle fond,
1231 FT_Long face_index,
1232 FT_Face* aface )
1233 {
1234 short have_sfnt, have_lwfn = 0;
1235 ResID sfnt_id, fond_id;
1236 OSType fond_type;
1237 Str255 fond_name;
1238 Str255 lwfn_file_name;
1239 UInt8 path_lwfn[PATH_MAX];
1240 OSErr err;
1241 FT_Error error = FT_Err_Ok;
1242
1243
1244 /* test for valid `aface' and `library' delayed to */
1245 /* `FT_New_Face_From_XXX' */
1246
1247 GetResInfo( fond, &fond_id, &fond_type, fond_name );
1248 if ( ResError() != noErr || fond_type != TTAG_FOND )
1249 return FT_THROW( Invalid_File_Format );
1250
1251 HLock( fond );
1252 parse_fond( *fond, &have_sfnt, &sfnt_id, lwfn_file_name, face_index );
1253 HUnlock( fond );
1254
1255 if ( lwfn_file_name[0] )
1256 {
1257 ResFileRefNum res;
1258
1259
1260 res = HomeResFile( fond );
1261 if ( noErr != ResError() )
1262 goto found_no_lwfn_file;
1263
1264 #if HAVE_FSREF
1265
1266 {
1267 UInt8 path_fond[PATH_MAX];
1268 FSRef ref;
1269
1270
1271 err = FSGetForkCBInfo( res, kFSInvalidVolumeRefNum,
1272 NULL, NULL, NULL, &ref, NULL );
1273 if ( noErr != err )
1274 goto found_no_lwfn_file;
1275
1276 err = FSRefMakePath( &ref, path_fond, sizeof ( path_fond ) );
1277 if ( noErr != err )
1278 goto found_no_lwfn_file;
1279
1280 error = lookup_lwfn_by_fond( path_fond, lwfn_file_name,
1281 path_lwfn, sizeof ( path_lwfn ) );
1282 if ( !error )
1283 have_lwfn = 1;
1284 }
1285
1286 #elif HAVE_FSSPEC
1287
1288 {
1289 UInt8 path_fond[PATH_MAX];
1290 FCBPBRec pb;
1291 Str255 fond_file_name;
1292 FSSpec spec;
1293
1294
1295 FT_MEM_SET( &spec, 0, sizeof ( FSSpec ) );
1296 FT_MEM_SET( &pb, 0, sizeof ( FCBPBRec ) );
1297
1298 pb.ioNamePtr = fond_file_name;
1299 pb.ioVRefNum = 0;
1300 pb.ioRefNum = res;
1301 pb.ioFCBIndx = 0;
1302
1303 err = PBGetFCBInfoSync( &pb );
1304 if ( noErr != err )
1305 goto found_no_lwfn_file;
1306
1307 err = FSMakeFSSpec( pb.ioFCBVRefNum, pb.ioFCBParID,
1308 fond_file_name, &spec );
1309 if ( noErr != err )
1310 goto found_no_lwfn_file;
1311
1312 err = FT_FSpMakePath( &spec, path_fond, sizeof ( path_fond ) );
1313 if ( noErr != err )
1314 goto found_no_lwfn_file;
1315
1316 error = lookup_lwfn_by_fond( path_fond, lwfn_file_name,
1317 path_lwfn, sizeof ( path_lwfn ) );
1318 if ( !error )
1319 have_lwfn = 1;
1320 }
1321
1322 #endif /* HAVE_FSREF, HAVE_FSSPEC */
1323
1324 }
1325
1326 if ( have_lwfn && ( !have_sfnt || PREFER_LWFN ) )
1327 error = FT_New_Face_From_LWFN( library,
1328 path_lwfn,
1329 face_index,
1330 aface );
1331 else
1332 error = FT_ERR( Unknown_File_Format );
1333
1334 found_no_lwfn_file:
1335 if ( have_sfnt && error )
1336 error = FT_New_Face_From_SFNT( library,
1337 sfnt_id,
1338 face_index,
1339 aface );
1340
1341 return error;
1342 }
1343
1344
1345 /* Common function to load a new FT_Face from a resource file. */
1346 static FT_Error
1347 FT_New_Face_From_Resource( FT_Library library,
1348 const UInt8* pathname,
1349 FT_Long face_index,
1350 FT_Face* aface )
1351 {
1352 OSType file_type;
1353 FT_Error error;
1354
1355
1356 /* LWFN is a (very) specific file format, check for it explicitly */
1357 file_type = get_file_type_from_path( pathname );
1358 if ( file_type == TTAG_LWFN )
1359 return FT_New_Face_From_LWFN( library, pathname, face_index, aface );
1360
1361 /* Otherwise the file type doesn't matter (there are more than */
1362 /* `FFIL' and `tfil'). Just try opening it as a font suitcase; */
1363 /* if it works, fine. */
1364
1365 error = FT_New_Face_From_Suitcase( library, pathname, face_index, aface );
1366 if ( !error )
1367 return error;
1368
1369 /* let it fall through to normal loader (.ttf, .otf, etc.); */
1370 /* we signal this by returning no error and no FT_Face */
1371 *aface = NULL;
1372 return 0;
1373 }
1374
1375
1376 /*************************************************************************/
1377 /* */
1378 /* <Function> */
1379 /* FT_New_Face */
1380 /* */
1381 /* <Description> */
1382 /* This is the Mac-specific implementation of FT_New_Face. In */
1383 /* addition to the standard FT_New_Face() functionality, it also */
1384 /* accepts pathnames to Mac suitcase files. For further */
1385 /* documentation see the original FT_New_Face() in freetype.h. */
1386 /* */
1387 FT_EXPORT_DEF( FT_Error )
1388 FT_New_Face( FT_Library library,
1389 const char* pathname,
1390 FT_Long face_index,
1391 FT_Face* aface )
1392 {
1393 FT_Open_Args args;
1394 FT_Error error;
1395
1396
1397 /* test for valid `library' and `aface' delayed to FT_Open_Face() */
1398 if ( !pathname )
1399 return FT_THROW( Invalid_Argument );
1400
1401 *aface = NULL;
1402
1403 /* try resourcefork based font: LWFN, FFIL */
1404 error = FT_New_Face_From_Resource( library, (UInt8 *)pathname,
1405 face_index, aface );
1406 if ( error || *aface )
1407 return error;
1408
1409 /* let it fall through to normal loader (.ttf, .otf, etc.) */
1410 args.flags = FT_OPEN_PATHNAME;
1411 args.pathname = (char*)pathname;
1412 return FT_Open_Face( library, &args, face_index, aface );
1413 }
1414
1415
1416 /*************************************************************************/
1417 /* */
1418 /* <Function> */
1419 /* FT_New_Face_From_FSRef */
1420 /* */
1421 /* <Description> */
1422 /* FT_New_Face_From_FSRef is identical to FT_New_Face except it */
1423 /* accepts an FSRef instead of a path. */
1424 /* */
1425 /* This function is deprecated because Carbon data types (FSRef) */
1426 /* are not cross-platform, and thus not suitable for the FreeType API. */
1427 FT_EXPORT_DEF( FT_Error )
1428 FT_New_Face_From_FSRef( FT_Library library,
1429 const FSRef* ref,
1430 FT_Long face_index,
1431 FT_Face* aface )
1432 {
1433
1434 #if !HAVE_FSREF
1435
1436 FT_UNUSED( library );
1437 FT_UNUSED( ref );
1438 FT_UNUSED( face_index );
1439 FT_UNUSED( aface );
1440
1441 return FT_THROW( Unimplemented_Feature );
1442
1443 #else
1444
1445 FT_Error error;
1446 FT_Open_Args args;
1447 OSErr err;
1448 UInt8 pathname[PATH_MAX];
1449
1450
1451 /* test for valid `library' and `aface' delayed to `FT_Open_Face' */
1452
1453 if ( !ref )
1454 return FT_THROW( Invalid_Argument );
1455
1456 err = FSRefMakePath( ref, pathname, sizeof ( pathname ) );
1457 if ( err )
1458 error = FT_ERR( Cannot_Open_Resource );
1459
1460 error = FT_New_Face_From_Resource( library, pathname, face_index, aface );
1461 if ( error || *aface )
1462 return error;
1463
1464 /* fallback to datafork font */
1465 args.flags = FT_OPEN_PATHNAME;
1466 args.pathname = (char*)pathname;
1467 return FT_Open_Face( library, &args, face_index, aface );
1468
1469 #endif /* HAVE_FSREF */
1470
1471 }
1472
1473
1474 /*************************************************************************/
1475 /* */
1476 /* <Function> */
1477 /* FT_New_Face_From_FSSpec */
1478 /* */
1479 /* <Description> */
1480 /* FT_New_Face_From_FSSpec is identical to FT_New_Face except it */
1481 /* accepts an FSSpec instead of a path. */
1482 /* */
1483 /* This function is deprecated because Carbon data types (FSSpec) */
1484 /* are not cross-platform, and thus not suitable for the FreeType API. */
1485 FT_EXPORT_DEF( FT_Error )
1486 FT_New_Face_From_FSSpec( FT_Library library,
1487 const FSSpec* spec,
1488 FT_Long face_index,
1489 FT_Face* aface )
1490 {
1491
1492 #if HAVE_FSREF
1493
1494 FSRef ref;
1495
1496
1497 if ( !spec || FSpMakeFSRef( spec, &ref ) != noErr )
1498 return FT_THROW( Invalid_Argument );
1499 else
1500 return FT_New_Face_From_FSRef( library, &ref, face_index, aface );
1501
1502 #elif HAVE_FSSPEC
1503
1504 FT_Error error;
1505 FT_Open_Args args;
1506 OSErr err;
1507 UInt8 pathname[PATH_MAX];
1508
1509
1510 if ( !spec )
1511 return FT_THROW( Invalid_Argument );
1512
1513 err = FT_FSpMakePath( spec, pathname, sizeof ( pathname ) );
1514 if ( err )
1515 error = FT_ERR( Cannot_Open_Resource );
1516
1517 error = FT_New_Face_From_Resource( library, pathname, face_index, aface );
1518 if ( error || *aface )
1519 return error;
1520
1521 /* fallback to datafork font */
1522 args.flags = FT_OPEN_PATHNAME;
1523 args.pathname = (char*)pathname;
1524 return FT_Open_Face( library, &args, face_index, aface );
1525
1526 #else
1527
1528 FT_UNUSED( library );
1529 FT_UNUSED( spec );
1530 FT_UNUSED( face_index );
1531 FT_UNUSED( aface );
1532
1533 return FT_THROW( Unimplemented_Feature );
1534
1535 #endif /* HAVE_FSREF, HAVE_FSSPEC */
1536
1537 }
1538
1539 #endif /* FT_MACINTOSH */
1540
1541
1542 /* END */