You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
258 lines
6.8 KiB
258 lines
6.8 KiB
/* |
|
Author: Viktor Semykin <thesame.ml@gmail.com> |
|
|
|
Written for fontello.com project. |
|
*/ |
|
|
|
'use strict'; |
|
|
|
|
|
var deflate = require('pako/lib/deflate.js').deflate; |
|
|
|
|
|
function ulong(t) { |
|
/* eslint-disable no-bitwise */ |
|
t &= 0xffffffff; |
|
if (t < 0) { |
|
t += 0x100000000; |
|
} |
|
return t; |
|
} |
|
|
|
function longAlign(n) { |
|
/* eslint-disable no-bitwise */ |
|
return (n + 3) & ~3; |
|
} |
|
|
|
function calc_checksum(buf) { |
|
var sum = 0; |
|
var nlongs = buf.length / 4; |
|
|
|
for (var i = 0; i < nlongs; ++i) { |
|
var t = buf.readUint32BE(i * 4); |
|
|
|
sum = ulong(sum + t); |
|
} |
|
return sum; |
|
} |
|
|
|
var WOFF_OFFSET = { |
|
MAGIC: 0, |
|
FLAVOR: 4, |
|
SIZE: 8, |
|
NUM_TABLES: 12, |
|
RESERVED: 14, |
|
SFNT_SIZE: 16, |
|
VERSION_MAJ: 20, |
|
VERSION_MIN: 22, |
|
META_OFFSET: 24, |
|
META_LENGTH: 28, |
|
META_ORIG_LENGTH: 32, |
|
PRIV_OFFSET: 36, |
|
PRIV_LENGTH: 40 |
|
}; |
|
|
|
var WOFF_ENTRY_OFFSET = { |
|
TAG: 0, |
|
OFFSET: 4, |
|
COMPR_LENGTH: 8, |
|
LENGTH: 12, |
|
CHECKSUM: 16 |
|
}; |
|
|
|
var SFNT_OFFSET = { |
|
TAG: 0, |
|
CHECKSUM: 4, |
|
OFFSET: 8, |
|
LENGTH: 12 |
|
}; |
|
|
|
var SFNT_ENTRY_OFFSET = { |
|
FLAVOR: 0, |
|
VERSION_MAJ: 4, |
|
VERSION_MIN: 6, |
|
CHECKSUM_ADJUSTMENT: 8 |
|
}; |
|
|
|
var MAGIC = { |
|
WOFF: 0x774F4646, |
|
CHECKSUM_ADJUSTMENT: 0xB1B0AFBA |
|
}; |
|
|
|
var SIZEOF = { |
|
WOFF_HEADER: 44, |
|
WOFF_ENTRY: 20, |
|
SFNT_HEADER: 12, |
|
SFNT_TABLE_ENTRY: 16 |
|
}; |
|
|
|
function woffAppendMetadata(src, metadata) { |
|
|
|
var zdata = deflate(metadata); |
|
|
|
src.writeUint32BE(src.length + zdata.length, WOFF_OFFSET.SIZE); |
|
src.writeUint32BE(src.length, WOFF_OFFSET.META_OFFSET); |
|
src.writeUint32BE(zdata.length, WOFF_OFFSET.META_LENGTH); |
|
src.writeUint32BE(metadata.length, WOFF_OFFSET.META_ORIG_LENGTH); |
|
|
|
return Buffer.concat([ src, zdata ]); |
|
} |
|
|
|
function ttf2woff(arr, options) { |
|
arr = Buffer.from(arr.buffer, arr.byteOffset, arr.length); |
|
|
|
options = options || {}; |
|
|
|
var version = { |
|
maj: 0, |
|
min: 1 |
|
}; |
|
var numTables = arr.readUint16BE(4); |
|
//var sfntVersion = arr.readUint32BE(0); |
|
var flavor = 0x10000; |
|
|
|
var woffHeader = Buffer.alloc(SIZEOF.WOFF_HEADER); |
|
|
|
woffHeader.writeUint32BE(MAGIC.WOFF, WOFF_OFFSET.MAGIC); |
|
woffHeader.writeUint16BE(numTables, WOFF_OFFSET.NUM_TABLES); |
|
woffHeader.writeUint16BE(0, WOFF_OFFSET.RESERVED); |
|
woffHeader.writeUint32BE(0, WOFF_OFFSET.SFNT_SIZE); |
|
woffHeader.writeUint32BE(0, WOFF_OFFSET.META_OFFSET); |
|
woffHeader.writeUint32BE(0, WOFF_OFFSET.META_LENGTH); |
|
woffHeader.writeUint32BE(0, WOFF_OFFSET.META_ORIG_LENGTH); |
|
woffHeader.writeUint32BE(0, WOFF_OFFSET.PRIV_OFFSET); |
|
woffHeader.writeUint32BE(0, WOFF_OFFSET.PRIV_LENGTH); |
|
|
|
var entries = []; |
|
|
|
var i, tableEntry; |
|
|
|
for (i = 0; i < numTables; ++i) { |
|
var data = arr.subarray(SIZEOF.SFNT_HEADER + i * SIZEOF.SFNT_TABLE_ENTRY); |
|
|
|
tableEntry = { |
|
Tag: data.subarray(SFNT_OFFSET.TAG, SFNT_OFFSET.TAG + 4), |
|
checkSum: data.readUint32BE(SFNT_OFFSET.CHECKSUM), |
|
Offset: data.readUint32BE(SFNT_OFFSET.OFFSET), |
|
Length: data.readUint32BE(SFNT_OFFSET.LENGTH) |
|
}; |
|
entries.push (tableEntry); |
|
} |
|
entries = entries.sort(function (a, b) { |
|
var aStr = String.fromCharCode.apply(null, a.Tag); |
|
var bStr = String.fromCharCode.apply(null, b.Tag); |
|
|
|
return aStr === bStr ? 0 : aStr < bStr ? -1 : 1; |
|
}); |
|
|
|
var offset = SIZEOF.WOFF_HEADER + numTables * SIZEOF.WOFF_ENTRY; |
|
var woffSize = offset; |
|
var sfntSize = SIZEOF.SFNT_HEADER + numTables * SIZEOF.SFNT_TABLE_ENTRY; |
|
|
|
var tableBuf = Buffer.alloc(numTables * SIZEOF.WOFF_ENTRY); |
|
|
|
for (i = 0; i < numTables; ++i) { |
|
tableEntry = entries[i]; |
|
|
|
if (String.fromCharCode.apply(null, tableEntry.Tag) !== 'head') { |
|
var algntable = arr.subarray(tableEntry.Offset, tableEntry.Offset + longAlign(tableEntry.Length)); |
|
|
|
if (calc_checksum(algntable) !== tableEntry.checkSum) { |
|
throw 'Checksum error in ' + String.fromCharCode.apply(null, tableEntry.Tag); |
|
} |
|
} |
|
|
|
tableBuf.writeUint32BE(tableEntry.Tag.readUint32BE(0), i * SIZEOF.WOFF_ENTRY + WOFF_ENTRY_OFFSET.TAG); |
|
tableBuf.writeUint32BE(tableEntry.Length, i * SIZEOF.WOFF_ENTRY + WOFF_ENTRY_OFFSET.LENGTH); |
|
tableBuf.writeUint32BE(tableEntry.checkSum, i * SIZEOF.WOFF_ENTRY + WOFF_ENTRY_OFFSET.CHECKSUM); |
|
sfntSize += longAlign(tableEntry.Length); |
|
} |
|
|
|
var sfntOffset = SIZEOF.SFNT_HEADER + entries.length * SIZEOF.SFNT_TABLE_ENTRY; |
|
var csum = calc_checksum(arr.subarray(0, SIZEOF.SFNT_HEADER)); |
|
|
|
for (i = 0; i < entries.length; ++i) { |
|
tableEntry = entries[i]; |
|
|
|
var b = Buffer.alloc(SIZEOF.SFNT_TABLE_ENTRY); |
|
|
|
b.writeUint32BE(tableEntry.Tag.readUint32BE(0), SFNT_OFFSET.TAG); |
|
b.writeUint32BE(tableEntry.checkSum, SFNT_OFFSET.CHECKSUM); |
|
b.writeUint32BE(sfntOffset, SFNT_OFFSET.OFFSET); |
|
b.writeUint32BE(tableEntry.Length, SFNT_OFFSET.LENGTH); |
|
sfntOffset += longAlign(tableEntry.Length); |
|
csum += calc_checksum(b); |
|
csum += tableEntry.checkSum; |
|
} |
|
|
|
var checksumAdjustment = ulong(MAGIC.CHECKSUM_ADJUSTMENT - csum); |
|
|
|
var len, woffDataChains = []; |
|
|
|
for (i = 0; i < entries.length; ++i) { |
|
tableEntry = entries[i]; |
|
|
|
var sfntData = arr.subarray(tableEntry.Offset, tableEntry.Offset + tableEntry.Length); |
|
|
|
if (String.fromCharCode.apply(null, tableEntry.Tag) === 'head') { |
|
version.maj = sfntData.readUint16BE(SFNT_ENTRY_OFFSET.VERSION_MAJ); |
|
version.min = sfntData.readUint16BE(SFNT_ENTRY_OFFSET.VERSION_MIN); |
|
flavor = sfntData.readUint32BE(SFNT_ENTRY_OFFSET.FLAVOR); |
|
sfntData.writeUint32BE(checksumAdjustment, SFNT_ENTRY_OFFSET.CHECKSUM_ADJUSTMENT); |
|
} |
|
|
|
var res = deflate(sfntData); |
|
|
|
var compLength; |
|
|
|
// We should use compression only if it really save space (standard requirement). |
|
// Also, data should be aligned to long (with zeros?). |
|
compLength = Math.min(res.length, sfntData.length); |
|
len = longAlign(compLength); |
|
|
|
var woffData = Buffer.alloc(len, 0); |
|
|
|
if (res.length >= sfntData.length) { |
|
woffData.set(sfntData); |
|
} else { |
|
woffData.set(res); |
|
} |
|
|
|
tableBuf.writeUint32BE(offset, i * SIZEOF.WOFF_ENTRY + WOFF_ENTRY_OFFSET.OFFSET); |
|
|
|
offset += woffData.length; |
|
woffSize += woffData.length; |
|
|
|
tableBuf.writeUint32BE(compLength, i * SIZEOF.WOFF_ENTRY + WOFF_ENTRY_OFFSET.COMPR_LENGTH); |
|
|
|
woffDataChains.push(woffData); |
|
} |
|
|
|
woffHeader.writeUint32BE(woffSize, WOFF_OFFSET.SIZE); |
|
woffHeader.writeUint32BE(sfntSize, WOFF_OFFSET.SFNT_SIZE); |
|
woffHeader.writeUint16BE(version.maj, WOFF_OFFSET.VERSION_MAJ); |
|
woffHeader.writeUint16BE(version.min, WOFF_OFFSET.VERSION_MIN); |
|
woffHeader.writeUint32BE(flavor, WOFF_OFFSET.FLAVOR); |
|
|
|
var out = Buffer.alloc(woffSize); |
|
var pos = 0; |
|
|
|
out.set(woffHeader, pos); |
|
pos += woffHeader.length; |
|
|
|
out.set(tableBuf, pos); |
|
pos += tableBuf.length; |
|
|
|
for (i = 0; i < woffDataChains.length; i++) { |
|
out.set(woffDataChains[i], pos); |
|
pos += woffDataChains[i].length; |
|
} |
|
|
|
if (options.metadata) { |
|
out = woffAppendMetadata(out, options.metadata); |
|
} |
|
|
|
return new Uint8Array(out.buffer, out.byteOffset, out.length); |
|
} |
|
|
|
module.exports = ttf2woff;
|
|
|