diff --git a/src/macfond/fonddrvr.c b/src/macfond/fonddrvr.c new file mode 100644 index 000000000..5a205ee29 --- /dev/null +++ b/src/macfond/fonddrvr.c @@ -0,0 +1,590 @@ +/***************************************************************************/ +/* */ +/* fonddrvr.c */ +/* */ +/* Mac FOND font driver. Written by just@letterror.com. */ +/* */ +/* 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. + + - Small note about memory: I use malloc() to allocate the buffer + for the Type 1 data. Of course it would be better to do this + through the library->memory stuff, but I can't access that from + my stream->close() method! Maybe it would be better to use the + Mac native NewPtr() and DisposePtr() calls. +*/ + +#include +#include + +#include +#include +#include + +#include /* for isupper() and isalnum() */ +#include /* 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 ; iindexes[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( 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 = malloc( 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++ = 128; + + code = (*post_data)[0]; + if ( code == 5 ) + *p++ = 3; + else if ( code == 2 ) + *p++ = 2; + else + *p++ = 1; + 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 ) + { + free( 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( &lwfn_spec, &size ); + if ( !type1_data ) + { + return FT_Err_Out_Of_Memory; + } + + #if 1 + { + FILE* f; + + f = fopen("Test.PFB", "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, + }; + + + + /*************************************************************************/ + /* */ + /* */ + /* getDriverInterface */ + /* */ + /* */ + /* 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. */ + /* */ + /* */ + /* 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 */