diff --git a/bin/ot b/bin/ot index af990cd2..c710699a 100755 --- a/bin/ot +++ b/bin/ot @@ -1,9 +1,10 @@ #!/usr/bin/env node /* eslint no-console: off */ -import fs from 'fs'; -import path from 'path'; -import { load } from '../src/opentype'; +const process = require('process'), + fs = require('fs'), + path = require('path'), + { load, woffToOTF } = require('../dist/opentype.js'); // Print out information about the font on the console. function printFontInfo(font) { @@ -30,11 +31,13 @@ function walk(dir, fn) { // Print out usage information. function printUsage() { - console.log('Usage: ot command [dir|file]'); + console.log('Usage: ot [...args]'); console.log(); console.log('Commands:'); console.log(); - console.log(' info Get information of specified font or fonts in the specified directory.'); + console.log(' info [dir|file] Get information of specified font or fonts in the specified directory.'); + console.log(); + console.log(' woff_2_otf [otf_font_file] Uncompress woff file into otf.'); console.log(); } @@ -65,7 +68,7 @@ if (process.argv.length < 3) { } else { var command = process.argv[2]; if (command === 'info') { - var fontpath = process.argv.length === 3 ? '.' : process.argv[3]; + var fontpath = process.argv.length === 4 ? '.' : process.argv[3]; if (fs.existsSync(fontpath)) { var ext = path.extname(fontpath).toLowerCase(); if (fs.statSync(fontpath).isDirectory()) { @@ -78,6 +81,23 @@ if (process.argv.length < 3) { } else { console.log('Path not found'); } + } + else if (command === 'woff_2_otf') { + if(process.argv.length < 4 ){ + printUsage(); + process.exit(1); + } + const fontpath = process.argv[3]; + if (!fs.existsSync(fontpath)) { + console.log('Font file at path not found.'); + process.exit(1); + } + const targetPath = process.argv.length >= 5 + ? process.argv[4] + : `${path.join(path.dirname(fontpath), path.basename(fontpath, path.extname(fontpath)))}.otf`, + buffer = fs.readFileSync(fontpath), + result = woffToOTF(buffer); + fs.writeFileSync(targetPath, new DataView(result)); } else { printUsage(); } diff --git a/src/opentype.mjs b/src/opentype.mjs index 60e8eae2..05084b2e 100644 --- a/src/opentype.mjs +++ b/src/opentype.mjs @@ -8,6 +8,7 @@ import Font from './font.mjs'; import Glyph from './glyph.mjs'; import { CmapEncoding, GlyphNames, addGlyphNames } from './encoding.mjs'; import parse from './parse.mjs'; +import { encode } from './types.mjs'; import BoundingBox from './bbox.mjs'; import Path from './path.mjs'; import cpal from './tables/cpal.mjs'; @@ -79,6 +80,7 @@ function parseWOFFTableEntries(data, numTables) { const offset = parse.getULong(data, p + 4); const compLength = parse.getULong(data, p + 8); const origLength = parse.getULong(data, p + 12); + const checksum = parse.getULong(data, p + 16); let compression; if (compLength < origLength) { compression = 'WOFF'; @@ -87,7 +89,7 @@ function parseWOFFTableEntries(data, numTables) { } tableEntries.push({tag: tag, offset: offset, compression: compression, - compressedLength: compLength, length: origLength}); + compressedLength: compLength, length: origLength, checksum: checksum}); p += 20; } @@ -436,7 +438,7 @@ function parseBuffer(buffer, opt={}) { font.tables.meta = meta.parse(metaTable.data, metaTable.offset); font.metas = font.tables.meta; } - + font.palettes = new PaletteManager(font); return font; @@ -465,6 +467,84 @@ function loadSync() { console.error('DEPRECATED! migrate to: opentype.parse(require("fs").readFileSync(url), opt)'); } +/** + * Convert/Uncompress a buffer of a woff font to otf/ttf without parsing + * table contents. + * @param {ArrayBuffer} + * @return {ArrayBuffer} + */ +function woffToOTF(buffer) { + if (buffer.constructor !== ArrayBuffer) + buffer = new Uint8Array(buffer).buffer; + + const data = new DataView(buffer, 0), + out = [], + signature = parse.getTag(data, 0); + + if (signature !== 'wOFF') + throw new Error(`TYPE ERROR signature must be wOFF but is: "${signature}"`); + + const flavor = parse.getTag(data, 4), + numTables = parse.getUShort(data, 12), + tableEntries = parseWOFFTableEntries(data, numTables), + max = []; + for (let n = 0; n < 64; n++) { + if (Math.pow(2, n) > numTables) + break; + max.splice(0, Infinity, n, 2 ** n); + } + + const searchRange = max[1] * 16, + entrySelector = max[0], + rangeShift = numTables * 16 - searchRange; + + out.push( + ...encode.TAG(flavor), + ...encode.USHORT(numTables), + ...encode.USHORT(searchRange), + ...encode.USHORT(entrySelector), + ...encode.USHORT(rangeShift) + ); + let offset = out.length + numTables * 16; + + for (let i=0; i {data: view, offset: 0}; + offset = tableEntry.outOffset + tableEntry.length, + padding = (offset % 4) !== 0 + ? 4 - (offset % 4) + : 0; + buffers.push( + new Uint8Array(table.data.buffer, table.offset, tableEntry.length), + new Uint8Array(padding) + ); + } + const result = new Uint8Array(buffers.reduce((accum, buffer)=>accum+buffer.byteLength, 0)); + buffers.reduce((offset, buffer)=>{ + result.set(buffer, offset); + return offset + buffer.byteLength; + }, 0); + return result.buffer; +} + export { Font, Glyph, @@ -473,5 +553,6 @@ export { parse as _parse, parseBuffer as parse, load, - loadSync + loadSync, + woffToOTF };