gecko/gfx/thebes/woff.c

1298 lines
36 KiB
C

/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#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 "mozilla/mozalloc.h"
# define malloc moz_malloc
# define realloc moz_realloc
# define free moz_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 = READ32BE(dirEntry->length);
uint32_t offset = READ32BE(dirEntry->offset);
uint32_t tag;
if (LONGALIGN(length) < length) { /* overflow */
return csum;
} else {
length = LONGALIGN(length);
}
if ((offset & 3) != 0) { /* invalid - not properly aligned */
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);
if (LONGALIGN(removedDsigSize) < removedDsigSize) {
FAIL(eWOFF_invalid);
}
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 */
/* cannot be too big because numTables is 16-bit */
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;
/* we always realloc woffData to a long-aligned size, so this is safe */
while ((tableOffset & 3) != 0) {
woffData[tableOffset++] = 0;
}
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 = compressBound(sourceLen);
if (LONGALIGN(destLen) < destLen) {
/* something weird is going on if this overflows! */
FAIL(eWOFF_invalid);
}
destLen = LONGALIGN(destLen);
if (tableOffset + destLen < tableOffset) {
FAIL(eWOFF_invalid);
}
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; /* checked for potential overflow above */
WOFFDIR[newIndex].compLen = READ32BE(destLen);
} else {
/* compression didn't make it smaller, so store original data instead */
if (LONGALIGN(sourceLen) < sourceLen) {
FAIL(eWOFF_invalid); /* overflow, bail out */
}
destLen = sourceLen;
/* reallocate to ensure enough space for the table,
plus potential padding after it */
if (tableOffset + LONGALIGN(sourceLen) < tableOffset) {
FAIL(eWOFF_invalid); /* overflow, bail out */
}
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);
if (tableOffset + sourceLen < tableOffset) {
FAIL(eWOFF_invalid); /* overflow, bail out */
}
tableOffset += sourceLen;
WOFFDIR[newIndex].compLen = WOFFDIR[newIndex].origLen;
}
/* update total size of uncompressed OpenType with table size */
if (totalSfntSize + sourceLen < totalSfntSize) {
FAIL(eWOFF_invalid); /* overflow, bail out */
}
totalSfntSize += sourceLen;
if (LONGALIGN(totalSfntSize) < totalSfntSize) {
FAIL(eWOFF_invalid);
}
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) {
if (totalSize + metaCompLen < totalSize) {
FAIL(eWOFF_invalid);
}
totalSize += metaCompLen;
}
if (privLen) {
if (LONGALIGN(totalSize) < totalSize) {
FAIL(eWOFF_invalid);
}
totalSize = LONGALIGN(totalSize);
if (totalSize + privLen < totalSize) {
FAIL(eWOFF_invalid);
}
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;
uint64_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) {
uint64_t offs = READ32BE(dirEntry->offset);
uint64_t orig = READ32BE(dirEntry->origLen);
uint64_t comp = READ32BE(dirEntry->compLen);
if (comp > orig || comp > woffLen || offs > woffLen - comp) {
return eWOFF_invalid;
}
orig = (orig + 3) & ~3;
tableTotal += orig;
if (tableTotal > 0xffffffffU) {
return eWOFF_invalid;
}
++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);
/* cannot be too big because numTables is 16-bit */
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;
}
/* functions to get size and data of a single table */
uint32_t woffGetTableSize(const uint8_t * woffData, uint32_t woffLen,
uint32_t tag, uint32_t * pStatus)
{
uint32_t status = eWOFF_ok;
const woffHeader * header;
uint16_t numTables;
uint16_t tableIndex;
const woffDirEntry * woffDir;
if (pStatus && WOFF_FAILURE(*pStatus)) {
return 0;
}
status = sanityCheck(woffData, woffLen);
if (WOFF_FAILURE(status)) {
FAIL(status);
}
header = (const woffHeader *) (woffData);
numTables = READ16BE(header->numTables);
woffDir = (const woffDirEntry *) (woffData + sizeof(woffHeader));
for (tableIndex = 0; tableIndex < numTables; ++tableIndex) {
uint32_t thisTag;
thisTag = READ32BE(woffDir[tableIndex].tag);
if (thisTag < tag) {
continue;
}
if (thisTag > tag) {
break;
}
return READ32BE(woffDir[tableIndex].origLen);
}
status = eWOFF_warn_no_such_table;
failure:
if (pStatus) {
*pStatus = status;
}
return 0;
}
void woffGetTableToBuffer(const uint8_t * woffData, uint32_t woffLen,
uint32_t tag, uint8_t * buffer, uint32_t bufferLen,
uint32_t * pTableLen, uint32_t * pStatus)
{
uint32_t status = eWOFF_ok;
const woffHeader * header;
uint16_t numTables;
uint16_t tableIndex;
const woffDirEntry * woffDir;
if (pStatus && WOFF_FAILURE(*pStatus)) {
return;
}
status = sanityCheck(woffData, woffLen);
if (WOFF_FAILURE(status)) {
FAIL(status);
}
header = (const woffHeader *) (woffData);
numTables = READ16BE(header->numTables);
woffDir = (const woffDirEntry *) (woffData + sizeof(woffHeader));
for (tableIndex = 0; tableIndex < numTables; ++tableIndex) {
uint32_t thisTag, origLen, compLen, sourceOffset;
thisTag = READ32BE(woffDir[tableIndex].tag);
if (thisTag < tag) {
continue;
}
if (thisTag > tag) {
break;
}
/* found the required table: decompress it (checking for overflow) */
origLen = READ32BE(woffDir[tableIndex].origLen);
if (origLen > bufferLen) {
FAIL(eWOFF_buffer_too_small);
}
compLen = READ32BE(woffDir[tableIndex].compLen);
sourceOffset = READ32BE(woffDir[tableIndex].offset);
if (compLen < origLen) {
uLongf destLen = origLen;
if (uncompress((Bytef *)(buffer), &destLen,
(const Bytef *)(woffData + sourceOffset),
compLen) != Z_OK || destLen != origLen) {
FAIL(eWOFF_compression_failure);
}
} else {
memcpy(buffer, woffData + sourceOffset, origLen);
}
if (pTableLen) {
*pTableLen = origLen;
}
return;
}
status = eWOFF_warn_no_such_table;
failure:
if (pStatus) {
*pStatus = status;
}
}
#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 */