compress.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. /* Copyright 2014 Mozilla Foundation
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. const fs = require("fs");
  16. const path = require("path");
  17. const parseAdobeCMap = require("./parse.js").parseAdobeCMap;
  18. const optimizeCMap = require("./optimize.js").optimizeCMap;
  19. function compressCmap(srcPath, destPath, verify) {
  20. const content = fs.readFileSync(srcPath).toString();
  21. const inputData = parseAdobeCMap(content);
  22. optimizeCMap(inputData);
  23. let out = writeByte((inputData.type << 1) | inputData.wmode);
  24. if (inputData.comment) {
  25. out += writeByte(0xe0) + writeString(inputData.comment);
  26. }
  27. if (inputData.usecmap) {
  28. out += writeByte(0xe1) + writeString(inputData.usecmap);
  29. }
  30. let i = 0;
  31. while (i < inputData.body.length) {
  32. const item = inputData.body[i++],
  33. subitems = item.items;
  34. const first = item.items[0];
  35. const sequence = item.sequence === true;
  36. const flags = (item.type << 5) | (sequence ? 0x10 : 0);
  37. let nextStart, nextCode;
  38. switch (item.type) {
  39. case 0:
  40. out +=
  41. writeByte(flags | getHexSize(first.start)) +
  42. writeNumber(subitems.length);
  43. out += first.start + writeNumber(subHex(first.end, first.start));
  44. nextStart = incHex(first.end);
  45. for (let j = 1; j < subitems.length; j++) {
  46. out +=
  47. writeNumber(subHex(subitems[j].start, nextStart)) +
  48. writeNumber(subHex(subitems[j].end, subitems[j].start));
  49. nextStart = incHex(subitems[j].end);
  50. }
  51. break;
  52. case 1:
  53. out +=
  54. writeByte(flags | getHexSize(first.start)) +
  55. writeNumber(subitems.length);
  56. out +=
  57. first.start +
  58. writeNumber(subHex(first.end, first.start)) +
  59. writeNumber(first.code);
  60. nextStart = incHex(first.end);
  61. for (let j = 1; j < subitems.length; j++) {
  62. out +=
  63. writeNumber(subHex(subitems[j].start, nextStart)) +
  64. writeNumber(subHex(subitems[j].end, subitems[j].start)) +
  65. writeNumber(subitems[j].code);
  66. nextStart = incHex(subitems[j].end);
  67. }
  68. break;
  69. case 2:
  70. out +=
  71. writeByte(flags | getHexSize(first.char)) +
  72. writeNumber(subitems.length);
  73. out += first.char + writeNumber(first.code);
  74. nextStart = incHex(first.char);
  75. nextCode = first.code + 1;
  76. for (let j = 1; j < subitems.length; j++) {
  77. out +=
  78. (sequence ? "" : writeNumber(subHex(subitems[j].char, nextStart))) +
  79. writeSigned(subitems[j].code - nextCode);
  80. nextStart = incHex(subitems[j].char);
  81. nextCode = item.items[j].code + 1;
  82. }
  83. break;
  84. case 3:
  85. out +=
  86. writeByte(flags | getHexSize(first.start)) +
  87. writeNumber(subitems.length);
  88. out +=
  89. first.start +
  90. writeNumber(subHex(first.end, first.start)) +
  91. writeNumber(first.code);
  92. nextStart = incHex(first.end);
  93. for (let j = 1; j < subitems.length; j++) {
  94. out +=
  95. (sequence
  96. ? ""
  97. : writeNumber(subHex(subitems[j].start, nextStart))) +
  98. writeNumber(subHex(subitems[j].end, subitems[j].start)) +
  99. writeNumber(subitems[j].code);
  100. nextStart = incHex(subitems[j].end);
  101. }
  102. break;
  103. case 4:
  104. out +=
  105. writeByte(flags | getHexSize(first.code)) +
  106. writeNumber(subitems.length);
  107. out += first.char + first.code;
  108. nextStart = incHex(first.char);
  109. nextCode = incHex(first.code);
  110. for (let j = 1; j < subitems.length; j++) {
  111. out +=
  112. (sequence ? "" : writeNumber(subHex(subitems[j].char, nextStart))) +
  113. writeSigned(subHex(subitems[j].code, nextCode));
  114. nextStart = incHex(subitems[j].char);
  115. nextCode = incHex(subitems[j].code);
  116. }
  117. break;
  118. case 5:
  119. out +=
  120. writeByte(flags | getHexSize(first.code)) +
  121. writeNumber(subitems.length);
  122. out +=
  123. first.start +
  124. writeNumber(subHex(first.end, first.start)) +
  125. first.code;
  126. nextStart = incHex(first.end);
  127. for (let j = 1; j < subitems.length; j++) {
  128. out +=
  129. (sequence
  130. ? ""
  131. : writeNumber(subHex(subitems[j].start, nextStart))) +
  132. writeNumber(subHex(subitems[j].end, subitems[j].start)) +
  133. subitems[j].code;
  134. nextStart = incHex(subitems[j].end);
  135. }
  136. break;
  137. }
  138. }
  139. fs.writeFileSync(destPath, Buffer.from(out, "hex"));
  140. if (verify) {
  141. const result2 = parseCMap(out);
  142. const isGood = JSON.stringify(inputData) === JSON.stringify(result2);
  143. if (!isGood) {
  144. throw new Error("Extracted data does not match the expected result");
  145. }
  146. }
  147. return {
  148. orig: fs.statSync(srcPath).size,
  149. packed: out.length >> 1,
  150. };
  151. }
  152. function parseCMap(binaryData) {
  153. const reader = {
  154. buffer: binaryData,
  155. pos: 0,
  156. end: binaryData.length,
  157. readByte() {
  158. if (this.pos >= this.end) {
  159. return -1;
  160. }
  161. const d1 = fromHexDigit(this.buffer[this.pos]);
  162. const d2 = fromHexDigit(this.buffer[this.pos + 1]);
  163. this.pos += 2;
  164. return (d1 << 4) | d2;
  165. },
  166. readNumber() {
  167. let n = 0;
  168. let last;
  169. do {
  170. const b = this.readByte();
  171. last = !(b & 0x80);
  172. n = (n << 7) | (b & 0x7f);
  173. } while (!last);
  174. return n;
  175. },
  176. readSigned() {
  177. const n = this.readNumber();
  178. return n & 1 ? -(n >>> 1) - 1 : n >>> 1;
  179. },
  180. readHex(size) {
  181. const lengthInChars = (size + 1) << 1;
  182. const s = this.buffer.substring(this.pos, this.pos + lengthInChars);
  183. this.pos += lengthInChars;
  184. return s;
  185. },
  186. readHexNumber(size) {
  187. const lengthInChars = (size + 1) << 1,
  188. stack = [];
  189. let last;
  190. do {
  191. const b = this.readByte();
  192. last = !(b & 0x80);
  193. stack.push(b & 0x7f);
  194. } while (!last);
  195. let s = "",
  196. buffer = 0,
  197. bufferSize = 0;
  198. while (s.length < lengthInChars) {
  199. while (bufferSize < 4 && stack.length > 0) {
  200. buffer |= stack.pop() << bufferSize;
  201. bufferSize += 7;
  202. }
  203. s = toHexDigit(buffer & 15) + s;
  204. buffer >>= 4;
  205. bufferSize -= 4;
  206. }
  207. return s;
  208. },
  209. readHexSigned(size) {
  210. const num = this.readHexNumber(size);
  211. const sign = fromHexDigit(num[num.length - 1]) & 1 ? 15 : 0;
  212. let c = 0;
  213. let result = "";
  214. for (const digit of num) {
  215. c = (c << 4) | fromHexDigit(digit);
  216. result += toHexDigit(sign ? (c >> 1) ^ sign : c >> 1);
  217. c &= 1;
  218. }
  219. return result;
  220. },
  221. readString() {
  222. const len = this.readNumber();
  223. let s = "";
  224. for (let i = 0; i < len; i++) {
  225. s += String.fromCharCode(this.readNumber());
  226. }
  227. return s;
  228. },
  229. };
  230. const header = reader.readByte();
  231. const result = {
  232. type: header >> 1,
  233. wmode: header & 1,
  234. comment: null,
  235. usecmap: null,
  236. body: [],
  237. };
  238. let b;
  239. while ((b = reader.readByte()) >= 0) {
  240. const type = b >> 5;
  241. if (type === 7) {
  242. switch (b & 0x1f) {
  243. case 0:
  244. result.comment = reader.readString();
  245. break;
  246. case 1:
  247. result.usecmap = reader.readString();
  248. break;
  249. }
  250. continue;
  251. }
  252. const sequence = !!(b & 0x10);
  253. const dataSize = b & 15;
  254. const subitems = [];
  255. const item = {
  256. type,
  257. items: subitems,
  258. };
  259. if (sequence) {
  260. item.sequence = true;
  261. }
  262. const ucs2DataSize = 1;
  263. const subitemsCount = reader.readNumber();
  264. let start, end, code, char;
  265. switch (type) {
  266. case 0:
  267. start = reader.readHex(dataSize);
  268. end = addHex(reader.readHexNumber(dataSize), start);
  269. subitems.push({ start, end });
  270. for (let i = 1; i < subitemsCount; i++) {
  271. start = addHex(reader.readHexNumber(dataSize), incHex(end));
  272. end = addHex(reader.readHexNumber(dataSize), start);
  273. subitems.push({ start, end });
  274. }
  275. break;
  276. case 1:
  277. start = reader.readHex(dataSize);
  278. end = addHex(reader.readHexNumber(dataSize), start);
  279. code = reader.readNumber();
  280. subitems.push({ start, end, code });
  281. for (let i = 1; i < subitemsCount; i++) {
  282. start = addHex(reader.readHexNumber(dataSize), incHex(end));
  283. end = addHex(reader.readHexNumber(dataSize), start);
  284. code = reader.readNumber();
  285. subitems.push({ start, end, code });
  286. }
  287. break;
  288. case 2:
  289. char = reader.readHex(dataSize);
  290. code = reader.readNumber();
  291. subitems.push({ char, code });
  292. for (let i = 1; i < subitemsCount; i++) {
  293. char = sequence
  294. ? incHex(char)
  295. : addHex(reader.readHexNumber(dataSize), incHex(char));
  296. code = reader.readSigned() + (code + 1);
  297. subitems.push({ char, code });
  298. }
  299. break;
  300. case 3:
  301. start = reader.readHex(dataSize);
  302. end = addHex(reader.readHexNumber(dataSize), start);
  303. code = reader.readNumber();
  304. subitems.push({ start, end, code });
  305. for (let i = 1; i < subitemsCount; i++) {
  306. start = sequence
  307. ? incHex(end)
  308. : addHex(reader.readHexNumber(dataSize), incHex(end));
  309. end = addHex(reader.readHexNumber(dataSize), start);
  310. code = reader.readNumber();
  311. subitems.push({ start, end, code });
  312. }
  313. break;
  314. case 4:
  315. char = reader.readHex(ucs2DataSize);
  316. code = reader.readHex(dataSize);
  317. subitems.push({ char, code });
  318. for (let i = 1; i < subitemsCount; i++) {
  319. char = sequence
  320. ? incHex(char)
  321. : addHex(reader.readHexNumber(ucs2DataSize), incHex(char));
  322. code = addHex(reader.readHexSigned(dataSize), incHex(code));
  323. subitems.push({ char, code });
  324. }
  325. break;
  326. case 5:
  327. start = reader.readHex(ucs2DataSize);
  328. end = addHex(reader.readHexNumber(ucs2DataSize), start);
  329. code = reader.readHex(dataSize);
  330. subitems.push({ start, end, code });
  331. for (let i = 1; i < subitemsCount; i++) {
  332. start = sequence
  333. ? incHex(end)
  334. : addHex(reader.readHexNumber(ucs2DataSize), incHex(end));
  335. end = addHex(reader.readHexNumber(ucs2DataSize), start);
  336. code = reader.readHex(dataSize);
  337. subitems.push({ start, end, code });
  338. }
  339. break;
  340. default:
  341. throw new Error("Unknown type: " + type);
  342. }
  343. result.body.push(item);
  344. }
  345. return result;
  346. }
  347. function toHexDigit(n) {
  348. return n.toString(16);
  349. }
  350. function fromHexDigit(s) {
  351. return parseInt(s, 16);
  352. }
  353. function getHexSize(s) {
  354. return (s.length >> 1) - 1;
  355. }
  356. function writeByte(b) {
  357. return toHexDigit((b >> 4) & 15) + toHexDigit(b & 15);
  358. }
  359. function writeNumber(n) {
  360. if (typeof n === "string") {
  361. let s = "",
  362. buffer = 0,
  363. bufferSize = 0;
  364. let i = n.length;
  365. while (i > 0) {
  366. --i;
  367. buffer |= fromHexDigit(n[i]) << bufferSize;
  368. bufferSize += 4;
  369. if (bufferSize >= 7) {
  370. s = writeByte((buffer & 0x7f) | (s.length > 0 ? 0x80 : 0)) + s;
  371. buffer >>>= 7;
  372. bufferSize -= 7;
  373. }
  374. }
  375. if (buffer > 0) {
  376. s = writeByte((buffer & 0x7f) | (s.length > 0 ? 0x80 : 0)) + s;
  377. }
  378. while (s.indexOf("80") === 0) {
  379. s = s.substring(2);
  380. }
  381. return s;
  382. }
  383. let s = writeByte(n & 0x7f);
  384. n >>>= 7;
  385. while (n > 0) {
  386. s = writeByte((n & 0x7f) | 0x80) + s;
  387. n >>>= 7;
  388. }
  389. return s;
  390. }
  391. function writeSigned(n) {
  392. if (typeof n === "string") {
  393. let t = "";
  394. let c = fromHexDigit(n[0]);
  395. const neg = c >= 8;
  396. c = neg ? c ^ 15 : c;
  397. for (let i = 1; i < n.length; i++) {
  398. const d = fromHexDigit(n[i]);
  399. c = (c << 4) | (neg ? d ^ 15 : d);
  400. t += toHexDigit(c >> 3);
  401. c &= 7;
  402. }
  403. t += toHexDigit((c << 1) | (neg ? 1 : 0));
  404. return writeNumber(t);
  405. }
  406. return n < 0 ? writeNumber(-2 * n - 1) : writeNumber(2 * n);
  407. }
  408. function writeString(s) {
  409. let t = writeNumber(s.length);
  410. for (let i = 0; i < s.length; i++) {
  411. t += writeNumber(s.charCodeAt(i));
  412. }
  413. return t;
  414. }
  415. function addHex(a, b) {
  416. let c = 0,
  417. s = "";
  418. for (let i = a.length - 1; i >= 0; i--) {
  419. c += fromHexDigit(a[i]) + fromHexDigit(b[i]);
  420. if (c >= 16) {
  421. s = toHexDigit(c - 16) + s;
  422. c = 1;
  423. } else {
  424. s = toHexDigit(c) + s;
  425. c = 0;
  426. }
  427. }
  428. return s;
  429. }
  430. function subHex(a, b) {
  431. let c = 0,
  432. s = "";
  433. for (let i = a.length - 1; i >= 0; i--) {
  434. c += fromHexDigit(a[i]) - fromHexDigit(b[i]);
  435. if (c < 0) {
  436. s = toHexDigit(c + 16) + s;
  437. c = -1;
  438. } else {
  439. s = toHexDigit(c) + s;
  440. c = 0;
  441. }
  442. }
  443. return s;
  444. }
  445. function incHex(a) {
  446. let c = 1,
  447. s = "";
  448. for (let i = a.length - 1; i >= 0; i--) {
  449. c += fromHexDigit(a[i]);
  450. if (c >= 16) {
  451. s = toHexDigit(c - 16) + s;
  452. c = 1;
  453. } else {
  454. s = toHexDigit(c) + s;
  455. c = 0;
  456. }
  457. }
  458. return s;
  459. }
  460. exports.compressCmaps = function (src, dest, verify) {
  461. const files = fs.readdirSync(src).filter(function (fn) {
  462. return !fn.includes("."); // skipping files with the extension
  463. });
  464. files.forEach(function (fn) {
  465. const srcPath = path.join(src, fn);
  466. const destPath = path.join(dest, fn + ".bcmap");
  467. const stats = compressCmap(srcPath, destPath, verify);
  468. console.log(
  469. "Compressing " +
  470. fn +
  471. ": " +
  472. stats.orig +
  473. " vs " +
  474. stats.packed +
  475. " " +
  476. ((stats.packed / stats.orig) * 100).toFixed(1) +
  477. "%"
  478. );
  479. });
  480. };