font_renderer.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905
  1. /* Copyright 2012 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. import {
  16. bytesToString,
  17. FONT_IDENTITY_MATRIX,
  18. FormatError,
  19. unreachable,
  20. warn,
  21. } from "../shared/util.js";
  22. import { CFFParser } from "./cff_parser.js";
  23. import { getGlyphsUnicode } from "./glyphlist.js";
  24. import { StandardEncoding } from "./encodings.js";
  25. import { Stream } from "./stream.js";
  26. // TODO: use DataView and its methods.
  27. function getUint32(data, offset) {
  28. return (
  29. ((data[offset] << 24) |
  30. (data[offset + 1] << 16) |
  31. (data[offset + 2] << 8) |
  32. data[offset + 3]) >>>
  33. 0
  34. );
  35. }
  36. function getUint16(data, offset) {
  37. return (data[offset] << 8) | data[offset + 1];
  38. }
  39. function getInt16(data, offset) {
  40. return ((data[offset] << 24) | (data[offset + 1] << 16)) >> 16;
  41. }
  42. function getInt8(data, offset) {
  43. return (data[offset] << 24) >> 24;
  44. }
  45. function getFloat214(data, offset) {
  46. return getInt16(data, offset) / 16384;
  47. }
  48. function getSubroutineBias(subrs) {
  49. const numSubrs = subrs.length;
  50. let bias = 32768;
  51. if (numSubrs < 1240) {
  52. bias = 107;
  53. } else if (numSubrs < 33900) {
  54. bias = 1131;
  55. }
  56. return bias;
  57. }
  58. function parseCmap(data, start, end) {
  59. const offset =
  60. getUint16(data, start + 2) === 1
  61. ? getUint32(data, start + 8)
  62. : getUint32(data, start + 16);
  63. const format = getUint16(data, start + offset);
  64. let ranges, p, i;
  65. if (format === 4) {
  66. getUint16(data, start + offset + 2); // length
  67. const segCount = getUint16(data, start + offset + 6) >> 1;
  68. p = start + offset + 14;
  69. ranges = [];
  70. for (i = 0; i < segCount; i++, p += 2) {
  71. ranges[i] = { end: getUint16(data, p) };
  72. }
  73. p += 2;
  74. for (i = 0; i < segCount; i++, p += 2) {
  75. ranges[i].start = getUint16(data, p);
  76. }
  77. for (i = 0; i < segCount; i++, p += 2) {
  78. ranges[i].idDelta = getUint16(data, p);
  79. }
  80. for (i = 0; i < segCount; i++, p += 2) {
  81. let idOffset = getUint16(data, p);
  82. if (idOffset === 0) {
  83. continue;
  84. }
  85. ranges[i].ids = [];
  86. for (let j = 0, jj = ranges[i].end - ranges[i].start + 1; j < jj; j++) {
  87. ranges[i].ids[j] = getUint16(data, p + idOffset);
  88. idOffset += 2;
  89. }
  90. }
  91. return ranges;
  92. } else if (format === 12) {
  93. const groups = getUint32(data, start + offset + 12);
  94. p = start + offset + 16;
  95. ranges = [];
  96. for (i = 0; i < groups; i++) {
  97. start = getUint32(data, p);
  98. ranges.push({
  99. start,
  100. end: getUint32(data, p + 4),
  101. idDelta: getUint32(data, p + 8) - start,
  102. });
  103. p += 12;
  104. }
  105. return ranges;
  106. }
  107. throw new FormatError(`unsupported cmap: ${format}`);
  108. }
  109. function parseCff(data, start, end, seacAnalysisEnabled) {
  110. const properties = {};
  111. const parser = new CFFParser(
  112. new Stream(data, start, end - start),
  113. properties,
  114. seacAnalysisEnabled
  115. );
  116. const cff = parser.parse();
  117. return {
  118. glyphs: cff.charStrings.objects,
  119. subrs:
  120. cff.topDict.privateDict &&
  121. cff.topDict.privateDict.subrsIndex &&
  122. cff.topDict.privateDict.subrsIndex.objects,
  123. gsubrs: cff.globalSubrIndex && cff.globalSubrIndex.objects,
  124. isCFFCIDFont: cff.isCIDFont,
  125. fdSelect: cff.fdSelect,
  126. fdArray: cff.fdArray,
  127. };
  128. }
  129. function parseGlyfTable(glyf, loca, isGlyphLocationsLong) {
  130. let itemSize, itemDecode;
  131. if (isGlyphLocationsLong) {
  132. itemSize = 4;
  133. itemDecode = getUint32;
  134. } else {
  135. itemSize = 2;
  136. itemDecode = (data, offset) => 2 * getUint16(data, offset);
  137. }
  138. const glyphs = [];
  139. let startOffset = itemDecode(loca, 0);
  140. for (let j = itemSize; j < loca.length; j += itemSize) {
  141. const endOffset = itemDecode(loca, j);
  142. glyphs.push(glyf.subarray(startOffset, endOffset));
  143. startOffset = endOffset;
  144. }
  145. return glyphs;
  146. }
  147. function lookupCmap(ranges, unicode) {
  148. const code = unicode.codePointAt(0);
  149. let gid = 0,
  150. l = 0,
  151. r = ranges.length - 1;
  152. while (l < r) {
  153. const c = (l + r + 1) >> 1;
  154. if (code < ranges[c].start) {
  155. r = c - 1;
  156. } else {
  157. l = c;
  158. }
  159. }
  160. if (ranges[l].start <= code && code <= ranges[l].end) {
  161. gid =
  162. (ranges[l].idDelta +
  163. (ranges[l].ids ? ranges[l].ids[code - ranges[l].start] : code)) &
  164. 0xffff;
  165. }
  166. return {
  167. charCode: code,
  168. glyphId: gid,
  169. };
  170. }
  171. function compileGlyf(code, cmds, font) {
  172. function moveTo(x, y) {
  173. cmds.push({ cmd: "moveTo", args: [x, y] });
  174. }
  175. function lineTo(x, y) {
  176. cmds.push({ cmd: "lineTo", args: [x, y] });
  177. }
  178. function quadraticCurveTo(xa, ya, x, y) {
  179. cmds.push({ cmd: "quadraticCurveTo", args: [xa, ya, x, y] });
  180. }
  181. let i = 0;
  182. const numberOfContours = getInt16(code, i);
  183. let flags;
  184. let x = 0,
  185. y = 0;
  186. i += 10;
  187. if (numberOfContours < 0) {
  188. // composite glyph
  189. do {
  190. flags = getUint16(code, i);
  191. const glyphIndex = getUint16(code, i + 2);
  192. i += 4;
  193. let arg1, arg2;
  194. if (flags & 0x01) {
  195. if (flags & 0x02) {
  196. arg1 = getInt16(code, i);
  197. arg2 = getInt16(code, i + 2);
  198. } else {
  199. arg1 = getUint16(code, i);
  200. arg2 = getUint16(code, i + 2);
  201. }
  202. i += 4;
  203. } else {
  204. if (flags & 0x02) {
  205. arg1 = getInt8(code, i++);
  206. arg2 = getInt8(code, i++);
  207. } else {
  208. arg1 = code[i++];
  209. arg2 = code[i++];
  210. }
  211. }
  212. if (flags & 0x02) {
  213. x = arg1;
  214. y = arg2;
  215. } else {
  216. x = 0;
  217. y = 0;
  218. }
  219. let scaleX = 1,
  220. scaleY = 1,
  221. scale01 = 0,
  222. scale10 = 0;
  223. if (flags & 0x08) {
  224. scaleX = scaleY = getFloat214(code, i);
  225. i += 2;
  226. } else if (flags & 0x40) {
  227. scaleX = getFloat214(code, i);
  228. scaleY = getFloat214(code, i + 2);
  229. i += 4;
  230. } else if (flags & 0x80) {
  231. scaleX = getFloat214(code, i);
  232. scale01 = getFloat214(code, i + 2);
  233. scale10 = getFloat214(code, i + 4);
  234. scaleY = getFloat214(code, i + 6);
  235. i += 8;
  236. }
  237. const subglyph = font.glyphs[glyphIndex];
  238. if (subglyph) {
  239. // TODO: the transform should be applied only if there is a scale:
  240. // https://github.com/freetype/freetype/blob/edd4fedc5427cf1cf1f4b045e53ff91eb282e9d4/src/truetype/ttgload.c#L1205
  241. cmds.push(
  242. { cmd: "save" },
  243. {
  244. cmd: "transform",
  245. args: [scaleX, scale01, scale10, scaleY, x, y],
  246. }
  247. );
  248. if (!(flags & 0x02)) {
  249. // TODO: we must use arg1 and arg2 to make something similar to:
  250. // https://github.com/freetype/freetype/blob/edd4fedc5427cf1cf1f4b045e53ff91eb282e9d4/src/truetype/ttgload.c#L1209
  251. }
  252. compileGlyf(subglyph, cmds, font);
  253. cmds.push({ cmd: "restore" });
  254. }
  255. } while (flags & 0x20);
  256. } else {
  257. // simple glyph
  258. const endPtsOfContours = [];
  259. let j, jj;
  260. for (j = 0; j < numberOfContours; j++) {
  261. endPtsOfContours.push(getUint16(code, i));
  262. i += 2;
  263. }
  264. const instructionLength = getUint16(code, i);
  265. i += 2 + instructionLength; // skipping the instructions
  266. const numberOfPoints = endPtsOfContours.at(-1) + 1;
  267. const points = [];
  268. while (points.length < numberOfPoints) {
  269. flags = code[i++];
  270. let repeat = 1;
  271. if (flags & 0x08) {
  272. repeat += code[i++];
  273. }
  274. while (repeat-- > 0) {
  275. points.push({ flags });
  276. }
  277. }
  278. for (j = 0; j < numberOfPoints; j++) {
  279. switch (points[j].flags & 0x12) {
  280. case 0x00:
  281. x += getInt16(code, i);
  282. i += 2;
  283. break;
  284. case 0x02:
  285. x -= code[i++];
  286. break;
  287. case 0x12:
  288. x += code[i++];
  289. break;
  290. }
  291. points[j].x = x;
  292. }
  293. for (j = 0; j < numberOfPoints; j++) {
  294. switch (points[j].flags & 0x24) {
  295. case 0x00:
  296. y += getInt16(code, i);
  297. i += 2;
  298. break;
  299. case 0x04:
  300. y -= code[i++];
  301. break;
  302. case 0x24:
  303. y += code[i++];
  304. break;
  305. }
  306. points[j].y = y;
  307. }
  308. let startPoint = 0;
  309. for (i = 0; i < numberOfContours; i++) {
  310. const endPoint = endPtsOfContours[i];
  311. // contours might have implicit points, which is located in the middle
  312. // between two neighboring off-curve points
  313. const contour = points.slice(startPoint, endPoint + 1);
  314. if (contour[0].flags & 1) {
  315. contour.push(contour[0]); // using start point at the contour end
  316. } else if (contour.at(-1).flags & 1) {
  317. // first is off-curve point, trying to use one from the end
  318. contour.unshift(contour.at(-1));
  319. } else {
  320. // start and end are off-curve points, creating implicit one
  321. const p = {
  322. flags: 1,
  323. x: (contour[0].x + contour.at(-1).x) / 2,
  324. y: (contour[0].y + contour.at(-1).y) / 2,
  325. };
  326. contour.unshift(p);
  327. contour.push(p);
  328. }
  329. moveTo(contour[0].x, contour[0].y);
  330. for (j = 1, jj = contour.length; j < jj; j++) {
  331. if (contour[j].flags & 1) {
  332. lineTo(contour[j].x, contour[j].y);
  333. } else if (contour[j + 1].flags & 1) {
  334. quadraticCurveTo(
  335. contour[j].x,
  336. contour[j].y,
  337. contour[j + 1].x,
  338. contour[j + 1].y
  339. );
  340. j++;
  341. } else {
  342. quadraticCurveTo(
  343. contour[j].x,
  344. contour[j].y,
  345. (contour[j].x + contour[j + 1].x) / 2,
  346. (contour[j].y + contour[j + 1].y) / 2
  347. );
  348. }
  349. }
  350. startPoint = endPoint + 1;
  351. }
  352. }
  353. }
  354. function compileCharString(charStringCode, cmds, font, glyphId) {
  355. function moveTo(x, y) {
  356. cmds.push({ cmd: "moveTo", args: [x, y] });
  357. }
  358. function lineTo(x, y) {
  359. cmds.push({ cmd: "lineTo", args: [x, y] });
  360. }
  361. function bezierCurveTo(x1, y1, x2, y2, x, y) {
  362. cmds.push({ cmd: "bezierCurveTo", args: [x1, y1, x2, y2, x, y] });
  363. }
  364. const stack = [];
  365. let x = 0,
  366. y = 0;
  367. let stems = 0;
  368. function parse(code) {
  369. let i = 0;
  370. while (i < code.length) {
  371. let stackClean = false;
  372. let v = code[i++];
  373. let xa, xb, ya, yb, y1, y2, y3, n, subrCode;
  374. switch (v) {
  375. case 1: // hstem
  376. stems += stack.length >> 1;
  377. stackClean = true;
  378. break;
  379. case 3: // vstem
  380. stems += stack.length >> 1;
  381. stackClean = true;
  382. break;
  383. case 4: // vmoveto
  384. y += stack.pop();
  385. moveTo(x, y);
  386. stackClean = true;
  387. break;
  388. case 5: // rlineto
  389. while (stack.length > 0) {
  390. x += stack.shift();
  391. y += stack.shift();
  392. lineTo(x, y);
  393. }
  394. break;
  395. case 6: // hlineto
  396. while (stack.length > 0) {
  397. x += stack.shift();
  398. lineTo(x, y);
  399. if (stack.length === 0) {
  400. break;
  401. }
  402. y += stack.shift();
  403. lineTo(x, y);
  404. }
  405. break;
  406. case 7: // vlineto
  407. while (stack.length > 0) {
  408. y += stack.shift();
  409. lineTo(x, y);
  410. if (stack.length === 0) {
  411. break;
  412. }
  413. x += stack.shift();
  414. lineTo(x, y);
  415. }
  416. break;
  417. case 8: // rrcurveto
  418. while (stack.length > 0) {
  419. xa = x + stack.shift();
  420. ya = y + stack.shift();
  421. xb = xa + stack.shift();
  422. yb = ya + stack.shift();
  423. x = xb + stack.shift();
  424. y = yb + stack.shift();
  425. bezierCurveTo(xa, ya, xb, yb, x, y);
  426. }
  427. break;
  428. case 10: // callsubr
  429. n = stack.pop();
  430. subrCode = null;
  431. if (font.isCFFCIDFont) {
  432. const fdIndex = font.fdSelect.getFDIndex(glyphId);
  433. if (fdIndex >= 0 && fdIndex < font.fdArray.length) {
  434. const fontDict = font.fdArray[fdIndex];
  435. let subrs;
  436. if (fontDict.privateDict && fontDict.privateDict.subrsIndex) {
  437. subrs = fontDict.privateDict.subrsIndex.objects;
  438. }
  439. if (subrs) {
  440. // Add subroutine bias.
  441. n += getSubroutineBias(subrs);
  442. subrCode = subrs[n];
  443. }
  444. } else {
  445. warn("Invalid fd index for glyph index.");
  446. }
  447. } else {
  448. subrCode = font.subrs[n + font.subrsBias];
  449. }
  450. if (subrCode) {
  451. parse(subrCode);
  452. }
  453. break;
  454. case 11: // return
  455. return;
  456. case 12:
  457. v = code[i++];
  458. switch (v) {
  459. case 34: // flex
  460. xa = x + stack.shift();
  461. xb = xa + stack.shift();
  462. y1 = y + stack.shift();
  463. x = xb + stack.shift();
  464. bezierCurveTo(xa, y, xb, y1, x, y1);
  465. xa = x + stack.shift();
  466. xb = xa + stack.shift();
  467. x = xb + stack.shift();
  468. bezierCurveTo(xa, y1, xb, y, x, y);
  469. break;
  470. case 35: // flex
  471. xa = x + stack.shift();
  472. ya = y + stack.shift();
  473. xb = xa + stack.shift();
  474. yb = ya + stack.shift();
  475. x = xb + stack.shift();
  476. y = yb + stack.shift();
  477. bezierCurveTo(xa, ya, xb, yb, x, y);
  478. xa = x + stack.shift();
  479. ya = y + stack.shift();
  480. xb = xa + stack.shift();
  481. yb = ya + stack.shift();
  482. x = xb + stack.shift();
  483. y = yb + stack.shift();
  484. bezierCurveTo(xa, ya, xb, yb, x, y);
  485. stack.pop(); // fd
  486. break;
  487. case 36: // hflex1
  488. xa = x + stack.shift();
  489. y1 = y + stack.shift();
  490. xb = xa + stack.shift();
  491. y2 = y1 + stack.shift();
  492. x = xb + stack.shift();
  493. bezierCurveTo(xa, y1, xb, y2, x, y2);
  494. xa = x + stack.shift();
  495. xb = xa + stack.shift();
  496. y3 = y2 + stack.shift();
  497. x = xb + stack.shift();
  498. bezierCurveTo(xa, y2, xb, y3, x, y);
  499. break;
  500. case 37: // flex1
  501. const x0 = x,
  502. y0 = y;
  503. xa = x + stack.shift();
  504. ya = y + stack.shift();
  505. xb = xa + stack.shift();
  506. yb = ya + stack.shift();
  507. x = xb + stack.shift();
  508. y = yb + stack.shift();
  509. bezierCurveTo(xa, ya, xb, yb, x, y);
  510. xa = x + stack.shift();
  511. ya = y + stack.shift();
  512. xb = xa + stack.shift();
  513. yb = ya + stack.shift();
  514. x = xb;
  515. y = yb;
  516. if (Math.abs(x - x0) > Math.abs(y - y0)) {
  517. x += stack.shift();
  518. } else {
  519. y += stack.shift();
  520. }
  521. bezierCurveTo(xa, ya, xb, yb, x, y);
  522. break;
  523. default:
  524. throw new FormatError(`unknown operator: 12 ${v}`);
  525. }
  526. break;
  527. case 14: // endchar
  528. if (stack.length >= 4) {
  529. const achar = stack.pop();
  530. const bchar = stack.pop();
  531. y = stack.pop();
  532. x = stack.pop();
  533. cmds.push({ cmd: "save" }, { cmd: "translate", args: [x, y] });
  534. let cmap = lookupCmap(
  535. font.cmap,
  536. String.fromCharCode(font.glyphNameMap[StandardEncoding[achar]])
  537. );
  538. compileCharString(
  539. font.glyphs[cmap.glyphId],
  540. cmds,
  541. font,
  542. cmap.glyphId
  543. );
  544. cmds.push({ cmd: "restore" });
  545. cmap = lookupCmap(
  546. font.cmap,
  547. String.fromCharCode(font.glyphNameMap[StandardEncoding[bchar]])
  548. );
  549. compileCharString(
  550. font.glyphs[cmap.glyphId],
  551. cmds,
  552. font,
  553. cmap.glyphId
  554. );
  555. }
  556. return;
  557. case 18: // hstemhm
  558. stems += stack.length >> 1;
  559. stackClean = true;
  560. break;
  561. case 19: // hintmask
  562. stems += stack.length >> 1;
  563. i += (stems + 7) >> 3;
  564. stackClean = true;
  565. break;
  566. case 20: // cntrmask
  567. stems += stack.length >> 1;
  568. i += (stems + 7) >> 3;
  569. stackClean = true;
  570. break;
  571. case 21: // rmoveto
  572. y += stack.pop();
  573. x += stack.pop();
  574. moveTo(x, y);
  575. stackClean = true;
  576. break;
  577. case 22: // hmoveto
  578. x += stack.pop();
  579. moveTo(x, y);
  580. stackClean = true;
  581. break;
  582. case 23: // vstemhm
  583. stems += stack.length >> 1;
  584. stackClean = true;
  585. break;
  586. case 24: // rcurveline
  587. while (stack.length > 2) {
  588. xa = x + stack.shift();
  589. ya = y + stack.shift();
  590. xb = xa + stack.shift();
  591. yb = ya + stack.shift();
  592. x = xb + stack.shift();
  593. y = yb + stack.shift();
  594. bezierCurveTo(xa, ya, xb, yb, x, y);
  595. }
  596. x += stack.shift();
  597. y += stack.shift();
  598. lineTo(x, y);
  599. break;
  600. case 25: // rlinecurve
  601. while (stack.length > 6) {
  602. x += stack.shift();
  603. y += stack.shift();
  604. lineTo(x, y);
  605. }
  606. xa = x + stack.shift();
  607. ya = y + stack.shift();
  608. xb = xa + stack.shift();
  609. yb = ya + stack.shift();
  610. x = xb + stack.shift();
  611. y = yb + stack.shift();
  612. bezierCurveTo(xa, ya, xb, yb, x, y);
  613. break;
  614. case 26: // vvcurveto
  615. if (stack.length % 2) {
  616. x += stack.shift();
  617. }
  618. while (stack.length > 0) {
  619. xa = x;
  620. ya = y + stack.shift();
  621. xb = xa + stack.shift();
  622. yb = ya + stack.shift();
  623. x = xb;
  624. y = yb + stack.shift();
  625. bezierCurveTo(xa, ya, xb, yb, x, y);
  626. }
  627. break;
  628. case 27: // hhcurveto
  629. if (stack.length % 2) {
  630. y += stack.shift();
  631. }
  632. while (stack.length > 0) {
  633. xa = x + stack.shift();
  634. ya = y;
  635. xb = xa + stack.shift();
  636. yb = ya + stack.shift();
  637. x = xb + stack.shift();
  638. y = yb;
  639. bezierCurveTo(xa, ya, xb, yb, x, y);
  640. }
  641. break;
  642. case 28:
  643. stack.push(((code[i] << 24) | (code[i + 1] << 16)) >> 16);
  644. i += 2;
  645. break;
  646. case 29: // callgsubr
  647. n = stack.pop() + font.gsubrsBias;
  648. subrCode = font.gsubrs[n];
  649. if (subrCode) {
  650. parse(subrCode);
  651. }
  652. break;
  653. case 30: // vhcurveto
  654. while (stack.length > 0) {
  655. xa = x;
  656. ya = y + stack.shift();
  657. xb = xa + stack.shift();
  658. yb = ya + stack.shift();
  659. x = xb + stack.shift();
  660. y = yb + (stack.length === 1 ? stack.shift() : 0);
  661. bezierCurveTo(xa, ya, xb, yb, x, y);
  662. if (stack.length === 0) {
  663. break;
  664. }
  665. xa = x + stack.shift();
  666. ya = y;
  667. xb = xa + stack.shift();
  668. yb = ya + stack.shift();
  669. y = yb + stack.shift();
  670. x = xb + (stack.length === 1 ? stack.shift() : 0);
  671. bezierCurveTo(xa, ya, xb, yb, x, y);
  672. }
  673. break;
  674. case 31: // hvcurveto
  675. while (stack.length > 0) {
  676. xa = x + stack.shift();
  677. ya = y;
  678. xb = xa + stack.shift();
  679. yb = ya + stack.shift();
  680. y = yb + stack.shift();
  681. x = xb + (stack.length === 1 ? stack.shift() : 0);
  682. bezierCurveTo(xa, ya, xb, yb, x, y);
  683. if (stack.length === 0) {
  684. break;
  685. }
  686. xa = x;
  687. ya = y + stack.shift();
  688. xb = xa + stack.shift();
  689. yb = ya + stack.shift();
  690. x = xb + stack.shift();
  691. y = yb + (stack.length === 1 ? stack.shift() : 0);
  692. bezierCurveTo(xa, ya, xb, yb, x, y);
  693. }
  694. break;
  695. default:
  696. if (v < 32) {
  697. throw new FormatError(`unknown operator: ${v}`);
  698. }
  699. if (v < 247) {
  700. stack.push(v - 139);
  701. } else if (v < 251) {
  702. stack.push((v - 247) * 256 + code[i++] + 108);
  703. } else if (v < 255) {
  704. stack.push(-(v - 251) * 256 - code[i++] - 108);
  705. } else {
  706. stack.push(
  707. ((code[i] << 24) |
  708. (code[i + 1] << 16) |
  709. (code[i + 2] << 8) |
  710. code[i + 3]) /
  711. 65536
  712. );
  713. i += 4;
  714. }
  715. break;
  716. }
  717. if (stackClean) {
  718. stack.length = 0;
  719. }
  720. }
  721. }
  722. parse(charStringCode);
  723. }
  724. const NOOP = [];
  725. class CompiledFont {
  726. constructor(fontMatrix) {
  727. if (this.constructor === CompiledFont) {
  728. unreachable("Cannot initialize CompiledFont.");
  729. }
  730. this.fontMatrix = fontMatrix;
  731. this.compiledGlyphs = Object.create(null);
  732. this.compiledCharCodeToGlyphId = Object.create(null);
  733. }
  734. getPathJs(unicode) {
  735. const { charCode, glyphId } = lookupCmap(this.cmap, unicode);
  736. let fn = this.compiledGlyphs[glyphId];
  737. if (!fn) {
  738. try {
  739. fn = this.compileGlyph(this.glyphs[glyphId], glyphId);
  740. this.compiledGlyphs[glyphId] = fn;
  741. } catch (ex) {
  742. // Avoid attempting to re-compile a corrupt glyph.
  743. this.compiledGlyphs[glyphId] = NOOP;
  744. if (this.compiledCharCodeToGlyphId[charCode] === undefined) {
  745. this.compiledCharCodeToGlyphId[charCode] = glyphId;
  746. }
  747. throw ex;
  748. }
  749. }
  750. if (this.compiledCharCodeToGlyphId[charCode] === undefined) {
  751. this.compiledCharCodeToGlyphId[charCode] = glyphId;
  752. }
  753. return fn;
  754. }
  755. compileGlyph(code, glyphId) {
  756. if (!code || code.length === 0 || code[0] === 14) {
  757. return NOOP;
  758. }
  759. let fontMatrix = this.fontMatrix;
  760. if (this.isCFFCIDFont) {
  761. // Top DICT's FontMatrix can be ignored because CFFCompiler always
  762. // removes it and copies to FDArray DICTs.
  763. const fdIndex = this.fdSelect.getFDIndex(glyphId);
  764. if (fdIndex >= 0 && fdIndex < this.fdArray.length) {
  765. const fontDict = this.fdArray[fdIndex];
  766. fontMatrix = fontDict.getByName("FontMatrix") || FONT_IDENTITY_MATRIX;
  767. } else {
  768. warn("Invalid fd index for glyph index.");
  769. }
  770. }
  771. const cmds = [
  772. { cmd: "save" },
  773. { cmd: "transform", args: fontMatrix.slice() },
  774. { cmd: "scale", args: ["size", "-size"] },
  775. ];
  776. this.compileGlyphImpl(code, cmds, glyphId);
  777. cmds.push({ cmd: "restore" });
  778. return cmds;
  779. }
  780. compileGlyphImpl() {
  781. unreachable("Children classes should implement this.");
  782. }
  783. hasBuiltPath(unicode) {
  784. const { charCode, glyphId } = lookupCmap(this.cmap, unicode);
  785. return (
  786. this.compiledGlyphs[glyphId] !== undefined &&
  787. this.compiledCharCodeToGlyphId[charCode] !== undefined
  788. );
  789. }
  790. }
  791. class TrueTypeCompiled extends CompiledFont {
  792. constructor(glyphs, cmap, fontMatrix) {
  793. super(fontMatrix || [0.000488, 0, 0, 0.000488, 0, 0]);
  794. this.glyphs = glyphs;
  795. this.cmap = cmap;
  796. }
  797. compileGlyphImpl(code, cmds) {
  798. compileGlyf(code, cmds, this);
  799. }
  800. }
  801. class Type2Compiled extends CompiledFont {
  802. constructor(cffInfo, cmap, fontMatrix, glyphNameMap) {
  803. super(fontMatrix || [0.001, 0, 0, 0.001, 0, 0]);
  804. this.glyphs = cffInfo.glyphs;
  805. this.gsubrs = cffInfo.gsubrs || [];
  806. this.subrs = cffInfo.subrs || [];
  807. this.cmap = cmap;
  808. this.glyphNameMap = glyphNameMap || getGlyphsUnicode();
  809. this.gsubrsBias = getSubroutineBias(this.gsubrs);
  810. this.subrsBias = getSubroutineBias(this.subrs);
  811. this.isCFFCIDFont = cffInfo.isCFFCIDFont;
  812. this.fdSelect = cffInfo.fdSelect;
  813. this.fdArray = cffInfo.fdArray;
  814. }
  815. compileGlyphImpl(code, cmds, glyphId) {
  816. compileCharString(code, cmds, this, glyphId);
  817. }
  818. }
  819. class FontRendererFactory {
  820. static create(font, seacAnalysisEnabled) {
  821. const data = new Uint8Array(font.data);
  822. let cmap, glyf, loca, cff, indexToLocFormat, unitsPerEm;
  823. const numTables = getUint16(data, 4);
  824. for (let i = 0, p = 12; i < numTables; i++, p += 16) {
  825. const tag = bytesToString(data.subarray(p, p + 4));
  826. const offset = getUint32(data, p + 8);
  827. const length = getUint32(data, p + 12);
  828. switch (tag) {
  829. case "cmap":
  830. cmap = parseCmap(data, offset, offset + length);
  831. break;
  832. case "glyf":
  833. glyf = data.subarray(offset, offset + length);
  834. break;
  835. case "loca":
  836. loca = data.subarray(offset, offset + length);
  837. break;
  838. case "head":
  839. unitsPerEm = getUint16(data, offset + 18);
  840. indexToLocFormat = getUint16(data, offset + 50);
  841. break;
  842. case "CFF ":
  843. cff = parseCff(data, offset, offset + length, seacAnalysisEnabled);
  844. break;
  845. }
  846. }
  847. if (glyf) {
  848. const fontMatrix = !unitsPerEm
  849. ? font.fontMatrix
  850. : [1 / unitsPerEm, 0, 0, 1 / unitsPerEm, 0, 0];
  851. return new TrueTypeCompiled(
  852. parseGlyfTable(glyf, loca, indexToLocFormat),
  853. cmap,
  854. fontMatrix
  855. );
  856. }
  857. return new Type2Compiled(cff, cmap, font.fontMatrix, font.glyphNameMap);
  858. }
  859. }
  860. export { FontRendererFactory };