ref: 20c15add91dc1495ebe80bc12ea7ff62f43046e3
dir: /src/macfond/fonddrvr.c/
/***************************************************************************/ /* */ /* fonddrvr.c */ /* */ /* Mac FOND font driver. Written by [email protected]. */ /* */ /* Copyright 1996-2000 by */ /* Just van Rossum, David Turner, Robert Wilhelm, and Werner Lemberg. */ /* */ /* This file is part of the FreeType project, and may only be used, */ /* modified, and distributed under the terms of the FreeType project */ /* license, LICENSE.TXT. By continuing to use, modify, or distribute */ /* this file you indicate that you have read the license and */ /* understand and accept it fully. */ /* */ /***************************************************************************/ /* Notes Mac suitcase files can (and often do!) contain multiple fonts. To support this I use the face_index argument of FT_(Open|New)_Face() functions, and pretend the suitcase file is a collection. Warning: although the FOND driver sets face->num_faces field to the number of available fonts, but the Type 1 driver sets it to 1 anyway. So this field is currently not reliable, and I don't see a clean way to resolve that. The face_index argument translates to Get1IndResource( 'FOND', face_index + 1 ); so clients should figure out the resource index of the FOND. (I'll try to provide some example code for this at some point.) The Mac FOND driver works roughly like this: - Check whether the offered stream points to a Mac suitcase file. This is done by checking the file type: it has to be 'FFIL' or 'tfil'. The stream that gets passed to our init_face() routine is a stdio stream, which isn't usable for us, since the FOND resources live in the resource fork. So we just grab the stream->pathname field. - Read the FOND resource into memory, then check whether there is a TrueType font and/or (!) a Type 1 font available. - If there is a Type 1 font available (as a separate 'LWFN' file), read it's data into memory, massage it slightly so it becomes PFB data, wrap it into a memory stream, load the Type 1 driver and delegate the rest of the work to it, by calling t1_driver->interface.init_face( ... ) (XXX TODO: after this has been done, the kerning data from the FOND resource should be appended to the face: on the Mac there are usually no AFM files available. However, this is tricky since we need to map Mac char codes to ps glyph names to glyph ID's...) - If there is a TrueType font (an 'sfnt' resource), read it into memory, wrap it into a memory stream, load the TrueType driver and delegate the rest of the work to it, by calling tt_driver->interface.init_face( ... ) - In both cases, the original stream gets closed and *reinitialized* to become a memory stream. Additionally, the face->driver field -- which is set to the FOND driver upon entering our init_face() -- gets *reset* to either the TT or the T1 driver. I had to make a minor change to ftobjs.c to make this work. */ #include <freetype/internal/ttobjs.h> #include <freetype/internal/t1objs.h> #include <Resources.h> #include <Fonts.h> #include <Errors.h> #include <ctype.h> /* for isupper() and isalnum() */ #include <stdlib.h> /* for malloc() and free() */ /* set PREFER_LWFN to 1 if LWFN (Type 1) is preferred over TrueType in case *both* are available */ #ifndef PREFER_LWFN #define PREFER_LWFN 1 #endif static FT_Error init_driver( FT_Driver driver ) { /* we don't keep no stinkin' state ;-) */ return FT_Err_Ok; } static FT_Error done_driver( FT_Driver driver ) { return FT_Err_Ok; } /* MacRoman glyph names, needed for FOND kerning support. */ /* XXX which is not implemented yet! */ static const char* mac_roman_glyph_names[256] = { ".null", }; /* The FOND face object is just a union of TT and T1: both is possible, and we don't need anything else. */ typedef union FOND_FaceRec_ { TT_FaceRec tt; T1_FaceRec t1; } FOND_FaceRec, *FOND_Face; /* given a pathname, fill in a File Spec */ static int make_file_spec( char* pathname, FSSpec *spec ) { Str255 p_path; int path_len; /* convert path to a pascal string */ path_len = strlen( pathname ); if ( path_len > 255 ) return -1; p_path[0] = path_len; strncpy( (char*)p_path+1, pathname, path_len ); if ( FSMakeFSSpec( 0, 0, p_path, spec ) != noErr ) return -1; else return 0; } /* is_suitcase() returns true if the file specified by 'pathname' is a Mac suitcase file, and false if it ain't. */ static int is_suitcase( FSSpec *spec ) { FInfo finfo; if ( FSpGetFInfo( spec, &finfo ) != noErr ) return 0; if ( finfo.fdType == 'FFIL' || finfo.fdType == 'tfil' ) return 1; else return 0; } /* Quick 'n' Dirty Pascal string to C string converter. */ static char * p2c_str( unsigned char *pstr ) { static char cstr[256]; strncpy( cstr, (char*)pstr+1, pstr[0] ); cstr[pstr[0]] = '\0'; return cstr; } /* Given a PostScript font name, create the Macintosh LWFN file name */ static void create_lwfn_name( char* ps_name, Str255 lwfn_file_name ) { int max = 5, count = 0; unsigned char* p = lwfn_file_name; char* q = ps_name; lwfn_file_name[0] = 0; while ( *q ) { if ( isupper(*q) ) { if ( count ) max = 3; count = 0; } if ( count < max && (isalnum(*q) || *q == '_' ) ) { *++p = *q; lwfn_file_name[0]++; count++; } q++; } } /* Suck the relevant info out of the FOND data */ static FT_Error parse_fond( char* fond_data, short *have_sfnt, short *sfnt_id, Str255 lwfn_file_name ) { AsscEntry* assoc; FamRec* fond; *sfnt_id = *have_sfnt = 0; lwfn_file_name[0] = 0; fond = (FamRec*)fond_data; assoc = (AsscEntry*)(fond_data + sizeof(FamRec) + 2); if ( assoc->fontSize == 0 ) { *have_sfnt = 1; *sfnt_id = assoc->fontID; } if ( fond->ffStylOff ) { unsigned char* p = (unsigned char*)fond_data; StyleTable* style; unsigned short string_count; unsigned char* name_table = 0; char ps_name[256]; unsigned char* names[64]; int i; p += fond->ffStylOff; style = (StyleTable*)p; p += sizeof(StyleTable); string_count = *(unsigned short*)(p); p += sizeof(short); for ( i=0 ; i<string_count && i<64; i++ ) { names[i] = p; p += names[i][0]; p++; } strcpy(ps_name, p2c_str(names[0])); /* Family name */ if ( style->indexes[0] > 1 ) { unsigned char* suffixes = names[style->indexes[0]-1]; for ( i=1; i<=suffixes[0]; i++ ) strcat( ps_name, p2c_str(names[suffixes[i]-1]) ); } create_lwfn_name( ps_name, lwfn_file_name ); } return FT_Err_Ok; } /* Read Type 1 data from the POST resources inside the LWFN file, return a PFB buffer -- apparently FT doesn't like a pure binary T1 stream. */ static char* read_type1_data( FT_Memory memory, FSSpec* lwfn_spec, unsigned long *size ) { short res_ref, res_id; unsigned char *buffer, *p; unsigned long total_size = 0; Handle post_data; res_ref = FSpOpenResFile( lwfn_spec, fsRdPerm ); if ( ResError() ) return NULL; UseResFile( res_ref ); /* first pass: load all POST resources, and determine the size of the output buffer */ res_id = 501; for (;;) { post_data = Get1Resource( 'POST', res_id++ ); if ( post_data == NULL ) break; if ( (*post_data)[0] != 5 ) total_size += GetHandleSize( post_data ) + 4; else total_size += 2; } buffer = memory->alloc( memory, total_size ); if ( !buffer ) goto error; /* second pass: append all POST data to the buffer, add PFB fields */ p = buffer; res_id = 501; for (;;) { long chunk_size; char code; post_data = Get1Resource( 'POST', res_id++ ); if ( post_data == NULL ) break; chunk_size = GetHandleSize( post_data ) - 2; *p++ = 0x80; code = (*post_data)[0]; if ( code == 5 ) *p++ = 0x03; /* the end */ else if ( code == 2 ) *p++ = 0x02; /* binary segment */ else *p++ = 0x01; /* ASCII segment */ if ( code != 5 ) { *p++ = chunk_size & 0xFF; *p++ = (chunk_size >> 8) & 0xFF; *p++ = (chunk_size >> 16) & 0xFF; *p++ = (chunk_size >> 24) & 0xFF; } memcpy( p, *post_data + 2, chunk_size ); p += chunk_size; } CloseResFile( res_ref ); *size = total_size; return (char*)buffer; error: CloseResFile( res_ref ); return NULL; } /* Finalizer for the sfnt stream */ static void sfnt_stream_close( FT_Stream stream ) { Handle sfnt_data = stream->descriptor.pointer; HUnlock( sfnt_data ); DisposeHandle( sfnt_data ); stream->descriptor.pointer = NULL; stream->size = 0; stream->base = 0; stream->close = 0; } /* Finalizer for the LWFN stream */ static void lwfn_stream_close( FT_Stream stream ) { stream->memory->free( stream->memory, stream->base ); stream->descriptor.pointer = NULL; stream->size = 0; stream->base = 0; stream->close = 0; } /* Main entry point. Determine whether we're dealing with a Mac suitcase or not; then determine if we're dealing with Type 1 or TrueType; delegate the work to the proper driver. */ static FT_Error init_face( FT_Stream stream, FT_Long face_index, FT_Face face ) { FT_Error err; FSSpec suit_spec, lwfn_spec; short res_ref; Handle fond_data, sfnt_data; short res_index, sfnt_id, have_sfnt; Str255 lwfn_file_name; if ( !stream->pathname.pointer ) return FT_Err_Invalid_Argument; if ( make_file_spec( stream->pathname.pointer, &suit_spec ) ) return FT_Err_Invalid_Argument; if ( !is_suitcase( &suit_spec ) ) return FT_Err_Invalid_File_Format; res_ref = FSpOpenResFile( &suit_spec, fsRdPerm ); if ( ResError() ) return FT_Err_Invalid_File_Format; UseResFile( res_ref ); /* face_index may be -1, in which case we just need to do a sanity check */ if ( face_index < 0) res_index = 1; else res_index = face_index + 1; fond_data = Get1IndResource( 'FOND', res_index ); if ( ResError() ) { CloseResFile( res_ref ); return FT_Err_Invalid_File_Format; } /* Set the number of faces. Not that it helps much: the t1 driver just sets it to 1 anyway :-( */ face->num_faces = Count1Resources('FOND'); HLock( fond_data ); err = parse_fond( *fond_data, &have_sfnt, &sfnt_id, lwfn_file_name ); HUnlock( fond_data ); if ( err ) { CloseResFile( res_ref ); return FT_Err_Invalid_Resource_Handle; } if ( lwfn_file_name[0] ) { /* We look for the LWFN file in the same directory as the suitcase file. ATM would look in other places, too, but this is the usual situation. */ err = FSMakeFSSpec( suit_spec.vRefNum, suit_spec.parID, lwfn_file_name, &lwfn_spec ); if ( err != noErr ) lwfn_file_name[0] = 0; /* no LWFN file found */ } if ( lwfn_file_name[0] && ( !have_sfnt || PREFER_LWFN ) ) { FT_Driver t1_driver; char* type1_data; unsigned long size; CloseResFile( res_ref ); /* XXX still need to read kerning! */ type1_data = read_type1_data( stream->memory, &lwfn_spec, &size ); if ( !type1_data ) { return FT_Err_Out_Of_Memory; } #if 0 { FILE* f; char * path; path = p2c_str( lwfn_file_name ); strcat( path, ".PFB" ); f = fopen(path, "wb"); if ( f ) { fwrite( type1_data, 1, size, f ); fclose( f ); } } #endif /* reinitialize the stream */ if ( stream->close ) stream->close( stream ); stream->close = lwfn_stream_close; stream->read = 0; /* it's now memory based */ stream->base = type1_data; stream->size = size; stream->pos = 0; /* just in case */ /* delegate the work to the Type 1 driver */ t1_driver = FT_Get_Driver( face->driver->library, "type1" ); if ( t1_driver ) { face->driver = t1_driver; return t1_driver->interface.init_face( stream, 0, face ); } else return FT_Err_Invalid_Driver_Handle; } else if ( have_sfnt ) { FT_Driver tt_driver; sfnt_data = Get1Resource( 'sfnt', sfnt_id ); if ( ResError() ) { CloseResFile( res_ref ); return FT_Err_Invalid_Resource_Handle; } DetachResource( sfnt_data ); CloseResFile( res_ref ); HLockHi( sfnt_data ); /* reinitialize the stream */ if ( stream->close ) stream->close( stream ); stream->close = sfnt_stream_close; stream->descriptor.pointer = sfnt_data; stream->read = 0; /* it's now memory based */ stream->base = *sfnt_data; stream->size = GetHandleSize( sfnt_data ); stream->pos = 0; /* just in case */ /* delegate the work to the TrueType driver */ tt_driver = FT_Get_Driver( face->driver->library, "truetype" ); if ( tt_driver ) { face->driver = tt_driver; return tt_driver->interface.init_face( stream, 0, face ); } else return FT_Err_Invalid_Driver_Handle; } else { CloseResFile( res_ref ); } return FT_Err_Invalid_File_Format; } static void done_face( FOND_Face face ) { /* We'll *only* get here if init_face() doesn't succeed, since if it *does* succeed, it has set the face->driver to either the TrueType driver or the Type 1 driver. And since we promise not leave any garbage if init_face() fails, there's nothing left to do. */ } /* The FT_DriverInterface structure is defined in ftdriver.h. */ const FT_DriverInterface fond_driver_interface = { sizeof ( FT_DriverRec ), sizeof ( FOND_FaceRec ), 0, 0, "fond", /* driver name */ 100, /* driver version == 1.0 */ 200, /* driver requires FreeType 2.0 or above */ (void*)0, (FTDriver_initDriver) init_driver, (FTDriver_doneDriver) done_driver, (FTDriver_getInterface) 0, (FTDriver_initFace) init_face, (FTDriver_doneFace) done_face, (FTDriver_getKerning) 0, (FTDriver_initSize) 0, (FTDriver_doneSize) 0, (FTDriver_setCharSizes) 0, (FTDriver_setPixelSizes) 0, (FTDriver_initGlyphSlot) 0, (FTDriver_doneGlyphSlot) 0, (FTDriver_loadGlyph) 0, (FTDriver_getCharIndex) 0, }; /*************************************************************************/ /* */ /* <Function> */ /* getDriverInterface */ /* */ /* <Description> */ /* This function is used when compiling the FOND driver as a */ /* shared library (`.DLL' or `.so'). It will be used by the */ /* high-level library of FreeType to retrieve the address of the */ /* driver's generic interface. */ /* */ /* It shouldn't be implemented in a static build, as each driver must */ /* have the same function as an exported entry point. */ /* */ /* <Return> */ /* The address of the TrueType's driver generic interface. The */ /* format-specific interface can then be retrieved through the method */ /* interface->get_format_interface. */ /* */ #ifdef FT_CONFIG_OPTION_DYNAMIC_DRIVERS EXPORT_FUNC FT_DriverInterface* getDriverInterface( void ) { return &fond_driver_interface; } #endif /* CONFIG_OPTION_DYNAMIC_DRIVERS */ /* END */