reamkit 1.22.0 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/esm/core/converter/ream.d.ts +16 -0
  2. package/dist/esm/core/converter/ream.js +59 -9
  3. package/dist/esm/core/document-model/types.d.ts +18 -0
  4. package/dist/esm/core/drawingml/chart-geometry.js +11 -5
  5. package/dist/esm/core/drawingml/colors.d.ts +4 -2
  6. package/dist/esm/core/drawingml/colors.js +15 -0
  7. package/dist/esm/core/drawingml/theme-parser.d.ts +38 -0
  8. package/dist/esm/core/drawingml/theme-parser.js +64 -1
  9. package/dist/esm/core/fonts/families.d.ts +10 -0
  10. package/dist/esm/core/fonts/families.js +44 -0
  11. package/dist/esm/core/fonts/index.d.ts +3 -2
  12. package/dist/esm/core/fonts/remote-fonts.d.ts +50 -0
  13. package/dist/esm/core/fonts/remote-fonts.js +114 -8
  14. package/dist/esm/core/fonts/scripts.d.ts +26 -0
  15. package/dist/esm/core/fonts/scripts.js +177 -0
  16. package/dist/esm/core/metafile/blit.d.ts +33 -0
  17. package/dist/esm/core/metafile/blit.js +67 -0
  18. package/dist/esm/core/metafile/dib.d.ts +61 -0
  19. package/dist/esm/core/metafile/dib.js +205 -0
  20. package/dist/esm/core/metafile/emf.js +137 -23
  21. package/dist/esm/core/metafile/picture.d.ts +12 -1
  22. package/dist/esm/core/metafile/wmf.js +149 -9
  23. package/dist/esm/core/ole/escher-blip.d.ts +12 -0
  24. package/dist/esm/core/ole/escher-blip.js +88 -0
  25. package/dist/esm/{pdf-reader → core}/png-encode.js +1 -1
  26. package/dist/esm/excel/print-model.js +3 -1
  27. package/dist/esm/excel/styles-parser.js +1 -1
  28. package/dist/esm/excel/xls/biff-reader.js +53 -18
  29. package/dist/esm/excel/xls/biff-styles.d.ts +5 -2
  30. package/dist/esm/excel/xls/biff-styles.js +9 -7
  31. package/dist/esm/excel/xls/escher.js +90 -24
  32. package/dist/esm/layout/page-doc.d.ts +39 -0
  33. package/dist/esm/layout/styled-layout.d.ts +2 -2
  34. package/dist/esm/layout/styled-layout.js +301 -35
  35. package/dist/esm/pdf/styled-page-emitter.js +35 -2
  36. package/dist/esm/pdf-reader/image-decode.js +1 -1
  37. package/dist/esm/pptx/embedded-fonts.d.ts +31 -0
  38. package/dist/esm/pptx/embedded-fonts.js +104 -0
  39. package/dist/esm/pptx/placeholder-cascade.d.ts +73 -6
  40. package/dist/esm/pptx/placeholder-cascade.js +114 -18
  41. package/dist/esm/pptx/ppt/ppt-reader.js +120 -10
  42. package/dist/esm/pptx/ppt/ppt-text.d.ts +66 -1
  43. package/dist/esm/pptx/ppt/ppt-text.js +576 -137
  44. package/dist/esm/pptx/pptx-reader.d.ts +8 -0
  45. package/dist/esm/pptx/pptx-reader.js +27 -5
  46. package/dist/esm/pptx/preset-table-styles.d.ts +9 -0
  47. package/dist/esm/pptx/preset-table-styles.js +27 -0
  48. package/dist/esm/pptx/slide-parser.d.ts +7 -1
  49. package/dist/esm/pptx/slide-parser.js +106 -33
  50. package/dist/esm/pptx/sp-helpers.d.ts +2 -1
  51. package/dist/esm/pptx/sp-helpers.js +3 -2
  52. package/dist/esm/pptx/table-style.d.ts +9 -1
  53. package/dist/esm/pptx/table-style.js +49 -7
  54. package/dist/esm/word/document-parser.d.ts +7 -1
  55. package/dist/esm/word/document-parser.js +6 -6
  56. package/dist/esm/word/docx-reader.js +6 -3
  57. package/dist/esm/word/docx-to-pdf.d.ts +8 -7
  58. package/dist/esm/word/docx-to-pdf.js +25 -7
  59. package/dist/esm/word/drawing-parser.js +31 -19
  60. package/dist/esm/word/numbering-parser.d.ts +2 -1
  61. package/dist/esm/word/numbering-parser.js +6 -6
  62. package/dist/esm/word/paragraph-properties.d.ts +2 -1
  63. package/dist/esm/word/paragraph-properties.js +2 -2
  64. package/dist/esm/word/run-properties.d.ts +5 -2
  65. package/dist/esm/word/run-properties.js +13 -8
  66. package/dist/esm/word/styles-parser.d.ts +2 -1
  67. package/dist/esm/word/styles-parser.js +12 -12
  68. package/dist/esm/word/table-parser.js +3 -3
  69. package/dist/esm/word/theme-fonts.d.ts +10 -0
  70. package/dist/esm/word/theme-fonts.js +28 -0
  71. package/package.json +1 -1
  72. /package/dist/esm/{pdf-reader → core}/png-encode.d.ts +0 -0
@@ -1,4 +1,5 @@
1
- import { resolveFamilyKey } from "../core/fonts/remote-fonts.js";
1
+ import { resolveFamilyStyle } from "../core/fonts/remote-fonts.js";
2
+ import { scriptForCodepoint } from "../core/fonts/scripts.js";
2
3
  import { shapeText } from "../core/font/opentype-layout.js";
3
4
  import { createFontMeasure } from "../core/font/measure.js";
4
5
  import { halfPtToPt, pt } from "../core/ir/units.js";
@@ -948,8 +949,9 @@ function layoutBodyElement(el, options, fontResources, imageResources, contentWi
948
949
  if (el.kind === "paragraph") return layoutParagraphBlock(el.paragraph, options, fontResources, imageResources, contentWidth);
949
950
  if (el.kind === "table") return layoutTableBlock(el.table, options, fontResources, imageResources, contentWidth);
950
951
  if (el.kind === "image") {
951
- const meta = el.image.resource ? imageResources?.get(el.image.resource)?.metafile : void 0;
952
- return meta ? layoutMetafileBlock(el.image, meta, options, fontResources, contentWidth, maxHeight, box) : layoutImageBlock(el.image, imageResources, contentWidth, maxHeight, box);
952
+ const res = el.image.resource ? imageResources?.get(el.image.resource) : void 0;
953
+ const meta = res?.metafile;
954
+ return meta ? layoutMetafileBlock(el.image, meta, res.metafileImages, options, fontResources, contentWidth, maxHeight, box) : layoutImageBlock(el.image, imageResources, contentWidth, maxHeight, box);
953
955
  }
954
956
  if (el.kind === "chart") return layoutChartBlock(el.chart, options, fontResources, contentWidth, maxHeight, imageResources);
955
957
  return layoutShapeBlock(el.shape, options, fontResources, imageResources, contentWidth, maxHeight, box);
@@ -1027,9 +1029,9 @@ function layoutShapeBlock(shape, options, fontResources, imageResources, content
1027
1029
  const fillGradient = shape.fill.kind === "gradient" ? shape.fill.gradient : void 0;
1028
1030
  const fillImage = shape.fill.kind === "picture" && shape.fill.imageResource !== void 0 ? imageResources?.get(shape.fill.imageResource) : void 0;
1029
1031
  const fillImageResourceName = fillImage?.resourceName;
1030
- const fillImageTile = shape.fill.kind === "picture" && shape.fill.tiled === true && fillImage?.prepared ? {
1031
- widthPt: fillImage.prepared.widthPx * 72 / 96,
1032
- heightPt: fillImage.prepared.heightPx * 72 / 96
1032
+ const fillImageTile = shape.fill.kind === "picture" && shape.fill.tiled === true && fillImage?.prepared ? shape.fill.tileSizePt ?? {
1033
+ widthPt: fillImage.prepared.widthPx * 72 / 96 * (shape.fill.tileScale?.sx ?? 1),
1034
+ heightPt: fillImage.prepared.heightPx * 72 / 96 * (shape.fill.tileScale?.sy ?? 1)
1033
1035
  } : void 0;
1034
1036
  const fillColorHex = shape.fill.kind === "solid" ? shape.fill.colorHex : fillGradient ? gradientToSolid(fillGradient) : void 0;
1035
1037
  const stroke = buildStroke(shape.line);
@@ -1225,11 +1227,11 @@ function layoutShapeBlock(shape, options, fontResources, imageResources, content
1225
1227
  * frame. So one is turned into the other rather than growing a block kind, a
1226
1228
  * pagination arm and an emitter of its own.
1227
1229
  */
1228
- function layoutMetafileBlock(image, pic, options, fontResources, contentWidth, maxHeight, box) {
1230
+ function layoutMetafileBlock(image, pic, blitted, options, fontResources, contentWidth, maxHeight, box) {
1229
1231
  const placed = layoutImageBlock(image, void 0, contentWidth, maxHeight, box);
1230
1232
  const widthPt = placed.widthPt;
1231
1233
  const heightPt = placed.heightPt;
1232
- const layout = rotateDrawing(metafileDrawing(pic, widthPt, heightPt, options, fontResources, image.colorChange), (image.rotation60k ?? 0) / 6e4, widthPt / 2, heightPt / 2);
1234
+ const layout = rotateDrawing(metafileDrawing(pic, widthPt, heightPt, options, fontResources, image.colorChange, blitted), (image.rotation60k ?? 0) / 6e4, widthPt / 2, heightPt / 2);
1233
1235
  const pp = image.paragraphProperties;
1234
1236
  return {
1235
1237
  kind: "chart",
@@ -1293,7 +1295,16 @@ function rotateDrawing(layout, deg, cx, cy) {
1293
1295
  y: p.y,
1294
1296
  rotationDeg: (t.rotationDeg ?? 0) - deg
1295
1297
  };
1296
- })
1298
+ }),
1299
+ ...layout.images ? { images: layout.images.map((img) => {
1300
+ const c = at(img.x + img.width / 2, img.y + img.height / 2);
1301
+ return {
1302
+ ...img,
1303
+ x: c.x - img.width / 2,
1304
+ y: c.y - img.height / 2,
1305
+ rotationDeg: (img.rotationDeg ?? 0) + deg
1306
+ };
1307
+ }) } : {}
1297
1308
  };
1298
1309
  }
1299
1310
  /**
@@ -1301,7 +1312,7 @@ function rotateDrawing(layout, deg, cx, cy) {
1301
1312
  * `heightPt` one and turned y-up about its bottom-left corner — the frame both
1302
1313
  * a chart block and an inline picture token are drawn in.
1303
1314
  */
1304
- function metafileDrawing(pic, widthPt, heightPt, options, fontResources, colorChange) {
1315
+ function metafileDrawing(pic, widthPt, heightPt, options, fontResources, colorChange, blitted) {
1305
1316
  const recolour = (hex) => {
1306
1317
  if (hex === void 0 || colorChange === void 0) return hex;
1307
1318
  if (hex.toUpperCase() !== colorChange.fromHex.toUpperCase()) return hex;
@@ -1313,8 +1324,26 @@ function metafileDrawing(pic, widthPt, heightPt, options, fontResources, colorCh
1313
1324
  const mapY = (y) => heightPt - (y - pic.top) * sy;
1314
1325
  const shapes = [];
1315
1326
  const texts = [];
1327
+ const images = [];
1316
1328
  let seq = 0;
1329
+ let blit = 0;
1317
1330
  for (const prim of pic.prims) {
1331
+ if (prim.kind === "image") {
1332
+ const resourceName = blitted?.[blit++] ?? "";
1333
+ if (resourceName === "") {
1334
+ seq++;
1335
+ continue;
1336
+ }
1337
+ images.push({
1338
+ seq: seq++,
1339
+ resourceName,
1340
+ x: mapX(prim.x),
1341
+ y: mapY(prim.y + prim.height),
1342
+ width: prim.width * sx,
1343
+ height: prim.height * sy
1344
+ });
1345
+ continue;
1346
+ }
1318
1347
  if (prim.kind === "path") {
1319
1348
  const fill = recolour(prim.fillColorHex);
1320
1349
  const strokeHex = recolour(prim.stroke?.colorHex);
@@ -1349,7 +1378,6 @@ function metafileDrawing(pic, widthPt, heightPt, options, fontResources, colorCh
1349
1378
  });
1350
1379
  continue;
1351
1380
  }
1352
- if (prim.kind !== "text") continue;
1353
1381
  const { variant } = options.registry.resolveByStyle(prim.bold === true, prim.italic === true);
1354
1382
  const font = fontResources.get(variant);
1355
1383
  if (!font) continue;
@@ -1372,7 +1400,8 @@ function metafileDrawing(pic, widthPt, heightPt, options, fontResources, colorCh
1372
1400
  }
1373
1401
  return {
1374
1402
  shapes,
1375
- texts
1403
+ texts,
1404
+ ...images.length > 0 ? { images } : {}
1376
1405
  };
1377
1406
  }
1378
1407
  function layoutChartBlock(block, options, fontResources, contentWidth, maxHeight, imageResources) {
@@ -1699,9 +1728,29 @@ function collectImageResources(body, headersFooters, options) {
1699
1728
  if (!bytes) continue;
1700
1729
  if (isEmf(bytes) || isWmf(bytes)) {
1701
1730
  try {
1731
+ const metafile = isEmf(bytes) ? readEmf(bytes) : readWmf(bytes);
1732
+ const metafileImages = [];
1733
+ for (const prim of metafile.prims) {
1734
+ if (prim.kind !== "image") continue;
1735
+ let blitted;
1736
+ try {
1737
+ blitted = prepareImage(prim.png, { flattenAlpha });
1738
+ } catch {
1739
+ metafileImages.push("");
1740
+ continue;
1741
+ }
1742
+ counter++;
1743
+ const name = `Im${counter}`;
1744
+ metafileImages.push(name);
1745
+ out.set(`${resourceId}#${String(metafileImages.length)}`, {
1746
+ resourceName: name,
1747
+ prepared: blitted
1748
+ });
1749
+ }
1702
1750
  out.set(resourceId, {
1703
1751
  resourceName: "",
1704
- metafile: isEmf(bytes) ? readEmf(bytes) : readWmf(bytes)
1752
+ metafile,
1753
+ ...metafileImages.length > 0 ? { metafileImages } : {}
1705
1754
  });
1706
1755
  } catch {}
1707
1756
  continue;
@@ -1722,7 +1771,7 @@ function collectImageResources(body, headersFooters, options) {
1722
1771
  }
1723
1772
  function chartPageItems(laid, x, bottomYUp, pageHeight, structId) {
1724
1773
  const fig = structId !== void 0 ? { structId } : {};
1725
- const ordered = laid.layout.shapes.some((sh) => sh.seq !== void 0);
1774
+ const ordered = laid.layout.shapes.some((sh) => sh.seq !== void 0) || (laid.layout.images?.length ?? 0) > 0;
1726
1775
  const picture = ordered ? { pictureId: nextPictureId++ } : {};
1727
1776
  const backdrop = [];
1728
1777
  if (laid.background) {
@@ -1791,7 +1840,23 @@ function chartPageItems(laid, x, bottomYUp, pageHeight, structId) {
1791
1840
  ...picture,
1792
1841
  seq: t.seq ?? Number.MAX_SAFE_INTEGER
1793
1842
  }));
1794
- const all = [...shapes, ...texts];
1843
+ const blits = (laid.layout.images ?? []).map((img) => ({
1844
+ type: "image",
1845
+ x: pt(x + img.x),
1846
+ y: pt(pageHeight - (bottomYUp + img.y + img.height)),
1847
+ width: pt(img.width),
1848
+ height: pt(img.height),
1849
+ imageResourceName: img.resourceName,
1850
+ ...img.rotationDeg ? { rotationDeg: img.rotationDeg } : {},
1851
+ ...fig,
1852
+ ...picture,
1853
+ seq: img.seq ?? 0
1854
+ }));
1855
+ const all = [
1856
+ ...shapes,
1857
+ ...texts,
1858
+ ...blits
1859
+ ];
1795
1860
  if (ordered) all.sort((a, b) => a.seq - b.seq);
1796
1861
  return [...backdrop, ...all.map(({ seq: _seq, ...item }) => item)];
1797
1862
  }
@@ -2173,24 +2238,197 @@ function runFontKeyAndParsed(options, ascii, bold, italic) {
2173
2238
  }
2174
2239
  const byFamily = options.registriesByFamily;
2175
2240
  if (byFamily && byFamily.size > 0) {
2176
- let key = resolveFamilyKey(ascii);
2241
+ const named = resolveFamilyStyle(ascii);
2242
+ let key = named.key;
2177
2243
  let reg = byFamily.get(key);
2178
2244
  if (!reg) {
2179
2245
  key = byFamily.keys().next().value;
2180
2246
  reg = byFamily.get(key);
2181
2247
  }
2182
- const { variant, parsed } = reg.resolveByStyle(bold, italic);
2248
+ const wantBold = bold || named.bold === true;
2249
+ const wantItalic = italic || named.italic === true;
2250
+ const { variant, parsed } = reg.resolveByStyle(wantBold, wantItalic);
2183
2251
  return {
2184
2252
  fontKey: `${key}:${variant}`,
2185
- parsed
2253
+ parsed,
2254
+ ...syntheticFace(variant, wantBold, wantItalic, named.widthScale)
2186
2255
  };
2187
2256
  }
2188
2257
  const { variant, parsed } = options.registry.resolveByStyle(bold, italic);
2189
2258
  return {
2190
2259
  fontKey: variant,
2191
- parsed
2260
+ parsed,
2261
+ ...syntheticFace(variant, bold, italic, resolveFamilyStyle(ascii).widthScale)
2192
2262
  };
2193
2263
  }
2264
+ /**
2265
+ * The face a CHARACTER falls back to when the run's own has no glyph for it.
2266
+ *
2267
+ * The curated substitutes are Latin families: Han, Kana, Hangul, Arabic, Thai
2268
+ * and the geometric symbols are a notdef box in every one of them, and 2145
2269
+ * characters of the corpus are exactly those. A face for the character's
2270
+ * writing system is fetched when the document holds one (see `addScriptFonts`),
2271
+ * and this is where a run reaches it — followed by every other family already
2272
+ * loaded, because a glyph one of them happens to carry is still better than a
2273
+ * box.
2274
+ *
2275
+ * Called from BOTH walks that need it — the glyph-subset collection and the
2276
+ * tokenizer — so the face a character is measured, subset and drawn with is one
2277
+ * decision made once.
2278
+ *
2279
+ * @param options The render options (the loaded registries).
2280
+ * @param cp The code point with no glyph in the run's own face.
2281
+ * @param bold Whether the run is bold…
2282
+ * @param italic …and whether it is italic.
2283
+ * @returns The face to draw it with, or `undefined` when nobody has it.
2284
+ */
2285
+ function fallbackFaceKey(options, cp, bold, italic) {
2286
+ const byFamily = options.registriesByFamily;
2287
+ if (!byFamily || byFamily.size === 0) return void 0;
2288
+ const script = scriptForCodepoint(cp, byFamily.has("jp") ? "jp" : byFamily.has("kr") ? "kr" : "sc");
2289
+ const order = script ? [script] : [];
2290
+ for (const key of byFamily.keys()) if (key !== script) order.push(key);
2291
+ for (const key of order) {
2292
+ const reg = byFamily.get(key);
2293
+ if (!reg) continue;
2294
+ const { variant, parsed } = reg.resolveByStyle(bold, italic);
2295
+ if (parsed.glyphForCodepoint(cp) !== 0) return {
2296
+ fontKey: `${key}:${variant}`,
2297
+ parsed
2298
+ };
2299
+ }
2300
+ }
2301
+ /**
2302
+ * §9.2.2 — the face a request had to settle for, and what is left to draw.
2303
+ *
2304
+ * A registry may hold one face and be asked for four: `FontBytesByVariant`
2305
+ * requires only `regular`, so a caller who supplies a single file (or a script
2306
+ * fallback with no bold cut) had every heading drawn plain. What the file does
2307
+ * not carry, the page draws: a stroke around the glyphs for weight, a shear for
2308
+ * slant — the same two tricks a word processor calls faux bold and faux italic.
2309
+ *
2310
+ * @param variant The face the registry actually gave.
2311
+ * @param bold Whether bold was asked for.
2312
+ * @param italic Whether italic was.
2313
+ * @param widthScale The squeeze a condensed NAME asks for, when it does.
2314
+ * @returns `{ synthetic }` when something is missing, `{}` when nothing is.
2315
+ */
2316
+ function syntheticFace(variant, bold, italic, widthScale) {
2317
+ const missingBold = bold && variant !== "bold" && variant !== "boldItalic";
2318
+ const missingItalic = italic && variant !== "italic" && variant !== "boldItalic";
2319
+ if (!missingBold && !missingItalic && widthScale === void 0) return {};
2320
+ return { synthetic: {
2321
+ ...missingBold ? { bold: true } : {},
2322
+ ...missingItalic ? { italic: true } : {},
2323
+ ...widthScale !== void 0 ? { widthScale } : {}
2324
+ } };
2325
+ }
2326
+ /**
2327
+ * `text` cut into the stretches ONE face draws — the whole string as a single
2328
+ * piece when the run's own face covers it, which is nearly always.
2329
+ *
2330
+ * @param plan The run being tokenized.
2331
+ * @param text The stretch of it to cut.
2332
+ */
2333
+ function splitByFace(plan, text) {
2334
+ const first = faceOf(plan, text.codePointAt(0) ?? 0);
2335
+ if (!plan.faceFor || plan.faceFor.size === 0) return [{
2336
+ text,
2337
+ face: first
2338
+ }];
2339
+ const out = [];
2340
+ let buf = "";
2341
+ let face = first;
2342
+ for (const ch of text) {
2343
+ const next = faceOf(plan, ch.codePointAt(0));
2344
+ if (buf !== "" && next.font !== face.font) {
2345
+ out.push({
2346
+ text: buf,
2347
+ face
2348
+ });
2349
+ buf = "";
2350
+ }
2351
+ face = next;
2352
+ buf += ch;
2353
+ }
2354
+ if (buf !== "") out.push({
2355
+ text: buf,
2356
+ face
2357
+ });
2358
+ return out;
2359
+ }
2360
+ /**
2361
+ * The face a character of a run is drawn with — its own, unless the run carries
2362
+ * a fallback for that code point (see {@link facesForRun}).
2363
+ */
2364
+ function faceOf(plan, cp) {
2365
+ return plan.faceFor?.get(cp) ?? {
2366
+ font: plan.font,
2367
+ ...plan.synthetic ? { synthetic: plan.synthetic } : {}
2368
+ };
2369
+ }
2370
+ /**
2371
+ * `text` cut into the stretches ONE face draws, by the same decision the
2372
+ * tokenizer makes — the whole string as a single span when the run's own face
2373
+ * covers it, which is nearly always.
2374
+ *
2375
+ * @param options The render options (the loaded registries).
2376
+ * @param primary The run's own face.
2377
+ * @param resolved Its resolved properties (weight and slant).
2378
+ * @param text The run's text.
2379
+ */
2380
+ function faceSpans(options, primary, resolved, text) {
2381
+ const out = [];
2382
+ let cur = primary;
2383
+ let buf = "";
2384
+ for (const ch of text) {
2385
+ const cp = ch.codePointAt(0);
2386
+ const face = primary.parsed.glyphForCodepoint(cp) !== 0 ? primary : fallbackFaceKey(options, cp, resolved.bold, resolved.italic) ?? primary;
2387
+ if (buf !== "" && face.fontKey !== cur.fontKey) {
2388
+ out.push({
2389
+ ...cur,
2390
+ text: buf
2391
+ });
2392
+ buf = "";
2393
+ }
2394
+ cur = face;
2395
+ buf += ch;
2396
+ }
2397
+ if (buf !== "") out.push({
2398
+ ...cur,
2399
+ text: buf
2400
+ });
2401
+ return out;
2402
+ }
2403
+ /**
2404
+ * The face for every character of `text` the run's own cannot draw.
2405
+ *
2406
+ * @param options The render options (the loaded registries).
2407
+ * @param fontResources The faces the document embeds, by key.
2408
+ * @param primary The run's own face.
2409
+ * @param resolved Its resolved properties (weight and slant).
2410
+ * @param text The run's text.
2411
+ * @returns A map from code point to face, or `undefined` when the run's own
2412
+ * face draws all of it.
2413
+ */
2414
+ function facesForRun(options, fontResources, primary, resolved, text) {
2415
+ let out;
2416
+ for (const ch of text) {
2417
+ const cp = ch.codePointAt(0);
2418
+ if (out?.has(cp) === true || primary.glyphForCodepoint(cp) !== 0) continue;
2419
+ const fallback = fallbackFaceKey(options, cp, resolved.bold, resolved.italic);
2420
+ if (!fallback) continue;
2421
+ const font = fontResources.get(fallback.fontKey);
2422
+ if (!font) continue;
2423
+ out ??= /* @__PURE__ */ new Map();
2424
+ const { variant } = { variant: fallback.fontKey.split(":")[1] ?? "" };
2425
+ out.set(cp, {
2426
+ font,
2427
+ ...syntheticFace(variant === "bold" || variant === "boldItalic" || variant === "italic" ? variant : "regular", resolved.bold, resolved.italic)
2428
+ });
2429
+ }
2430
+ return out;
2431
+ }
2194
2432
  function lookupFont(resources, fontKey) {
2195
2433
  return resources.get(fontKey) ?? resources.values().next().value;
2196
2434
  }
@@ -2207,8 +2445,22 @@ function collectFontResources(body, headersFooters, options, imageResources) {
2207
2445
  };
2208
2446
  used.set(fontKey, bucket);
2209
2447
  }
2210
- const shaped = shapeText(run.listMarker === true ? markerText(run.text, parsed) : run.text, parsed.glyphForCodepoint, parsed.advanceWidths, parsed.ligatures, parsed.kerning, parsed.joiningForms);
2211
- for (const g of shaped.gids) bucket.gids.add(g);
2448
+ const text = run.listMarker === true ? markerText(run.text, parsed) : run.text;
2449
+ for (const span of faceSpans(options, {
2450
+ fontKey,
2451
+ parsed
2452
+ }, resolved, text)) {
2453
+ let sink = used.get(span.fontKey);
2454
+ if (!sink) {
2455
+ sink = {
2456
+ parsed: span.parsed,
2457
+ gids: /* @__PURE__ */ new Set()
2458
+ };
2459
+ used.set(span.fontKey, sink);
2460
+ }
2461
+ const shaped = shapeText(span.text, span.parsed.glyphForCodepoint, span.parsed.advanceWidths, span.parsed.ligatures, span.parsed.kerning, span.parsed.joiningForms);
2462
+ for (const g of shaped.gids) sink.gids.add(g);
2463
+ }
2212
2464
  if (run.text.includes(" ")) for (const stop of resolveParagraphProperties(para.properties, options.styles).tabs) {
2213
2465
  const ch = stop.leader === void 0 ? void 0 : TAB_LEADER_CHARS.get(stop.leader);
2214
2466
  if (ch !== void 0) bucket.gids.add(parsed.glyphForCodepoint(ch.codePointAt(0)));
@@ -2578,7 +2830,7 @@ function tokenizeParagraph(paragraph, options, fontResources, imageResources, co
2578
2830
  imageWidthPt: widthPt,
2579
2831
  imageHeightPt: heightPt,
2580
2832
  imageResourceName: res?.resourceName ?? "",
2581
- ...res?.metafile ? { imageMetafile: metafileDrawing(res.metafile, drawBox?.widthPt ?? widthPt, drawBox?.heightPt ?? heightPt, options, fontResources) } : {},
2833
+ ...res?.metafile ? { imageMetafile: metafileDrawing(res.metafile, drawBox?.widthPt ?? widthPt, drawBox?.heightPt ?? heightPt, options, fontResources, void 0, res.metafileImages) } : {},
2582
2834
  ...run.inlineImage.outline ? { imageOutline: run.inlineImage.outline } : {},
2583
2835
  ...run.inlineImage.shadow ? { imageShadow: run.inlineImage.shadow } : {},
2584
2836
  ...run.inlineImage.wash ? { imageWash: run.inlineImage.wash } : {},
@@ -2618,12 +2870,16 @@ function tokenizeParagraph(paragraph, options, fontResources, imageResources, co
2618
2870
  };
2619
2871
  }
2620
2872
  const resolvedRun = resolveRunProperties(run.properties, paragraph.properties, options.styles);
2621
- const { fontKey } = runFontKeyAndParsed(options, resolvedRun.fontFamily.ascii, resolvedRun.bold, resolvedRun.italic);
2873
+ const { fontKey, synthetic } = runFontKeyAndParsed(options, resolvedRun.fontFamily.ascii, resolvedRun.bold, resolvedRun.italic);
2622
2874
  const script = SCRIPT_OFFSET[resolvedRun.verticalAlign] ?? 0;
2875
+ const own = lookupFont(fontResources, fontKey);
2876
+ const faceFor = facesForRun(options, fontResources, own.parsed, resolvedRun, run.text);
2623
2877
  return {
2624
2878
  run,
2625
2879
  resolvedRun,
2626
- font: lookupFont(fontResources, fontKey),
2880
+ font: own,
2881
+ ...faceFor ? { faceFor } : {},
2882
+ ...synthetic ? { synthetic } : {},
2627
2883
  fontSizePt: script === 0 ? resolvedRun.fontSizePt : resolvedRun.fontSizePt * SCRIPT_SCALE,
2628
2884
  ...script === 0 ? {} : { risePt: resolvedRun.fontSizePt * script },
2629
2885
  isImage: false,
@@ -2690,8 +2946,8 @@ function tokenizePlansLtr(plans) {
2690
2946
  continue;
2691
2947
  }
2692
2948
  const highlight = (plan.run.commentRangeRefs?.length ?? 0) > 0;
2693
- for (const t of tokenizeText(plan.run.text)) {
2694
- const text = plan.run.listMarker === true ? markerText(t.text, plan.font.parsed) : t.text;
2949
+ for (const t of tokenizeText(plan.run.text)) for (const piece of splitByFace(plan, t.text)) {
2950
+ const text = plan.run.listMarker === true ? markerText(piece.text, plan.font.parsed) : piece.text;
2695
2951
  tokens.push({
2696
2952
  kind: "text",
2697
2953
  text,
@@ -2704,10 +2960,11 @@ function tokenizePlansLtr(plans) {
2704
2960
  ...plan.run.columnBreak ? { columnBreak: true } : {},
2705
2961
  ...highlight ? { highlight: true } : {},
2706
2962
  resolvedRun: plan.resolvedRun,
2707
- font: plan.font,
2963
+ font: piece.face.font,
2964
+ ...piece.face.synthetic ? { synthetic: piece.face.synthetic } : {},
2708
2965
  fontSizePt: plan.fontSizePt,
2709
2966
  ...plan.risePt !== void 0 ? { risePt: plan.risePt } : {},
2710
- widthPt: measureRunText(plan, text),
2967
+ widthPt: measureRunText(plan, text, piece.face),
2711
2968
  bidiLevel: 0
2712
2969
  });
2713
2970
  }
@@ -2747,8 +3004,12 @@ function markerText(text, parsed) {
2747
3004
  * @param text The token's text.
2748
3005
  * @returns The advance width in points.
2749
3006
  */
2750
- function measureRunText(plan, text) {
2751
- const base = plan.font.measure.textWidthPt(text, plan.fontSizePt);
3007
+ function measureRunText(plan, text, face) {
3008
+ const drawn = face ?? {
3009
+ font: plan.font,
3010
+ ...plan.synthetic ? { synthetic: plan.synthetic } : {}
3011
+ };
3012
+ const base = drawn.font.measure.textWidthPt(text, plan.fontSizePt) * (drawn.synthetic?.widthScale ?? 1);
2752
3013
  const extra = plan.resolvedRun.letterSpacingPt;
2753
3014
  return extra === void 0 || extra === 0 ? base : base + extra * [...text].length;
2754
3015
  }
@@ -2795,6 +3056,7 @@ function tokenizePlansBidi(plans, realLevels) {
2795
3056
  let bufStart = 0;
2796
3057
  let curSpace = false;
2797
3058
  let curLevel = -1;
3059
+ let curFace = { font: plan.font };
2798
3060
  const flush = (endExclusive) => {
2799
3061
  if (endExclusive <= bufStart) return;
2800
3062
  const raw = chars.slice(bufStart, endExclusive).join("");
@@ -2810,10 +3072,11 @@ function tokenizePlansBidi(plans, realLevels) {
2810
3072
  ...plan.run.columnBreak ? { columnBreak: true } : {},
2811
3073
  ...(plan.run.commentRangeRefs?.length ?? 0) > 0 ? { highlight: true } : {},
2812
3074
  resolvedRun: plan.resolvedRun,
2813
- font: plan.font,
3075
+ font: curFace.font,
3076
+ ...curFace.synthetic ? { synthetic: curFace.synthetic } : {},
2814
3077
  fontSizePt: plan.fontSizePt,
2815
3078
  ...plan.risePt !== void 0 ? { risePt: plan.risePt } : {},
2816
- widthPt: measureRunText(plan, text),
3079
+ widthPt: measureRunText(plan, text, curFace),
2817
3080
  bidiLevel: curLevel
2818
3081
  });
2819
3082
  };
@@ -2821,14 +3084,17 @@ function tokenizePlansBidi(plans, realLevels) {
2821
3084
  const ch = chars[c];
2822
3085
  const isSpace = /\s/.test(ch);
2823
3086
  const level = realLevels[realIdx] ?? 0;
3087
+ const face = faceOf(plan, ch.codePointAt(0));
2824
3088
  if (c === 0) {
2825
3089
  curSpace = isSpace;
2826
3090
  curLevel = level;
2827
- } else if (isSpace !== curSpace || level !== curLevel) {
3091
+ curFace = face;
3092
+ } else if (isSpace !== curSpace || level !== curLevel || face.font !== curFace.font) {
2828
3093
  flush(c);
2829
3094
  bufStart = c;
2830
3095
  curSpace = isSpace;
2831
3096
  curLevel = level;
3097
+ curFace = face;
2832
3098
  }
2833
3099
  realIdx++;
2834
3100
  }
@@ -3656,10 +3922,10 @@ function measureSingleLine(paragraph, options, fontResources) {
3656
3922
  let total = 0;
3657
3923
  for (const run of paragraph.runs) {
3658
3924
  const resolved = resolveRunProperties(run.properties, paragraph.properties, options.styles);
3659
- const { fontKey } = runFontKeyAndParsed(options, resolved.fontFamily.ascii, resolved.bold, resolved.italic);
3925
+ const { fontKey, synthetic } = runFontKeyAndParsed(options, resolved.fontFamily.ascii, resolved.bold, resolved.italic);
3660
3926
  const font = lookupFont(fontResources, fontKey);
3661
3927
  const fontSizePt = resolved.fontSizePt;
3662
- total += font.measure.textWidthPt(run.text, fontSizePt);
3928
+ total += font.measure.textWidthPt(run.text, fontSizePt) * (synthetic?.widthScale ?? 1);
3663
3929
  }
3664
3930
  return total;
3665
3931
  }
@@ -621,6 +621,7 @@ var BORDER_DASHES = new Map([
621
621
  2.5
622
622
  ]]
623
623
  ]);
624
+ var FAUX_ITALIC_SHEAR = .2126;
624
625
  /** The PDF dash array for a border style; an empty string means a solid rule. */
625
626
  function dashPatternFor(style) {
626
627
  const pattern = style !== void 0 ? BORDER_DASHES.get(style) : void 0;
@@ -642,6 +643,8 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
642
643
  let lastTc = 0;
643
644
  let lastSize = -1;
644
645
  let lastColor = "";
646
+ let lastFauxWidth = 0;
647
+ let lastTz = 100;
645
648
  const switchFontIfNeeded = (tok) => {
646
649
  const fontKey = tok.font.resourceName;
647
650
  if (fontKey !== lastFont || tok.fontSizePt !== lastSize) {
@@ -649,6 +652,16 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
649
652
  lastFont = fontKey;
650
653
  lastSize = tok.fontSizePt;
651
654
  }
655
+ const fauxWidth = tok.synthetic?.bold === true ? tok.fontSizePt * .03 : 0;
656
+ if (fauxWidth !== lastFauxWidth) {
657
+ out.push(fauxWidth > 0 ? `2 Tr ${formatNumber(fauxWidth)} w` : "0 Tr");
658
+ lastFauxWidth = fauxWidth;
659
+ }
660
+ const tz = Math.round((tok.synthetic?.widthScale ?? 1) * 100);
661
+ if (tz !== lastTz) {
662
+ out.push(`${formatNumber(tz)} Tz`);
663
+ lastTz = tz;
664
+ }
652
665
  const tc = tok.resolvedRun.letterSpacingPt ?? 0;
653
666
  if (tc !== lastTc) {
654
667
  out.push(`${formatNumber(tc)} Tc`);
@@ -670,6 +683,12 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
670
683
  const b = tok.drawBox;
671
684
  const ox = x + (b?.dxPt ?? 0);
672
685
  const oy = baselineY + (b?.dyPt ?? 0);
686
+ for (const img of meta.images ?? []) {
687
+ if (img.resourceName === "") continue;
688
+ out.push("q");
689
+ out.push(...placeImage(ox + img.x, oy + img.y, img.width, img.height, void 0, img.rotationDeg));
690
+ out.push(`/${img.resourceName} Do`, "Q");
691
+ }
673
692
  for (const sh of meta.shapes) for (const op of emitVectorShape({
674
693
  ...sh,
675
694
  transform: [
@@ -693,6 +712,8 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
693
712
  out.push("ET");
694
713
  }
695
714
  lastFont = "";
715
+ lastFauxWidth = 0;
716
+ lastTz = 100;
696
717
  lastSize = -1;
697
718
  lastColor = "";
698
719
  return;
@@ -739,6 +760,8 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
739
760
  out.push("Q");
740
761
  }
741
762
  lastFont = "";
763
+ lastFauxWidth = 0;
764
+ lastTz = 100;
742
765
  lastSize = -1;
743
766
  lastColor = "";
744
767
  lastTc = 0;
@@ -771,6 +794,8 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
771
794
  out.push("f");
772
795
  out.push("Q");
773
796
  lastFont = "";
797
+ lastFauxWidth = 0;
798
+ lastTz = 100;
774
799
  lastSize = -1;
775
800
  lastColor = "";
776
801
  } else {
@@ -796,6 +821,8 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
796
821
  };
797
822
  for (const op of emitVectorShape(shape)) out.push(op);
798
823
  lastFont = "";
824
+ lastFauxWidth = 0;
825
+ lastTz = 100;
799
826
  lastSize = -1;
800
827
  lastColor = "";
801
828
  }
@@ -813,6 +840,8 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
813
840
  out.push("q");
814
841
  out.push(`${formatNumber(clip.x)} ${formatNumber(H - clip.y - clip.height)} ${formatNumber(clip.width)} ${formatNumber(clip.height)} re W n`);
815
842
  lastFont = "";
843
+ lastFauxWidth = 0;
844
+ lastTz = 100;
816
845
  lastSize = -1;
817
846
  lastColor = "";
818
847
  lastTc = 0;
@@ -825,6 +854,8 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
825
854
  }
826
855
  out.push("Q");
827
856
  lastFont = "";
857
+ lastFauxWidth = 0;
858
+ lastTz = 100;
828
859
  lastSize = -1;
829
860
  lastColor = "";
830
861
  lastTc = 0;
@@ -977,6 +1008,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
977
1008
  out.push("Q");
978
1009
  }
979
1010
  const hasRise = line.tokens.some((t) => t.kind === "text" && t.risePt !== void 0);
1011
+ const hasFauxItalic = line.tokens.some((t) => t.kind === "text" && t.synthetic?.italic === true);
980
1012
  if (rotationDeg !== 0 && !hasImageToken && !hasMathToken) {
981
1013
  if (!inBT) {
982
1014
  out.push("BT");
@@ -991,7 +1023,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
991
1023
  switchFontIfNeeded(tok);
992
1024
  out.push(tok.font.measure.showText(tok.text));
993
1025
  }
994
- } else if (extraPerSpace !== 0 || hasImageToken || hasMathToken || hasRtl || hasRise || hasTab) {
1026
+ } else if (extraPerSpace !== 0 || hasImageToken || hasMathToken || hasRtl || hasRise || hasFauxItalic || hasTab) {
995
1027
  const order = hasRtl ? lineVisualOrder(line) : line.tokens.map((_, i) => i);
996
1028
  let x = originX;
997
1029
  for (const ti of order) {
@@ -1013,7 +1045,8 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
1013
1045
  inBT = true;
1014
1046
  }
1015
1047
  switchFontIfNeeded(tok);
1016
- out.push(`1 0 0 1 ${formatNumber(x)} ${formatNumber(baselineY + (tok.risePt ?? 0))} Tm`);
1048
+ const slant = tok.synthetic?.italic === true ? FAUX_ITALIC_SHEAR : 0;
1049
+ out.push(`1 0 ${formatNumber(slant)} 1 ${formatNumber(x)} ${formatNumber(baselineY + (tok.risePt ?? 0))} Tm`);
1017
1050
  out.push(showToken(tok));
1018
1051
  const tokenX0 = x;
1019
1052
  x += tok.widthPt;
@@ -1,8 +1,8 @@
1
1
  import { PDF_NULL, PdfHexString, PdfName, PdfStream } from "../pdf/objects.js";
2
2
  import { lzwDecodeMsb } from "../core/lzw.js";
3
+ import { encodePng } from "../core/png-encode.js";
3
4
  import { reversePredictor } from "./predictor.js";
4
5
  import { decodeCcitt } from "./ccitt.js";
5
- import { encodePng } from "./png-encode.js";
6
6
  import { unzlibSync } from "fflate";
7
7
  //#region src/pdf-reader/image-decode.ts
8
8
  var MAX_PIXELS = 4e7;