diff options
-rw-r--r-- | Makefile | 16 | ||||
-rw-r--r-- | sfnt2woff.c | 196 | ||||
-rw-r--r-- | woff-private.h | 151 | ||||
-rw-r--r-- | woff.c | 1170 | ||||
-rw-r--r-- | woff.h | 211 | ||||
-rw-r--r-- | woff2sfnt.c | 225 |
6 files changed, 1969 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f9942eb --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +all: sfnt2woff woff2sfnt + +sfnt2woff: sfnt2woff.o woff.o woff.h Makefile + $(CC) $(LDFLAGS) -o $@ $< woff.o -lz + +woff2sfnt: woff2sfnt.o woff.o woff.h Makefile + $(CC) $(LDFLAGS) -o $@ $< woff.o -lz + +sfnt2woff.o: sfnt2woff.c woff.h Makefile + +woff2sfnt.o: woff2sfnt.c woff.h Makefile + +woff.o: woff.c woff.h woff-private.h Makefile + +clean: + $(RM) -r *.o *.dSYM diff --git a/sfnt2woff.c b/sfnt2woff.c new file mode 100644 index 0000000..2faae4a --- /dev/null +++ b/sfnt2woff.c @@ -0,0 +1,196 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is WOFF font packaging code. + * + * The Initial Developer of the Original Code is Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Jonathan Kew <jfkthame@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "woff.h" + +static void +die(const char * msg) +{ + fprintf(stderr, "# fatal error: %s\n", msg); + exit(2); +} + +static void +reportErr(uint32_t status) +{ + woffPrintStatus(stderr, status, "### "); + exit(2); +} + +static void +usage(const char * progName) +{ + fprintf(stderr, "Usage:\n" + " %s [-v <maj>.<min>] [-m <metadata.xml>] [-p <private.dat>] <otffile>\n" + " package OpenType <otffile> as WOFF, creating <otffile>.woff\n" + "Options:\n" + " -v <maj>.<min> set font version number (major and minor, both integers)\n" + " -m <metadata.xml> include metadata from <metadata.xml> (not validated)\n" + " -p <private.dat> include private data block\n" + , progName); +} + +const uint8_t * +readFile(const char * name, uint32_t * len) +{ + FILE * inFile = fopen(name, "rb"); + if (!inFile) { + char buf[200]; + sprintf(buf, "unable to open file %s", name); + die(buf); + } + + if (fseek(inFile, 0, SEEK_END) != 0) + die("seek failure"); + *len = ftell(inFile); + if (fseek(inFile, 0, SEEK_SET) != 0) + die("seek failure"); + + uint8_t * data = (uint8_t *) malloc(*len); + if (!data) + die("malloc failure"); + if (fread(data, 1, *len, inFile) != *len) + die("file read failure"); + fclose(inFile); + + return data; +} + +int +main(int argc, char * argv[]) +{ + const char * progName = argv[0]; + const char * metadataFile = NULL; + const char * privateFile = NULL; + unsigned int maj = 0, min = 0; + uint32_t status = eWOFF_ok; + + int opt; + while ((opt = getopt(argc, argv, "v:m:p:h")) != -1) { + switch (opt) { + case 'v': + if (sscanf(optarg, "%u.%u", &maj, &min) < 2 || maj > 0xffff || min > 0xffff) { + fprintf(stderr, "# bad version number, setting to 0.0\n"); + maj = min = 0; + } + break; + case 'm': + metadataFile = optarg; + break; + case 'p': + privateFile = optarg; + break; + case 'h': + case '?': + usage(progName); + exit(0); + default: + fprintf(stderr, "# unknown option \"%c\"\n", opt); + break; + } + } + argc -= optind; + argv += optind; + + if (argc != 1) { + usage(progName); + exit(1); + } + + uint32_t sfntLen; + const uint8_t * sfntData = readFile(argv[0], &sfntLen); + + uint32_t woffLen; + const uint8_t * woffData = woffEncode(sfntData, sfntLen, maj, min, &woffLen, &status); + free((void *)sfntData); + if (WOFF_FAILURE(status)) { + reportErr(status); + } + + if (metadataFile) { + uint32_t len; + const uint8_t * data = readFile(metadataFile, &len); + woffData = woffSetMetadata(woffData, &woffLen, data, len, &status); + free((void *)data); + if (WOFF_FAILURE(status)) { + reportErr(status); + } + } + + if (privateFile) { + uint32_t len; + const uint8_t * data = readFile(privateFile, &len); + woffData = woffSetPrivateData(woffData, &woffLen, data, len, &status); + free((void *)data); + if (WOFF_FAILURE(status)) { + reportErr(status); + } + } + + if (WOFF_WARNING(status)) { + woffPrintStatus(stderr, status, "### "); + } + + char * outName = (char *) malloc(strlen(argv[0]) + 8); + if (!outName) + die("malloc failure"); + strcpy(outName, argv[0]); + char * ext = strrchr(outName, '.'); + if (ext && (!strcmp(ext, ".ttf") || !strcmp(ext, ".otf"))) + *ext = 0; + strcat(outName, ".woff"); + + if (woffData) { + FILE * outFile = fopen(outName, "wb"); + free(outName); + if (!outFile) + die("unable to open output file"); + if (fwrite(woffData, 1, woffLen, outFile) != woffLen) + die("file write failure"); + fclose(outFile); + free((void *) woffData); + } else { + die("unable to create WOFF data"); + } + + return 0; +} diff --git a/woff-private.h b/woff-private.h new file mode 100644 index 0000000..8a41aa0 --- /dev/null +++ b/woff-private.h @@ -0,0 +1,151 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is WOFF font packaging code. + * + * The Initial Developer of the Original Code is Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Jonathan Kew <jfkthame@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef WOFF_PRIVATE_H_ +#define WOFF_PRIVATE_H_ + +#include "woff.h" + +/* private definitions used in the WOFF encoder/decoder functions */ + +/* create an OT tag from 4 characters */ +#define TAG(a,b,c,d) ((a)<<24 | (b)<<16 | (c)<<8 | (d)) + +#define WOFF_SIGNATURE TAG('w','O','F','F') + +#define SFNT_VERSION_CFF TAG('O','T','T','O') +#define SFNT_VERSION_TT 0x00010000 +#define SFNT_VERSION_true TAG('t','r','u','e') + +#define TABLE_TAG_DSIG TAG('D','S','I','G') +#define TABLE_TAG_head TAG('h','e','a','d') +#define TABLE_TAG_bhed TAG('b','h','e','d') + +#define SFNT_CHECKSUM_CALC_CONST 0xB1B0AFBAU /* from the TT/OT spec */ + +#ifdef WOFF_MOZILLA_CLIENT +# include <prnetdb.h> +# define READ32BE(x) PR_ntohl(x) +# define READ16BE(x) PR_ntohs(x) +#else +/* These macros to read values as big-endian only work on "real" variables, + not general expressions, because of the use of &(x), but they are + designed to work on both BE and LE machines without the need for a + configure check. For production code, we might want to replace this + with something more efficient. */ +/* read a 32-bit BigEndian value */ +# define READ32BE(x) ( ( (uint32_t) ((uint8_t*)&(x))[0] << 24 ) + \ + ( (uint32_t) ((uint8_t*)&(x))[1] << 16 ) + \ + ( (uint32_t) ((uint8_t*)&(x))[2] << 8 ) + \ + (uint32_t) ((uint8_t*)&(x))[3] ) +/* read a 16-bit BigEndian value */ +# define READ16BE(x) ( ( (uint16_t) ((uint8_t*)&(x))[0] << 8 ) + \ + (uint16_t) ((uint8_t*)&(x))[1] ) +#endif + +#pragma pack(push,1) + +typedef struct { + uint32_t version; + uint16_t numTables; + uint16_t searchRange; + uint16_t entrySelector; + uint16_t rangeShift; +} sfntHeader; + +typedef struct { + uint32_t tag; + uint32_t checksum; + uint32_t offset; + uint32_t length; +} sfntDirEntry; + +typedef struct { + uint32_t signature; + uint32_t flavor; + uint32_t length; + uint16_t numTables; + uint16_t reserved; + uint32_t totalSfntSize; + uint16_t majorVersion; + uint16_t minorVersion; + uint32_t metaOffset; + uint32_t metaCompLen; + uint32_t metaOrigLen; + uint32_t privOffset; + uint32_t privLen; +} woffHeader; + +typedef struct { + uint32_t tag; + uint32_t offset; + uint32_t compLen; + uint32_t origLen; + uint32_t checksum; +} woffDirEntry; + +typedef struct { + uint32_t version; + uint32_t fontRevision; + uint32_t checkSumAdjustment; + uint32_t magicNumber; + uint16_t flags; + uint16_t unitsPerEm; + uint32_t created[2]; + uint32_t modified[2]; + int16_t xMin; + int16_t yMin; + int16_t xMax; + int16_t yMax; + uint16_t macStyle; + uint16_t lowestRecPpem; + int16_t fontDirectionHint; + int16_t indexToLocFormat; + int16_t glyphDataFormat; +} sfntHeadTable; + +#define HEAD_TABLE_SIZE 54 /* sizeof(sfntHeadTable) may report 56 because of alignment */ + +typedef struct { + uint32_t offset; + uint16_t oldIndex; + uint16_t newIndex; +} tableOrderRec; + +#pragma pack(pop) + +#endif @@ -0,0 +1,1170 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is WOFF font packaging code. + * + * The Initial Developer of the Original Code is Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Jonathan Kew <jfkthame@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "woff-private.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <zlib.h> + +#ifdef WOFF_MOZILLA_CLIENT /* define this when building as part of Gecko */ +# include "prmem.h" +# define malloc PR_Malloc +# define realloc PR_Realloc +# define free PR_Free +#endif + +/* + * Just simple whole-file encoding and decoding functions; a more extensive + * WOFF library could provide support for accessing individual tables from a + * compressed font, alternative options for memory allocation/ownership and + * error handling, etc. + */ + +/* on errors, each function sets a status variable and jumps to failure: */ +#undef FAIL +#define FAIL(err) do { status |= err; goto failure; } while (0) + +/* adjust an offset for longword alignment */ +#define LONGALIGN(x) (((x) + 3) & ~3) + +static int +compareOffsets(const void * lhs, const void * rhs) +{ + const tableOrderRec * a = (const tableOrderRec *) lhs; + const tableOrderRec * b = (const tableOrderRec *) rhs; + /* don't simply return a->offset - b->offset because these are unsigned + offset values; could convert to int, but possible integer overflow */ + return a->offset > b->offset ? 1 : + a->offset < b->offset ? -1 : + 0; +} + +#ifndef WOFF_MOZILLA_CLIENT + +/******************************************************************/ +/* * * * * * * * * * * * * * ENCODING * * * * * * * * * * * * * * */ +/******************************************************************/ + +static uint32_t +calcChecksum(const sfntDirEntry * dirEntry, + const uint8_t * sfntData, uint32_t sfntLen) +{ + /* just returns zero on errors, they will be detected again elsewhere */ + const uint32_t * csumPtr; + const uint32_t * csumEnd; + uint32_t csum = 0; + uint32_t length = LONGALIGN(READ32BE(dirEntry->length)); + uint32_t offset = READ32BE(dirEntry->offset); + uint32_t tag; + if ((offset & 3) != 0) { + return csum; + } + if (length > sfntLen || offset > sfntLen - length) { + return csum; + } + csumPtr = (const uint32_t *) (sfntData + offset); + csumEnd = csumPtr + length / 4; + while (csumPtr < csumEnd) { + csum += READ32BE(*csumPtr); + csumPtr++; + } + tag = READ32BE(dirEntry->tag); + if (tag == TABLE_TAG_head || tag == TABLE_TAG_bhed) { + const sfntHeadTable * head; + if (length < HEAD_TABLE_SIZE) { + return 0; + } + head = (const sfntHeadTable *)(sfntData + offset); + csum -= READ32BE(head->checkSumAdjustment); + } + return csum; +} + +const uint8_t * +woffEncode(const uint8_t * sfntData, uint32_t sfntLen, + uint16_t majorVersion, uint16_t minorVersion, + uint32_t * woffLen, uint32_t * pStatus) +{ + uint8_t * woffData = NULL; + tableOrderRec * tableOrder = NULL; + + uint32_t tableOffset; + uint32_t totalSfntSize; + + uint16_t numOrigTables; + uint16_t numTables; + uint16_t tableIndex; + uint16_t order; + const sfntDirEntry * sfntDir; + uint32_t tableBase; + uint32_t checkSumAdjustment = 0; + woffHeader * newHeader; + uint32_t tag = 0; + uint32_t removedDsigSize = 0; + uint32_t status = eWOFF_ok; + + const sfntHeader * header = (const sfntHeader *) (sfntData); + const sfntHeadTable * head = NULL; + + if (pStatus && WOFF_FAILURE(*pStatus)) { + return NULL; + } + + if (READ32BE(header->version) != SFNT_VERSION_TT && + READ32BE(header->version) != SFNT_VERSION_CFF && + READ32BE(header->version) != SFNT_VERSION_true) { + status |= eWOFF_warn_unknown_version; + } + + numOrigTables = READ16BE(header->numTables); + sfntDir = (const sfntDirEntry *) (sfntData + sizeof(sfntHeader)); + + for (tableIndex = 0; tableIndex < numOrigTables; ++tableIndex) { + /* validate table checksums, to figure out if we need to drop DSIG; + also check that table directory is correctly sorted */ + uint32_t prevTag = tag; + uint32_t csum = calcChecksum(&sfntDir[tableIndex], sfntData, sfntLen); + if (csum != READ32BE(sfntDir[tableIndex].checksum)) { + status |= eWOFF_warn_checksum_mismatch; + } + checkSumAdjustment += csum; + tag = READ32BE(sfntDir[tableIndex].tag); + if (tag <= prevTag) { + FAIL(eWOFF_invalid); + } + if (tag == TABLE_TAG_head || tag == TABLE_TAG_bhed) { + if (READ32BE(sfntDir[tableIndex].length) < HEAD_TABLE_SIZE) { + FAIL(eWOFF_invalid); + } + head = (const sfntHeadTable *)(sfntData + + READ32BE(sfntDir[tableIndex].offset)); + } + } + if (!head) { + FAIL(eWOFF_invalid); + } + if ((status & eWOFF_warn_checksum_mismatch) == 0) { + /* no point even checking if we already have an error, + as fixing that will change the overall checksum too */ + const uint32_t * csumPtr = (const uint32_t *) sfntData; + const uint32_t * csumEnd = csumPtr + 3 + 4 * numOrigTables; + while (csumPtr < csumEnd) { + checkSumAdjustment += READ32BE(*csumPtr); + ++csumPtr; + } + checkSumAdjustment = 0xB1B0AFBA - checkSumAdjustment; + if (checkSumAdjustment != READ32BE(head->checkSumAdjustment)) { + status |= eWOFF_warn_checksum_mismatch; + } + } + + /* Fixing checkSumAdjustment is tricky, because if there's a DSIG table, + we're going to have to remove that, which in turn means that table + offsets in the directory will all change. + And recalculating checkSumAdjustment requires taking account of any + individual table checksum corrections, but they have not actually been + applied to the sfnt data at this point. + And finally, we'd need to get the corrected checkSumAdjustment into the + encoded head table (but we can't modify the original sfnt data). + An easier way out seems to be to go ahead and encode the font, knowing + that checkSumAdjustment will be wrong; then (if the status flag + eWOFF_warn_checksum_mismatch is set) we'll decode the font back to + sfnt format. This will fix up the checkSumAdjustment (and return a + warning status). We'll ignore that warning, and then re-encode the + new, cleaned-up sfnt to get the final WOFF data. Perhaps not the most + efficient approach, but it seems simpler than trying to predict the + correct final checkSumAdjustment and incorporate it into the head + table on the fly. */ + + tableOrder = (tableOrderRec *) malloc(numOrigTables * sizeof(tableOrderRec)); + if (!tableOrder) { + FAIL(eWOFF_out_of_memory); + } + for (tableIndex = 0, numTables = 0; + tableIndex < numOrigTables; ++tableIndex) { + if ((status & eWOFF_warn_checksum_mismatch) != 0) { + /* check for DSIG table that we must drop if we're fixing checksums */ + tag = READ32BE(sfntDir[tableIndex].tag); + if (tag == TABLE_TAG_DSIG) { + status |= eWOFF_warn_removed_DSIG; + removedDsigSize = READ32BE(sfntDir[tableIndex].length); + continue; + } + } + tableOrder[numTables].offset = READ32BE(sfntDir[tableIndex].offset); + tableOrder[numTables].oldIndex = tableIndex; + tableOrder[numTables].newIndex = numTables; + ++numTables; + } + qsort(tableOrder, numTables, sizeof(tableOrderRec), compareOffsets); + + /* initially, allocate space for header and directory */ + tableOffset = sizeof(woffHeader) + numTables * sizeof(woffDirEntry); + woffData = (uint8_t *) malloc(tableOffset); + if (!woffData) { + FAIL(eWOFF_out_of_memory); + } + + /* accumulator for total expected size of decoded font */ + totalSfntSize = sizeof(sfntHeader) + numTables * sizeof(sfntDirEntry); + +/* + * We use a macro for this rather than creating a variable because woffData + * will get reallocated during encoding. The macro avoids the risk of using a + * stale pointer, and the compiler should optimize multiple successive uses. + */ +#define WOFFDIR ((woffDirEntry *) (woffData + sizeof(woffHeader))) + + for (order = 0; order < numTables; ++order) { + uLong sourceLen, destLen; + uint32_t sourceOffset; + + uint16_t oldIndex = tableOrder[order].oldIndex; + uint16_t newIndex = tableOrder[order].newIndex; + + WOFFDIR[newIndex].tag = sfntDir[oldIndex].tag; + if ((status & eWOFF_warn_checksum_mismatch) != 0) { + uint32_t csum = calcChecksum(&sfntDir[oldIndex], sfntData, sfntLen); + WOFFDIR[newIndex].checksum = READ32BE(csum); + } else { + WOFFDIR[newIndex].checksum = sfntDir[oldIndex].checksum; + } + WOFFDIR[newIndex].origLen = sfntDir[oldIndex].length; + WOFFDIR[newIndex].offset = READ32BE(tableOffset); + + /* allocate enough space for upper bound of compressed size */ + sourceOffset = READ32BE(sfntDir[oldIndex].offset); + if ((sourceOffset & 3) != 0) { + status |= eWOFF_warn_misaligned_table; + } + sourceLen = READ32BE(sfntDir[oldIndex].length); + if (sourceLen > sfntLen || sourceOffset > sfntLen - sourceLen) { + FAIL(eWOFF_invalid); + } + destLen = LONGALIGN(compressBound(sourceLen)); + woffData = (uint8_t *) realloc(woffData, tableOffset + destLen); + if (!woffData) { + FAIL(eWOFF_out_of_memory); + } + + /* do the compression directly into the WOFF data block */ + if (compress2((Bytef *) (woffData + tableOffset), &destLen, + (const Bytef *) (sfntData + sourceOffset), + sourceLen, 9) != Z_OK) { + FAIL(eWOFF_compression_failure); + } + if (destLen < sourceLen) { + /* compressed table was smaller */ + tableOffset += destLen; + WOFFDIR[newIndex].compLen = READ32BE(destLen); + } else { + /* compression didn't make it smaller, so store original data instead */ + destLen = sourceLen; + /* reallocate to ensure enough space for the table, + plus potential padding after it */ + woffData = (uint8_t *) realloc(woffData, + tableOffset + LONGALIGN(sourceLen)); + if (!woffData) { + FAIL(eWOFF_out_of_memory); + } + /* copy the original data into place */ + memcpy(woffData + tableOffset, + sfntData + READ32BE(sfntDir[oldIndex].offset), sourceLen); + tableOffset += sourceLen; + WOFFDIR[newIndex].compLen = WOFFDIR[newIndex].origLen; + } + + /* we always realloc woffData to a long-aligned size, so this is safe */ + while ((tableOffset & 3) != 0) { + woffData[tableOffset++] = 0; + } + + /* update total size of uncompressed OpenType with table size */ + totalSfntSize += sourceLen; + totalSfntSize = LONGALIGN(totalSfntSize); + } + + if (totalSfntSize > sfntLen) { + if (totalSfntSize > LONGALIGN(sfntLen)) { + FAIL(eWOFF_invalid); + } else { + status |= eWOFF_warn_unpadded_table; + } + } else if (totalSfntSize < sfntLen) { + /* check if the remaining data is a DSIG we're removing; + if so, we're already warning about that */ + if ((status & eWOFF_warn_removed_DSIG) != 0 || + sfntLen - totalSfntSize > + LONGALIGN(removedDsigSize) + sizeof(sfntDirEntry)) { + status |= eWOFF_warn_trailing_data; + } + } + + /* write the header */ + newHeader = (woffHeader *) (woffData); + newHeader->signature = WOFF_SIGNATURE; + newHeader->signature = READ32BE(newHeader->signature); + newHeader->flavor = header->version; + newHeader->length = READ32BE(tableOffset); + newHeader->numTables = READ16BE(numTables); + newHeader->reserved = 0; + newHeader->totalSfntSize = READ32BE(totalSfntSize); + newHeader->majorVersion = READ16BE(majorVersion); + newHeader->minorVersion = READ16BE(minorVersion); + newHeader->metaOffset = 0; + newHeader->metaCompLen = 0; + newHeader->metaOrigLen = 0; + newHeader->privOffset = 0; + newHeader->privLen = 0; + + free(tableOrder); + + if ((status & eWOFF_warn_checksum_mismatch) != 0) { + /* The original font had checksum errors, so we now decode our WOFF data + back to sfnt format (which fixes checkSumAdjustment), then re-encode + to get a clean copy. */ + const uint8_t * cleanSfnt = woffDecode(woffData, tableOffset, + &sfntLen, &status); + if (WOFF_FAILURE(status)) { + FAIL(status); + } + free(woffData); + woffData = (uint8_t *) woffEncode(cleanSfnt, sfntLen, + majorVersion, minorVersion, + &tableOffset, &status); + free((void *) cleanSfnt); + if (WOFF_FAILURE(status)) { + FAIL(status); + } + } + + if (woffLen) { + *woffLen = tableOffset; + } + if (pStatus) { + *pStatus |= status; + } + return woffData; + +failure: + if (tableOrder) { + free(tableOrder); + } + if (woffData) { + free(woffData); + } + if (pStatus) { + *pStatus = status; + } + return NULL; +} + +static const uint8_t * +rebuildWoff(const uint8_t * woffData, uint32_t * woffLen, + const uint8_t * metaData, uint32_t metaCompLen, uint32_t metaOrigLen, + const uint8_t * privData, uint32_t privLen, uint32_t * pStatus) +{ + const woffHeader * origHeader; + const woffDirEntry * woffDir; + uint8_t * newData = NULL; + uint8_t * tableData = NULL; + woffHeader * newHeader; + uint16_t numTables; + uint32_t tableLimit, totalSize, offset; + uint16_t i; + uint32_t status = eWOFF_ok; + + if (*woffLen < sizeof(woffHeader)) { + FAIL(eWOFF_invalid); + } + origHeader = (const woffHeader *) (woffData); + + if (READ32BE(origHeader->signature) != WOFF_SIGNATURE) { + FAIL(eWOFF_bad_signature); + } + + numTables = READ16BE(origHeader->numTables); + woffDir = (const woffDirEntry *) (woffData + sizeof(woffHeader)); + tableLimit = 0; + for (i = 0; i < numTables; ++i) { + uint32_t end = READ32BE(woffDir[i].offset) + READ32BE(woffDir[i].compLen); + if (end > tableLimit) { + tableLimit = end; + } + } + tableLimit = LONGALIGN(tableLimit); + + /* check for broken input (meta/priv data before sfnt tables) */ + offset = READ32BE(origHeader->metaOffset); + if (offset != 0 && offset < tableLimit) { + FAIL(eWOFF_illegal_order); + } + offset = READ32BE(origHeader->privOffset); + if (offset != 0 && offset < tableLimit) { + FAIL(eWOFF_illegal_order); + } + + totalSize = tableLimit; /* already long-aligned */ + if (metaCompLen) { + totalSize += metaCompLen; + } + if (privLen) { + totalSize = LONGALIGN(totalSize) + privLen; + } + newData = malloc(totalSize); + if (!newData) { + FAIL(eWOFF_out_of_memory); + } + + /* copy the header, directory, and sfnt tables */ + memcpy(newData, woffData, tableLimit); + + /* then overwrite the header fields that should be changed */ + newHeader = (woffHeader *) newData; + newHeader->length = READ32BE(totalSize); + newHeader->metaOffset = 0; + newHeader->metaCompLen = 0; + newHeader->metaOrigLen = 0; + newHeader->privOffset = 0; + newHeader->privLen = 0; + + offset = tableLimit; + if (metaData && metaCompLen > 0 && metaOrigLen > 0) { + newHeader->metaOffset = READ32BE(offset); + newHeader->metaCompLen = READ32BE(metaCompLen); + newHeader->metaOrigLen = READ32BE(metaOrigLen); + memcpy(newData + offset, metaData, metaCompLen); + offset += metaCompLen; + } + + if (privData && privLen > 0) { + while ((offset & 3) != 0) { + newData[offset++] = 0; + } + newHeader->privOffset = READ32BE(offset); + newHeader->privLen = READ32BE(privLen); + memcpy(newData + offset, privData, privLen); + offset += privLen; + } + + *woffLen = offset; + free((void *) woffData); + + if (pStatus) { + *pStatus |= status; + } + return newData; + +failure: + if (newData) { + free(newData); + } + if (pStatus) { + *pStatus = status; + } + return NULL; +} + +const uint8_t * +woffSetMetadata(const uint8_t * woffData, uint32_t * woffLen, + const uint8_t * metaData, uint32_t metaLen, + uint32_t * pStatus) +{ + const woffHeader * header; + uLong compLen = 0; + uint8_t * compData = NULL; + const uint8_t * privData = NULL; + uint32_t privLen = 0; + uint32_t status = eWOFF_ok; + + if (pStatus && WOFF_FAILURE(*pStatus)) { + return NULL; + } + + if (!woffData || !woffLen) { + FAIL(eWOFF_bad_parameter); + } + + if (*woffLen < sizeof(woffHeader)) { + FAIL(eWOFF_invalid); + } + header = (const woffHeader *) (woffData); + + if (READ32BE(header->signature) != WOFF_SIGNATURE) { + FAIL(eWOFF_bad_signature); + } + + if (header->privOffset != 0 && header->privLen != 0) { + privData = woffData + READ32BE(header->privOffset); + privLen = READ32BE(header->privLen); + if (privData + privLen > woffData + *woffLen) { + FAIL(eWOFF_invalid); + } + } + + if (metaData && metaLen > 0) { + compLen = compressBound(metaLen); + compData = malloc(compLen); + if (!compData) { + FAIL(eWOFF_out_of_memory); + } + + if (compress2((Bytef *) compData, &compLen, + (const Bytef *) metaData, metaLen, 9) != Z_OK) { + FAIL(eWOFF_compression_failure); + } + } + + woffData = rebuildWoff(woffData, woffLen, + compData, compLen, metaLen, + privData, privLen, pStatus); + free(compData); + return woffData; + +failure: + if (compData) { + free(compData); + } + if (pStatus) { + *pStatus = status; + } + return NULL; +} + +const uint8_t * +woffSetPrivateData(const uint8_t * woffData, uint32_t * woffLen, + const uint8_t * privData, uint32_t privLen, + uint32_t * pStatus) +{ + const woffHeader * header; + const uint8_t * metaData = NULL; + uint32_t metaLen = 0; + uint32_t status = eWOFF_ok; + + if (pStatus && WOFF_FAILURE(*pStatus)) { + return NULL; + } + + if (!woffData || !woffLen) { + FAIL(eWOFF_bad_parameter); + } + + if (*woffLen < sizeof(woffHeader)) { + FAIL(eWOFF_invalid); + } + header = (const woffHeader *) (woffData); + + if (READ32BE(header->signature) != WOFF_SIGNATURE) { + FAIL(eWOFF_bad_signature); + } + + if (header->metaOffset != 0 && header->metaCompLen != 0) { + metaData = woffData + READ32BE(header->metaOffset); + metaLen = READ32BE(header->metaCompLen); + if (metaData + metaLen > woffData + *woffLen) { + FAIL(eWOFF_invalid); + } + } + + woffData = rebuildWoff(woffData, woffLen, + metaData, metaLen, READ32BE(header->metaOrigLen), + privData, privLen, pStatus); + return woffData; + +failure: + if (pStatus) { + *pStatus = status; + } + return NULL; +} + +#endif /* WOFF_MOZILLA_CLIENT */ + +/******************************************************************/ +/* * * * * * * * * * * * * * DECODING * * * * * * * * * * * * * * */ +/******************************************************************/ + +static uint32_t +sanityCheck(const uint8_t * woffData, uint32_t woffLen) +{ + const woffHeader * header; + uint16_t numTables, i; + const woffDirEntry * dirEntry; + uint32_t tableTotal = 0; + + if (!woffData || !woffLen) { + return eWOFF_bad_parameter; + } + + if (woffLen < sizeof(woffHeader)) { + return eWOFF_invalid; + } + + header = (const woffHeader *) (woffData); + if (READ32BE(header->signature) != WOFF_SIGNATURE) { + return eWOFF_bad_signature; + } + + if (READ32BE(header->length) != woffLen || header->reserved != 0) { + return eWOFF_invalid; + } + + numTables = READ16BE(header->numTables); + if (woffLen < sizeof(woffHeader) + numTables * sizeof(woffDirEntry)) { + return eWOFF_invalid; + } + + dirEntry = (const woffDirEntry *) (woffData + sizeof(woffHeader)); + for (i = 0; i < numTables; ++i) { + uint32_t offs = READ32BE(dirEntry->offset); + uint32_t orig = READ32BE(dirEntry->origLen); + uint32_t comp = READ32BE(dirEntry->compLen); + if (comp > orig || comp > woffLen || offs > woffLen - comp) { + return eWOFF_invalid; + } + orig = (orig + 3) & ~3; + if (tableTotal > 0xffffffffU - orig) { + return eWOFF_invalid; + } + tableTotal += orig; + ++dirEntry; + } + + if (tableTotal > 0xffffffffU - sizeof(sfntHeader) - + numTables * sizeof(sfntDirEntry) || + READ32BE(header->totalSfntSize) != + tableTotal + sizeof(sfntHeader) + numTables * sizeof(sfntDirEntry)) { + return eWOFF_invalid; + } + + return eWOFF_ok; +} + +uint32_t +woffGetDecodedSize(const uint8_t * woffData, uint32_t woffLen, + uint32_t * pStatus) +{ + uint32_t status = eWOFF_ok; + uint32_t totalLen = 0; + + if (pStatus && WOFF_FAILURE(*pStatus)) { + return 0; + } + + status = sanityCheck(woffData, woffLen); + if (WOFF_FAILURE(status)) { + FAIL(status); + } + + totalLen = READ32BE(((const woffHeader *) (woffData))->totalSfntSize); + /* totalLen must be correctly rounded up to 4-byte alignment, otherwise + sanityCheck would have failed */ + +failure: + if (pStatus) { + *pStatus = status; + } + return totalLen; +} + +static void +woffDecodeToBufferInternal(const uint8_t * woffData, uint32_t woffLen, + uint8_t * sfntData, uint32_t bufferLen, + uint32_t * pActualSfntLen, uint32_t * pStatus) +{ + /* this is only called after sanityCheck has verified that + (a) basic header fields are ok + (b) all the WOFF table offset/length pairs are valid (within the data) + (c) the sum of original sizes + header/directory matches totalSfntSize + so we don't have to re-check those overflow conditions here */ + tableOrderRec * tableOrder = NULL; + const woffHeader * header; + uint16_t numTables; + uint16_t tableIndex; + uint16_t order; + const woffDirEntry * woffDir; + uint32_t totalLen; + sfntHeader * newHeader; + uint16_t searchRange, rangeShift, entrySelector; + uint32_t offset; + sfntDirEntry * sfntDir; + uint32_t headOffset = 0, headLength = 0; + sfntHeadTable * head; + uint32_t csum = 0; + const uint32_t * csumPtr; + uint32_t oldCheckSumAdjustment; + uint32_t status = eWOFF_ok; + + if (pStatus && WOFF_FAILURE(*pStatus)) { + return; + } + + /* check basic header fields */ + header = (const woffHeader *) (woffData); + if (READ32BE(header->flavor) != SFNT_VERSION_TT && + READ32BE(header->flavor) != SFNT_VERSION_CFF && + READ32BE(header->flavor) != SFNT_VERSION_true) { + status |= eWOFF_warn_unknown_version; + } + + numTables = READ16BE(header->numTables); + woffDir = (const woffDirEntry *) (woffData + sizeof(woffHeader)); + + totalLen = READ32BE(header->totalSfntSize); + + /* construct the sfnt header */ + newHeader = (sfntHeader *) (sfntData); + newHeader->version = header->flavor; + newHeader->numTables = READ16BE(numTables); + + /* calculate header fields for binary search */ + searchRange = numTables; + searchRange |= (searchRange >> 1); + searchRange |= (searchRange >> 2); + searchRange |= (searchRange >> 4); + searchRange |= (searchRange >> 8); + searchRange &= ~(searchRange >> 1); + searchRange *= 16; + newHeader->searchRange = READ16BE(searchRange); + rangeShift = numTables * 16 - searchRange; + newHeader->rangeShift = READ16BE(rangeShift); + entrySelector = 0; + while (searchRange > 16) { + ++entrySelector; + searchRange >>= 1; + } + newHeader->entrySelector = READ16BE(entrySelector); + + tableOrder = (tableOrderRec *) malloc(numTables * sizeof(tableOrderRec)); + if (!tableOrder) { + FAIL(eWOFF_out_of_memory); + } + for (tableIndex = 0; tableIndex < numTables; ++tableIndex) { + tableOrder[tableIndex].offset = READ32BE(woffDir[tableIndex].offset); + tableOrder[tableIndex].oldIndex = tableIndex; + } + qsort(tableOrder, numTables, sizeof(tableOrderRec), compareOffsets); + + /* process each table, filling in the sfnt directory */ + offset = sizeof(sfntHeader) + numTables * sizeof(sfntDirEntry); + sfntDir = (sfntDirEntry *) (sfntData + sizeof(sfntHeader)); + for (order = 0; order < numTables; ++order) { + uint32_t origLen, compLen, tag, sourceOffset; + tableIndex = tableOrder[order].oldIndex; + + /* validity of these was confirmed by sanityCheck */ + origLen = READ32BE(woffDir[tableIndex].origLen); + compLen = READ32BE(woffDir[tableIndex].compLen); + sourceOffset = READ32BE(woffDir[tableIndex].offset); + + sfntDir[tableIndex].tag = woffDir[tableIndex].tag; + sfntDir[tableIndex].offset = READ32BE(offset); + sfntDir[tableIndex].length = woffDir[tableIndex].origLen; + sfntDir[tableIndex].checksum = woffDir[tableIndex].checksum; + csum += READ32BE(sfntDir[tableIndex].checksum); + + if (compLen < origLen) { + uLongf destLen = origLen; + if (uncompress((Bytef *)(sfntData + offset), &destLen, + (const Bytef *)(woffData + sourceOffset), + compLen) != Z_OK || destLen != origLen) { + FAIL(eWOFF_compression_failure); + } + } else { + memcpy(sfntData + offset, woffData + sourceOffset, origLen); + } + + /* note that old Mac bitmap-only fonts have no 'head' table + (eg NISC18030.ttf) but a 'bhed' table instead */ + tag = READ32BE(sfntDir[tableIndex].tag); + if (tag == TABLE_TAG_head || tag == TABLE_TAG_bhed) { + headOffset = offset; + headLength = origLen; + } + + offset += origLen; + + while (offset < totalLen && (offset & 3) != 0) { + sfntData[offset++] = 0; + } + } + + if (headOffset > 0) { + /* the font checksum in the 'head' table depends on all the individual + table checksums (collected above), plus the header and directory + which are added in here */ + if (headLength < HEAD_TABLE_SIZE) { + FAIL(eWOFF_invalid); + } + head = (sfntHeadTable *)(sfntData + headOffset); + oldCheckSumAdjustment = READ32BE(head->checkSumAdjustment); + head->checkSumAdjustment = 0; + csumPtr = (const uint32_t *)sfntData; + while (csumPtr < (const uint32_t *)(sfntData + sizeof(sfntHeader) + + numTables * sizeof(sfntDirEntry))) { + csum += READ32BE(*csumPtr); + csumPtr++; + } + csum = SFNT_CHECKSUM_CALC_CONST - csum; + + if (oldCheckSumAdjustment != csum) { + /* if the checksum doesn't match, we fix it; but this will invalidate + any DSIG that may be present */ + status |= eWOFF_warn_checksum_mismatch; + } + head->checkSumAdjustment = READ32BE(csum); + } + + if (pActualSfntLen) { + *pActualSfntLen = totalLen; + } + if (pStatus) { + *pStatus |= status; + } + free(tableOrder); + return; + +failure: + if (tableOrder) { + free(tableOrder); + } + if (pActualSfntLen) { + *pActualSfntLen = 0; + } + if (pStatus) { + *pStatus = status; + } +} + +void +woffDecodeToBuffer(const uint8_t * woffData, uint32_t woffLen, + uint8_t * sfntData, uint32_t bufferLen, + uint32_t * pActualSfntLen, uint32_t * pStatus) +{ + uint32_t status = eWOFF_ok; + uint32_t totalLen; + + if (pStatus && WOFF_FAILURE(*pStatus)) { + return; + } + + status = sanityCheck(woffData, woffLen); + if (WOFF_FAILURE(status)) { + FAIL(status); + } + + if (!sfntData) { + FAIL(eWOFF_bad_parameter); + } + + totalLen = READ32BE(((const woffHeader *) (woffData))->totalSfntSize); + if (bufferLen < totalLen) { + FAIL(eWOFF_buffer_too_small); + } + + woffDecodeToBufferInternal(woffData, woffLen, sfntData, bufferLen, + pActualSfntLen, pStatus); + return; + +failure: + if (pActualSfntLen) { + *pActualSfntLen = 0; + } + if (pStatus) { + *pStatus = status; + } +} + +const uint8_t * +woffDecode(const uint8_t * woffData, uint32_t woffLen, + uint32_t * sfntLen, uint32_t * pStatus) +{ + uint32_t status = eWOFF_ok; + uint8_t * sfntData = NULL; + uint32_t bufLen; + + if (pStatus && WOFF_FAILURE(*pStatus)) { + return NULL; + } + + status = sanityCheck(woffData, woffLen); + if (WOFF_FAILURE(status)) { + FAIL(status); + } + + bufLen = READ32BE(((const woffHeader *) (woffData))->totalSfntSize); + sfntData = (uint8_t *) malloc(bufLen); + if (!sfntData) { + FAIL(eWOFF_out_of_memory); + } + + woffDecodeToBufferInternal(woffData, woffLen, sfntData, bufLen, + sfntLen, &status); + if (WOFF_FAILURE(status)) { + FAIL(status); + } + + if (pStatus) { + *pStatus |= status; + } + return sfntData; + +failure: + if (sfntData) { + free(sfntData); + } + if (pStatus) { + *pStatus = status; + } + return NULL; +} + +#ifndef WOFF_MOZILLA_CLIENT + +const uint8_t * +woffGetMetadata(const uint8_t * woffData, uint32_t woffLen, + uint32_t * metaLen, uint32_t * pStatus) +{ + const woffHeader * header; + uint32_t offset, compLen; + uLong origLen; + uint8_t * data = NULL; + uint32_t status = eWOFF_ok; + + if (pStatus && WOFF_FAILURE(*pStatus)) { + return NULL; + } + + status = sanityCheck(woffData, woffLen); + if (WOFF_FAILURE(status)) { + FAIL(status); + } + + header = (const woffHeader *) (woffData); + + offset = READ32BE(header->metaOffset); + compLen = READ32BE(header->metaCompLen); + origLen = READ32BE(header->metaOrigLen); + if (offset == 0 || compLen == 0 || origLen == 0) { + return NULL; + } + + if (compLen > woffLen || offset > woffLen - compLen) { + FAIL(eWOFF_invalid); + } + + data = malloc(origLen); + if (!data) { + FAIL(eWOFF_out_of_memory); + } + + if (uncompress((Bytef *)data, &origLen, + (const Bytef *)woffData + offset, compLen) != Z_OK || + origLen != READ32BE(header->metaOrigLen)) { + FAIL(eWOFF_compression_failure); + } + + if (metaLen) { + *metaLen = origLen; + } + if (pStatus) { + *pStatus |= status; + } + return data; + +failure: + if (data) { + free(data); + } + if (pStatus) { + *pStatus = status; + } + return NULL; +} + +const uint8_t * +woffGetPrivateData(const uint8_t * woffData, uint32_t woffLen, + uint32_t * privLen, uint32_t * pStatus) +{ + const woffHeader * header; + uint32_t offset, length; + uint8_t * data = NULL; + uint32_t status = eWOFF_ok; + + if (pStatus && WOFF_FAILURE(*pStatus)) { + return NULL; + } + + status = sanityCheck(woffData, woffLen); + if (WOFF_FAILURE(status)) { + FAIL(status); + } + + header = (const woffHeader *) (woffData); + + offset = READ32BE(header->privOffset); + length = READ32BE(header->privLen); + if (offset == 0 || length == 0) { + return NULL; + } + + if (length > woffLen || offset > woffLen - length) { + FAIL(eWOFF_invalid); + } + + data = malloc(length); + if (!data) { + FAIL(eWOFF_out_of_memory); + } + + memcpy(data, woffData + offset, length); + + if (privLen) { + *privLen = length; + } + if (pStatus) { + *pStatus |= status; + } + return data; + +failure: + if (data) { + free(data); + } + if (pStatus) { + *pStatus = status; + } + return NULL; +} + +void +woffGetFontVersion(const uint8_t * woffData, uint32_t woffLen, + uint16_t * major, uint16_t * minor, uint32_t * pStatus) +{ + const woffHeader * header; + uint32_t status = eWOFF_ok; + + if (pStatus && WOFF_FAILURE(*pStatus)) { + return; + } + + status = sanityCheck(woffData, woffLen); + if (WOFF_FAILURE(status)) { + FAIL(status); + } + + if (!major || !minor) { + FAIL(eWOFF_bad_parameter); + } + + *major = *minor = 0; + + header = (const woffHeader *) (woffData); + + *major = READ16BE(header->majorVersion); + *minor = READ16BE(header->minorVersion); + +failure: + if (pStatus) { + *pStatus = status; + } +} + +/* utility to print messages corresponding to WOFF encoder/decoder errors */ +void +woffPrintStatus(FILE * f, uint32_t status, const char * prefix) +{ + if (!prefix) { + prefix = ""; + } + if (WOFF_WARNING(status)) { + const char * template = "%sWOFF warning: %s\n"; + if (status & eWOFF_warn_unknown_version) { + fprintf(f, template, prefix, "unrecognized sfnt version"); + } + if (status & eWOFF_warn_checksum_mismatch) { + fprintf(f, template, prefix, "checksum mismatch (corrected)"); + } + if (status & eWOFF_warn_misaligned_table) { + fprintf(f, template, prefix, "misaligned font table"); + } + if (status & eWOFF_warn_trailing_data) { + fprintf(f, template, prefix, "extraneous input data discarded"); + } + if (status & eWOFF_warn_unpadded_table) { + fprintf(f, template, prefix, "final table not correctly padded"); + } + if (status & eWOFF_warn_removed_DSIG) { + fprintf(f, template, prefix, "digital signature (DSIG) table removed"); + } + } + if (WOFF_FAILURE(status)) { + const char * template = "%sWOFF error: %s\n"; + const char * msg; + switch (status & 0xff) { + case eWOFF_out_of_memory: + msg = "memory allocation failure"; + break; + case eWOFF_invalid: + msg = "invalid input font"; + break; + case eWOFF_compression_failure: + msg = "zlib compression/decompression failure"; + break; + case eWOFF_bad_signature: + msg = "incorrect WOFF file signature"; + break; + case eWOFF_buffer_too_small: + msg = "buffer too small"; + break; + case eWOFF_bad_parameter: + msg = "bad parameter to WOFF function"; + break; + case eWOFF_illegal_order: + msg = "incorrect table directory order"; + break; + default: + msg = "unknown internal error"; + break; + } + fprintf(f, template, prefix, msg); + } +} + +#endif /* not WOFF_MOZILLA_CLIENT */ @@ -0,0 +1,211 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is WOFF font packaging code. + * + * The Initial Developer of the Original Code is Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Jonathan Kew <jfkthame@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef WOFF_H_ +#define WOFF_H_ + +/* API for the WOFF encoder and decoder */ + +#ifdef _MSC_VER /* MS VC lacks inttypes.h + but we can make do with a few definitons here */ +typedef char int8_t; +typedef short int16_t; +typedef int int32_t; +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +#else +#include <inttypes.h> +#endif + +#include <stdio.h> /* only for FILE, needed for woffPrintStatus */ + +/* error codes returned in the status parameter of WOFF functions */ +enum { + /* Success */ + eWOFF_ok = 0, + + /* Errors: no valid result returned */ + eWOFF_out_of_memory = 1, /* malloc or realloc failed */ + eWOFF_invalid = 2, /* invalid input file (e.g., bad offset) */ + eWOFF_compression_failure = 3, /* error in zlib call */ + eWOFF_bad_signature = 4, /* unrecognized file signature */ + eWOFF_buffer_too_small = 5, /* the provided buffer is too small */ + eWOFF_bad_parameter = 6, /* bad parameter (e.g., null source ptr) */ + eWOFF_illegal_order = 7, /* improperly ordered chunks in WOFF font */ + + /* Warnings: call succeeded but something odd was noticed. + Multiple warnings may be OR'd together. */ + eWOFF_warn_unknown_version = 0x0100, /* unrecognized version of sfnt, + not standard TrueType or CFF */ + eWOFF_warn_checksum_mismatch = 0x0200, /* bad checksum, use with caution; + any DSIG will be invalid */ + eWOFF_warn_misaligned_table = 0x0400, /* table not long-aligned; fixing, + but DSIG will be invalid */ + eWOFF_warn_trailing_data = 0x0800, /* trailing junk discarded, + any DSIG may be invalid */ + eWOFF_warn_unpadded_table = 0x1000, /* sfnt not correctly padded, + any DSIG may be invalid */ + eWOFF_warn_removed_DSIG = 0x2000 /* removed digital signature + while fixing checksum errors */ +}; + +/* Note: status parameters must be initialized to eWOFF_ok before calling + WOFF functions. If the status parameter contains an error code, + functions will return immediately. */ + +#define WOFF_SUCCESS(status) (((uint32_t)(status) & 0xff) == eWOFF_ok) +#define WOFF_FAILURE(status) (!WOFF_SUCCESS(status)) +#define WOFF_WARNING(status) ((uint32_t)(status) & ~0xff) + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef WOFF_DISABLE_ENCODING + +/***************************************************************************** + * Returns a new malloc() block containing the encoded data, or NULL on error; + * caller should free() this when finished with it. + * Returns length of the encoded data in woffLen. + * The new WOFF has no metadata or private block; + * see the following functions to update these elements. + */ +const uint8_t * woffEncode(const uint8_t * sfntData, uint32_t sfntLen, + uint16_t majorVersion, uint16_t minorVersion, + uint32_t * woffLen, uint32_t * status); + + +/***************************************************************************** + * Add the given metadata block to the WOFF font, replacing any existing + * metadata block. The block will be zlib-compressed. + * Metadata is required to be valid XML (use of UTF-8 is recommended), + * though this function does not currently check this. + * The woffData pointer must be a malloc() block (typically from woffEncode); + * it will be freed by this function and a new malloc() block will be returned. + * Returns NULL if an error occurs, in which case the original WOFF is NOT freed. + */ +const uint8_t * woffSetMetadata(const uint8_t * woffData, uint32_t * woffLen, + const uint8_t * metaData, uint32_t metaLen, + uint32_t * status); + + +/***************************************************************************** + * Add the given private data block to the WOFF font, replacing any existing + * private block. The block will NOT be zlib-compressed. + * Private data may be any arbitrary block of bytes; it may be externally + * compressed by the client if desired. + * The woffData pointer must be a malloc() block (typically from woffEncode); + * it will be freed by this function and a new malloc() block will be returned. + * Returns NULL if an error occurs, in which case the original WOFF is NOT freed. + */ +const uint8_t * woffSetPrivateData(const uint8_t * woffData, uint32_t * woffLen, + const uint8_t * privData, uint32_t privLen, + uint32_t * status); + +#endif /* WOFF_DISABLE_ENCODING */ + +/***************************************************************************** + * Returns the size of buffer needed to decode the font (or zero on error). + */ +uint32_t woffGetDecodedSize(const uint8_t * woffData, uint32_t woffLen, + uint32_t * pStatus); + + +/***************************************************************************** + * Decodes WOFF font to a caller-supplied buffer of size bufferLen. + * Returns the actual size of the decoded sfnt data in pActualSfntLen + * (must be <= bufferLen, otherwise an error will be returned). + */ +void woffDecodeToBuffer(const uint8_t * woffData, uint32_t woffLen, + uint8_t * sfntData, uint32_t bufferLen, + uint32_t * pActualSfntLen, uint32_t * pStatus); + + +/***************************************************************************** + * Returns a new malloc() block containing the decoded data, or NULL on error; + * caller should free() this when finished with it. + * Returns length of the decoded data in sfntLen. + */ +const uint8_t * woffDecode(const uint8_t * woffData, uint32_t woffLen, + uint32_t * sfntLen, uint32_t * status); + + +/***************************************************************************** + * Returns a new malloc() block containing the metadata from the WOFF font, + * or NULL if an error occurs or no metadata is present. + * Length of the metadata is returned in metaLen. + * The metadata is decompressed before returning. + */ +const uint8_t * woffGetMetadata(const uint8_t * woffData, uint32_t woffLen, + uint32_t * metaLen, uint32_t * status); + + +/***************************************************************************** + * Returns a new malloc() block containing the private data from the WOFF font, + * or NULL if an error occurs or no private data is present. + * Length of the private data is returned in privLen. + */ +const uint8_t * woffGetPrivateData(const uint8_t * woffData, uint32_t woffLen, + uint32_t * privLen, uint32_t * status); + + +/***************************************************************************** + * Returns the font version numbers from the WOFF font in the major and minor + * parameters. + * Check the status result to know if the function succeeded. + */ +void woffGetFontVersion(const uint8_t * woffData, uint32_t woffLen, + uint16_t * major, uint16_t * minor, + uint32_t * status); + + +/***************************************************************************** + * Utility to print warning and/or error status to the specified FILE*. + * The prefix string will be prepended to each line (ok to pass NULL if no + * prefix is wanted). + * (Provides terse English messages only, not intended for end-user display; + * user-friendly tools should map the status codes to their own messages.) + */ +void woffPrintStatus(FILE * f, uint32_t status, const char * prefix); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/woff2sfnt.c b/woff2sfnt.c new file mode 100644 index 0000000..c4d39fd --- /dev/null +++ b/woff2sfnt.c @@ -0,0 +1,225 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is WOFF font packaging code. + * + * The Initial Developer of the Original Code is Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Jonathan Kew <jfkthame@gmail.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#ifdef WIN32 +#include <io.h> +#endif + +#include "woff.h" + +static void +die(const char * msg) +{ + fprintf(stderr, "# fatal error: %s\n", msg); + exit(2); +} + +static void +reportErr(uint32_t status) +{ + woffPrintStatus(stderr, status, "### "); + exit(status & 0xff); +} + +static void +usage(const char * progName) +{ + fprintf(stderr, "Usage:\n" + " %s [-v | -m | -p] <woff>\n" + " decode WOFF file <woff>, writing OpenType data to stdout\n" + "Options (instead of decoding to OpenType format)\n" + " -v write font version to stdout\n" + " -m write WOFF metadata block to stdout\n" + " -p write private data block to stdout\n" + "Note: only one of -v, -m, -p may be used at a time.\n" + , progName); +} + +const uint8_t * +readFile(const char * name, uint32_t * len) +{ + FILE * inFile = fopen(name, "rb"); + if (!inFile) { + char buf[200]; + sprintf(buf, "unable to open file %s", name); + die(buf); + } + + if (fseek(inFile, 0, SEEK_END) != 0) + die("seek failure"); + *len = ftell(inFile); + if (fseek(inFile, 0, SEEK_SET) != 0) + die("seek failure"); + + uint8_t * data = (uint8_t *) malloc(*len); + if (!data) + die("malloc failure"); + if (fread(data, 1, *len, inFile) != *len) + die("file read failure"); + fclose(inFile); + + return data; +} + +int +main(int argc, char *argv[]) +{ + const char * progName = argv[0]; + uint32_t status = eWOFF_ok; + + int opt; + int option = 0; + while ((opt = getopt(argc, argv, "vmph")) != -1) { + switch (opt) { + case 'v': + if (option) + fprintf(stderr, "# ignoring option '%c', already got '%c'\n", opt, option); + else + option = 'v'; + break; + case 'm': + if (option) + fprintf(stderr, "# ignoring option '%c', already got '%c'\n", opt, option); + else + option = 'm'; + break; + case 'p': + if (option) + fprintf(stderr, "# ignoring option '%c', already got '%c'\n", opt, option); + else + option = 'p'; + break; + case 'h': + case '?': + usage(progName); + exit(0); + default: + fprintf(stderr, "# unknown option '%c'\n", opt); + break; + } + } + argc -= optind; + argv += optind; + + if (argc != 1) { + usage(progName); + exit(1); + } + + uint32_t woffLen; + const uint8_t * woffData = readFile(argv[0], &woffLen); + + uint32_t len; + const uint8_t * data; + + switch (option) { + case 'v': + { + uint16_t maj, min; + woffGetFontVersion(woffData, woffLen, &maj, &min, &status); + if (WOFF_FAILURE(status)) { + reportErr(status); + } + printf("%u.%u\n", maj, min); + } + break; + + case 'm': + { + data = woffGetMetadata(woffData, woffLen, &len, &status); + if (WOFF_FAILURE(status)) { + reportErr(status); + } + if (data) { + if (fwrite(data, 1, len, stdout) != len) + die("error writing metadata to output"); + free((void *) data); + } else { + printf("<!-- No WOFF metadata available. -->\n"); + } + } + break; + + case 'p': + { + data = woffGetPrivateData(woffData, woffLen, &len, &status); + if (WOFF_FAILURE(status)) { + reportErr(status); + } + if (data) { +#ifdef WIN32 + setmode(fileno(stdout), O_BINARY); +#endif + if (fwrite(data, 1, len, stdout) != len) + die("error writing private data to output"); + free((void *) data); + } + } + break; + + default: + { + data = woffDecode(woffData, woffLen, &len, &status); + if (WOFF_FAILURE(status)) { + reportErr(status); + } + if (data) { +#ifdef WIN32 + setmode(fileno(stdout), O_BINARY); +#endif + if (fwrite(data, 1, len, stdout) != len) + die("error writing sfnt data to output"); + free((void *) data); + } else { + die("unable to decode WOFF data"); + } + } + break; + } + + if (WOFF_WARNING(status)) { + woffPrintStatus(stderr, status, "### "); + } + + free((void *) woffData); + + return 0; +} |