123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492 |
- /* Copyright 2014 Mozilla Foundation
- *
- * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- const fs = require("fs");
- const path = require("path");
- const parseAdobeCMap = require("./parse.js").parseAdobeCMap;
- const optimizeCMap = require("./optimize.js").optimizeCMap;
- function compressCmap(srcPath, destPath, verify) {
- const content = fs.readFileSync(srcPath).toString();
- const inputData = parseAdobeCMap(content);
- optimizeCMap(inputData);
- let out = writeByte((inputData.type << 1) | inputData.wmode);
- if (inputData.comment) {
- out += writeByte(0xe0) + writeString(inputData.comment);
- }
- if (inputData.usecmap) {
- out += writeByte(0xe1) + writeString(inputData.usecmap);
- }
- let i = 0;
- while (i < inputData.body.length) {
- const item = inputData.body[i++],
- subitems = item.items;
- const first = item.items[0];
- const sequence = item.sequence === true;
- const flags = (item.type << 5) | (sequence ? 0x10 : 0);
- let nextStart, nextCode;
- switch (item.type) {
- case 0:
- out +=
- writeByte(flags | getHexSize(first.start)) +
- writeNumber(subitems.length);
- out += first.start + writeNumber(subHex(first.end, first.start));
- nextStart = incHex(first.end);
- for (let j = 1; j < subitems.length; j++) {
- out +=
- writeNumber(subHex(subitems[j].start, nextStart)) +
- writeNumber(subHex(subitems[j].end, subitems[j].start));
- nextStart = incHex(subitems[j].end);
- }
- break;
- case 1:
- out +=
- writeByte(flags | getHexSize(first.start)) +
- writeNumber(subitems.length);
- out +=
- first.start +
- writeNumber(subHex(first.end, first.start)) +
- writeNumber(first.code);
- nextStart = incHex(first.end);
- for (let j = 1; j < subitems.length; j++) {
- out +=
- writeNumber(subHex(subitems[j].start, nextStart)) +
- writeNumber(subHex(subitems[j].end, subitems[j].start)) +
- writeNumber(subitems[j].code);
- nextStart = incHex(subitems[j].end);
- }
- break;
- case 2:
- out +=
- writeByte(flags | getHexSize(first.char)) +
- writeNumber(subitems.length);
- out += first.char + writeNumber(first.code);
- nextStart = incHex(first.char);
- nextCode = first.code + 1;
- for (let j = 1; j < subitems.length; j++) {
- out +=
- (sequence ? "" : writeNumber(subHex(subitems[j].char, nextStart))) +
- writeSigned(subitems[j].code - nextCode);
- nextStart = incHex(subitems[j].char);
- nextCode = item.items[j].code + 1;
- }
- break;
- case 3:
- out +=
- writeByte(flags | getHexSize(first.start)) +
- writeNumber(subitems.length);
- out +=
- first.start +
- writeNumber(subHex(first.end, first.start)) +
- writeNumber(first.code);
- nextStart = incHex(first.end);
- for (let j = 1; j < subitems.length; j++) {
- out +=
- (sequence
- ? ""
- : writeNumber(subHex(subitems[j].start, nextStart))) +
- writeNumber(subHex(subitems[j].end, subitems[j].start)) +
- writeNumber(subitems[j].code);
- nextStart = incHex(subitems[j].end);
- }
- break;
- case 4:
- out +=
- writeByte(flags | getHexSize(first.code)) +
- writeNumber(subitems.length);
- out += first.char + first.code;
- nextStart = incHex(first.char);
- nextCode = incHex(first.code);
- for (let j = 1; j < subitems.length; j++) {
- out +=
- (sequence ? "" : writeNumber(subHex(subitems[j].char, nextStart))) +
- writeSigned(subHex(subitems[j].code, nextCode));
- nextStart = incHex(subitems[j].char);
- nextCode = incHex(subitems[j].code);
- }
- break;
- case 5:
- out +=
- writeByte(flags | getHexSize(first.code)) +
- writeNumber(subitems.length);
- out +=
- first.start +
- writeNumber(subHex(first.end, first.start)) +
- first.code;
- nextStart = incHex(first.end);
- for (let j = 1; j < subitems.length; j++) {
- out +=
- (sequence
- ? ""
- : writeNumber(subHex(subitems[j].start, nextStart))) +
- writeNumber(subHex(subitems[j].end, subitems[j].start)) +
- subitems[j].code;
- nextStart = incHex(subitems[j].end);
- }
- break;
- }
- }
- fs.writeFileSync(destPath, Buffer.from(out, "hex"));
- if (verify) {
- const result2 = parseCMap(out);
- const isGood = JSON.stringify(inputData) === JSON.stringify(result2);
- if (!isGood) {
- throw new Error("Extracted data does not match the expected result");
- }
- }
- return {
- orig: fs.statSync(srcPath).size,
- packed: out.length >> 1,
- };
- }
- function parseCMap(binaryData) {
- const reader = {
- buffer: binaryData,
- pos: 0,
- end: binaryData.length,
- readByte() {
- if (this.pos >= this.end) {
- return -1;
- }
- const d1 = fromHexDigit(this.buffer[this.pos]);
- const d2 = fromHexDigit(this.buffer[this.pos + 1]);
- this.pos += 2;
- return (d1 << 4) | d2;
- },
- readNumber() {
- let n = 0;
- let last;
- do {
- const b = this.readByte();
- last = !(b & 0x80);
- n = (n << 7) | (b & 0x7f);
- } while (!last);
- return n;
- },
- readSigned() {
- const n = this.readNumber();
- return n & 1 ? -(n >>> 1) - 1 : n >>> 1;
- },
- readHex(size) {
- const lengthInChars = (size + 1) << 1;
- const s = this.buffer.substring(this.pos, this.pos + lengthInChars);
- this.pos += lengthInChars;
- return s;
- },
- readHexNumber(size) {
- const lengthInChars = (size + 1) << 1,
- stack = [];
- let last;
- do {
- const b = this.readByte();
- last = !(b & 0x80);
- stack.push(b & 0x7f);
- } while (!last);
- let s = "",
- buffer = 0,
- bufferSize = 0;
- while (s.length < lengthInChars) {
- while (bufferSize < 4 && stack.length > 0) {
- buffer |= stack.pop() << bufferSize;
- bufferSize += 7;
- }
- s = toHexDigit(buffer & 15) + s;
- buffer >>= 4;
- bufferSize -= 4;
- }
- return s;
- },
- readHexSigned(size) {
- const num = this.readHexNumber(size);
- const sign = fromHexDigit(num[num.length - 1]) & 1 ? 15 : 0;
- let c = 0;
- let result = "";
- for (const digit of num) {
- c = (c << 4) | fromHexDigit(digit);
- result += toHexDigit(sign ? (c >> 1) ^ sign : c >> 1);
- c &= 1;
- }
- return result;
- },
- readString() {
- const len = this.readNumber();
- let s = "";
- for (let i = 0; i < len; i++) {
- s += String.fromCharCode(this.readNumber());
- }
- return s;
- },
- };
- const header = reader.readByte();
- const result = {
- type: header >> 1,
- wmode: header & 1,
- comment: null,
- usecmap: null,
- body: [],
- };
- let b;
- while ((b = reader.readByte()) >= 0) {
- const type = b >> 5;
- if (type === 7) {
- switch (b & 0x1f) {
- case 0:
- result.comment = reader.readString();
- break;
- case 1:
- result.usecmap = reader.readString();
- break;
- }
- continue;
- }
- const sequence = !!(b & 0x10);
- const dataSize = b & 15;
- const subitems = [];
- const item = {
- type,
- items: subitems,
- };
- if (sequence) {
- item.sequence = true;
- }
- const ucs2DataSize = 1;
- const subitemsCount = reader.readNumber();
- let start, end, code, char;
- switch (type) {
- case 0:
- start = reader.readHex(dataSize);
- end = addHex(reader.readHexNumber(dataSize), start);
- subitems.push({ start, end });
- for (let i = 1; i < subitemsCount; i++) {
- start = addHex(reader.readHexNumber(dataSize), incHex(end));
- end = addHex(reader.readHexNumber(dataSize), start);
- subitems.push({ start, end });
- }
- break;
- case 1:
- start = reader.readHex(dataSize);
- end = addHex(reader.readHexNumber(dataSize), start);
- code = reader.readNumber();
- subitems.push({ start, end, code });
- for (let i = 1; i < subitemsCount; i++) {
- start = addHex(reader.readHexNumber(dataSize), incHex(end));
- end = addHex(reader.readHexNumber(dataSize), start);
- code = reader.readNumber();
- subitems.push({ start, end, code });
- }
- break;
- case 2:
- char = reader.readHex(dataSize);
- code = reader.readNumber();
- subitems.push({ char, code });
- for (let i = 1; i < subitemsCount; i++) {
- char = sequence
- ? incHex(char)
- : addHex(reader.readHexNumber(dataSize), incHex(char));
- code = reader.readSigned() + (code + 1);
- subitems.push({ char, code });
- }
- break;
- case 3:
- start = reader.readHex(dataSize);
- end = addHex(reader.readHexNumber(dataSize), start);
- code = reader.readNumber();
- subitems.push({ start, end, code });
- for (let i = 1; i < subitemsCount; i++) {
- start = sequence
- ? incHex(end)
- : addHex(reader.readHexNumber(dataSize), incHex(end));
- end = addHex(reader.readHexNumber(dataSize), start);
- code = reader.readNumber();
- subitems.push({ start, end, code });
- }
- break;
- case 4:
- char = reader.readHex(ucs2DataSize);
- code = reader.readHex(dataSize);
- subitems.push({ char, code });
- for (let i = 1; i < subitemsCount; i++) {
- char = sequence
- ? incHex(char)
- : addHex(reader.readHexNumber(ucs2DataSize), incHex(char));
- code = addHex(reader.readHexSigned(dataSize), incHex(code));
- subitems.push({ char, code });
- }
- break;
- case 5:
- start = reader.readHex(ucs2DataSize);
- end = addHex(reader.readHexNumber(ucs2DataSize), start);
- code = reader.readHex(dataSize);
- subitems.push({ start, end, code });
- for (let i = 1; i < subitemsCount; i++) {
- start = sequence
- ? incHex(end)
- : addHex(reader.readHexNumber(ucs2DataSize), incHex(end));
- end = addHex(reader.readHexNumber(ucs2DataSize), start);
- code = reader.readHex(dataSize);
- subitems.push({ start, end, code });
- }
- break;
- default:
- throw new Error("Unknown type: " + type);
- }
- result.body.push(item);
- }
- return result;
- }
- function toHexDigit(n) {
- return n.toString(16);
- }
- function fromHexDigit(s) {
- return parseInt(s, 16);
- }
- function getHexSize(s) {
- return (s.length >> 1) - 1;
- }
- function writeByte(b) {
- return toHexDigit((b >> 4) & 15) + toHexDigit(b & 15);
- }
- function writeNumber(n) {
- if (typeof n === "string") {
- let s = "",
- buffer = 0,
- bufferSize = 0;
- let i = n.length;
- while (i > 0) {
- --i;
- buffer |= fromHexDigit(n[i]) << bufferSize;
- bufferSize += 4;
- if (bufferSize >= 7) {
- s = writeByte((buffer & 0x7f) | (s.length > 0 ? 0x80 : 0)) + s;
- buffer >>>= 7;
- bufferSize -= 7;
- }
- }
- if (buffer > 0) {
- s = writeByte((buffer & 0x7f) | (s.length > 0 ? 0x80 : 0)) + s;
- }
- while (s.indexOf("80") === 0) {
- s = s.substring(2);
- }
- return s;
- }
- let s = writeByte(n & 0x7f);
- n >>>= 7;
- while (n > 0) {
- s = writeByte((n & 0x7f) | 0x80) + s;
- n >>>= 7;
- }
- return s;
- }
- function writeSigned(n) {
- if (typeof n === "string") {
- let t = "";
- let c = fromHexDigit(n[0]);
- const neg = c >= 8;
- c = neg ? c ^ 15 : c;
- for (let i = 1; i < n.length; i++) {
- const d = fromHexDigit(n[i]);
- c = (c << 4) | (neg ? d ^ 15 : d);
- t += toHexDigit(c >> 3);
- c &= 7;
- }
- t += toHexDigit((c << 1) | (neg ? 1 : 0));
- return writeNumber(t);
- }
- return n < 0 ? writeNumber(-2 * n - 1) : writeNumber(2 * n);
- }
- function writeString(s) {
- let t = writeNumber(s.length);
- for (let i = 0; i < s.length; i++) {
- t += writeNumber(s.charCodeAt(i));
- }
- return t;
- }
- function addHex(a, b) {
- let c = 0,
- s = "";
- for (let i = a.length - 1; i >= 0; i--) {
- c += fromHexDigit(a[i]) + fromHexDigit(b[i]);
- if (c >= 16) {
- s = toHexDigit(c - 16) + s;
- c = 1;
- } else {
- s = toHexDigit(c) + s;
- c = 0;
- }
- }
- return s;
- }
- function subHex(a, b) {
- let c = 0,
- s = "";
- for (let i = a.length - 1; i >= 0; i--) {
- c += fromHexDigit(a[i]) - fromHexDigit(b[i]);
- if (c < 0) {
- s = toHexDigit(c + 16) + s;
- c = -1;
- } else {
- s = toHexDigit(c) + s;
- c = 0;
- }
- }
- return s;
- }
- function incHex(a) {
- let c = 1,
- s = "";
- for (let i = a.length - 1; i >= 0; i--) {
- c += fromHexDigit(a[i]);
- if (c >= 16) {
- s = toHexDigit(c - 16) + s;
- c = 1;
- } else {
- s = toHexDigit(c) + s;
- c = 0;
- }
- }
- return s;
- }
- exports.compressCmaps = function (src, dest, verify) {
- const files = fs.readdirSync(src).filter(function (fn) {
- return !fn.includes("."); // skipping files with the extension
- });
- files.forEach(function (fn) {
- const srcPath = path.join(src, fn);
- const destPath = path.join(dest, fn + ".bcmap");
- const stats = compressCmap(srcPath, destPath, verify);
- console.log(
- "Compressing " +
- fn +
- ": " +
- stats.orig +
- " vs " +
- stats.packed +
- " " +
- ((stats.packed / stats.orig) * 100).toFixed(1) +
- "%"
- );
- });
- };
|