/* Copyright (C) 2010,2011 by George Williams */ /* * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* Support for woff files */ /* Which are defined here: http://people.mozilla.com/~jkew/woff/woff-2009-09-16.html */ /* Basically sfnts with compressed tables and some more metadata */ #include "fontforge.h" #include #include #ifdef _NO_LIBPNG SplineFont *_SFReadWOFF(FILE *woff,int flags,enum openflags openflags, char *filename,struct fontdict *fd) { ff_post_error(_("WOFF not supported"), _("This version of fontforge cannot handle WOFF files. You need to recompile it with libpng and zlib") ); return( NULL ); } int _WriteWOFFFont(FILE *woff,SplineFont *sf, enum fontformat format, int32 *bsizes, enum bitmapformat bf,int flags,EncMap *enc,int layer) { ff_post_error(_("WOFF not supported"), _("This version of fontforge cannot handle WOFF files. You need to recompile it with libpng and zlib") ); return( 1 ); } int WriteWOFFFont(char *fontname,SplineFont *sf, enum fontformat format, int32 *bsizes, enum bitmapformat bf,int flags,EncMap *enc,int layer) { ff_post_error(_("WOFF not supported"), _("This version of fontforge cannot handle WOFF files. You need to recompile it with libpng and zlib") ); return( 1 ); } int CanWoff(void) { return( 0 ); } #else # if !defined(_STATIC_LIBPNG) && !defined(NODYNAMIC) # include # endif # include # if !defined(_STATIC_LIBPNG) && !defined(NODYNAMIC) static DL_CONST void *zlib=NULL; static int (*_inflateInit_)(z_stream *,const char *,int); static int (*_inflate)(z_stream *,int flags); static int (*_inflateEnd)(z_stream *); static int (*_deflateInit_)(z_stream *,int level, const char *,int); static int (*_deflate)(z_stream *,int flags); static int (*_deflateEnd)(z_stream *); static int (*_uncompress)(Bytef *, uLongf *, const Bytef *, uLong); static int (*_compress)(Bytef *, uLongf *, const Bytef *, uLong); static int haszlib(void) { if ( zlib!=NULL ) return( true ); if ( (zlib = dlopen("libz" SO_EXT,RTLD_GLOBAL|RTLD_LAZY))==NULL ) { LogError( "%s", dlerror()); return( false ); } _inflateInit_ = (int (*)(z_stream *,const char *,int)) dlsym(zlib,"inflateInit_"); _inflate = (int (*)(z_stream *,int )) dlsym(zlib,"inflate"); _inflateEnd = (int (*)(z_stream *)) dlsym(zlib,"inflateEnd"); _deflateInit_ = (int (*)(z_stream *,int,const char *,int)) dlsym(zlib,"deflateInit_"); _deflate = (int (*)(z_stream *,int )) dlsym(zlib,"deflate"); _deflateEnd = (int (*)(z_stream *)) dlsym(zlib,"deflateEnd"); _uncompress = (int (*)(Bytef *, uLongf *, const Bytef *, uLong)) dlsym(zlib,"uncompress"); _compress = (int (*)(Bytef *, uLongf *, const Bytef *, uLong)) dlsym(zlib,"compress"); if ( _inflateInit_==NULL || _inflate==NULL || _inflateEnd==NULL || _deflateInit_==NULL || _deflate==NULL || _deflateEnd==NULL ) { LogError( "%s", dlerror()); dlclose(zlib); zlib=NULL; return( false ); } return( true ); } /* Grump. zlib defines this as a macro */ #define _inflateInit(strm) \ _inflateInit_((strm), ZLIB_VERSION, sizeof(z_stream)) #define _deflateInit(strm,level) \ _deflateInit_((strm),level, ZLIB_VERSION, sizeof(z_stream)) # else /* Either statically linked, or loaded at start up */ static int haszlib(void) { return( true ); } #define _inflateInit inflateInit #define _inflate inflate #define _inflateEnd inflateEnd #define _deflateInit deflateInit #define _deflate deflate #define _deflateEnd deflateEnd #define _compress compress #define _uncompress uncompress # endif /* !defined(_STATIC_LIBPNG) && !defined(NODYNAMIC) */ static void copydata(FILE *to,int off_to,FILE *from,int off_from, int len) { int ch, i; fseek(to ,off_to ,SEEK_SET); fseek(from,off_from,SEEK_SET); for ( i=0; iCHUNK ) amount = CHUNK; strm.avail_in = fread(in, 1, amount, from); len -= strm.avail_in; if (ferror(from)) { (void)_inflateEnd(&strm); return( true ); } if (strm.avail_in == 0) break; strm.next_in = in; do { strm.avail_out = CHUNK; strm.next_out = out; ret = _inflate(&strm, Z_NO_FLUSH); if ( ret!=Z_OK && ret!=Z_STREAM_END ) { (void)_inflateEnd(&strm); return( true ); } amount = CHUNK - strm.avail_out; if ( fwrite(out,1,amount,to)!= amount || ferror(to) ) { (void)_inflateEnd(&strm); return( true ); } } while ( strm.avail_out==0 ); } while ( ret!=Z_STREAM_END ); (void)_inflateEnd(&strm); if ( uncomplen!=strm.total_out ) { LogError(_("Decompressed length did not match expected length for table")); return( true ); } return( false ); } static int compressOrNot(FILE *to,int off_to, FILE *from,int off_from, int len, int forcecompress ) { char in[CHUNK]; char out[CHUNK]; z_stream strm; int ret, err=0; int amount; FILE *tmp; int uncompLen = len; /* Empty table, nothing to do */ if ( len==0 ) return(0); fseek(from,off_from,SEEK_SET); memset(&strm,0,sizeof(strm)); ret = _deflateInit(&strm,Z_DEFAULT_COMPRESSION); if ( ret!=Z_OK ) { fprintf( stderr,"Compression initialization failed.\n" ); return(0); } tmp = tmpfile(); do { if ( len<=0 ) { (void)_deflateEnd(&strm); break; } amount = len; if ( amount>CHUNK ) amount = CHUNK; strm.avail_in = fread(in, 1, amount, from); len -= strm.avail_in; if (ferror(from)) { (void)_deflateEnd(&strm); fprintf( stderr, "IO error.\n" ); break; } if (strm.avail_in == 0) break; strm.next_in = in; do { strm.avail_out = CHUNK; strm.next_out = out; ret = _deflate(&strm, len==0 ? Z_FINISH : Z_NO_FLUSH); if ( ret==Z_STREAM_ERROR ) { (void)_deflateEnd(&strm); fprintf( stderr, "Compression failed somehow.\n"); err = 1; break; } amount = CHUNK - strm.avail_out; if ( fwrite(out,1,amount,tmp)!= amount || ferror(tmp) ) { (void)_deflateEnd(&strm); fprintf( stderr, "IO Error.\n"); err=1; break; } } while ( strm.avail_out==0 ); if ( err ) break; } while ( ret!=Z_STREAM_END ); (void)_deflateEnd(&strm); if ( strm.total_out>=uncompLen ) { /* Didn't actually make the data smaller, so store uncompressed */ fclose(tmp); copydata(to,off_to,from,off_from,uncompLen); return( uncompLen ); } else { copydata(to,off_to,tmp,0,strm.total_out); fclose(tmp); return( strm.total_out ); } } SplineFont *_SFReadWOFF(FILE *woff,int flags,enum openflags openflags, char *filename,struct fontdict *fd) { int flavour; int iscff; int len, len_stated; int num_tabs; int major, minor; int metaOffset, metaLenCompressed, metaLenUncompressed; int privOffset, privLength; int i,j,err; int tag, offset, compLen, uncompLen, checksum; FILE *sfnt; int here, next, tab_start; int head_pos = -1; SplineFont *sf; if ( !haszlib()) { ff_post_error(_("WOFF not supported"), _("Could not find the zlib library which is needed to understand WOFF") ); return( NULL ); } fseek(woff,0,SEEK_END); len = ftell(woff); rewind(woff); if ( getlong(woff)!=CHR('w','O','F','F') ) { LogError(_("Bad signature in WOFF")); return( NULL ); } flavour = getlong(woff); iscff = (flavour==CHR('O','T','T','O')); len_stated = getlong(woff); if ( len!=len_stated ) { LogError(_("File length as specified in the WOFF header does not match the actual file length.")); return( NULL ); } num_tabs = getushort(woff); if ( getushort(woff)!=0 ) { LogError(_("Bad WOFF header, a field which must be 0 is not.")); return( NULL ); } /* total_uncompressed_sfnt_size = */ getlong(woff); major = getushort(woff); minor = getushort(woff); metaOffset = getlong(woff); metaLenCompressed = getlong(woff); metaLenUncompressed = getlong(woff); privOffset = getlong(woff); privLength = getlong(woff); sfnt = tmpfile(); if ( sfnt==NULL ) { LogError(_("Could not open temporary file.")); return( NULL ); } putlong(sfnt,flavour); putshort(sfnt,num_tabs); for ( i=1, j=0; 2*i<=num_tabs; i<<=1, ++j ); putshort(sfnt,i*16); putshort(sfnt,j); putshort(sfnt,(num_tabs-i)*16); /* dummy space for table pointers */ tab_start = ftell(sfnt); for ( i=0; i<4*num_tabs; ++i ) putlong(sfnt,0); for ( i=0; iuncompLen || offset+compLen>len ) { fclose(sfnt); if ( compLen>uncompLen ) LogError(_("Invalid compressed table length for '%c%c%c%c'."), tag>>24, tag>>16, tag>>8, tag); else LogError(_("Table length stretches beyond end of file for '%c%c%c%c'."), tag>>24, tag>>16, tag>>8, tag); return( NULL ); } here = ftell(woff); next = ftell(sfnt); fseek(sfnt,tab_start,SEEK_SET); putlong(sfnt,tag); putlong(sfnt,checksum); putlong(sfnt,next); putlong(sfnt,uncompLen); if ( tag==CHR('h','e','a','d')) head_pos = next; tab_start = ftell(sfnt); fseek(sfnt,next,SEEK_SET); if ( compLen==uncompLen ) { /* Not compressed, copy verbatim */ copydata(sfnt,next,woff,offset,compLen); } else { err = decompressdata(sfnt,next,woff,offset,compLen,uncompLen); if ( err ) { LogError(_("Problem decompressing '%c%c%c%c' table."), tag>>24, tag>>16, tag>>8, tag); fclose(sfnt); return( NULL ); } } if ( (ftell(sfnt)&3)!=0 ) { /* Pad to a 4 byte boundary */ if ( ftell(sfnt)&1 ) putc('\0',sfnt); if ( ftell(sfnt)&2 ) putshort(sfnt,0); } fseek(woff,here,SEEK_SET); } /* I assumed at first that the check sum would just be right */ /* but I've reordered the tables (probably) so I've got a different */ /* set of offsets and I must figure it out for myself */ /* Usually, I don't care. But if fontlint is on, then do it right */ if ( (openflags & of_fontlint) && head_pos!=-1 ) { int checksum; fseek(sfnt,head_pos+8,SEEK_SET); putlong(sfnt,0); /* Clear what was there */ checksum = filechecksum(sfnt); /* Recalc */ checksum = 0xb1b0afba-checksum; fseek(sfnt,head_pos+8,SEEK_SET); putlong(sfnt,checksum); } rewind(sfnt); sf = _SFReadTTF(sfnt,flags,openflags,filename,fd); fclose(sfnt); if ( sf!=NULL ) { sf->woffMajor = major; sf->woffMinor = minor; } if ( sf!=NULL && metaOffset!=0 ) { char *temp = galloc(metaLenCompressed+1); uLongf len = metaLenUncompressed; fseek(woff,metaOffset,SEEK_SET); fread(temp,1,metaLenCompressed,woff); sf->woffMetadata = galloc(metaLenUncompressed+1); sf->woffMetadata[metaLenUncompressed] ='\0'; _uncompress(sf->woffMetadata,&len,temp,metaLenCompressed); sf->woffMetadata[len] ='\0'; free(temp); } return( sf ); } int _WriteWOFFFont(FILE *woff,SplineFont *sf, enum fontformat format, int32 *bsizes, enum bitmapformat bf,int flags,EncMap *enc,int layer) { int ret; FILE *sfnt; int major=sf->woffMajor, minor=sf->woffMinor; int flavour, num_tabs; int filelen, len; int i; int compLen, uncompLen, newoffset; int tag, checksum, offset; int tab_start, here; if ( !haszlib()) { ff_post_error(_("WOFF not supported"), _("Could not find the zlib library which is needed to understand WOFF") ); return( 0 ); } if ( major==woffUnset ) { struct ttflangname *useng; major = 1; minor = 0; for ( useng=sf->names; useng!=NULL; useng=useng->next ) if ( useng->lang==0x409 ) break; if ( useng!=NULL && useng->names[ttf_version]!=NULL && sscanf(useng->names[ttf_version], "Version %d.%d", &major, &minor)>=1 ) { /* All done */ } else if ( sf->subfontcnt!=0 ) { major = floor(sf->cidversion); minor = floor(1000.*(sf->cidversion-major)); } else if ( sf->version!=NULL ) { char *pt=sf->version; char *end; while ( *pt && !isdigit(*pt) && *pt!='.' ) ++pt; if ( *pt ) { major = strtol(pt,&end,10); if ( *end=='.' ) minor = strtol(end+1,NULL,10); } } } format = sf->subfonts!=NULL ? ff_otfcid : sf->layers[layer].order2 ? ff_ttf : ff_otf; sfnt = tmpfile(); ret = _WriteTTFFont(sfnt,sf,format,bsizes,bf,flags,enc,layer); if ( !ret ) { fclose(sfnt); return( ret ); } fseek(sfnt,0,SEEK_END); filelen = ftell(sfnt); rewind(sfnt); flavour = getlong(sfnt); /* The woff standard says we should accept all flavours of sfnt, so can't */ /* test flavour to make sure we've got a valid sfnt */ /* But we can test the rest of the header for consistancy */ num_tabs = getushort(sfnt); (void) getushort(sfnt); (void) getushort(sfnt); (void) getushort(sfnt); rewind(woff); putlong(woff,CHR('w','O','F','F')); putlong(woff,flavour); putlong(woff,0); /* Off: 8. total length of file, fill in later */ putshort(woff,num_tabs); putshort(woff,0); /* Must be zero */ putlong(woff,filelen); putshort(woff,major); /* Major and minor version numbers of font */ putshort(woff,minor); putlong(woff,0); /* Off: 24. Offset to metadata table */ putlong(woff,0); /* Off: 28. Length (compressed) of metadata */ putlong(woff,0); /* Off: 32. Length (uncompressed) */ putlong(woff,0); /* Off: 36. Offset to private data */ putlong(woff,0); /* Off: 40. Length of private data */ tab_start = ftell(woff); for ( i=0; i<5*num_tabs; ++i ) putlong(woff,0); for ( i=0; iwoffMetadata!= NULL ) { int uncomplen = strlen(sf->woffMetadata); uLongf complen = 2*uncomplen; char *temp=galloc(complen+1); newoffset = ftell(woff); _compress(temp,&complen,sf->woffMetadata,uncomplen); fwrite(temp,1,complen,woff); free(temp); if ( (ftell(woff)&3)!=0 ) { /* Pad to a 4 byte boundary */ if ( ftell(woff)&1 ) putc('\0',woff); if ( ftell(woff)&2 ) putshort(woff,0); } fseek(woff,24,SEEK_SET); putlong(woff,newoffset); putlong(woff,complen); putlong(woff,uncomplen); fseek(woff,0,SEEK_END); } fseek(woff,0,SEEK_END); len = ftell(woff); fseek(woff,8,SEEK_SET); putlong(woff,len); return( true ); /* No errors */ } int WriteWOFFFont(char *fontname,SplineFont *sf, enum fontformat format, int32 *bsizes, enum bitmapformat bf,int flags,EncMap *enc,int layer) { FILE *woff; int ret; if ( strstr(fontname,"://")!=NULL ) { if (( woff = tmpfile())==NULL ) return( 0 ); } else { if (( woff=fopen(fontname,"wb+"))==NULL ) return( 0 ); } ret = _WriteWOFFFont(woff,sf,format,bsizes,bf,flags,enc,layer); if ( strstr(fontname,"://")!=NULL && ret ) ret = URLFromFile(fontname,woff); if ( fclose(woff)==-1 ) return( 0 ); return( ret ); } int CanWoff(void) { return( haszlib()); } #endif /* NOLIBPNG */