glyf.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. /**
  2. * @licstart The following is the entire license notice for the
  3. * Javascript code in this page
  4. *
  5. * Copyright 2021 Mozilla Foundation
  6. *
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. *
  19. * @licend The above is the entire license notice for the
  20. * Javascript code in this page
  21. */
  22. "use strict";
  23. Object.defineProperty(exports, "__esModule", {
  24. value: true
  25. });
  26. exports.GlyfTable = void 0;
  27. const ON_CURVE_POINT = 1 << 0;
  28. const X_SHORT_VECTOR = 1 << 1;
  29. const Y_SHORT_VECTOR = 1 << 2;
  30. const REPEAT_FLAG = 1 << 3;
  31. const X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR = 1 << 4;
  32. const Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR = 1 << 5;
  33. const OVERLAP_SIMPLE = 1 << 6;
  34. const ARG_1_AND_2_ARE_WORDS = 1 << 0;
  35. const ARGS_ARE_XY_VALUES = 1 << 1;
  36. const WE_HAVE_A_SCALE = 1 << 3;
  37. const MORE_COMPONENTS = 1 << 5;
  38. const WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6;
  39. const WE_HAVE_A_TWO_BY_TWO = 1 << 7;
  40. const WE_HAVE_INSTRUCTIONS = 1 << 8;
  41. class GlyfTable {
  42. constructor({
  43. glyfTable,
  44. isGlyphLocationsLong,
  45. locaTable,
  46. numGlyphs
  47. }) {
  48. this.glyphs = [];
  49. const loca = new DataView(locaTable.buffer, locaTable.byteOffset, locaTable.byteLength);
  50. const glyf = new DataView(glyfTable.buffer, glyfTable.byteOffset, glyfTable.byteLength);
  51. const offsetSize = isGlyphLocationsLong ? 4 : 2;
  52. let prev = isGlyphLocationsLong ? loca.getUint32(0) : 2 * loca.getUint16(0);
  53. let pos = 0;
  54. for (let i = 0; i < numGlyphs; i++) {
  55. pos += offsetSize;
  56. const next = isGlyphLocationsLong ? loca.getUint32(pos) : 2 * loca.getUint16(pos);
  57. if (next === prev) {
  58. this.glyphs.push(new Glyph({}));
  59. continue;
  60. }
  61. const glyph = Glyph.parse(prev, glyf);
  62. this.glyphs.push(glyph);
  63. prev = next;
  64. }
  65. }
  66. getSize() {
  67. return this.glyphs.reduce((a, g) => {
  68. const size = g.getSize();
  69. return a + (size + 3 & ~3);
  70. }, 0);
  71. }
  72. write() {
  73. const totalSize = this.getSize();
  74. const glyfTable = new DataView(new ArrayBuffer(totalSize));
  75. const isLocationLong = totalSize > 0x1fffe;
  76. const offsetSize = isLocationLong ? 4 : 2;
  77. const locaTable = new DataView(new ArrayBuffer((this.glyphs.length + 1) * offsetSize));
  78. if (isLocationLong) {
  79. locaTable.setUint32(0, 0);
  80. } else {
  81. locaTable.setUint16(0, 0);
  82. }
  83. let pos = 0;
  84. let locaIndex = 0;
  85. for (const glyph of this.glyphs) {
  86. pos += glyph.write(pos, glyfTable);
  87. pos = pos + 3 & ~3;
  88. locaIndex += offsetSize;
  89. if (isLocationLong) {
  90. locaTable.setUint32(locaIndex, pos);
  91. } else {
  92. locaTable.setUint16(locaIndex, pos >> 1);
  93. }
  94. }
  95. return {
  96. isLocationLong,
  97. loca: new Uint8Array(locaTable.buffer),
  98. glyf: new Uint8Array(glyfTable.buffer)
  99. };
  100. }
  101. scale(factors) {
  102. for (let i = 0, ii = this.glyphs.length; i < ii; i++) {
  103. this.glyphs[i].scale(factors[i]);
  104. }
  105. }
  106. }
  107. exports.GlyfTable = GlyfTable;
  108. class Glyph {
  109. constructor({
  110. header = null,
  111. simple = null,
  112. composites = null
  113. }) {
  114. this.header = header;
  115. this.simple = simple;
  116. this.composites = composites;
  117. }
  118. static parse(pos, glyf) {
  119. const [read, header] = GlyphHeader.parse(pos, glyf);
  120. pos += read;
  121. if (header.numberOfContours < 0) {
  122. const composites = [];
  123. while (true) {
  124. const [n, composite] = CompositeGlyph.parse(pos, glyf);
  125. pos += n;
  126. composites.push(composite);
  127. if (!(composite.flags & MORE_COMPONENTS)) {
  128. break;
  129. }
  130. }
  131. return new Glyph({
  132. header,
  133. composites
  134. });
  135. }
  136. const simple = SimpleGlyph.parse(pos, glyf, header.numberOfContours);
  137. return new Glyph({
  138. header,
  139. simple
  140. });
  141. }
  142. getSize() {
  143. if (!this.header) {
  144. return 0;
  145. }
  146. const size = this.simple ? this.simple.getSize() : this.composites.reduce((a, c) => a + c.getSize(), 0);
  147. return this.header.getSize() + size;
  148. }
  149. write(pos, buf) {
  150. if (!this.header) {
  151. return 0;
  152. }
  153. const spos = pos;
  154. pos += this.header.write(pos, buf);
  155. if (this.simple) {
  156. pos += this.simple.write(pos, buf);
  157. } else {
  158. for (const composite of this.composites) {
  159. pos += composite.write(pos, buf);
  160. }
  161. }
  162. return pos - spos;
  163. }
  164. scale(factor) {
  165. if (!this.header) {
  166. return;
  167. }
  168. const xMiddle = (this.header.xMin + this.header.xMax) / 2;
  169. this.header.scale(xMiddle, factor);
  170. if (this.simple) {
  171. this.simple.scale(xMiddle, factor);
  172. } else {
  173. for (const composite of this.composites) {
  174. composite.scale(xMiddle, factor);
  175. }
  176. }
  177. }
  178. }
  179. class GlyphHeader {
  180. constructor({
  181. numberOfContours,
  182. xMin,
  183. yMin,
  184. xMax,
  185. yMax
  186. }) {
  187. this.numberOfContours = numberOfContours;
  188. this.xMin = xMin;
  189. this.yMin = yMin;
  190. this.xMax = xMax;
  191. this.yMax = yMax;
  192. }
  193. static parse(pos, glyf) {
  194. return [10, new GlyphHeader({
  195. numberOfContours: glyf.getInt16(pos),
  196. xMin: glyf.getInt16(pos + 2),
  197. yMin: glyf.getInt16(pos + 4),
  198. xMax: glyf.getInt16(pos + 6),
  199. yMax: glyf.getInt16(pos + 8)
  200. })];
  201. }
  202. getSize() {
  203. return 10;
  204. }
  205. write(pos, buf) {
  206. buf.setInt16(pos, this.numberOfContours);
  207. buf.setInt16(pos + 2, this.xMin);
  208. buf.setInt16(pos + 4, this.yMin);
  209. buf.setInt16(pos + 6, this.xMax);
  210. buf.setInt16(pos + 8, this.yMax);
  211. return 10;
  212. }
  213. scale(x, factor) {
  214. this.xMin = Math.round(x + (this.xMin - x) * factor);
  215. this.xMax = Math.round(x + (this.xMax - x) * factor);
  216. }
  217. }
  218. class Contour {
  219. constructor({
  220. flags,
  221. xCoordinates,
  222. yCoordinates
  223. }) {
  224. this.xCoordinates = xCoordinates;
  225. this.yCoordinates = yCoordinates;
  226. this.flags = flags;
  227. }
  228. }
  229. class SimpleGlyph {
  230. constructor({
  231. contours,
  232. instructions
  233. }) {
  234. this.contours = contours;
  235. this.instructions = instructions;
  236. }
  237. static parse(pos, glyf, numberOfContours) {
  238. const endPtsOfContours = [];
  239. for (let i = 0; i < numberOfContours; i++) {
  240. const endPt = glyf.getUint16(pos);
  241. pos += 2;
  242. endPtsOfContours.push(endPt);
  243. }
  244. const numberOfPt = endPtsOfContours[numberOfContours - 1] + 1;
  245. const instructionLength = glyf.getUint16(pos);
  246. pos += 2;
  247. const instructions = new Uint8Array(glyf).slice(pos, pos + instructionLength);
  248. pos += instructionLength;
  249. const flags = [];
  250. for (let i = 0; i < numberOfPt; pos++, i++) {
  251. let flag = glyf.getUint8(pos);
  252. flags.push(flag);
  253. if (flag & REPEAT_FLAG) {
  254. const count = glyf.getUint8(++pos);
  255. flag ^= REPEAT_FLAG;
  256. for (let m = 0; m < count; m++) {
  257. flags.push(flag);
  258. }
  259. i += count;
  260. }
  261. }
  262. const allXCoordinates = [];
  263. let xCoordinates = [];
  264. let yCoordinates = [];
  265. let pointFlags = [];
  266. const contours = [];
  267. let endPtsOfContoursIndex = 0;
  268. let lastCoordinate = 0;
  269. for (let i = 0; i < numberOfPt; i++) {
  270. const flag = flags[i];
  271. if (flag & X_SHORT_VECTOR) {
  272. const x = glyf.getUint8(pos++);
  273. lastCoordinate += flag & X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR ? x : -x;
  274. xCoordinates.push(lastCoordinate);
  275. } else if (flag & X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR) {
  276. xCoordinates.push(lastCoordinate);
  277. } else {
  278. lastCoordinate += glyf.getInt16(pos);
  279. pos += 2;
  280. xCoordinates.push(lastCoordinate);
  281. }
  282. if (endPtsOfContours[endPtsOfContoursIndex] === i) {
  283. endPtsOfContoursIndex++;
  284. allXCoordinates.push(xCoordinates);
  285. xCoordinates = [];
  286. }
  287. }
  288. lastCoordinate = 0;
  289. endPtsOfContoursIndex = 0;
  290. for (let i = 0; i < numberOfPt; i++) {
  291. const flag = flags[i];
  292. if (flag & Y_SHORT_VECTOR) {
  293. const y = glyf.getUint8(pos++);
  294. lastCoordinate += flag & Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR ? y : -y;
  295. yCoordinates.push(lastCoordinate);
  296. } else if (flag & Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR) {
  297. yCoordinates.push(lastCoordinate);
  298. } else {
  299. lastCoordinate += glyf.getInt16(pos);
  300. pos += 2;
  301. yCoordinates.push(lastCoordinate);
  302. }
  303. pointFlags.push(flag & ON_CURVE_POINT | flag & OVERLAP_SIMPLE);
  304. if (endPtsOfContours[endPtsOfContoursIndex] === i) {
  305. xCoordinates = allXCoordinates[endPtsOfContoursIndex];
  306. endPtsOfContoursIndex++;
  307. contours.push(new Contour({
  308. flags: pointFlags,
  309. xCoordinates,
  310. yCoordinates
  311. }));
  312. yCoordinates = [];
  313. pointFlags = [];
  314. }
  315. }
  316. return new SimpleGlyph({
  317. contours,
  318. instructions
  319. });
  320. }
  321. getSize() {
  322. let size = this.contours.length * 2 + 2 + this.instructions.length;
  323. let lastX = 0;
  324. let lastY = 0;
  325. for (const contour of this.contours) {
  326. size += contour.flags.length;
  327. for (let i = 0, ii = contour.xCoordinates.length; i < ii; i++) {
  328. const x = contour.xCoordinates[i];
  329. const y = contour.yCoordinates[i];
  330. let abs = Math.abs(x - lastX);
  331. if (abs > 255) {
  332. size += 2;
  333. } else if (abs > 0) {
  334. size += 1;
  335. }
  336. lastX = x;
  337. abs = Math.abs(y - lastY);
  338. if (abs > 255) {
  339. size += 2;
  340. } else if (abs > 0) {
  341. size += 1;
  342. }
  343. lastY = y;
  344. }
  345. }
  346. return size;
  347. }
  348. write(pos, buf) {
  349. const spos = pos;
  350. const xCoordinates = [];
  351. const yCoordinates = [];
  352. const flags = [];
  353. let lastX = 0;
  354. let lastY = 0;
  355. for (const contour of this.contours) {
  356. for (let i = 0, ii = contour.xCoordinates.length; i < ii; i++) {
  357. let flag = contour.flags[i];
  358. const x = contour.xCoordinates[i];
  359. let delta = x - lastX;
  360. if (delta === 0) {
  361. flag |= X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR;
  362. xCoordinates.push(0);
  363. } else {
  364. const abs = Math.abs(delta);
  365. if (abs <= 255) {
  366. flag |= delta >= 0 ? X_SHORT_VECTOR | X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR : X_SHORT_VECTOR;
  367. xCoordinates.push(abs);
  368. } else {
  369. xCoordinates.push(delta);
  370. }
  371. }
  372. lastX = x;
  373. const y = contour.yCoordinates[i];
  374. delta = y - lastY;
  375. if (delta === 0) {
  376. flag |= Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR;
  377. yCoordinates.push(0);
  378. } else {
  379. const abs = Math.abs(delta);
  380. if (abs <= 255) {
  381. flag |= delta >= 0 ? Y_SHORT_VECTOR | Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR : Y_SHORT_VECTOR;
  382. yCoordinates.push(abs);
  383. } else {
  384. yCoordinates.push(delta);
  385. }
  386. }
  387. lastY = y;
  388. flags.push(flag);
  389. }
  390. buf.setUint16(pos, xCoordinates.length - 1);
  391. pos += 2;
  392. }
  393. buf.setUint16(pos, this.instructions.length);
  394. pos += 2;
  395. if (this.instructions.length) {
  396. new Uint8Array(buf.buffer, 0, buf.buffer.byteLength).set(this.instructions, pos);
  397. pos += this.instructions.length;
  398. }
  399. for (const flag of flags) {
  400. buf.setUint8(pos++, flag);
  401. }
  402. for (let i = 0, ii = xCoordinates.length; i < ii; i++) {
  403. const x = xCoordinates[i];
  404. const flag = flags[i];
  405. if (flag & X_SHORT_VECTOR) {
  406. buf.setUint8(pos++, x);
  407. } else if (!(flag & X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR)) {
  408. buf.setInt16(pos, x);
  409. pos += 2;
  410. }
  411. }
  412. for (let i = 0, ii = yCoordinates.length; i < ii; i++) {
  413. const y = yCoordinates[i];
  414. const flag = flags[i];
  415. if (flag & Y_SHORT_VECTOR) {
  416. buf.setUint8(pos++, y);
  417. } else if (!(flag & Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR)) {
  418. buf.setInt16(pos, y);
  419. pos += 2;
  420. }
  421. }
  422. return pos - spos;
  423. }
  424. scale(x, factor) {
  425. for (const contour of this.contours) {
  426. if (contour.xCoordinates.length === 0) {
  427. continue;
  428. }
  429. for (let i = 0, ii = contour.xCoordinates.length; i < ii; i++) {
  430. contour.xCoordinates[i] = Math.round(x + (contour.xCoordinates[i] - x) * factor);
  431. }
  432. }
  433. }
  434. }
  435. class CompositeGlyph {
  436. constructor({
  437. flags,
  438. glyphIndex,
  439. argument1,
  440. argument2,
  441. transf,
  442. instructions
  443. }) {
  444. this.flags = flags;
  445. this.glyphIndex = glyphIndex;
  446. this.argument1 = argument1;
  447. this.argument2 = argument2;
  448. this.transf = transf;
  449. this.instructions = instructions;
  450. }
  451. static parse(pos, glyf) {
  452. const spos = pos;
  453. const transf = [];
  454. let flags = glyf.getUint16(pos);
  455. const glyphIndex = glyf.getUint16(pos + 2);
  456. pos += 4;
  457. let argument1, argument2;
  458. if (flags & ARG_1_AND_2_ARE_WORDS) {
  459. if (flags & ARGS_ARE_XY_VALUES) {
  460. argument1 = glyf.getInt16(pos);
  461. argument2 = glyf.getInt16(pos + 2);
  462. } else {
  463. argument1 = glyf.getUint16(pos);
  464. argument2 = glyf.getUint16(pos + 2);
  465. }
  466. pos += 4;
  467. flags ^= ARG_1_AND_2_ARE_WORDS;
  468. } else {
  469. argument1 = glyf.getUint8(pos);
  470. argument2 = glyf.getUint8(pos + 1);
  471. if (flags & ARGS_ARE_XY_VALUES) {
  472. const abs1 = argument1 & 0x7f;
  473. argument1 = argument1 & 0x80 ? -abs1 : abs1;
  474. const abs2 = argument2 & 0x7f;
  475. argument2 = argument2 & 0x80 ? -abs2 : abs2;
  476. }
  477. pos += 2;
  478. }
  479. if (flags & WE_HAVE_A_SCALE) {
  480. transf.push(glyf.getUint16(pos));
  481. pos += 2;
  482. } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) {
  483. transf.push(glyf.getUint16(pos), glyf.getUint16(pos + 2));
  484. pos += 4;
  485. } else if (flags & WE_HAVE_A_TWO_BY_TWO) {
  486. transf.push(glyf.getUint16(pos), glyf.getUint16(pos + 2), glyf.getUint16(pos + 4), glyf.getUint16(pos + 6));
  487. pos += 8;
  488. }
  489. let instructions = null;
  490. if (flags & WE_HAVE_INSTRUCTIONS) {
  491. const instructionLength = glyf.getUint16(pos);
  492. pos += 2;
  493. instructions = new Uint8Array(glyf).slice(pos, pos + instructionLength);
  494. pos += instructionLength;
  495. }
  496. return [pos - spos, new CompositeGlyph({
  497. flags,
  498. glyphIndex,
  499. argument1,
  500. argument2,
  501. transf,
  502. instructions
  503. })];
  504. }
  505. getSize() {
  506. let size = 2 + 2 + this.transf.length * 2;
  507. if (this.flags & WE_HAVE_INSTRUCTIONS) {
  508. size += 2 + this.instructions.length;
  509. }
  510. size += 2;
  511. if (this.flags & 2) {
  512. if (!(this.argument1 >= -128 && this.argument1 <= 127 && this.argument2 >= -128 && this.argument2 <= 127)) {
  513. size += 2;
  514. }
  515. } else {
  516. if (!(this.argument1 >= 0 && this.argument1 <= 255 && this.argument2 >= 0 && this.argument2 <= 255)) {
  517. size += 2;
  518. }
  519. }
  520. return size;
  521. }
  522. write(pos, buf) {
  523. const spos = pos;
  524. if (this.flags & ARGS_ARE_XY_VALUES) {
  525. if (!(this.argument1 >= -128 && this.argument1 <= 127 && this.argument2 >= -128 && this.argument2 <= 127)) {
  526. this.flags |= ARG_1_AND_2_ARE_WORDS;
  527. }
  528. } else {
  529. if (!(this.argument1 >= 0 && this.argument1 <= 255 && this.argument2 >= 0 && this.argument2 <= 255)) {
  530. this.flags |= ARG_1_AND_2_ARE_WORDS;
  531. }
  532. }
  533. buf.setUint16(pos, this.flags);
  534. buf.setUint16(pos + 2, this.glyphIndex);
  535. pos += 4;
  536. if (this.flags & ARG_1_AND_2_ARE_WORDS) {
  537. if (this.flags & ARGS_ARE_XY_VALUES) {
  538. buf.setInt16(pos, this.argument1);
  539. buf.setInt16(pos + 2, this.argument2);
  540. } else {
  541. buf.setUint16(pos, this.argument1);
  542. buf.setUint16(pos + 2, this.argument2);
  543. }
  544. pos += 4;
  545. } else {
  546. buf.setUint8(pos, this.argument1);
  547. buf.setUint8(pos + 1, this.argument2);
  548. pos += 2;
  549. }
  550. if (this.flags & WE_HAVE_INSTRUCTIONS) {
  551. buf.setUint16(pos, this.instructions.length);
  552. pos += 2;
  553. if (this.instructions.length) {
  554. new Uint8Array(buf.buffer, 0, buf.buffer.byteLength).set(this.instructions, pos);
  555. pos += this.instructions.length;
  556. }
  557. }
  558. return pos - spos;
  559. }
  560. scale(x, factor) {}
  561. }