web-doc 0.11.0 → 0.12.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 (35) hide show
  1. package/dist/adapters/pdf.js +19 -8
  2. package/dist/edit/engine.d.ts +2 -1
  3. package/dist/edit/pdf/engine/document.d.ts +4 -2
  4. package/dist/edit/pdf/engine/document.js +115 -12
  5. package/dist/edit/pdf/engine/elements.d.ts +5 -1
  6. package/dist/edit/pdf/engine/elements.js +88 -6
  7. package/dist/edit/pdf/engine/existing-text.js +16 -61
  8. package/dist/edit/pdf/engine/fonts.d.ts +14 -0
  9. package/dist/edit/pdf/engine/fonts.js +114 -0
  10. package/dist/edit/pdf/engine/handler.js +2 -0
  11. package/dist/edit/pdf/engine/layout.js +1 -1
  12. package/dist/edit/pdf/engine/operations.d.ts +7 -1
  13. package/dist/edit/pdf/engine/paragraph-edit.d.ts +4 -0
  14. package/dist/edit/pdf/engine/paragraph-edit.js +331 -0
  15. package/dist/edit/pdf/engine/paragraph.d.ts +31 -0
  16. package/dist/edit/pdf/engine/paragraph.js +445 -0
  17. package/dist/edit/pdf/engine/pdfium.d.ts +3 -0
  18. package/dist/edit/pdf/engine/text-box.js +16 -3
  19. package/dist/edit/pdf/engine/text-font.js +2 -19
  20. package/dist/edit/pdf/engine/transform.js +14 -3
  21. package/dist/edit/pdf/provider.d.ts +3 -1
  22. package/dist/edit/pdf/provider.js +3 -0
  23. package/dist/edit/pdf/range-map.d.ts +2 -1
  24. package/dist/edit/pdf/range-map.js +38 -8
  25. package/dist/edit/pdf/schemas.d.ts +2 -0
  26. package/dist/edit/pdf/schemas.js +37 -0
  27. package/dist/edit/pdf/session.d.ts +3 -1
  28. package/dist/edit/pdf/session.js +35 -6
  29. package/dist/edit/pdf/types.d.ts +38 -8
  30. package/dist/edit/session.js +5 -0
  31. package/dist/edit/types.d.ts +13 -0
  32. package/dist/viewport.js +11 -0
  33. package/dist/worker-protocol.d.ts +1 -1
  34. package/dist/workers/pdf-edit-worker.js +2019 -1003
  35. package/package.json +1 -1
@@ -3415,8 +3415,8 @@ var init_index_browser = __esm({
3415
3415
  var overGrownHeapSize = oldSize * (1 + 0.2 / cutDown);
3416
3416
  overGrownHeapSize = Math.min(overGrownHeapSize, requestedSize + 100663296);
3417
3417
  var newSize = Math.min(maxHeapSize, alignMemory(Math.max(requestedSize, overGrownHeapSize), 65536));
3418
- var replacement = growMemory(newSize);
3419
- if (replacement) {
3418
+ var replacement2 = growMemory(newSize);
3419
+ if (replacement2) {
3420
3420
  return true;
3421
3421
  }
3422
3422
  }
@@ -5799,6 +5799,15 @@ var pdfOperationSchemas = Object.freeze({
5799
5799
  },
5800
5800
  ["target", "style"]
5801
5801
  ),
5802
+ replaceParagraphText: operation(
5803
+ "replaceParagraphText",
5804
+ {
5805
+ target: { $ref: "#/$defs/target" },
5806
+ text: { type: "string", maxLength: 2e4 },
5807
+ range: { $ref: "#/$defs/textRange" }
5808
+ },
5809
+ ["target", "text"]
5810
+ ),
5802
5811
  resizeElement: operation(
5803
5812
  "resizeElement",
5804
5813
  {
@@ -5990,6 +5999,37 @@ var textBoxMarkSchema = {
5990
5999
  },
5991
6000
  $defs: definitions
5992
6001
  };
6002
+ var paragraphMarkSchema = {
6003
+ type: "object",
6004
+ required: ["kind", "id", "rect", "text", "lines", "style", "baselineOffset"],
6005
+ additionalProperties: false,
6006
+ properties: {
6007
+ kind: { const: "paragraph" },
6008
+ id: { $ref: "#/$defs/target" },
6009
+ rect: { $ref: "#/$defs/rect" },
6010
+ text: { type: "string", maxLength: 2e4 },
6011
+ baselineOffset: { type: "number", minimum: 0, maximum: 2 },
6012
+ lines: {
6013
+ type: "array",
6014
+ maxItems: 2e4,
6015
+ items: { type: "string", maxLength: 2e4 }
6016
+ },
6017
+ style: {
6018
+ type: "object",
6019
+ required: [
6020
+ "fontFamily",
6021
+ "fontSize",
6022
+ "bold",
6023
+ "italic",
6024
+ "color",
6025
+ "lineHeight"
6026
+ ],
6027
+ additionalProperties: false,
6028
+ properties: definitions.textBoxStyle.properties
6029
+ }
6030
+ },
6031
+ $defs: definitions
6032
+ };
5993
6033
 
5994
6034
  // src/edit/schema.ts
5995
6035
  var BASE64 = /^[A-Za-z0-9+/]*={0,2}$/;
@@ -6336,6 +6376,20 @@ var MARK_NAME = "WebDoc";
6336
6376
  var MARK_PARAM = "webdoc";
6337
6377
  var FLAG_ITALIC = 1 << 6;
6338
6378
  var FLAG_FORCE_BOLD = 1 << 18;
6379
+ function paragraphSpecOf(mark) {
6380
+ return validateSchema(mark, paragraphMarkSchema, 0).length === 0 ? mark : void 0;
6381
+ }
6382
+ function paragraphBounds(spec, geometry) {
6383
+ if (geometry.rotation === 0) return spec.rect;
6384
+ const upright = { ...geometry, rotation: 0 };
6385
+ const top = pageToUser(upright, spec.rect.x, spec.rect.y);
6386
+ const bottom = pageToUser(
6387
+ upright,
6388
+ spec.rect.x + spec.rect.width,
6389
+ spec.rect.y + spec.rect.height
6390
+ );
6391
+ return roundRect(userRectToPage(geometry, top.x, bottom.y, bottom.x, top.y));
6392
+ }
6339
6393
  function scanPage(pdfium, page, textPage, pageIndex, geometry, records) {
6340
6394
  const { lib } = pdfium;
6341
6395
  const byObject = /* @__PURE__ */ new Map();
@@ -6368,7 +6422,7 @@ function scanPage(pdfium, page, textPage, pageIndex, geometry, records) {
6368
6422
  elements: elements.map((element) => {
6369
6423
  const members = groups.get(element.id);
6370
6424
  const record = members && records.find((entry) => entry.id === element.id);
6371
- return members && record?.mark ? compositeElement(record.mark, members, pageIndex) : element;
6425
+ return members && record?.mark ? compositeElement(record.mark, members, pageIndex, geometry) : element;
6372
6426
  }),
6373
6427
  byObject
6374
6428
  };
@@ -6403,6 +6457,8 @@ function isValidMark(params) {
6403
6457
  switch (params.kind) {
6404
6458
  case "textBox":
6405
6459
  return validateSchema(params, textBoxMarkSchema, 0).length === 0;
6460
+ case "paragraph":
6461
+ return validateSchema(params, paragraphMarkSchema, 0).length === 0;
6406
6462
  case "table":
6407
6463
  return validateSchema(params, tableMarkSchema, 0).length === 0 || validateSchema(params, tableMemberMarkSchema, 0).length === 0;
6408
6464
  default:
@@ -6419,11 +6475,50 @@ function markIsFresh(pdfium, page, textPage, geometry, mark, indexes) {
6419
6475
  objects.filter((object) => lib.FPDFPageObj_GetType(object) === OBJECT_TEXT).map((object) => textOf(pdfium, object, textPage)).join(" ")
6420
6476
  );
6421
6477
  const tolerance = 2;
6422
- if (mark.kind === "textBox") {
6478
+ if (mark.kind === "paragraph") {
6479
+ const spec = paragraphSpecOf(mark);
6480
+ if (!spec || objects.some((object) => {
6481
+ if (lib.FPDFPageObj_GetType(object) !== OBJECT_TEXT) {
6482
+ if (spec.text.trim().length !== 0 || lib.FPDFPageObj_GetType(object) !== OBJECT_PATH)
6483
+ return true;
6484
+ const mode = pdfium.readNumbers(
6485
+ 2,
6486
+ "i32",
6487
+ ([fill, stroke]) => lib.FPDFPath_GetDrawMode(object, fill, stroke)
6488
+ );
6489
+ const color = pdfium.readNumbers(
6490
+ 4,
6491
+ "i32",
6492
+ ([r, g, b, a]) => lib.FPDFPageObj_GetFillColor(object, r, g, b, a)
6493
+ );
6494
+ return mode?.[0] !== 1 || mode[1] !== 0 || color?.[3] !== 0;
6495
+ }
6496
+ const current = textStyle(pdfium, object);
6497
+ return current.fontSize !== round(spec.style.fontSize) || current.color !== spec.style.color || current.bold !== spec.style.bold || current.italic !== spec.style.italic;
6498
+ }))
6499
+ return false;
6500
+ const frame = paragraphBounds(spec, geometry);
6501
+ if (union.y + union.height > frame.y + frame.height + tolerance)
6502
+ return false;
6503
+ const nativeLines = objects.filter((object) => lib.FPDFPageObj_GetType(object) === OBJECT_TEXT).map((object) => textOf(pdfium, object, textPage).trimEnd());
6504
+ if (nativeLines.length !== spec.lines.length || nativeLines.some((line, index) => line !== spec.lines[index]))
6505
+ return false;
6506
+ let cursor = 0;
6507
+ for (const line of nativeLines) {
6508
+ while (!spec.text.startsWith(line, cursor) && cursor < spec.text.length && /\s/u.test(spec.text[cursor]))
6509
+ cursor += 1;
6510
+ if (!spec.text.startsWith(line, cursor)) return false;
6511
+ cursor += line.length;
6512
+ }
6513
+ if (spec.text.slice(cursor).trim()) return false;
6514
+ if (geometry.rotation !== 0)
6515
+ return union.x >= frame.x - tolerance && union.y >= frame.y - tolerance && union.x + union.width <= frame.x + frame.width + tolerance;
6516
+ }
6517
+ if (mark.kind === "textBox" || mark.kind === "paragraph") {
6423
6518
  const rect = mark.rect;
6424
6519
  const text = mark.text;
6425
6520
  const style2 = mark.style;
6426
- return drawn === normalizeText(text) && union.x >= rect.x - tolerance && union.x + union.width <= rect.x + rect.width + tolerance && // The first line's ink starts inside the box's first line band; the
6521
+ return (mark.kind === "paragraph" || drawn === normalizeText(text)) && union.x >= rect.x - tolerance && union.x + union.width <= rect.x + rect.width + tolerance && // The first line's ink starts inside the box's first line band; the
6427
6522
  // bottom is not checked because overflowing text runs past the box.
6428
6523
  union.y >= rect.y - tolerance && union.y <= rect.y + style2.fontSize * style2.lineHeight + tolerance;
6429
6524
  }
@@ -6479,7 +6574,7 @@ function plainElement(pdfium, object, textPage, record, pageIndex, geometry) {
6479
6574
  return { ...base, kind: "other", operations: TRANSFORM_OPERATIONS };
6480
6575
  }
6481
6576
  }
6482
- function compositeElement(mark, members, pageIndex) {
6577
+ function compositeElement(mark, members, pageIndex, geometry) {
6483
6578
  const bounds = roundRect(unionRects(members.map((member) => member.bounds)));
6484
6579
  const text = members.filter((member) => member.kind === "text" && member.text).map((member) => member.text).join("\n");
6485
6580
  if (mark.kind === "table") {
@@ -6502,6 +6597,23 @@ function compositeElement(mark, members, pageIndex) {
6502
6597
  operations: TABLE_OPERATIONS
6503
6598
  };
6504
6599
  }
6600
+ const paragraph = paragraphSpecOf(mark);
6601
+ if (paragraph)
6602
+ return {
6603
+ id: mark.id,
6604
+ kind: "paragraph",
6605
+ pageIndex,
6606
+ bounds: paragraphBounds(paragraph, geometry),
6607
+ text: paragraph.text,
6608
+ textStyle: paragraph.style,
6609
+ ...geometry.rotation ? { rotation: geometry.rotation * 90 } : {},
6610
+ operations: geometry.rotation ? ["deleteElement"] : [
6611
+ "replaceText",
6612
+ "replaceParagraphText",
6613
+ "setTextStyle",
6614
+ "deleteElement"
6615
+ ]
6616
+ };
6505
6617
  const style = members.find((member) => member.textStyle)?.textStyle;
6506
6618
  return {
6507
6619
  id: mark.id,
@@ -6639,7 +6751,7 @@ function objectRotation(pdfium, object, geometry) {
6639
6751
 
6640
6752
  // src/edit/pdf/engine/layout.ts
6641
6753
  var HIT_TOLERANCE = 2;
6642
- var TEXT_KINDS = /* @__PURE__ */ new Set(["text", "textBox", "table"]);
6754
+ var TEXT_KINDS = /* @__PURE__ */ new Set(["text", "textBox", "table", "paragraph"]);
6643
6755
  function layoutOf(pdfium, scan, element) {
6644
6756
  if (!TEXT_KINDS.has(element.kind)) return void 0;
6645
6757
  const glyphs = glyphsOf(
@@ -6775,10 +6887,10 @@ function linesOf(pdfium, scan, glyphs) {
6775
6887
  text: members.map((glyph) => glyph.char).join(""),
6776
6888
  bounds: roundRect(unionRects(members.map((glyph) => glyph.box))),
6777
6889
  baseline: members[0].origin,
6778
- glyphs: members.map(({ position, box, advance }) => ({
6890
+ glyphs: members.map(({ position, box, advance: advance2 }) => ({
6779
6891
  offset: position.offset,
6780
6892
  box,
6781
- advance
6893
+ advance: advance2
6782
6894
  })),
6783
6895
  fontFamily: style.fontFamily,
6784
6896
  fontSize: style.fontSize,
@@ -6811,893 +6923,989 @@ function roundPoint(point) {
6811
6923
  return { x: round(point.x), y: round(point.y) };
6812
6924
  }
6813
6925
 
6814
- // src/edit/pdf/engine/fonts.ts
6815
- var STANDARD_FAMILIES = {
6816
- helvetica: [
6817
- "Helvetica",
6818
- "Helvetica-Bold",
6819
- "Helvetica-Oblique",
6820
- "Helvetica-BoldOblique"
6821
- ],
6822
- times: ["Times-Roman", "Times-Bold", "Times-Italic", "Times-BoldItalic"],
6823
- courier: [
6824
- "Courier",
6825
- "Courier-Bold",
6826
- "Courier-Oblique",
6827
- "Courier-BoldOblique"
6828
- ]
6829
- };
6830
- var WIN_ANSI_EXTRAS = new Set(
6831
- "\u20AC\u201A\u0192\u201E\u2026\u2020\u2021\u02C6\u2030\u0160\u2039\u0152\u017D\u2018\u2019\u201C\u201D\u2022\u2013\u2014\u02DC\u2122\u0161\u203A\u0153\u017E\u0178".split("").map((c) => c.codePointAt(0))
6926
+ // src/edit/pdf/engine/glyph-names.ts
6927
+ var AGLFN = `
6928
+ A 0041 AE 00C6 AEacute 01FC Aacute 00C1 Abreve 0102 Acircumflex 00C2
6929
+ Adieresis 00C4 Agrave 00C0 Alpha 0391 Alphatonos 0386 Amacron 0100
6930
+ Aogonek 0104 Aring 00C5 Aringacute 01FA Atilde 00C3 B 0042 Beta 0392 C 0043
6931
+ Cacute 0106 Ccaron 010C Ccedilla 00C7 Ccircumflex 0108 Cdotaccent 010A
6932
+ Chi 03A7 D 0044 Dcaron 010E Dcroat 0110 Delta 2206 E 0045 Eacute 00C9
6933
+ Ebreve 0114 Ecaron 011A Ecircumflex 00CA Edieresis 00CB Edotaccent 0116
6934
+ Egrave 00C8 Emacron 0112 Eng 014A Eogonek 0118 Epsilon 0395
6935
+ Epsilontonos 0388 Eta 0397 Etatonos 0389 Eth 00D0 Euro 20AC F 0046 G 0047
6936
+ Gamma 0393 Gbreve 011E Gcaron 01E6 Gcircumflex 011C Gdotaccent 0120 H 0048
6937
+ H18533 25CF H18543 25AA H18551 25AB H22073 25A1 Hbar 0126 Hcircumflex 0124
6938
+ I 0049 IJ 0132 Iacute 00CD Ibreve 012C Icircumflex 00CE Idieresis 00CF
6939
+ Idotaccent 0130 Ifraktur 2111 Igrave 00CC Imacron 012A Iogonek 012E
6940
+ Iota 0399 Iotadieresis 03AA Iotatonos 038A Itilde 0128 J 004A
6941
+ Jcircumflex 0134 K 004B Kappa 039A L 004C Lacute 0139 Lambda 039B
6942
+ Lcaron 013D Ldot 013F Lslash 0141 M 004D Mu 039C N 004E Nacute 0143
6943
+ Ncaron 0147 Ntilde 00D1 Nu 039D O 004F OE 0152 Oacute 00D3 Obreve 014E
6944
+ Ocircumflex 00D4 Odieresis 00D6 Ograve 00D2 Ohorn 01A0 Ohungarumlaut 0150
6945
+ Omacron 014C Omega 2126 Omegatonos 038F Omicron 039F Omicrontonos 038C
6946
+ Oslash 00D8 Oslashacute 01FE Otilde 00D5 P 0050 Phi 03A6 Pi 03A0 Psi 03A8
6947
+ Q 0051 R 0052 Racute 0154 Rcaron 0158 Rfraktur 211C Rho 03A1 S 0053
6948
+ SF010000 250C SF020000 2514 SF030000 2510 SF040000 2518 SF050000 253C
6949
+ SF060000 252C SF070000 2534 SF080000 251C SF090000 2524 SF100000 2500
6950
+ SF110000 2502 SF190000 2561 SF200000 2562 SF210000 2556 SF220000 2555
6951
+ SF230000 2563 SF240000 2551 SF250000 2557 SF260000 255D SF270000 255C
6952
+ SF280000 255B SF360000 255E SF370000 255F SF380000 255A SF390000 2554
6953
+ SF400000 2569 SF410000 2566 SF420000 2560 SF430000 2550 SF440000 256C
6954
+ SF450000 2567 SF460000 2568 SF470000 2564 SF480000 2565 SF490000 2559
6955
+ SF500000 2558 SF510000 2552 SF520000 2553 SF530000 256B SF540000 256A
6956
+ Sacute 015A Scaron 0160 Scedilla 015E Scircumflex 015C Sigma 03A3 T 0054
6957
+ Tau 03A4 Tbar 0166 Tcaron 0164 Theta 0398 Thorn 00DE U 0055 Uacute 00DA
6958
+ Ubreve 016C Ucircumflex 00DB Udieresis 00DC Ugrave 00D9 Uhorn 01AF
6959
+ Uhungarumlaut 0170 Umacron 016A Uogonek 0172 Upsilon 03A5 Upsilon1 03D2
6960
+ Upsilondieresis 03AB Upsilontonos 038E Uring 016E Utilde 0168 V 0056 W 0057
6961
+ Wacute 1E82 Wcircumflex 0174 Wdieresis 1E84 Wgrave 1E80 X 0058 Xi 039E
6962
+ Y 0059 Yacute 00DD Ycircumflex 0176 Ydieresis 0178 Ygrave 1EF2 Z 005A
6963
+ Zacute 0179 Zcaron 017D Zdotaccent 017B Zeta 0396 a 0061 aacute 00E1
6964
+ abreve 0103 acircumflex 00E2 acute 00B4 acutecomb 0301 adieresis 00E4
6965
+ ae 00E6 aeacute 01FD agrave 00E0 aleph 2135 alpha 03B1 alphatonos 03AC
6966
+ amacron 0101 ampersand 0026 angle 2220 angleleft 2329 angleright 232A
6967
+ anoteleia 0387 aogonek 0105 approxequal 2248 aring 00E5 aringacute 01FB
6968
+ arrowboth 2194 arrowdblboth 21D4 arrowdbldown 21D3 arrowdblleft 21D0
6969
+ arrowdblright 21D2 arrowdblup 21D1 arrowdown 2193 arrowleft 2190
6970
+ arrowright 2192 arrowup 2191 arrowupdn 2195 arrowupdnbse 21A8
6971
+ asciicircum 005E asciitilde 007E asterisk 002A asteriskmath 2217 at 0040
6972
+ atilde 00E3 b 0062 backslash 005C bar 007C beta 03B2 block 2588
6973
+ braceleft 007B braceright 007D bracketleft 005B bracketright 005D breve 02D8
6974
+ brokenbar 00A6 bullet 2022 c 0063 cacute 0107 caron 02C7 carriagereturn 21B5
6975
+ ccaron 010D ccedilla 00E7 ccircumflex 0109 cdotaccent 010B cedilla 00B8
6976
+ cent 00A2 chi 03C7 circle 25CB circlemultiply 2297 circleplus 2295
6977
+ circumflex 02C6 club 2663 colon 003A colonmonetary 20A1 comma 002C
6978
+ congruent 2245 copyright 00A9 currency 00A4 d 0064 dagger 2020
6979
+ daggerdbl 2021 dcaron 010F dcroat 0111 degree 00B0 delta 03B4 diamond 2666
6980
+ dieresis 00A8 dieresistonos 0385 divide 00F7 dkshade 2593 dnblock 2584
6981
+ dollar 0024 dong 20AB dotaccent 02D9 dotbelowcomb 0323 dotlessi 0131
6982
+ dotmath 22C5 e 0065 eacute 00E9 ebreve 0115 ecaron 011B ecircumflex 00EA
6983
+ edieresis 00EB edotaccent 0117 egrave 00E8 eight 0038 element 2208
6984
+ ellipsis 2026 emacron 0113 emdash 2014 emptyset 2205 endash 2013 eng 014B
6985
+ eogonek 0119 epsilon 03B5 epsilontonos 03AD equal 003D equivalence 2261
6986
+ estimated 212E eta 03B7 etatonos 03AE eth 00F0 exclam 0021 exclamdbl 203C
6987
+ exclamdown 00A1 existential 2203 f 0066 female 2640 figuredash 2012
6988
+ filledbox 25A0 filledrect 25AC five 0035 fiveeighths 215D florin 0192
6989
+ four 0034 fraction 2044 franc 20A3 g 0067 gamma 03B3 gbreve 011F gcaron 01E7
6990
+ gcircumflex 011D gdotaccent 0121 germandbls 00DF gradient 2207 grave 0060
6991
+ gravecomb 0300 greater 003E greaterequal 2265 guillemotleft 00AB
6992
+ guillemotright 00BB guilsinglleft 2039 guilsinglright 203A h 0068 hbar 0127
6993
+ hcircumflex 0125 heart 2665 hookabovecomb 0309 house 2302 hungarumlaut 02DD
6994
+ hyphen 002D i 0069 iacute 00ED ibreve 012D icircumflex 00EE idieresis 00EF
6995
+ igrave 00EC ij 0133 imacron 012B infinity 221E integral 222B integralbt 2321
6996
+ integraltp 2320 intersection 2229 invbullet 25D8 invcircle 25D9
6997
+ invsmileface 263B iogonek 012F iota 03B9 iotadieresis 03CA
6998
+ iotadieresistonos 0390 iotatonos 03AF itilde 0129 j 006A jcircumflex 0135
6999
+ k 006B kappa 03BA kgreenlandic 0138 l 006C lacute 013A lambda 03BB
7000
+ lcaron 013E ldot 0140 less 003C lessequal 2264 lfblock 258C lira 20A4
7001
+ logicaland 2227 logicalnot 00AC logicalor 2228 longs 017F lozenge 25CA
7002
+ lslash 0142 ltshade 2591 m 006D macron 00AF male 2642 minus 2212 minute 2032
7003
+ mu 00B5 multiply 00D7 musicalnote 266A musicalnotedbl 266B n 006E
7004
+ nacute 0144 napostrophe 0149 ncaron 0148 nine 0039 notelement 2209
7005
+ notequal 2260 notsubset 2284 ntilde 00F1 nu 03BD numbersign 0023 o 006F
7006
+ oacute 00F3 obreve 014F ocircumflex 00F4 odieresis 00F6 oe 0153 ogonek 02DB
7007
+ ograve 00F2 ohorn 01A1 ohungarumlaut 0151 omacron 014D omega 03C9
7008
+ omega1 03D6 omegatonos 03CE omicron 03BF omicrontonos 03CC one 0031
7009
+ onedotenleader 2024 oneeighth 215B onehalf 00BD onequarter 00BC
7010
+ onethird 2153 openbullet 25E6 ordfeminine 00AA ordmasculine 00BA
7011
+ orthogonal 221F oslash 00F8 oslashacute 01FF otilde 00F5 p 0070
7012
+ paragraph 00B6 parenleft 0028 parenright 0029 partialdiff 2202 percent 0025
7013
+ period 002E periodcentered 00B7 perpendicular 22A5 perthousand 2030
7014
+ peseta 20A7 phi 03C6 phi1 03D5 pi 03C0 plus 002B plusminus 00B1
7015
+ prescription 211E product 220F propersubset 2282 propersuperset 2283
7016
+ proportional 221D psi 03C8 q 0071 question 003F questiondown 00BF
7017
+ quotedbl 0022 quotedblbase 201E quotedblleft 201C quotedblright 201D
7018
+ quoteleft 2018 quotereversed 201B quoteright 2019 quotesinglbase 201A
7019
+ quotesingle 0027 r 0072 racute 0155 radical 221A rcaron 0159
7020
+ reflexsubset 2286 reflexsuperset 2287 registered 00AE revlogicalnot 2310
7021
+ rho 03C1 ring 02DA rtblock 2590 s 0073 sacute 015B scaron 0161 scedilla 015F
7022
+ scircumflex 015D second 2033 section 00A7 semicolon 003B seven 0037
7023
+ seveneighths 215E shade 2592 sigma 03C3 sigma1 03C2 similar 223C six 0036
7024
+ slash 002F smileface 263A space 0020 spade 2660 sterling 00A3 suchthat 220B
7025
+ summation 2211 sun 263C t 0074 tau 03C4 tbar 0167 tcaron 0165 therefore 2234
7026
+ theta 03B8 theta1 03D1 thorn 00FE three 0033 threeeighths 215C
7027
+ threequarters 00BE tilde 02DC tildecomb 0303 tonos 0384 trademark 2122
7028
+ triagdn 25BC triaglf 25C4 triagrt 25BA triagup 25B2 two 0032
7029
+ twodotenleader 2025 twothirds 2154 u 0075 uacute 00FA ubreve 016D
7030
+ ucircumflex 00FB udieresis 00FC ugrave 00F9 uhorn 01B0 uhungarumlaut 0171
7031
+ umacron 016B underscore 005F underscoredbl 2017 union 222A universal 2200
7032
+ uogonek 0173 upblock 2580 upsilon 03C5 upsilondieresis 03CB
7033
+ upsilondieresistonos 03B0 upsilontonos 03CD uring 016F utilde 0169 v 0076
7034
+ w 0077 wacute 1E83 wcircumflex 0175 wdieresis 1E85 weierstrass 2118
7035
+ wgrave 1E81 x 0078 xi 03BE y 0079 yacute 00FD ycircumflex 0177
7036
+ ydieresis 00FF yen 00A5 ygrave 1EF3 z 007A zacute 017A zcaron 017E
7037
+ zdotaccent 017C zero 0030 zeta 03B6
7038
+ `;
7039
+ var AGL_EXTRAS = `
7040
+ fi FB01 fl FB02 ff FB00 ffi FB03 ffl FB04 onesuperior 00B9 twosuperior 00B2
7041
+ threesuperior 00B3 nbspace 00A0
7042
+ `;
7043
+ var GLYPH_UNICODE = new Map(
7044
+ `${AGLFN} ${AGL_EXTRAS}`.trim().split(/\s+/).flatMap(
7045
+ (word, index, words) => index % 2 === 0 ? [[word, Number.parseInt(words[index + 1], 16)]] : []
7046
+ )
6832
7047
  );
6833
- var FONT_TRUETYPE = 1;
6834
- var FALLBACK_FAMILY = "Noto Sans";
6835
- function isStandardFamily(family) {
6836
- return family.toLowerCase() in STANDARD_FAMILIES;
6837
- }
6838
- function standardFontName(family, bold, italic) {
6839
- const variants = STANDARD_FAMILIES[family.toLowerCase()];
6840
- if (!variants) return void 0;
6841
- return variants[(bold ? 1 : 0) + (italic ? 2 : 0)];
6842
- }
6843
- function firstNonWinAnsi(text) {
6844
- for (const character of text) {
6845
- const code = character.codePointAt(0);
6846
- if (code === 10) continue;
6847
- if (code >= 32 && code <= 126 || code >= 160 && code <= 255 || WIN_ANSI_EXTRAS.has(code))
6848
- continue;
6849
- return character;
6850
- }
6851
- return void 0;
7048
+
7049
+ // src/edit/pdf/engine/cff.ts
7050
+ var ISO_ADOBE_NAMES = `
7051
+ .notdef space exclam quotedbl numbersign dollar percent ampersand
7052
+ quoteright parenleft parenright asterisk plus comma hyphen period slash
7053
+ zero one two three four five six seven eight nine colon semicolon less
7054
+ equal greater question at A B C D E F G H I J K L M N O P Q R S T U V W X Y
7055
+ Z bracketleft backslash bracketright asciicircum underscore quoteleft a b c
7056
+ d e f g h i j k l m n o p q r s t u v w x y z braceleft bar braceright
7057
+ asciitilde exclamdown cent sterling fraction yen florin section currency
7058
+ quotesingle quotedblleft guillemotleft guilsinglleft guilsinglright fi fl
7059
+ endash dagger daggerdbl periodcentered paragraph bullet quotesinglbase
7060
+ quotedblbase quotedblright guillemotright ellipsis perthousand questiondown
7061
+ grave acute circumflex tilde macron breve dotaccent dieresis ring cedilla
7062
+ hungarumlaut ogonek caron emdash AE ordfeminine Lslash Oslash OE
7063
+ ordmasculine ae dotlessi lslash oslash oe germandbls onesuperior logicalnot
7064
+ mu trademark Eth onehalf plusminus Thorn onequarter divide brokenbar degree
7065
+ thorn threequarters twosuperior registered minus eth multiply threesuperior
7066
+ copyright Aacute Acircumflex Adieresis Agrave Aring Atilde Ccedilla Eacute
7067
+ Ecircumflex Edieresis Egrave Iacute Icircumflex Idieresis Igrave Ntilde
7068
+ Oacute Ocircumflex Odieresis Ograve Otilde Scaron Uacute Ucircumflex
7069
+ Udieresis Ugrave Yacute Ydieresis Zcaron aacute acircumflex adieresis
7070
+ agrave aring atilde ccedilla eacute ecircumflex edieresis egrave iacute
7071
+ icircumflex idieresis igrave ntilde oacute ocircumflex odieresis ograve
7072
+ otilde scaron uacute ucircumflex udieresis ugrave yacute ydieresis zcaron
7073
+ `.trim().split(/\s+/);
7074
+ function glyphUnicode(name) {
7075
+ if (!name || name === ".notdef") return void 0;
7076
+ const known = GLYPH_UNICODE.get(name);
7077
+ if (known !== void 0) return known;
7078
+ const match = /^uni([0-9A-F]{4})$|^u([0-9A-F]{4,6})$/.exec(name);
7079
+ const hex2 = match?.[1] ?? match?.[2];
7080
+ if (!hex2) return void 0;
7081
+ const code = Number.parseInt(hex2, 16);
7082
+ const surrogate = code >= 55296 && code <= 57343;
7083
+ const nonCharacter = (code & 65534) === 65534;
7084
+ return code <= 1114111 && !surrogate && !nonCharacter ? code : void 0;
6852
7085
  }
6853
- var FontLibrary = class {
6854
- #registered = [];
6855
- #fetch;
6856
- #bytes = /* @__PURE__ */ new Map();
6857
- #coverage = /* @__PURE__ */ new Map();
6858
- /** Loaded PDFium font handles, keyed by document and font bytes. */
6859
- #handles = /* @__PURE__ */ new Map();
6860
- #fallbackUrl;
6861
- #fallback;
6862
- constructor(fetchBytes2) {
6863
- this.#fetch = fetchBytes2;
6864
- }
6865
- setFallbackUrl(url) {
6866
- this.#fallbackUrl = url;
6867
- }
6868
- register(fonts) {
6869
- this.#registered.length = 0;
6870
- this.#registered.push(...fonts);
6871
- }
6872
- /** Whether `family` names a registered font. */
6873
- hasFamily(family) {
6874
- return this.#registered.some((font) => sameFamily(font.family, family));
6875
- }
6876
- /**
6877
- * Fetches whatever the given texts and families may need, so that
6878
- * validation and drawing can run synchronously afterwards.
6879
- */
6880
- async prepare(requests) {
6881
- const families = new Set(
6882
- requests.map((request) => request.family.toLowerCase())
6883
- );
6884
- const fonts = this.#registered.filter(
6885
- (font) => families.has(font.family.toLowerCase())
6886
- );
6887
- for (const font of fonts) await this.#bytesOf(font);
6888
- const needsFallback = requests.some(
6889
- (request) => (
6890
- // Text the standard fonts cannot encode, or a family only the
6891
- // fallback can stand in for (an embedded font that lost a glyph).
6892
- (firstNonWinAnsi(request.text) !== void 0 || !isStandardFamily(request.family) && !this.hasFamily(request.family)) && !this.#coveringRegistered({ ...request, bold: false, italic: false })
6893
- )
7086
+ var OP = {
7087
+ fontBBox: 5,
7088
+ charset: 15,
7089
+ charStrings: 17,
7090
+ private: 18,
7091
+ defaultWidthX: 20,
7092
+ nominalWidthX: 21,
7093
+ subrs: 19,
7094
+ charstringType: 1206,
7095
+ fontMatrix: 1207,
7096
+ ros: 1230
7097
+ };
7098
+ var STANDARD_STRINGS = 391;
7099
+ function readCff(bytes2) {
7100
+ try {
7101
+ const at = (offset2) => {
7102
+ if (!Number.isInteger(offset2) || offset2 < 0 || offset2 >= bytes2.length)
7103
+ throw new RangeError("Outside the font");
7104
+ return bytes2[offset2];
7105
+ };
7106
+ const u162 = (offset2) => at(offset2) << 8 | at(offset2 + 1);
7107
+ if (at(0) !== 1) return void 0;
7108
+ const names2 = readIndex(bytes2, at(2), at, u162);
7109
+ const tops = readIndex(bytes2, names2.end, at, u162);
7110
+ const strings = readIndex(bytes2, tops.end, at, u162);
7111
+ const globalSubrs = readIndex(bytes2, strings.end, at, u162);
7112
+ if (names2.items.length !== 1 || tops.items.length !== 1) return void 0;
7113
+ const top = readDict(bytes2, ...tops.items[0], at);
7114
+ const charStringsAt = top.get(OP.charStrings)?.[0];
7115
+ if (charStringsAt === void 0) return void 0;
7116
+ if ((top.get(OP.charstringType)?.[0] ?? 2) !== 2) return void 0;
7117
+ const charStrings = readIndex(bytes2, charStringsAt, at, u162);
7118
+ const glyphCount = charStrings.items.length;
7119
+ if (glyphCount === 0) return void 0;
7120
+ const [scale = 1e-3, skewX = 0, skewY = 0, scaleY = 1e-3] = top.get(OP.fontMatrix) ?? [];
7121
+ if (skewX !== 0 || skewY !== 0 || scale !== scaleY || scale <= 0)
7122
+ return void 0;
7123
+ const unitsPerEm = Math.round(1 / scale);
7124
+ if (unitsPerEm < 16 || unitsPerEm > 16384) return void 0;
7125
+ const [xMin = 0, yMin = 0, xMax = 0, yMax = 0] = top.get(OP.fontBBox) ?? [];
7126
+ const cid = top.has(OP.ros);
7127
+ const [nameStart, nameEnd] = names2.items[0];
7128
+ const name = latin1(
7129
+ bytes2.subarray(nameStart, Math.min(nameEnd, nameStart + 127))
6894
7130
  );
6895
- if (needsFallback && !this.#fallback && this.#fallbackUrl)
6896
- this.#fallback = await this.#fetch(this.#fallbackUrl);
6897
- }
6898
- /** Checks a request against what is loaded; `undefined` means it will resolve. */
6899
- problem(request) {
6900
- const standard = isStandardFamily(request.family);
6901
- if (!standard && !this.hasFamily(request.family))
7131
+ if (cid)
6902
7132
  return {
6903
- path: "/style/fontFamily",
6904
- code: "unknown-font",
6905
- message: `Unknown font family ${request.family}; Helvetica, Times, Courier and registered fonts are available`
7133
+ name,
7134
+ cid,
7135
+ glyphNames: [],
7136
+ widths: [],
7137
+ bbox: [xMin, yMin, xMax, yMax],
7138
+ unitsPerEm
6906
7139
  };
6907
- if (standard && firstNonWinAnsi(request.text) === void 0)
6908
- return void 0;
6909
- if (this.#coveringRegistered(request)) return void 0;
6910
- if (this.#fallback && this.#covers(this.#fallback, request.text))
6911
- return void 0;
6912
- const bad = this.#fallback ? this.#firstMissing(this.#fallback, request.text) : firstNonWinAnsi(request.text);
6913
- return {
6914
- path: "/text",
6915
- code: "font-unavailable",
6916
- message: bad ? `No available font can draw "${bad}" (U+${bad.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")})` : "No available font can draw the text"
7140
+ const custom = /* @__PURE__ */ new Map();
7141
+ const stringOf = (sid) => {
7142
+ if (sid < ISO_ADOBE_NAMES.length) return ISO_ADOBE_NAMES[sid];
7143
+ const item = strings.items[sid - STANDARD_STRINGS];
7144
+ if (!item) return void 0;
7145
+ if (!custom.has(sid))
7146
+ custom.set(
7147
+ sid,
7148
+ // A glyph name is 63 characters at most.
7149
+ item[1] - item[0] <= 63 ? latin1(bytes2.subarray(...item)) : void 0
7150
+ );
7151
+ return custom.get(sid);
6917
7152
  };
6918
- }
6919
- /** The font to draw `request` with in `document`; `prepare` must have run. */
6920
- resolve(pdfium, document2, request) {
6921
- const { lib } = pdfium;
6922
- if (isStandardFamily(request.family) && firstNonWinAnsi(request.text) === void 0) {
6923
- const name = standardFontName(
6924
- request.family,
6925
- request.bold,
6926
- request.italic
6927
- );
6928
- return {
6929
- handle: lib.FPDFText_LoadStandardFont(document2, name),
6930
- family: name
6931
- };
6932
- }
6933
- const registered = this.#coveringRegistered(request);
6934
- if (registered) {
6935
- const exact = isBold(registered.font) === request.bold && isItalic(registered.font) === request.italic;
6936
- return {
6937
- handle: this.#load(pdfium, document2, registered.bytes),
6938
- family: registered.font.family,
6939
- ...exact ? {} : {
6940
- substitution: `${registered.font.family} has no matching bold or italic face`
6941
- }
6942
- };
6943
- }
6944
- if (!this.#fallback || !this.#covers(this.#fallback, request.text))
6945
- throw new Error(`No font covers "${request.text}"`);
6946
- return {
6947
- handle: this.#load(pdfium, document2, this.#fallback),
6948
- family: FALLBACK_FAMILY,
6949
- substitution: `${request.family} cannot draw this text; ${FALLBACK_FAMILY} is used`
7153
+ const glyphNames = charsetNames(
7154
+ top.get(OP.charset)?.[0] ?? 0,
7155
+ glyphCount,
7156
+ stringOf,
7157
+ at,
7158
+ u162
7159
+ );
7160
+ const [privateSize, privateAt] = top.get(OP.private) ?? [0, 0];
7161
+ const privateDict = privateSize ? readDict(bytes2, privateAt, privateAt + privateSize, at) : /* @__PURE__ */ new Map();
7162
+ const localAt = privateDict.get(OP.subrs)?.[0];
7163
+ const metrics = {
7164
+ bytes: bytes2,
7165
+ defaultWidth: privateDict.get(OP.defaultWidthX)?.[0] ?? 0,
7166
+ nominalWidth: privateDict.get(OP.nominalWidthX)?.[0] ?? 0,
7167
+ global: globalSubrs.items,
7168
+ local: localAt === void 0 ? [] : readIndex(bytes2, privateAt + localAt, at, u162).items
6950
7169
  };
6951
- }
6952
- /** Closes the fonts loaded into a document that is going away. */
6953
- release(pdfium, document2) {
6954
- const handles = this.#handles.get(document2);
6955
- if (!handles) return;
6956
- for (const handle of handles.values()) pdfium.lib.FPDFFont_Close(handle);
6957
- this.#handles.delete(document2);
6958
- }
6959
- covers(bytes2, text) {
6960
- return this.#covers(bytes2, text);
6961
- }
6962
- #coveringRegistered(request) {
6963
- const candidates = this.#registered.filter((font) => sameFamily(font.family, request.family)).map((font) => ({ font, bytes: this.#loadedBytes(font) })).filter(
6964
- (entry) => entry.bytes !== void 0 && this.#covers(entry.bytes, request.text)
7170
+ const widths = charStrings.items.map(
7171
+ ([start, end]) => charstringWidth(metrics, start, end)
6965
7172
  );
6966
- const score = (font) => (isBold(font) === request.bold ? 2 : 0) + (isItalic(font) === request.italic ? 1 : 0);
6967
- return candidates.sort((a, b) => score(b.font) - score(a.font))[0];
6968
- }
6969
- #covers(bytes2, text) {
6970
- return this.#firstMissing(bytes2, text) === void 0;
6971
- }
6972
- /** The first character of `text` the font has no glyph for; layout handles line breaks and spaces. */
6973
- #firstMissing(bytes2, text) {
6974
- let coverage = this.#coverage.get(bytes2);
6975
- if (!coverage) {
6976
- coverage = parseCmap(bytes2);
6977
- this.#coverage.set(bytes2, coverage);
6978
- }
6979
- for (const character of text) {
6980
- const code = character.codePointAt(0);
6981
- if (code === 10 || code === 32) continue;
6982
- if (!coverage.has(code)) return character;
6983
- }
6984
- return void 0;
7173
+ return {
7174
+ name,
7175
+ cid,
7176
+ glyphNames,
7177
+ widths,
7178
+ bbox: [xMin, yMin, xMax, yMax],
7179
+ unitsPerEm
7180
+ };
7181
+ } catch (error) {
7182
+ if (error instanceof RangeError) return void 0;
7183
+ throw error;
6985
7184
  }
6986
- #load(pdfium, document2, bytes2) {
6987
- let handles = this.#handles.get(document2);
6988
- if (!handles) {
6989
- handles = /* @__PURE__ */ new Map();
6990
- this.#handles.set(document2, handles);
6991
- }
6992
- let handle = handles.get(bytes2);
6993
- if (handle === void 0) {
6994
- const pointer = pdfium.writeBytes(bytes2);
6995
- try {
6996
- handle = pdfium.lib.FPDFText_LoadFont(
6997
- document2,
6998
- pointer,
6999
- bytes2.byteLength,
7000
- FONT_TRUETYPE,
7001
- true
7002
- );
7003
- } finally {
7004
- pdfium.free(pointer);
7005
- }
7006
- if (!handle) throw new Error("PDFium could not load the font");
7007
- handles.set(bytes2, handle);
7185
+ }
7186
+ function cffWithSafeName(bytes2) {
7187
+ try {
7188
+ const at = (offset2) => {
7189
+ if (offset2 < 0 || offset2 >= bytes2.length)
7190
+ throw new RangeError("Outside the font");
7191
+ return bytes2[offset2];
7192
+ };
7193
+ const u162 = (offset2) => at(offset2) << 8 | at(offset2 + 1);
7194
+ const names2 = readIndex(bytes2, at(2), at, u162);
7195
+ const [start, end] = names2.items[0] ?? [0, 0];
7196
+ if (names2.items.length !== 1 || end - start > 127) return void 0;
7197
+ const safe = bytes2.slice();
7198
+ for (let offset2 = start; offset2 < end; offset2 += 1) {
7199
+ const byte = safe[offset2];
7200
+ if (offset2 === start && byte === 0) continue;
7201
+ if (byte < 33 || byte > 126 || UNSAFE_NAME_BYTES.has(byte))
7202
+ safe[offset2] = 95;
7008
7203
  }
7009
- return handle;
7010
- }
7011
- #loadedBytes(font) {
7012
- return typeof font.source === "string" ? this.#bytes.get(font.source) : this.#bytes.get(sourceKey(font)) ?? new Uint8Array(font.source);
7204
+ return safe;
7205
+ } catch (error) {
7206
+ if (error instanceof RangeError) return void 0;
7207
+ throw error;
7013
7208
  }
7014
- async #bytesOf(font) {
7015
- const key = sourceKey(font);
7016
- let bytes2 = this.#bytes.get(key);
7017
- if (!bytes2) {
7018
- bytes2 = typeof font.source === "string" ? await this.#fetch(font.source) : new Uint8Array(font.source);
7019
- this.#bytes.set(key, bytes2);
7020
- }
7021
- return bytes2;
7022
- }
7023
- };
7024
- function sourceKey(font) {
7025
- return typeof font.source === "string" ? font.source : `${font.family}:${font.weight}:${font.style}:${font.source.byteLength}`;
7026
- }
7027
- function sameFamily(a, b) {
7028
- return a.toLowerCase() === b.toLowerCase();
7029
- }
7030
- function isBold(font) {
7031
- return font.weight >= 600;
7032
- }
7033
- function isItalic(font) {
7034
- return font.style !== "normal";
7035
7209
  }
7036
- function glyphOutlines(view, tables) {
7037
- const head2 = tables.get("head");
7038
- const maxp = tables.get("maxp");
7039
- const loca = tables.get("loca");
7040
- if (!head2 || !maxp || !loca || !tables.has("glyf")) return void 0;
7041
- const longOffsets = view.getInt16(head2.offset + 50) === 1;
7042
- const count = view.getUint16(maxp.offset + 4);
7043
- return (glyph) => {
7044
- if (glyph < 0 || glyph >= count) return false;
7045
- const at = loca.offset + glyph * (longOffsets ? 4 : 2);
7046
- const start = longOffsets ? view.getUint32(at) : view.getUint16(at) * 2;
7047
- const end = longOffsets ? view.getUint32(at + 4) : view.getUint16(at + 2) * 2;
7048
- return end > start;
7210
+ var UNSAFE_NAME_BYTES = new Set(
7211
+ [..."[](){}<>/%"].map((character) => character.charCodeAt(0))
7212
+ );
7213
+ function readIndex(bytes2, start, at, u162) {
7214
+ const count = u162(start);
7215
+ if (count === 0) return { items: [], end: start + 2 };
7216
+ const offSize = at(start + 2);
7217
+ if (offSize < 1 || offSize > 4) throw new RangeError("Bad offset size");
7218
+ const offset2 = (index) => {
7219
+ let value = 0;
7220
+ for (let byte = 0; byte < offSize; byte += 1)
7221
+ value = value * 256 + at(start + 3 + index * offSize + byte);
7222
+ return value;
7049
7223
  };
7224
+ if (offset2(0) !== 1) throw new RangeError("Bad INDEX");
7225
+ const base = start + 2 + (count + 1) * offSize;
7226
+ const items = [];
7227
+ for (let index = 0; index < count; index += 1) {
7228
+ const from = base + offset2(index);
7229
+ const to = base + offset2(index + 1);
7230
+ if (to < from || to > bytes2.length) throw new RangeError("Bad INDEX");
7231
+ items.push([from, to]);
7232
+ }
7233
+ return { items, end: base + offset2(count) };
7050
7234
  }
7051
- function parseCmap(bytes2) {
7052
- const view = new DataView(bytes2.buffer, bytes2.byteOffset, bytes2.byteLength);
7053
- const ranges = [];
7054
- const glyphs = /* @__PURE__ */ new Map();
7055
- let outlines;
7056
- try {
7057
- const tables = view.getUint16(4);
7058
- let cmapOffset = -1;
7059
- const offsets = /* @__PURE__ */ new Map();
7060
- for (let index = 0; index < tables; index += 1) {
7061
- const record = 12 + index * 16;
7062
- const tag = String.fromCharCode(
7063
- bytes2[record],
7064
- bytes2[record + 1],
7065
- bytes2[record + 2],
7066
- bytes2[record + 3]
7235
+ function readDict(bytes2, start, end, at) {
7236
+ if (end > bytes2.length) throw new RangeError("DICT past the font");
7237
+ const entries = /* @__PURE__ */ new Map();
7238
+ let operands = [];
7239
+ let offset2 = start;
7240
+ while (offset2 < end) {
7241
+ const byte = at(offset2);
7242
+ if (byte <= 21) {
7243
+ const operator = byte === 12 ? 1200 + at(offset2 + 1) : byte;
7244
+ offset2 += byte === 12 ? 2 : 1;
7245
+ entries.set(operator, operands);
7246
+ operands = [];
7247
+ } else if (byte === 28) {
7248
+ operands.push(int16(at(offset2 + 1) << 8 | at(offset2 + 2)));
7249
+ offset2 += 3;
7250
+ } else if (byte === 29) {
7251
+ operands.push(
7252
+ at(offset2 + 1) << 24 | at(offset2 + 2) << 16 | at(offset2 + 3) << 8 | at(offset2 + 4)
7067
7253
  );
7068
- offsets.set(tag, {
7069
- offset: view.getUint32(record + 8),
7070
- length: view.getUint32(record + 12)
7071
- });
7072
- if (tag === "cmap") cmapOffset = view.getUint32(record + 8);
7073
- }
7074
- outlines = glyphOutlines(view, offsets);
7075
- if (cmapOffset < 0)
7076
- return {
7077
- has: () => false,
7078
- drawable: () => false,
7079
- glyph: () => void 0
7080
- };
7081
- const subtables = view.getUint16(cmapOffset + 2);
7082
- let best;
7083
- for (let index = 0; index < subtables; index += 1) {
7084
- const record = cmapOffset + 4 + index * 8;
7085
- const platform = view.getUint16(record);
7086
- const encoding = view.getUint16(record + 2);
7087
- const offset2 = cmapOffset + view.getUint32(record + 4);
7088
- const format = view.getUint16(offset2);
7089
- const unicode = platform === 0 || platform === 3 && (encoding === 1 || encoding === 10);
7090
- if (!unicode || format !== 4 && format !== 12) continue;
7091
- if (!best || format > best.format) best = { offset: offset2, format };
7092
- }
7093
- if (!best)
7094
- return {
7095
- has: () => false,
7096
- drawable: () => false,
7097
- glyph: () => void 0
7098
- };
7099
- if (best.format === 12) {
7100
- const groups = view.getUint32(best.offset + 12);
7101
- for (let index = 0; index < groups; index += 1) {
7102
- const group = best.offset + 16 + index * 12;
7103
- const start = view.getUint32(group);
7104
- const end = view.getUint32(group + 4);
7105
- const startGlyph = view.getUint32(group + 8);
7106
- ranges.push([start, end]);
7107
- for (let code = start; code <= end && code - start < 65536; code += 1)
7108
- glyphs.set(code, startGlyph + (code - start));
7109
- }
7110
- } else {
7111
- const segments = view.getUint16(best.offset + 6) / 2;
7112
- const ends = best.offset + 14;
7113
- const starts = ends + segments * 2 + 2;
7114
- const deltas = starts + segments * 2;
7115
- const rangeOffsets = deltas + segments * 2;
7116
- for (let index = 0; index < segments; index += 1) {
7117
- const end = view.getUint16(ends + index * 2);
7118
- const start = view.getUint16(starts + index * 2);
7119
- if (start === 65535) continue;
7120
- ranges.push([start, end]);
7121
- const delta = view.getInt16(deltas + index * 2);
7122
- const rangeOffset = view.getUint16(rangeOffsets + index * 2);
7123
- for (let code = start; code <= end; code += 1) {
7124
- let glyph;
7125
- if (rangeOffset === 0) glyph = code + delta & 65535;
7126
- else {
7127
- const at = rangeOffsets + index * 2 + rangeOffset + (code - start) * 2;
7128
- const raw = view.getUint16(at);
7129
- glyph = raw === 0 ? 0 : raw + delta & 65535;
7254
+ offset2 += 5;
7255
+ } else if (byte === 30) {
7256
+ let text = "";
7257
+ offset2 += 1;
7258
+ for (let done = false; !done; offset2 += 1) {
7259
+ const pair = at(offset2);
7260
+ for (const nibble of [pair >> 4, pair & 15]) {
7261
+ if (nibble === 15) {
7262
+ done = true;
7263
+ break;
7130
7264
  }
7131
- glyphs.set(code, glyph);
7265
+ text += REAL_NIBBLES[nibble] ?? "";
7132
7266
  }
7133
7267
  }
7134
- }
7135
- } catch {
7136
- return { has: () => false, drawable: () => false, glyph: () => void 0 };
7268
+ operands.push(Number.parseFloat(text));
7269
+ } else if (byte >= 32 && byte <= 246) {
7270
+ operands.push(byte - 139);
7271
+ offset2 += 1;
7272
+ } else if (byte >= 247 && byte <= 250) {
7273
+ operands.push((byte - 247) * 256 + at(offset2 + 1) + 108);
7274
+ offset2 += 2;
7275
+ } else if (byte >= 251 && byte <= 254) {
7276
+ operands.push(-(byte - 251) * 256 - at(offset2 + 1) - 108);
7277
+ offset2 += 2;
7278
+ } else throw new RangeError("Bad DICT byte");
7137
7279
  }
7138
- ranges.sort((a, b) => a[0] - b[0]);
7139
- return {
7140
- glyph(codePoint) {
7141
- return glyphs.get(codePoint);
7142
- },
7143
- drawable(codePoint) {
7144
- const glyph = glyphs.get(codePoint);
7145
- if (glyph === void 0 || glyph === 0) return false;
7146
- return outlines ? outlines(glyph) : true;
7147
- },
7148
- has(codePoint) {
7149
- let low = 0;
7150
- let high = ranges.length - 1;
7151
- while (low <= high) {
7152
- const middle = low + high >> 1;
7153
- const [start, end] = ranges[middle];
7154
- if (codePoint < start) high = middle - 1;
7155
- else if (codePoint > end) low = middle + 1;
7156
- else return true;
7157
- }
7158
- return false;
7159
- }
7160
- };
7280
+ return entries;
7161
7281
  }
7162
- var TextMeasurer = class {
7163
- #pdfium;
7164
- #document;
7165
- #advances = /* @__PURE__ */ new Map();
7166
- #metrics = /* @__PURE__ */ new Map();
7167
- constructor(pdfium, document2) {
7168
- this.#pdfium = pdfium;
7169
- this.#document = document2;
7170
- }
7171
- /** Width of `text` in points at `fontSize`, without kerning. */
7172
- advance(font, fontSize, text) {
7173
- let total = 0;
7174
- for (const character of text) total += this.#advanceOf(font, character);
7175
- return total * fontSize / 1e3;
7282
+ var REAL_NIBBLES = [
7283
+ "0",
7284
+ "1",
7285
+ "2",
7286
+ "3",
7287
+ "4",
7288
+ "5",
7289
+ "6",
7290
+ "7",
7291
+ "8",
7292
+ "9",
7293
+ ".",
7294
+ "E",
7295
+ "E-",
7296
+ "",
7297
+ "-"
7298
+ ];
7299
+ function charsetNames(offset2, glyphCount, stringOf, at, u162) {
7300
+ const names2 = [".notdef"];
7301
+ if (offset2 === 0) {
7302
+ for (let glyph = 1; glyph < glyphCount; glyph += 1)
7303
+ names2.push(stringOf(glyph));
7304
+ return names2;
7176
7305
  }
7177
- /** Ascent above and descent below the baseline, in points, both positive. */
7178
- metrics(font, fontSize) {
7179
- let unit = this.#metrics.get(font);
7180
- if (!unit) {
7181
- const { lib } = this.#pdfium;
7182
- const ascent = this.#pdfium.readNumbers(
7183
- 1,
7184
- "float",
7185
- ([pointer]) => lib.FPDFFont_GetAscent(font, 1e3, pointer)
7186
- )?.[0] ?? 750;
7187
- const descent = this.#pdfium.readNumbers(
7188
- 1,
7189
- "float",
7190
- ([pointer]) => lib.FPDFFont_GetDescent(font, 1e3, pointer)
7191
- )?.[0] ?? -250;
7192
- unit = { ascent, descent: Math.abs(descent) };
7193
- this.#metrics.set(font, unit);
7194
- }
7195
- return {
7196
- ascent: unit.ascent * fontSize / 1e3,
7197
- descent: unit.descent * fontSize / 1e3
7198
- };
7199
- }
7200
- #advanceOf(font, character) {
7201
- let cache = this.#advances.get(font);
7202
- if (!cache) {
7203
- cache = /* @__PURE__ */ new Map();
7204
- this.#advances.set(font, cache);
7205
- }
7206
- let advance = cache.get(character);
7207
- if (advance === void 0) {
7208
- advance = this.#inkWidth(font, `H${character}H`) - this.#inkWidth(font, "HH");
7209
- cache.set(character, advance);
7306
+ if (offset2 <= 2) return [...names2, ...Array(glyphCount - 1)];
7307
+ const format = at(offset2);
7308
+ let cursor = offset2 + 1;
7309
+ if (format === 0) {
7310
+ for (let glyph = 1; glyph < glyphCount; glyph += 1, cursor += 2)
7311
+ names2.push(stringOf(u162(cursor)));
7312
+ } else if (format === 1 || format === 2) {
7313
+ while (names2.length < glyphCount) {
7314
+ const first = u162(cursor);
7315
+ const left = format === 1 ? at(cursor + 2) : u162(cursor + 2);
7316
+ cursor += format === 1 ? 3 : 4;
7317
+ for (let step = 0; step <= left && names2.length < glyphCount; step += 1)
7318
+ names2.push(stringOf(first + step));
7210
7319
  }
7211
- return advance;
7212
- }
7213
- #inkWidth(font, text) {
7214
- const { lib } = this.#pdfium;
7215
- const object = lib.FPDFPageObj_CreateTextObj(this.#document, font, 1e3);
7216
- try {
7217
- const wide = this.#pdfium.writeWideString(text);
7218
- try {
7219
- lib.FPDFText_SetText(object, wide);
7220
- } finally {
7221
- this.#pdfium.free(wide);
7320
+ } else throw new RangeError("Bad charset format");
7321
+ return names2;
7322
+ }
7323
+ function charstringWidth(context, start, end) {
7324
+ const { bytes: bytes2, defaultWidth, nominalWidth } = context;
7325
+ const stack = [];
7326
+ const walk = (from, to, depth) => {
7327
+ let offset2 = from;
7328
+ while (offset2 < to) {
7329
+ const byte = bytes2[offset2];
7330
+ const size = byte === 28 ? 3 : byte === 255 ? 5 : byte >= 247 && byte <= 254 ? 2 : 1;
7331
+ if (offset2 + size > to) return defaultWidth;
7332
+ if (byte === 28 || byte >= 32) {
7333
+ const next = (step) => bytes2[offset2 + step];
7334
+ stack.push(
7335
+ byte === 28 ? int16(next(1) << 8 | next(2)) : byte <= 246 ? byte - 139 : byte <= 250 ? (byte - 247) * 256 + next(1) + 108 : byte <= 254 ? -(byte - 251) * 256 - next(1) - 108 : (next(1) << 24 | next(2) << 16 | next(3) << 8 | next(4)) / 65536
7336
+ );
7337
+ if (stack.length > 48) return defaultWidth;
7338
+ offset2 += size;
7339
+ continue;
7222
7340
  }
7223
- const bounds = this.#pdfium.readNumbers(
7224
- 4,
7225
- "float",
7226
- ([l, b, r, t]) => lib.FPDFPageObj_GetBounds(object, l, b, r, t)
7227
- );
7228
- return bounds ? bounds[2] - bounds[0] : 0;
7229
- } finally {
7230
- lib.FPDFPageObj_Destroy(object);
7341
+ offset2 += 1;
7342
+ if (byte === 10 || byte === 29) {
7343
+ const subrs = byte === 10 ? context.local : context.global;
7344
+ const index = stack.pop();
7345
+ const subr = index === void 0 ? void 0 : subrs[index + subrBias(subrs.length)];
7346
+ if (!subr || depth >= 10) return defaultWidth;
7347
+ const width = walk(subr[0], subr[1], depth + 1);
7348
+ if (width !== void 0) return width;
7349
+ continue;
7350
+ }
7351
+ if (byte === 11) return depth > 0 ? void 0 : defaultWidth;
7352
+ const takes = STACK_CLEARING[byte];
7353
+ if (takes === void 0) return defaultWidth;
7354
+ const count = stack.length;
7355
+ const widthFirst = takes === "pairs" ? count % 2 === 1 : takes === "endchar" ? count === 1 || count === 5 : count > takes;
7356
+ return widthFirst ? stack[0] + nominalWidth : defaultWidth;
7231
7357
  }
7232
- }
7358
+ return depth > 0 ? void 0 : defaultWidth;
7359
+ };
7360
+ return walk(start, end, 0) ?? defaultWidth;
7361
+ }
7362
+ function subrBias(count) {
7363
+ return count < 1240 ? 107 : count < 33900 ? 1131 : 32768;
7364
+ }
7365
+ var STACK_CLEARING = {
7366
+ 1: "pairs",
7367
+ // hstem
7368
+ 3: "pairs",
7369
+ // vstem
7370
+ 18: "pairs",
7371
+ // hstemhm
7372
+ 23: "pairs",
7373
+ // vstemhm
7374
+ 19: "pairs",
7375
+ // hintmask, after implied vstems
7376
+ 20: "pairs",
7377
+ // cntrmask
7378
+ 21: 2,
7379
+ // rmoveto
7380
+ 22: 1,
7381
+ // hmoveto
7382
+ 4: 1,
7383
+ // vmoveto
7384
+ 14: "endchar"
7385
+ // endchar: none, or four for an accented character
7233
7386
  };
7387
+ function int16(value) {
7388
+ return value >= 32768 ? value - 65536 : value;
7389
+ }
7390
+ function latin1(bytes2) {
7391
+ return String.fromCharCode(...bytes2);
7392
+ }
7234
7393
 
7235
- // src/edit/pdf/engine/glyph-names.ts
7236
- var AGLFN = `
7237
- A 0041 AE 00C6 AEacute 01FC Aacute 00C1 Abreve 0102 Acircumflex 00C2
7238
- Adieresis 00C4 Agrave 00C0 Alpha 0391 Alphatonos 0386 Amacron 0100
7239
- Aogonek 0104 Aring 00C5 Aringacute 01FA Atilde 00C3 B 0042 Beta 0392 C 0043
7240
- Cacute 0106 Ccaron 010C Ccedilla 00C7 Ccircumflex 0108 Cdotaccent 010A
7241
- Chi 03A7 D 0044 Dcaron 010E Dcroat 0110 Delta 2206 E 0045 Eacute 00C9
7242
- Ebreve 0114 Ecaron 011A Ecircumflex 00CA Edieresis 00CB Edotaccent 0116
7243
- Egrave 00C8 Emacron 0112 Eng 014A Eogonek 0118 Epsilon 0395
7244
- Epsilontonos 0388 Eta 0397 Etatonos 0389 Eth 00D0 Euro 20AC F 0046 G 0047
7245
- Gamma 0393 Gbreve 011E Gcaron 01E6 Gcircumflex 011C Gdotaccent 0120 H 0048
7246
- H18533 25CF H18543 25AA H18551 25AB H22073 25A1 Hbar 0126 Hcircumflex 0124
7247
- I 0049 IJ 0132 Iacute 00CD Ibreve 012C Icircumflex 00CE Idieresis 00CF
7248
- Idotaccent 0130 Ifraktur 2111 Igrave 00CC Imacron 012A Iogonek 012E
7249
- Iota 0399 Iotadieresis 03AA Iotatonos 038A Itilde 0128 J 004A
7250
- Jcircumflex 0134 K 004B Kappa 039A L 004C Lacute 0139 Lambda 039B
7251
- Lcaron 013D Ldot 013F Lslash 0141 M 004D Mu 039C N 004E Nacute 0143
7252
- Ncaron 0147 Ntilde 00D1 Nu 039D O 004F OE 0152 Oacute 00D3 Obreve 014E
7253
- Ocircumflex 00D4 Odieresis 00D6 Ograve 00D2 Ohorn 01A0 Ohungarumlaut 0150
7254
- Omacron 014C Omega 2126 Omegatonos 038F Omicron 039F Omicrontonos 038C
7255
- Oslash 00D8 Oslashacute 01FE Otilde 00D5 P 0050 Phi 03A6 Pi 03A0 Psi 03A8
7256
- Q 0051 R 0052 Racute 0154 Rcaron 0158 Rfraktur 211C Rho 03A1 S 0053
7257
- SF010000 250C SF020000 2514 SF030000 2510 SF040000 2518 SF050000 253C
7258
- SF060000 252C SF070000 2534 SF080000 251C SF090000 2524 SF100000 2500
7259
- SF110000 2502 SF190000 2561 SF200000 2562 SF210000 2556 SF220000 2555
7260
- SF230000 2563 SF240000 2551 SF250000 2557 SF260000 255D SF270000 255C
7261
- SF280000 255B SF360000 255E SF370000 255F SF380000 255A SF390000 2554
7262
- SF400000 2569 SF410000 2566 SF420000 2560 SF430000 2550 SF440000 256C
7263
- SF450000 2567 SF460000 2568 SF470000 2564 SF480000 2565 SF490000 2559
7264
- SF500000 2558 SF510000 2552 SF520000 2553 SF530000 256B SF540000 256A
7265
- Sacute 015A Scaron 0160 Scedilla 015E Scircumflex 015C Sigma 03A3 T 0054
7266
- Tau 03A4 Tbar 0166 Tcaron 0164 Theta 0398 Thorn 00DE U 0055 Uacute 00DA
7267
- Ubreve 016C Ucircumflex 00DB Udieresis 00DC Ugrave 00D9 Uhorn 01AF
7268
- Uhungarumlaut 0170 Umacron 016A Uogonek 0172 Upsilon 03A5 Upsilon1 03D2
7269
- Upsilondieresis 03AB Upsilontonos 038E Uring 016E Utilde 0168 V 0056 W 0057
7270
- Wacute 1E82 Wcircumflex 0174 Wdieresis 1E84 Wgrave 1E80 X 0058 Xi 039E
7271
- Y 0059 Yacute 00DD Ycircumflex 0176 Ydieresis 0178 Ygrave 1EF2 Z 005A
7272
- Zacute 0179 Zcaron 017D Zdotaccent 017B Zeta 0396 a 0061 aacute 00E1
7273
- abreve 0103 acircumflex 00E2 acute 00B4 acutecomb 0301 adieresis 00E4
7274
- ae 00E6 aeacute 01FD agrave 00E0 aleph 2135 alpha 03B1 alphatonos 03AC
7275
- amacron 0101 ampersand 0026 angle 2220 angleleft 2329 angleright 232A
7276
- anoteleia 0387 aogonek 0105 approxequal 2248 aring 00E5 aringacute 01FB
7277
- arrowboth 2194 arrowdblboth 21D4 arrowdbldown 21D3 arrowdblleft 21D0
7278
- arrowdblright 21D2 arrowdblup 21D1 arrowdown 2193 arrowleft 2190
7279
- arrowright 2192 arrowup 2191 arrowupdn 2195 arrowupdnbse 21A8
7280
- asciicircum 005E asciitilde 007E asterisk 002A asteriskmath 2217 at 0040
7281
- atilde 00E3 b 0062 backslash 005C bar 007C beta 03B2 block 2588
7282
- braceleft 007B braceright 007D bracketleft 005B bracketright 005D breve 02D8
7283
- brokenbar 00A6 bullet 2022 c 0063 cacute 0107 caron 02C7 carriagereturn 21B5
7284
- ccaron 010D ccedilla 00E7 ccircumflex 0109 cdotaccent 010B cedilla 00B8
7285
- cent 00A2 chi 03C7 circle 25CB circlemultiply 2297 circleplus 2295
7286
- circumflex 02C6 club 2663 colon 003A colonmonetary 20A1 comma 002C
7287
- congruent 2245 copyright 00A9 currency 00A4 d 0064 dagger 2020
7288
- daggerdbl 2021 dcaron 010F dcroat 0111 degree 00B0 delta 03B4 diamond 2666
7289
- dieresis 00A8 dieresistonos 0385 divide 00F7 dkshade 2593 dnblock 2584
7290
- dollar 0024 dong 20AB dotaccent 02D9 dotbelowcomb 0323 dotlessi 0131
7291
- dotmath 22C5 e 0065 eacute 00E9 ebreve 0115 ecaron 011B ecircumflex 00EA
7292
- edieresis 00EB edotaccent 0117 egrave 00E8 eight 0038 element 2208
7293
- ellipsis 2026 emacron 0113 emdash 2014 emptyset 2205 endash 2013 eng 014B
7294
- eogonek 0119 epsilon 03B5 epsilontonos 03AD equal 003D equivalence 2261
7295
- estimated 212E eta 03B7 etatonos 03AE eth 00F0 exclam 0021 exclamdbl 203C
7296
- exclamdown 00A1 existential 2203 f 0066 female 2640 figuredash 2012
7297
- filledbox 25A0 filledrect 25AC five 0035 fiveeighths 215D florin 0192
7298
- four 0034 fraction 2044 franc 20A3 g 0067 gamma 03B3 gbreve 011F gcaron 01E7
7299
- gcircumflex 011D gdotaccent 0121 germandbls 00DF gradient 2207 grave 0060
7300
- gravecomb 0300 greater 003E greaterequal 2265 guillemotleft 00AB
7301
- guillemotright 00BB guilsinglleft 2039 guilsinglright 203A h 0068 hbar 0127
7302
- hcircumflex 0125 heart 2665 hookabovecomb 0309 house 2302 hungarumlaut 02DD
7303
- hyphen 002D i 0069 iacute 00ED ibreve 012D icircumflex 00EE idieresis 00EF
7304
- igrave 00EC ij 0133 imacron 012B infinity 221E integral 222B integralbt 2321
7305
- integraltp 2320 intersection 2229 invbullet 25D8 invcircle 25D9
7306
- invsmileface 263B iogonek 012F iota 03B9 iotadieresis 03CA
7307
- iotadieresistonos 0390 iotatonos 03AF itilde 0129 j 006A jcircumflex 0135
7308
- k 006B kappa 03BA kgreenlandic 0138 l 006C lacute 013A lambda 03BB
7309
- lcaron 013E ldot 0140 less 003C lessequal 2264 lfblock 258C lira 20A4
7310
- logicaland 2227 logicalnot 00AC logicalor 2228 longs 017F lozenge 25CA
7311
- lslash 0142 ltshade 2591 m 006D macron 00AF male 2642 minus 2212 minute 2032
7312
- mu 00B5 multiply 00D7 musicalnote 266A musicalnotedbl 266B n 006E
7313
- nacute 0144 napostrophe 0149 ncaron 0148 nine 0039 notelement 2209
7314
- notequal 2260 notsubset 2284 ntilde 00F1 nu 03BD numbersign 0023 o 006F
7315
- oacute 00F3 obreve 014F ocircumflex 00F4 odieresis 00F6 oe 0153 ogonek 02DB
7316
- ograve 00F2 ohorn 01A1 ohungarumlaut 0151 omacron 014D omega 03C9
7317
- omega1 03D6 omegatonos 03CE omicron 03BF omicrontonos 03CC one 0031
7318
- onedotenleader 2024 oneeighth 215B onehalf 00BD onequarter 00BC
7319
- onethird 2153 openbullet 25E6 ordfeminine 00AA ordmasculine 00BA
7320
- orthogonal 221F oslash 00F8 oslashacute 01FF otilde 00F5 p 0070
7321
- paragraph 00B6 parenleft 0028 parenright 0029 partialdiff 2202 percent 0025
7322
- period 002E periodcentered 00B7 perpendicular 22A5 perthousand 2030
7323
- peseta 20A7 phi 03C6 phi1 03D5 pi 03C0 plus 002B plusminus 00B1
7324
- prescription 211E product 220F propersubset 2282 propersuperset 2283
7325
- proportional 221D psi 03C8 q 0071 question 003F questiondown 00BF
7326
- quotedbl 0022 quotedblbase 201E quotedblleft 201C quotedblright 201D
7327
- quoteleft 2018 quotereversed 201B quoteright 2019 quotesinglbase 201A
7328
- quotesingle 0027 r 0072 racute 0155 radical 221A rcaron 0159
7329
- reflexsubset 2286 reflexsuperset 2287 registered 00AE revlogicalnot 2310
7330
- rho 03C1 ring 02DA rtblock 2590 s 0073 sacute 015B scaron 0161 scedilla 015F
7331
- scircumflex 015D second 2033 section 00A7 semicolon 003B seven 0037
7332
- seveneighths 215E shade 2592 sigma 03C3 sigma1 03C2 similar 223C six 0036
7333
- slash 002F smileface 263A space 0020 spade 2660 sterling 00A3 suchthat 220B
7334
- summation 2211 sun 263C t 0074 tau 03C4 tbar 0167 tcaron 0165 therefore 2234
7335
- theta 03B8 theta1 03D1 thorn 00FE three 0033 threeeighths 215C
7336
- threequarters 00BE tilde 02DC tildecomb 0303 tonos 0384 trademark 2122
7337
- triagdn 25BC triaglf 25C4 triagrt 25BA triagup 25B2 two 0032
7338
- twodotenleader 2025 twothirds 2154 u 0075 uacute 00FA ubreve 016D
7339
- ucircumflex 00FB udieresis 00FC ugrave 00F9 uhorn 01B0 uhungarumlaut 0171
7340
- umacron 016B underscore 005F underscoredbl 2017 union 222A universal 2200
7341
- uogonek 0173 upblock 2580 upsilon 03C5 upsilondieresis 03CB
7342
- upsilondieresistonos 03B0 upsilontonos 03CD uring 016F utilde 0169 v 0076
7343
- w 0077 wacute 1E83 wcircumflex 0175 wdieresis 1E85 weierstrass 2118
7344
- wgrave 1E81 x 0078 xi 03BE y 0079 yacute 00FD ycircumflex 0177
7345
- ydieresis 00FF yen 00A5 ygrave 1EF3 z 007A zacute 017A zcaron 017E
7346
- zdotaccent 017C zero 0030 zeta 03B6
7347
- `;
7348
- var AGL_EXTRAS = `
7349
- fi FB01 fl FB02 ff FB00 ffi FB03 ffl FB04 onesuperior 00B9 twosuperior 00B2
7350
- threesuperior 00B3 nbspace 00A0
7351
- `;
7352
- var GLYPH_UNICODE = new Map(
7353
- `${AGLFN} ${AGL_EXTRAS}`.trim().split(/\s+/).flatMap(
7354
- (word, index, words) => index % 2 === 0 ? [[word, Number.parseInt(words[index + 1], 16)]] : []
7355
- )
7394
+ // src/edit/pdf/engine/fonts.ts
7395
+ var STANDARD_FAMILIES = {
7396
+ helvetica: [
7397
+ "Helvetica",
7398
+ "Helvetica-Bold",
7399
+ "Helvetica-Oblique",
7400
+ "Helvetica-BoldOblique"
7401
+ ],
7402
+ times: ["Times-Roman", "Times-Bold", "Times-Italic", "Times-BoldItalic"],
7403
+ courier: [
7404
+ "Courier",
7405
+ "Courier-Bold",
7406
+ "Courier-Oblique",
7407
+ "Courier-BoldOblique"
7408
+ ]
7409
+ };
7410
+ var WIN_ANSI_EXTRAS = new Set(
7411
+ "\u20AC\u201A\u0192\u201E\u2026\u2020\u2021\u02C6\u2030\u0160\u2039\u0152\u017D\u2018\u2019\u201C\u201D\u2022\u2013\u2014\u02DC\u2122\u0161\u203A\u0153\u017E\u0178".split("").map((c) => c.codePointAt(0))
7356
7412
  );
7357
-
7358
- // src/edit/pdf/engine/cff.ts
7359
- var ISO_ADOBE_NAMES = `
7360
- .notdef space exclam quotedbl numbersign dollar percent ampersand
7361
- quoteright parenleft parenright asterisk plus comma hyphen period slash
7362
- zero one two three four five six seven eight nine colon semicolon less
7363
- equal greater question at A B C D E F G H I J K L M N O P Q R S T U V W X Y
7364
- Z bracketleft backslash bracketright asciicircum underscore quoteleft a b c
7365
- d e f g h i j k l m n o p q r s t u v w x y z braceleft bar braceright
7366
- asciitilde exclamdown cent sterling fraction yen florin section currency
7367
- quotesingle quotedblleft guillemotleft guilsinglleft guilsinglright fi fl
7368
- endash dagger daggerdbl periodcentered paragraph bullet quotesinglbase
7369
- quotedblbase quotedblright guillemotright ellipsis perthousand questiondown
7370
- grave acute circumflex tilde macron breve dotaccent dieresis ring cedilla
7371
- hungarumlaut ogonek caron emdash AE ordfeminine Lslash Oslash OE
7372
- ordmasculine ae dotlessi lslash oslash oe germandbls onesuperior logicalnot
7373
- mu trademark Eth onehalf plusminus Thorn onequarter divide brokenbar degree
7374
- thorn threequarters twosuperior registered minus eth multiply threesuperior
7375
- copyright Aacute Acircumflex Adieresis Agrave Aring Atilde Ccedilla Eacute
7376
- Ecircumflex Edieresis Egrave Iacute Icircumflex Idieresis Igrave Ntilde
7377
- Oacute Ocircumflex Odieresis Ograve Otilde Scaron Uacute Ucircumflex
7378
- Udieresis Ugrave Yacute Ydieresis Zcaron aacute acircumflex adieresis
7379
- agrave aring atilde ccedilla eacute ecircumflex edieresis egrave iacute
7380
- icircumflex idieresis igrave ntilde oacute ocircumflex odieresis ograve
7381
- otilde scaron uacute ucircumflex udieresis ugrave yacute ydieresis zcaron
7382
- `.trim().split(/\s+/);
7383
- function glyphUnicode(name) {
7384
- if (!name || name === ".notdef") return void 0;
7385
- const known = GLYPH_UNICODE.get(name);
7386
- if (known !== void 0) return known;
7387
- const match = /^uni([0-9A-F]{4})$|^u([0-9A-F]{4,6})$/.exec(name);
7388
- const hex2 = match?.[1] ?? match?.[2];
7389
- if (!hex2) return void 0;
7390
- const code = Number.parseInt(hex2, 16);
7391
- const surrogate = code >= 55296 && code <= 57343;
7392
- const nonCharacter = (code & 65534) === 65534;
7393
- return code <= 1114111 && !surrogate && !nonCharacter ? code : void 0;
7413
+ var FONT_TRUETYPE = 1;
7414
+ var FALLBACK_FAMILY = "Noto Sans";
7415
+ function isStandardFamily(family) {
7416
+ return family.toLowerCase() in STANDARD_FAMILIES;
7394
7417
  }
7395
- var OP = {
7396
- fontBBox: 5,
7397
- charset: 15,
7398
- charStrings: 17,
7399
- private: 18,
7400
- defaultWidthX: 20,
7401
- nominalWidthX: 21,
7402
- subrs: 19,
7403
- charstringType: 1206,
7404
- fontMatrix: 1207,
7405
- ros: 1230
7406
- };
7407
- var STANDARD_STRINGS = 391;
7408
- function readCff(bytes2) {
7409
- try {
7410
- const at = (offset2) => {
7411
- if (!Number.isInteger(offset2) || offset2 < 0 || offset2 >= bytes2.length)
7412
- throw new RangeError("Outside the font");
7413
- return bytes2[offset2];
7414
- };
7415
- const u162 = (offset2) => at(offset2) << 8 | at(offset2 + 1);
7416
- if (at(0) !== 1) return void 0;
7417
- const names2 = readIndex(bytes2, at(2), at, u162);
7418
- const tops = readIndex(bytes2, names2.end, at, u162);
7419
- const strings = readIndex(bytes2, tops.end, at, u162);
7420
- const globalSubrs = readIndex(bytes2, strings.end, at, u162);
7421
- if (names2.items.length !== 1 || tops.items.length !== 1) return void 0;
7422
- const top = readDict(bytes2, ...tops.items[0], at);
7423
- const charStringsAt = top.get(OP.charStrings)?.[0];
7424
- if (charStringsAt === void 0) return void 0;
7425
- if ((top.get(OP.charstringType)?.[0] ?? 2) !== 2) return void 0;
7426
- const charStrings = readIndex(bytes2, charStringsAt, at, u162);
7427
- const glyphCount = charStrings.items.length;
7428
- if (glyphCount === 0) return void 0;
7429
- const [scale = 1e-3, skewX = 0, skewY = 0, scaleY = 1e-3] = top.get(OP.fontMatrix) ?? [];
7430
- if (skewX !== 0 || skewY !== 0 || scale !== scaleY || scale <= 0)
7431
- return void 0;
7432
- const unitsPerEm = Math.round(1 / scale);
7433
- if (unitsPerEm < 16 || unitsPerEm > 16384) return void 0;
7434
- const [xMin = 0, yMin = 0, xMax = 0, yMax = 0] = top.get(OP.fontBBox) ?? [];
7435
- const cid = top.has(OP.ros);
7436
- const [nameStart, nameEnd] = names2.items[0];
7437
- const name = latin1(
7438
- bytes2.subarray(nameStart, Math.min(nameEnd, nameStart + 127))
7418
+ function standardFontName(family, bold, italic) {
7419
+ const variants = STANDARD_FAMILIES[family.toLowerCase()];
7420
+ if (!variants) return void 0;
7421
+ return variants[(bold ? 1 : 0) + (italic ? 2 : 0)];
7422
+ }
7423
+ function firstNonWinAnsi(text) {
7424
+ for (const character of text) {
7425
+ const code = character.codePointAt(0);
7426
+ if (code === 10) continue;
7427
+ if (code >= 32 && code <= 126 || code >= 160 && code <= 255 || WIN_ANSI_EXTRAS.has(code))
7428
+ continue;
7429
+ return character;
7430
+ }
7431
+ return void 0;
7432
+ }
7433
+ var FontLibrary = class {
7434
+ #registered = [];
7435
+ #fetch;
7436
+ #bytes = /* @__PURE__ */ new Map();
7437
+ #coverage = /* @__PURE__ */ new Map();
7438
+ /** Loaded PDFium font handles, keyed by document and font bytes. */
7439
+ #handles = /* @__PURE__ */ new Map();
7440
+ #fallbackUrl;
7441
+ #fallback;
7442
+ constructor(fetchBytes2) {
7443
+ this.#fetch = fetchBytes2;
7444
+ }
7445
+ setFallbackUrl(url) {
7446
+ this.#fallbackUrl = url;
7447
+ }
7448
+ register(fonts) {
7449
+ this.#registered.length = 0;
7450
+ this.#registered.push(...fonts);
7451
+ }
7452
+ /** Whether `family` names a registered font. */
7453
+ hasFamily(family) {
7454
+ return this.#registered.some((font) => sameFamily(font.family, family));
7455
+ }
7456
+ /**
7457
+ * Fetches whatever the given texts and families may need, so that
7458
+ * validation and drawing can run synchronously afterwards.
7459
+ */
7460
+ async prepare(requests) {
7461
+ const families = new Set(
7462
+ requests.map((request) => request.family.toLowerCase())
7439
7463
  );
7440
- if (cid)
7464
+ const fonts = this.#registered.filter(
7465
+ (font) => families.has(font.family.toLowerCase())
7466
+ );
7467
+ for (const font of fonts) await this.#bytesOf(font);
7468
+ const needsFallback = requests.some(
7469
+ (request) => (
7470
+ // Text the standard fonts cannot encode, or a family only the
7471
+ // fallback can stand in for (an embedded font that lost a glyph).
7472
+ (firstNonWinAnsi(request.text) !== void 0 || !isStandardFamily(request.family) && !this.hasFamily(request.family)) && !this.#coveringRegistered({ ...request, bold: false, italic: false })
7473
+ )
7474
+ );
7475
+ if (needsFallback && !this.#fallback && this.#fallbackUrl)
7476
+ this.#fallback = await this.#fetch(this.#fallbackUrl);
7477
+ }
7478
+ /** Checks a request against what is loaded; `undefined` means it will resolve. */
7479
+ problem(request) {
7480
+ const standard = isStandardFamily(request.family);
7481
+ if (!standard && !this.hasFamily(request.family))
7441
7482
  return {
7442
- name,
7443
- cid,
7444
- glyphNames: [],
7445
- widths: [],
7446
- bbox: [xMin, yMin, xMax, yMax],
7447
- unitsPerEm
7483
+ path: "/style/fontFamily",
7484
+ code: "unknown-font",
7485
+ message: `Unknown font family ${request.family}; Helvetica, Times, Courier and registered fonts are available`
7448
7486
  };
7449
- const custom = /* @__PURE__ */ new Map();
7450
- const stringOf = (sid) => {
7451
- if (sid < ISO_ADOBE_NAMES.length) return ISO_ADOBE_NAMES[sid];
7452
- const item = strings.items[sid - STANDARD_STRINGS];
7453
- if (!item) return void 0;
7454
- if (!custom.has(sid))
7455
- custom.set(
7456
- sid,
7457
- // A glyph name is 63 characters at most.
7458
- item[1] - item[0] <= 63 ? latin1(bytes2.subarray(...item)) : void 0
7459
- );
7460
- return custom.get(sid);
7487
+ if (standard && firstNonWinAnsi(request.text) === void 0)
7488
+ return void 0;
7489
+ if (this.#coveringRegistered(request)) return void 0;
7490
+ if (this.#fallback && this.#covers(this.#fallback, request.text))
7491
+ return void 0;
7492
+ const bad = this.#fallback ? this.#firstMissing(this.#fallback, request.text) : firstNonWinAnsi(request.text);
7493
+ return {
7494
+ path: "/text",
7495
+ code: "font-unavailable",
7496
+ message: bad ? `No available font can draw "${bad}" (U+${bad.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")})` : "No available font can draw the text"
7461
7497
  };
7462
- const glyphNames = charsetNames(
7463
- top.get(OP.charset)?.[0] ?? 0,
7464
- glyphCount,
7465
- stringOf,
7466
- at,
7467
- u162
7468
- );
7469
- const [privateSize, privateAt] = top.get(OP.private) ?? [0, 0];
7470
- const privateDict = privateSize ? readDict(bytes2, privateAt, privateAt + privateSize, at) : /* @__PURE__ */ new Map();
7471
- const localAt = privateDict.get(OP.subrs)?.[0];
7472
- const metrics = {
7473
- bytes: bytes2,
7474
- defaultWidth: privateDict.get(OP.defaultWidthX)?.[0] ?? 0,
7475
- nominalWidth: privateDict.get(OP.nominalWidthX)?.[0] ?? 0,
7476
- global: globalSubrs.items,
7477
- local: localAt === void 0 ? [] : readIndex(bytes2, privateAt + localAt, at, u162).items
7498
+ }
7499
+ /** The font to draw `request` with in `document`; `prepare` must have run. */
7500
+ resolve(pdfium, document2, request) {
7501
+ const { lib } = pdfium;
7502
+ if (isStandardFamily(request.family) && firstNonWinAnsi(request.text) === void 0) {
7503
+ const name = standardFontName(
7504
+ request.family,
7505
+ request.bold,
7506
+ request.italic
7507
+ );
7508
+ return {
7509
+ handle: lib.FPDFText_LoadStandardFont(document2, name),
7510
+ family: name
7511
+ };
7512
+ }
7513
+ const registered = this.#coveringRegistered(request);
7514
+ if (registered) {
7515
+ const exact = isBold(registered.font) === request.bold && isItalic(registered.font) === request.italic;
7516
+ return {
7517
+ handle: this.#load(pdfium, document2, registered.bytes),
7518
+ family: registered.font.family,
7519
+ ...exact ? {} : {
7520
+ substitution: `${registered.font.family} has no matching bold or italic face`
7521
+ }
7522
+ };
7523
+ }
7524
+ if (!this.#fallback || !this.#covers(this.#fallback, request.text))
7525
+ throw new Error(`No font covers "${request.text}"`);
7526
+ return {
7527
+ handle: this.#load(pdfium, document2, this.#fallback),
7528
+ family: FALLBACK_FAMILY,
7529
+ substitution: `${request.family} cannot draw this text; ${FALLBACK_FAMILY} is used`
7478
7530
  };
7479
- const widths = charStrings.items.map(
7480
- ([start, end]) => charstringWidth(metrics, start, end)
7531
+ }
7532
+ /** Closes the fonts loaded into a document that is going away. */
7533
+ release(pdfium, document2) {
7534
+ const handles = this.#handles.get(document2);
7535
+ if (!handles) return;
7536
+ for (const handle of handles.values()) pdfium.lib.FPDFFont_Close(handle);
7537
+ this.#handles.delete(document2);
7538
+ }
7539
+ covers(bytes2, text) {
7540
+ return this.#covers(bytes2, text);
7541
+ }
7542
+ #coveringRegistered(request) {
7543
+ const candidates = this.#registered.filter((font) => sameFamily(font.family, request.family)).map((font) => ({ font, bytes: this.#loadedBytes(font) })).filter(
7544
+ (entry) => entry.bytes !== void 0 && this.#covers(entry.bytes, request.text)
7481
7545
  );
7546
+ const score = (font) => (isBold(font) === request.bold ? 2 : 0) + (isItalic(font) === request.italic ? 1 : 0);
7547
+ return candidates.sort((a, b) => score(b.font) - score(a.font))[0];
7548
+ }
7549
+ #covers(bytes2, text) {
7550
+ return this.#firstMissing(bytes2, text) === void 0;
7551
+ }
7552
+ /** The first character of `text` the font has no glyph for; layout handles line breaks and spaces. */
7553
+ #firstMissing(bytes2, text) {
7554
+ let coverage = this.#coverage.get(bytes2);
7555
+ if (!coverage) {
7556
+ coverage = parseCmap(bytes2);
7557
+ this.#coverage.set(bytes2, coverage);
7558
+ }
7559
+ for (const character of text) {
7560
+ const code = character.codePointAt(0);
7561
+ if (code === 10 || code === 32) continue;
7562
+ if (!coverage.has(code)) return character;
7563
+ }
7564
+ return void 0;
7565
+ }
7566
+ #load(pdfium, document2, bytes2) {
7567
+ let handles = this.#handles.get(document2);
7568
+ if (!handles) {
7569
+ handles = /* @__PURE__ */ new Map();
7570
+ this.#handles.set(document2, handles);
7571
+ }
7572
+ let handle = handles.get(bytes2);
7573
+ if (handle === void 0) {
7574
+ const pointer = pdfium.writeBytes(bytes2);
7575
+ try {
7576
+ handle = pdfium.lib.FPDFText_LoadFont(
7577
+ document2,
7578
+ pointer,
7579
+ bytes2.byteLength,
7580
+ FONT_TRUETYPE,
7581
+ true
7582
+ );
7583
+ } finally {
7584
+ pdfium.free(pointer);
7585
+ }
7586
+ if (!handle) throw new Error("PDFium could not load the font");
7587
+ handles.set(bytes2, handle);
7588
+ }
7589
+ return handle;
7590
+ }
7591
+ #loadedBytes(font) {
7592
+ return typeof font.source === "string" ? this.#bytes.get(font.source) : this.#bytes.get(sourceKey(font)) ?? new Uint8Array(font.source);
7593
+ }
7594
+ async #bytesOf(font) {
7595
+ const key = sourceKey(font);
7596
+ let bytes2 = this.#bytes.get(key);
7597
+ if (!bytes2) {
7598
+ bytes2 = typeof font.source === "string" ? await this.#fetch(font.source) : new Uint8Array(font.source);
7599
+ this.#bytes.set(key, bytes2);
7600
+ }
7601
+ return bytes2;
7602
+ }
7603
+ };
7604
+ function sourceKey(font) {
7605
+ return typeof font.source === "string" ? font.source : `${font.family}:${font.weight}:${font.style}:${font.source.byteLength}`;
7606
+ }
7607
+ function sameFamily(a, b) {
7608
+ return a.toLowerCase() === b.toLowerCase();
7609
+ }
7610
+ function isBold(font) {
7611
+ return font.weight >= 600;
7612
+ }
7613
+ function isItalic(font) {
7614
+ return font.style !== "normal";
7615
+ }
7616
+ function glyphOutlines(view, tables) {
7617
+ const head2 = tables.get("head");
7618
+ const maxp = tables.get("maxp");
7619
+ const loca = tables.get("loca");
7620
+ if (!head2 || !maxp || !loca || !tables.has("glyf")) return void 0;
7621
+ const longOffsets = view.getInt16(head2.offset + 50) === 1;
7622
+ const count = view.getUint16(maxp.offset + 4);
7623
+ return (glyph) => {
7624
+ if (glyph < 0 || glyph >= count) return false;
7625
+ const at = loca.offset + glyph * (longOffsets ? 4 : 2);
7626
+ const start = longOffsets ? view.getUint32(at) : view.getUint16(at) * 2;
7627
+ const end = longOffsets ? view.getUint32(at + 4) : view.getUint16(at + 2) * 2;
7628
+ return end > start;
7629
+ };
7630
+ }
7631
+ function parseCmap(bytes2) {
7632
+ const view = new DataView(bytes2.buffer, bytes2.byteOffset, bytes2.byteLength);
7633
+ const ranges = [];
7634
+ const glyphs = /* @__PURE__ */ new Map();
7635
+ let outlines;
7636
+ try {
7637
+ const tables = view.getUint16(4);
7638
+ let cmapOffset = -1;
7639
+ const offsets = /* @__PURE__ */ new Map();
7640
+ for (let index = 0; index < tables; index += 1) {
7641
+ const record = 12 + index * 16;
7642
+ const tag = String.fromCharCode(
7643
+ bytes2[record],
7644
+ bytes2[record + 1],
7645
+ bytes2[record + 2],
7646
+ bytes2[record + 3]
7647
+ );
7648
+ offsets.set(tag, {
7649
+ offset: view.getUint32(record + 8),
7650
+ length: view.getUint32(record + 12)
7651
+ });
7652
+ if (tag === "cmap") cmapOffset = view.getUint32(record + 8);
7653
+ }
7654
+ outlines = glyphOutlines(view, offsets);
7655
+ if (cmapOffset < 0)
7656
+ return {
7657
+ has: () => false,
7658
+ drawable: () => false,
7659
+ glyph: () => void 0
7660
+ };
7661
+ const subtables = view.getUint16(cmapOffset + 2);
7662
+ let best;
7663
+ for (let index = 0; index < subtables; index += 1) {
7664
+ const record = cmapOffset + 4 + index * 8;
7665
+ const platform = view.getUint16(record);
7666
+ const encoding = view.getUint16(record + 2);
7667
+ const offset2 = cmapOffset + view.getUint32(record + 4);
7668
+ const format = view.getUint16(offset2);
7669
+ const unicode = platform === 0 || platform === 3 && (encoding === 1 || encoding === 10);
7670
+ if (!unicode || format !== 4 && format !== 12) continue;
7671
+ if (!best || format > best.format) best = { offset: offset2, format };
7672
+ }
7673
+ if (!best)
7674
+ return {
7675
+ has: () => false,
7676
+ drawable: () => false,
7677
+ glyph: () => void 0
7678
+ };
7679
+ if (best.format === 12) {
7680
+ const groups = view.getUint32(best.offset + 12);
7681
+ for (let index = 0; index < groups; index += 1) {
7682
+ const group = best.offset + 16 + index * 12;
7683
+ const start = view.getUint32(group);
7684
+ const end = view.getUint32(group + 4);
7685
+ const startGlyph = view.getUint32(group + 8);
7686
+ ranges.push([start, end]);
7687
+ for (let code = start; code <= end && code - start < 65536; code += 1)
7688
+ glyphs.set(code, startGlyph + (code - start));
7689
+ }
7690
+ } else {
7691
+ const segments = view.getUint16(best.offset + 6) / 2;
7692
+ const ends = best.offset + 14;
7693
+ const starts = ends + segments * 2 + 2;
7694
+ const deltas = starts + segments * 2;
7695
+ const rangeOffsets = deltas + segments * 2;
7696
+ for (let index = 0; index < segments; index += 1) {
7697
+ const end = view.getUint16(ends + index * 2);
7698
+ const start = view.getUint16(starts + index * 2);
7699
+ if (start === 65535) continue;
7700
+ ranges.push([start, end]);
7701
+ const delta = view.getInt16(deltas + index * 2);
7702
+ const rangeOffset = view.getUint16(rangeOffsets + index * 2);
7703
+ for (let code = start; code <= end; code += 1) {
7704
+ let glyph;
7705
+ if (rangeOffset === 0) glyph = code + delta & 65535;
7706
+ else {
7707
+ const at = rangeOffsets + index * 2 + rangeOffset + (code - start) * 2;
7708
+ const raw = view.getUint16(at);
7709
+ glyph = raw === 0 ? 0 : raw + delta & 65535;
7710
+ }
7711
+ glyphs.set(code, glyph);
7712
+ }
7713
+ }
7714
+ }
7715
+ } catch {
7716
+ return { has: () => false, drawable: () => false, glyph: () => void 0 };
7717
+ }
7718
+ ranges.sort((a, b) => a[0] - b[0]);
7719
+ return {
7720
+ glyph(codePoint) {
7721
+ return glyphs.get(codePoint);
7722
+ },
7723
+ drawable(codePoint) {
7724
+ const glyph = glyphs.get(codePoint);
7725
+ if (glyph === void 0 || glyph === 0) return false;
7726
+ return outlines ? outlines(glyph) : true;
7727
+ },
7728
+ has(codePoint) {
7729
+ let low = 0;
7730
+ let high = ranges.length - 1;
7731
+ while (low <= high) {
7732
+ const middle = low + high >> 1;
7733
+ const [start, end] = ranges[middle];
7734
+ if (codePoint < start) high = middle - 1;
7735
+ else if (codePoint > end) low = middle + 1;
7736
+ else return true;
7737
+ }
7738
+ return false;
7739
+ }
7740
+ };
7741
+ }
7742
+ var TextMeasurer = class {
7743
+ #pdfium;
7744
+ #document;
7745
+ #advances = /* @__PURE__ */ new Map();
7746
+ #metrics = /* @__PURE__ */ new Map();
7747
+ constructor(pdfium, document2) {
7748
+ this.#pdfium = pdfium;
7749
+ this.#document = document2;
7750
+ }
7751
+ /** Width of `text` in points at `fontSize`, without kerning. */
7752
+ advance(font, fontSize, text) {
7753
+ let total = 0;
7754
+ for (const character of text) total += this.#advanceOf(font, character);
7755
+ return total * fontSize / 1e3;
7756
+ }
7757
+ /** Ascent above and descent below the baseline, in points, both positive. */
7758
+ metrics(font, fontSize) {
7759
+ let unit = this.#metrics.get(font);
7760
+ if (!unit) {
7761
+ const { lib } = this.#pdfium;
7762
+ const ascent = this.#pdfium.readNumbers(
7763
+ 1,
7764
+ "float",
7765
+ ([pointer]) => lib.FPDFFont_GetAscent(font, 1e3, pointer)
7766
+ )?.[0] ?? 750;
7767
+ const descent = this.#pdfium.readNumbers(
7768
+ 1,
7769
+ "float",
7770
+ ([pointer]) => lib.FPDFFont_GetDescent(font, 1e3, pointer)
7771
+ )?.[0] ?? -250;
7772
+ unit = { ascent, descent: Math.abs(descent) };
7773
+ this.#metrics.set(font, unit);
7774
+ }
7482
7775
  return {
7483
- name,
7484
- cid,
7485
- glyphNames,
7486
- widths,
7487
- bbox: [xMin, yMin, xMax, yMax],
7488
- unitsPerEm
7776
+ ascent: unit.ascent * fontSize / 1e3,
7777
+ descent: unit.descent * fontSize / 1e3
7489
7778
  };
7490
- } catch (error) {
7491
- if (error instanceof RangeError) return void 0;
7492
- throw error;
7493
7779
  }
7494
- }
7495
- function cffWithSafeName(bytes2) {
7496
- try {
7497
- const at = (offset2) => {
7498
- if (offset2 < 0 || offset2 >= bytes2.length)
7499
- throw new RangeError("Outside the font");
7500
- return bytes2[offset2];
7501
- };
7502
- const u162 = (offset2) => at(offset2) << 8 | at(offset2 + 1);
7503
- const names2 = readIndex(bytes2, at(2), at, u162);
7504
- const [start, end] = names2.items[0] ?? [0, 0];
7505
- if (names2.items.length !== 1 || end - start > 127) return void 0;
7506
- const safe = bytes2.slice();
7507
- for (let offset2 = start; offset2 < end; offset2 += 1) {
7508
- const byte = safe[offset2];
7509
- if (offset2 === start && byte === 0) continue;
7510
- if (byte < 33 || byte > 126 || UNSAFE_NAME_BYTES.has(byte))
7511
- safe[offset2] = 95;
7780
+ #advanceOf(font, character) {
7781
+ let cache = this.#advances.get(font);
7782
+ if (!cache) {
7783
+ cache = /* @__PURE__ */ new Map();
7784
+ this.#advances.set(font, cache);
7512
7785
  }
7513
- return safe;
7514
- } catch (error) {
7515
- if (error instanceof RangeError) return void 0;
7516
- throw error;
7517
- }
7518
- }
7519
- var UNSAFE_NAME_BYTES = new Set(
7520
- [..."[](){}<>/%"].map((character) => character.charCodeAt(0))
7521
- );
7522
- function readIndex(bytes2, start, at, u162) {
7523
- const count = u162(start);
7524
- if (count === 0) return { items: [], end: start + 2 };
7525
- const offSize = at(start + 2);
7526
- if (offSize < 1 || offSize > 4) throw new RangeError("Bad offset size");
7527
- const offset2 = (index) => {
7528
- let value = 0;
7529
- for (let byte = 0; byte < offSize; byte += 1)
7530
- value = value * 256 + at(start + 3 + index * offSize + byte);
7531
- return value;
7532
- };
7533
- if (offset2(0) !== 1) throw new RangeError("Bad INDEX");
7534
- const base = start + 2 + (count + 1) * offSize;
7535
- const items = [];
7536
- for (let index = 0; index < count; index += 1) {
7537
- const from = base + offset2(index);
7538
- const to = base + offset2(index + 1);
7539
- if (to < from || to > bytes2.length) throw new RangeError("Bad INDEX");
7540
- items.push([from, to]);
7786
+ let advance2 = cache.get(character);
7787
+ if (advance2 === void 0) {
7788
+ advance2 = this.#inkWidth(font, `H${character}H`) - this.#inkWidth(font, "HH");
7789
+ cache.set(character, advance2);
7790
+ }
7791
+ return advance2;
7541
7792
  }
7542
- return { items, end: base + offset2(count) };
7543
- }
7544
- function readDict(bytes2, start, end, at) {
7545
- if (end > bytes2.length) throw new RangeError("DICT past the font");
7546
- const entries = /* @__PURE__ */ new Map();
7547
- let operands = [];
7548
- let offset2 = start;
7549
- while (offset2 < end) {
7550
- const byte = at(offset2);
7551
- if (byte <= 21) {
7552
- const operator = byte === 12 ? 1200 + at(offset2 + 1) : byte;
7553
- offset2 += byte === 12 ? 2 : 1;
7554
- entries.set(operator, operands);
7555
- operands = [];
7556
- } else if (byte === 28) {
7557
- operands.push(int16(at(offset2 + 1) << 8 | at(offset2 + 2)));
7558
- offset2 += 3;
7559
- } else if (byte === 29) {
7560
- operands.push(
7561
- at(offset2 + 1) << 24 | at(offset2 + 2) << 16 | at(offset2 + 3) << 8 | at(offset2 + 4)
7562
- );
7563
- offset2 += 5;
7564
- } else if (byte === 30) {
7565
- let text = "";
7566
- offset2 += 1;
7567
- for (let done = false; !done; offset2 += 1) {
7568
- const pair = at(offset2);
7569
- for (const nibble of [pair >> 4, pair & 15]) {
7570
- if (nibble === 15) {
7571
- done = true;
7572
- break;
7573
- }
7574
- text += REAL_NIBBLES[nibble] ?? "";
7575
- }
7793
+ #inkWidth(font, text) {
7794
+ const { lib } = this.#pdfium;
7795
+ const object = lib.FPDFPageObj_CreateTextObj(this.#document, font, 1e3);
7796
+ try {
7797
+ const wide = this.#pdfium.writeWideString(text);
7798
+ try {
7799
+ lib.FPDFText_SetText(object, wide);
7800
+ } finally {
7801
+ this.#pdfium.free(wide);
7576
7802
  }
7577
- operands.push(Number.parseFloat(text));
7578
- } else if (byte >= 32 && byte <= 246) {
7579
- operands.push(byte - 139);
7580
- offset2 += 1;
7581
- } else if (byte >= 247 && byte <= 250) {
7582
- operands.push((byte - 247) * 256 + at(offset2 + 1) + 108);
7583
- offset2 += 2;
7584
- } else if (byte >= 251 && byte <= 254) {
7585
- operands.push(-(byte - 251) * 256 - at(offset2 + 1) - 108);
7586
- offset2 += 2;
7587
- } else throw new RangeError("Bad DICT byte");
7803
+ const bounds = this.#pdfium.readNumbers(
7804
+ 4,
7805
+ "float",
7806
+ ([l, b, r, t]) => lib.FPDFPageObj_GetBounds(object, l, b, r, t)
7807
+ );
7808
+ return bounds ? bounds[2] - bounds[0] : 0;
7809
+ } finally {
7810
+ lib.FPDFPageObj_Destroy(object);
7811
+ }
7588
7812
  }
7589
- return entries;
7590
- }
7591
- var REAL_NIBBLES = [
7592
- "0",
7593
- "1",
7594
- "2",
7595
- "3",
7596
- "4",
7597
- "5",
7598
- "6",
7599
- "7",
7600
- "8",
7601
- "9",
7602
- ".",
7603
- "E",
7604
- "E-",
7605
- "",
7606
- "-"
7607
- ];
7608
- function charsetNames(offset2, glyphCount, stringOf, at, u162) {
7609
- const names2 = [".notdef"];
7610
- if (offset2 === 0) {
7611
- for (let glyph = 1; glyph < glyphCount; glyph += 1)
7612
- names2.push(stringOf(glyph));
7613
- return names2;
7813
+ };
7814
+ function fontCanDraw(pdfium, font, text) {
7815
+ const { lib } = pdfium;
7816
+ if (!lib.FPDFFont_GetIsEmbedded(font)) {
7817
+ const base = pdfium.readUtf8String(
7818
+ (buffer, bytes2) => lib.FPDFFont_GetBaseFontName(font, buffer, bytes2)
7819
+ );
7820
+ return isStandardFamily(base.split(/[-,]/)[0] ?? "") && firstNonWinAnsi(text) === void 0;
7614
7821
  }
7615
- if (offset2 <= 2) return [...names2, ...Array(glyphCount - 1)];
7616
- const format = at(offset2);
7617
- let cursor = offset2 + 1;
7618
- if (format === 0) {
7619
- for (let glyph = 1; glyph < glyphCount; glyph += 1, cursor += 2)
7620
- names2.push(stringOf(u162(cursor)));
7621
- } else if (format === 1 || format === 2) {
7622
- while (names2.length < glyphCount) {
7623
- const first = u162(cursor);
7624
- const left = format === 1 ? at(cursor + 2) : u162(cursor + 2);
7625
- cursor += format === 1 ? 3 : 4;
7626
- for (let step = 0; step <= left && names2.length < glyphCount; step += 1)
7627
- names2.push(stringOf(first + step));
7628
- }
7629
- } else throw new RangeError("Bad charset format");
7630
- return names2;
7822
+ const coverage = fontCoverage(pdfium, font);
7823
+ if (!coverage) return false;
7824
+ return [...text].every((character) => {
7825
+ const code = character.codePointAt(0);
7826
+ if (code === 32) return (coverage.glyph(code) ?? 0) !== 0;
7827
+ return code === 10 || coverage.drawable(code);
7828
+ });
7631
7829
  }
7632
- function charstringWidth(context, start, end) {
7633
- const { bytes: bytes2, defaultWidth, nominalWidth } = context;
7634
- const stack = [];
7635
- const walk = (from, to, depth) => {
7636
- let offset2 = from;
7637
- while (offset2 < to) {
7638
- const byte = bytes2[offset2];
7639
- const size = byte === 28 ? 3 : byte === 255 ? 5 : byte >= 247 && byte <= 254 ? 2 : 1;
7640
- if (offset2 + size > to) return defaultWidth;
7641
- if (byte === 28 || byte >= 32) {
7642
- const next = (step) => bytes2[offset2 + step];
7643
- stack.push(
7644
- byte === 28 ? int16(next(1) << 8 | next(2)) : byte <= 246 ? byte - 139 : byte <= 250 ? (byte - 247) * 256 + next(1) + 108 : byte <= 254 ? -(byte - 251) * 256 - next(1) - 108 : (next(1) << 24 | next(2) << 16 | next(3) << 8 | next(4)) / 65536
7645
- );
7646
- if (stack.length > 48) return defaultWidth;
7647
- offset2 += size;
7648
- continue;
7649
- }
7650
- offset2 += 1;
7651
- if (byte === 10 || byte === 29) {
7652
- const subrs = byte === 10 ? context.local : context.global;
7653
- const index = stack.pop();
7654
- const subr = index === void 0 ? void 0 : subrs[index + subrBias(subrs.length)];
7655
- if (!subr || depth >= 10) return defaultWidth;
7656
- const width = walk(subr[0], subr[1], depth + 1);
7657
- if (width !== void 0) return width;
7658
- continue;
7659
- }
7660
- if (byte === 11) return depth > 0 ? void 0 : defaultWidth;
7661
- const takes = STACK_CLEARING[byte];
7662
- if (takes === void 0) return defaultWidth;
7663
- const count = stack.length;
7664
- const widthFirst = takes === "pairs" ? count % 2 === 1 : takes === "endchar" ? count === 1 || count === 5 : count > takes;
7665
- return widthFirst ? stack[0] + nominalWidth : defaultWidth;
7830
+ function fontCanRewrite(pdfium, font, text) {
7831
+ const characters = [...text].filter((character) => character !== "\n");
7832
+ if (!characters.every(
7833
+ (character) => advance(pdfium, font, character.codePointAt(0)) > 0
7834
+ ))
7835
+ return false;
7836
+ if (fontCanDraw(pdfium, font, text)) return true;
7837
+ if (!pdfium.lib.FPDFFont_GetIsEmbedded(font)) return false;
7838
+ const data = fontBytes(pdfium, font);
7839
+ const coverage = data && cffCoverage(data);
7840
+ if (!coverage) return false;
7841
+ return characters.every(
7842
+ (character) => coverage.has(character.codePointAt(0))
7843
+ );
7844
+ }
7845
+ function advance(pdfium, font, code) {
7846
+ if (code > 65535) return 0;
7847
+ return pdfium.readNumbers(
7848
+ 1,
7849
+ "float",
7850
+ ([width]) => pdfium.lib.FPDFFont_GetGlyphWidth(font, code, 1e3, width)
7851
+ )?.[0] ?? 0;
7852
+ }
7853
+ function cffCoverage(data) {
7854
+ const cff = readCff(data);
7855
+ if (!cff || cff.cid) return void 0;
7856
+ const glyphs = /* @__PURE__ */ new Map();
7857
+ cff.glyphNames.forEach((name, glyph) => {
7858
+ const code = glyphUnicode(name);
7859
+ if (glyph !== 0 && code !== void 0 && !glyphs.has(code))
7860
+ glyphs.set(code, glyph);
7861
+ });
7862
+ return {
7863
+ has: (code) => glyphs.has(code),
7864
+ glyph: (code) => glyphs.get(code),
7865
+ drawable: (code) => glyphs.has(code)
7866
+ };
7867
+ }
7868
+ function fontCoverage(pdfium, font) {
7869
+ const data = fontBytes(pdfium, font);
7870
+ if (!data) return void 0;
7871
+ const tag = String.fromCharCode(...data.slice(0, 4));
7872
+ const truetype = data[0] === 0 && data[1] === 1 && data[2] === 0 && data[3] === 0 || tag === "true";
7873
+ return truetype ? parseCmap(data) : void 0;
7874
+ }
7875
+ function fontBytes(pdfium, font) {
7876
+ const { lib } = pdfium;
7877
+ const size = pdfium.readNumbers(
7878
+ 1,
7879
+ "i32",
7880
+ ([out]) => lib.FPDFFont_GetFontData(font, 0, 0, out)
7881
+ )?.[0];
7882
+ if (!size) return void 0;
7883
+ const buffer = pdfium.malloc(size);
7884
+ try {
7885
+ const out = pdfium.malloc(4);
7886
+ try {
7887
+ if (!lib.FPDFFont_GetFontData(font, buffer, size, out)) return void 0;
7888
+ } finally {
7889
+ pdfium.free(out);
7666
7890
  }
7667
- return depth > 0 ? void 0 : defaultWidth;
7668
- };
7669
- return walk(start, end, 0) ?? defaultWidth;
7670
- }
7671
- function subrBias(count) {
7672
- return count < 1240 ? 107 : count < 33900 ? 1131 : 32768;
7673
- }
7674
- var STACK_CLEARING = {
7675
- 1: "pairs",
7676
- // hstem
7677
- 3: "pairs",
7678
- // vstem
7679
- 18: "pairs",
7680
- // hstemhm
7681
- 23: "pairs",
7682
- // vstemhm
7683
- 19: "pairs",
7684
- // hintmask, after implied vstems
7685
- 20: "pairs",
7686
- // cntrmask
7687
- 21: 2,
7688
- // rmoveto
7689
- 22: 1,
7690
- // hmoveto
7691
- 4: 1,
7692
- // vmoveto
7693
- 14: "endchar"
7694
- // endchar: none, or four for an accented character
7695
- };
7696
- function int16(value) {
7697
- return value >= 32768 ? value - 65536 : value;
7891
+ return pdfium.readBytes(buffer, size);
7892
+ } finally {
7893
+ pdfium.free(buffer);
7894
+ }
7698
7895
  }
7699
- function latin1(bytes2) {
7700
- return String.fromCharCode(...bytes2);
7896
+ function validateScript(text, issue) {
7897
+ for (const character of text) {
7898
+ const code = character.codePointAt(0);
7899
+ if (code >= 1424 && code <= 2303 || code >= 64285 && code <= 65023 || code >= 65136 && code <= 65279) {
7900
+ issue(
7901
+ "/text",
7902
+ "unsupported-script",
7903
+ `Right-to-left text such as "${character}" cannot be edited yet`
7904
+ );
7905
+ return false;
7906
+ }
7907
+ }
7908
+ return true;
7701
7909
  }
7702
7910
 
7703
7911
  // src/edit/pdf/engine/opentype.ts
@@ -8088,7 +8296,7 @@ function textFaceOf(pdfium, object, cache) {
8088
8296
  );
8089
8297
  if (!lib.FPDFFont_GetIsEmbedded(font))
8090
8298
  return { key: `name:${baseName}`, family, missing: "not-embedded" };
8091
- const program = fontProgram(pdfium, font);
8299
+ const program = fontBytes(pdfium, font);
8092
8300
  if (!program)
8093
8301
  return { key: `embedded:${baseName}`, family, missing: "unreadable" };
8094
8302
  const key = `${baseName}#${fnv1a(program).toString(16)}`;
@@ -8118,23 +8326,6 @@ function faceOf(program, baseName) {
8118
8326
  const data = cffOpenType(program, cff, unicode);
8119
8327
  return data ? { face: { data, format: "opentype" } } : { missing: "no-unicode" };
8120
8328
  }
8121
- function fontProgram(pdfium, font) {
8122
- const { lib } = pdfium;
8123
- const size = pdfium.readNumbers(
8124
- 1,
8125
- "i32",
8126
- ([out2]) => lib.FPDFFont_GetFontData(font, 0, 0, out2)
8127
- )?.[0];
8128
- if (!size) return void 0;
8129
- const buffer = pdfium.malloc(size);
8130
- const out = pdfium.malloc(4);
8131
- try {
8132
- return lib.FPDFFont_GetFontData(font, buffer, size, out) ? pdfium.readBytes(buffer, size) : void 0;
8133
- } finally {
8134
- pdfium.free(out);
8135
- pdfium.free(buffer);
8136
- }
8137
- }
8138
8329
  function fnv1a(bytes2) {
8139
8330
  let hash = 2166136261;
8140
8331
  for (const byte of bytes2) hash = Math.imul(hash ^ byte, 16777619);
@@ -8474,7 +8665,7 @@ function resolveTableStyle(style) {
8474
8665
  headerFill: style.headerFill ?? null
8475
8666
  };
8476
8667
  }
8477
- function layoutTable(spec, ascent, advance) {
8668
+ function layoutTable(spec, ascent, advance2) {
8478
8669
  const { cellPadding, fontSize } = spec.style;
8479
8670
  const columns = [];
8480
8671
  let x = spec.at.x;
@@ -8494,7 +8685,7 @@ function layoutTable(spec, ascent, advance) {
8494
8685
  lineHeight: LINE_HEIGHT,
8495
8686
  align: "left",
8496
8687
  ascent,
8497
- advance
8688
+ advance: advance2
8498
8689
  })
8499
8690
  );
8500
8691
  const height = Math.max(...layouts.map((layout) => layout.height)) + 2 * cellPadding;
@@ -8698,9 +8889,9 @@ function removeObjects(context, location) {
8698
8889
  const object = lib.FPDFPage_GetObject(page, index);
8699
8890
  lib.FPDFPage_RemoveObject(page, object);
8700
8891
  lib.FPDFPageObj_Destroy(object);
8892
+ context.spliceObjects(location.pageIndex, index, 1, []);
8701
8893
  }
8702
8894
  });
8703
- context.spliceObjects(location.pageIndex, first, location.indexes.length, []);
8704
8895
  return first;
8705
8896
  }
8706
8897
  function textBoxTarget(target, context, issue = () => {
@@ -8761,6 +8952,7 @@ function fontRequestsOf(operations, markOf, elementOf) {
8761
8952
  text: operation2.text
8762
8953
  });
8763
8954
  break;
8955
+ case "replaceParagraphText":
8764
8956
  case "replaceText": {
8765
8957
  const box = spec(operation2.target);
8766
8958
  if (box) {
@@ -8768,14 +8960,20 @@ function fontRequestsOf(operations, markOf, elementOf) {
8768
8960
  break;
8769
8961
  }
8770
8962
  const element = elementOf(operation2.target);
8771
- if (element?.kind === "text")
8963
+ if (element?.kind === "text" || element?.kind === "paragraph")
8772
8964
  requests.push({
8773
8965
  family: element.textStyle?.fontFamily ?? "Helvetica",
8774
- text: operation2.text
8966
+ text: element.kind === "paragraph" && operation2.range ? (element.text ?? "").slice(0, operation2.range.start.offset) + operation2.text + (element.text ?? "").slice(operation2.range.end.offset) : operation2.text
8775
8967
  });
8776
8968
  break;
8777
8969
  }
8778
8970
  case "setTextStyle": {
8971
+ const paragraph = elementOf(operation2.target);
8972
+ if (paragraph?.kind === "paragraph" && operation2.style.fontSize !== void 0 && operation2.style.fontSize !== paragraph.textStyle?.fontSize)
8973
+ requests.push({
8974
+ family: paragraph.textStyle?.fontFamily ?? "Helvetica",
8975
+ text: paragraph.text ?? ""
8976
+ });
8779
8977
  const box = spec(operation2.target);
8780
8978
  if (box)
8781
8979
  requests.push({
@@ -8813,83 +9011,826 @@ ${operation2.text}`
8813
9011
  });
8814
9012
  break;
8815
9013
  }
8816
- default:
8817
- break;
9014
+ default:
9015
+ break;
9016
+ }
9017
+ }
9018
+ return requests;
9019
+ }
9020
+ function fontRequest(style, text) {
9021
+ return {
9022
+ family: style.fontFamily,
9023
+ bold: style.bold,
9024
+ italic: style.italic,
9025
+ text
9026
+ };
9027
+ }
9028
+ function validateFont(request, context, issue) {
9029
+ const problem = context.fonts.problem(request);
9030
+ if (problem) issue(problem.path, problem.code, problem.message);
9031
+ }
9032
+ function placeUpright(pdfium, object, geometry, pageX, pageY) {
9033
+ const [a, b, c, d] = ROTATIONS[geometry.rotation % 4];
9034
+ const origin = pageToUser(geometry, pageX, pageY);
9035
+ pdfium.lib.FPDFPageObj_Transform(object, a, b, c, d, origin.x, origin.y);
9036
+ }
9037
+ var ROTATIONS = [
9038
+ [1, 0, 0, 1],
9039
+ [0, 1, -1, 0],
9040
+ [-1, 0, 0, -1],
9041
+ [0, -1, 1, 0]
9042
+ ];
9043
+ function setText(pdfium, object, text) {
9044
+ const wide = pdfium.writeWideString(text);
9045
+ try {
9046
+ pdfium.lib.FPDFText_SetText(object, wide);
9047
+ } finally {
9048
+ pdfium.free(wide);
9049
+ }
9050
+ }
9051
+ function parseColor(color) {
9052
+ return [1, 3, 5].map(
9053
+ (offset2) => Number.parseInt(color.slice(offset2, offset2 + 2), 16)
9054
+ );
9055
+ }
9056
+ function resolveStyle(style) {
9057
+ return {
9058
+ fontFamily: style.fontFamily ?? "Helvetica",
9059
+ fontSize: style.fontSize ?? 12,
9060
+ bold: style.bold ?? false,
9061
+ italic: style.italic ?? false,
9062
+ color: style.color ?? "#000000",
9063
+ align: style.align ?? "left",
9064
+ lineHeight: style.lineHeight ?? 1.2
9065
+ };
9066
+ }
9067
+ function definedFields(style) {
9068
+ return Object.fromEntries(
9069
+ Object.entries(style).filter(([, value]) => value !== void 0)
9070
+ );
9071
+ }
9072
+ function validateRect(rect, geometry, issue, path = "/rect") {
9073
+ const size = displayedSize(geometry);
9074
+ const tolerance = 0.01;
9075
+ if (rect.x < -tolerance || rect.y < -tolerance || rect.x + rect.width > size.width + tolerance || rect.y + rect.height > size.height + tolerance)
9076
+ issue(
9077
+ path,
9078
+ "range",
9079
+ `The rectangle must lie within the ${size.width}\xD7${size.height} pt page`
9080
+ );
9081
+ }
9082
+
9083
+ // src/edit/pdf/engine/operations.ts
9084
+ function issueCollector(operationIndex, issues) {
9085
+ return (path, code, message) => issues.push({ operationIndex, path, code, message });
9086
+ }
9087
+
9088
+ // src/edit/pdf/engine/paragraph.ts
9089
+ function paragraphElement(paragraph) {
9090
+ return {
9091
+ id: paragraph.id,
9092
+ kind: "paragraph",
9093
+ pageIndex: paragraph.pageIndex,
9094
+ text: paragraph.text,
9095
+ bounds: paragraph.bounds,
9096
+ textStyle: paragraph.textStyle,
9097
+ operations: [
9098
+ "replaceParagraphText",
9099
+ "replaceText",
9100
+ "setTextStyle",
9101
+ "deleteElement"
9102
+ ]
9103
+ };
9104
+ }
9105
+ var MIN_CONTINUATION_FILL = 0.9;
9106
+ var MAX_CONNECTED_EDGE_GAP = 0.65;
9107
+ var MAX_LEFT_ALIGNMENT_DELTA = 1;
9108
+ var ADJACENT_BASELINE_EM = 0.25;
9109
+ var ADJACENT_TEXT_GAP_EM = 3;
9110
+ var LIST_ITEM_START = /^(?:[•◦▪‣·*–—-]|\(?\d{1,3}[.)]|\(?[a-z][.)])\s/iu;
9111
+ var NUMERIC_VALUE = /^[\d\s.,:/%()+−–—-]+$/u;
9112
+ var GLYPH_POSITION_PRECISION = 1e-3;
9113
+ var MAX_PAIR_ADJUSTMENT_EM = 0.1;
9114
+ var MATRIX_RELATIVE_PRECISION = 1e-6;
9115
+ function discoverParagraphs(pdfium, page, pageIndex, geometry, records, elements, textPage, measurer) {
9116
+ const result3 = /* @__PURE__ */ new Map();
9117
+ if (geometry.rotation !== 0) return result3;
9118
+ const indexesById = /* @__PURE__ */ new Map();
9119
+ records.forEach((record, index) => {
9120
+ const indexes = indexesById.get(record.id) ?? [];
9121
+ indexes.push(index);
9122
+ indexesById.set(record.id, indexes);
9123
+ });
9124
+ const elementsById = /* @__PURE__ */ new Map();
9125
+ for (const element of elements)
9126
+ if (!elementsById.has(element.id)) elementsById.set(element.id, element);
9127
+ for (const element of elements) {
9128
+ const indexes = indexesById.get(element.id) ?? [];
9129
+ const spec = paragraphSpecOf(records[indexes[0] ?? -1]?.mark);
9130
+ if (!spec) continue;
9131
+ const paragraph = {
9132
+ id: element.id,
9133
+ pageIndex,
9134
+ text: spec.text,
9135
+ bounds: spec.rect,
9136
+ textStyle: spec.style,
9137
+ memberIds: [element.id],
9138
+ members: [{ elementId: element.id, start: 0, end: spec.text.length }]
9139
+ };
9140
+ result3.set(element.id, { paragraph, spec, indexes });
9141
+ }
9142
+ const rows = [];
9143
+ const uprightTexts = /* @__PURE__ */ new Map();
9144
+ const fontKeys = /* @__PURE__ */ new Map();
9145
+ const programs = [];
9146
+ const fontKey = (font) => {
9147
+ const cached = fontKeys.get(font);
9148
+ if (cached) return cached;
9149
+ let key;
9150
+ if (!pdfium.lib.FPDFFont_GetIsEmbedded(font))
9151
+ key = `standard:${pdfium.readUtf8String((buffer, bytes2) => pdfium.lib.FPDFFont_GetBaseFontName(font, buffer, bytes2))}`;
9152
+ else {
9153
+ const bytes2 = fontBytes(pdfium, font);
9154
+ let at = bytes2 ? programs.findIndex(
9155
+ (program) => program.length === bytes2.length && program.every((byte, index) => byte === bytes2[index])
9156
+ ) : -1;
9157
+ if (at < 0 && bytes2) {
9158
+ at = programs.length;
9159
+ programs.push(bytes2);
9160
+ }
9161
+ key = bytes2 ? `embedded:${at}` : `unreadable:${font}`;
9162
+ }
9163
+ fontKeys.set(font, key);
9164
+ return key;
9165
+ };
9166
+ const { lib } = pdfium;
9167
+ const rightEdges = /* @__PURE__ */ new Map();
9168
+ const glyphsByObject = /* @__PURE__ */ new Map();
9169
+ for (let at = 0; at < lib.FPDFText_CountChars(textPage); at += 1) {
9170
+ const unicode = lib.FPDFText_GetUnicode(textPage, at);
9171
+ if (!Number.isInteger(unicode) || unicode < 32 || unicode > 1114111 || unicode >= 55296 && unicode <= 57343)
9172
+ continue;
9173
+ const object = lib.FPDFText_GetTextObject(textPage, at);
9174
+ if (!object) continue;
9175
+ const text = String.fromCodePoint(unicode);
9176
+ const origin = pdfium.readNumbers(
9177
+ 2,
9178
+ "double",
9179
+ ([x, y]) => lib.FPDFText_GetCharOrigin(textPage, at, x, y)
9180
+ );
9181
+ if (origin) {
9182
+ const point = userToPage(geometry, origin[0], origin[1]);
9183
+ const glyphs = glyphsByObject.get(object) ?? [];
9184
+ glyphs.push({ text, ...point });
9185
+ glyphsByObject.set(object, glyphs);
9186
+ }
9187
+ if (/\s/u.test(text)) continue;
9188
+ const box = pdfium.readNumbers(
9189
+ 4,
9190
+ "float",
9191
+ ([out]) => lib.FPDFText_GetLooseCharBox(textPage, at, out)
9192
+ );
9193
+ if (box)
9194
+ rightEdges.set(
9195
+ object,
9196
+ Math.max(
9197
+ rightEdges.get(object) ?? -Infinity,
9198
+ userToPage(geometry, box[2], box[1]).x
9199
+ )
9200
+ );
9201
+ }
9202
+ records.forEach((record, index) => {
9203
+ if (record.mark || record.staleMarkId || record.type !== OBJECT_TEXT || record.id.includes("/"))
9204
+ return;
9205
+ const element = elementsById.get(record.id);
9206
+ if (!element?.text?.trim()) return;
9207
+ const object = lib.FPDFPage_GetObject(page, index);
9208
+ const matrix = pdfium.readNumbers(
9209
+ 6,
9210
+ "float",
9211
+ ([out]) => lib.FPDFPageObj_GetMatrix(object, out)
9212
+ );
9213
+ if (!matrix || matrix[0] <= 0 || matrix[3] <= 0 || Math.abs(matrix[1]) > 1e-3 || Math.abs(matrix[2]) > 1e-3)
9214
+ return;
9215
+ const origin = userToPage(geometry, matrix[4], matrix[5]);
9216
+ uprightTexts.set(element.id, { element, baseline: origin.y });
9217
+ if (!element.textStyle || element.text !== element.text.trimStart() || element.rotation || element.text.includes("") || /[\r\n\t]/u.test(element.text) || LIST_ITEM_START.test(element.text) || NUMERIC_VALUE.test(element.text) || Math.abs(matrix[0] - matrix[3]) > Math.max(matrix[0], matrix[3]) * MATRIX_RELATIVE_PRECISION)
9218
+ return;
9219
+ const rgba = pdfium.readNumbers(
9220
+ 4,
9221
+ "i32",
9222
+ ([r, g, b, a]) => lib.FPDFPageObj_GetFillColor(object, r, g, b, a)
9223
+ );
9224
+ if (rgba?.[3] !== 255 || lib.FPDFTextObj_GetTextRenderMode(object) !== 0)
9225
+ return;
9226
+ const font = lib.FPDFTextObj_GetFont(object);
9227
+ const rawSize = pdfium.readNumbers(
9228
+ 1,
9229
+ "float",
9230
+ ([out]) => lib.FPDFTextObj_GetFontSize(object, out)
9231
+ )?.[0];
9232
+ if (!rawSize) return;
9233
+ const effective = {
9234
+ ...element,
9235
+ textStyle: {
9236
+ ...element.textStyle,
9237
+ fontSize: round(rawSize * matrix[0])
9238
+ }
9239
+ };
9240
+ if (effective.textStyle.fontSize < 1 || effective.textStyle.fontSize > 500)
9241
+ return;
9242
+ if (fontCanDraw(pdfium, font, element.text) && !hasSupportedAdvances(
9243
+ glyphsByObject.get(object) ?? [],
9244
+ element.text,
9245
+ font,
9246
+ rawSize * matrix[0],
9247
+ measurer
9248
+ ))
9249
+ return;
9250
+ const width = (rightEdges.get(object) ?? element.bounds.x + element.bounds.width) - origin.x;
9251
+ if (!(width > 0) || element.bounds.width > width + 2) return;
9252
+ rows.push({
9253
+ element: effective,
9254
+ index,
9255
+ font: fontKey(font),
9256
+ fontHandle: font,
9257
+ x: origin.x,
9258
+ baseline: origin.y,
9259
+ width,
9260
+ firstWordAdvance: observedFirstWordAdvance(
9261
+ glyphsByObject.get(object) ?? [],
9262
+ element.text
9263
+ )
9264
+ });
9265
+ });
9266
+ rows.sort((a, b) => a.baseline - b.baseline || a.x - b.x);
9267
+ const blockedRows = rowsWithAdjacentText(rows, [...uprightTexts.values()]);
9268
+ const used = /* @__PURE__ */ new Set();
9269
+ for (const first of rows) {
9270
+ if (used.has(first.element.id) || blockedRows.has(first.element.id))
9271
+ continue;
9272
+ const aligned = rows.filter(
9273
+ (row) => !used.has(row.element.id) && row.baseline >= first.baseline && compatible(first, row)
9274
+ );
9275
+ const gaps = aligned.slice(1).map((row, index) => row.baseline - aligned[index].baseline).filter(
9276
+ (gap) => gap >= first.element.textStyle.fontSize * 0.8 && gap <= first.element.textStyle.fontSize * 2.1
9277
+ );
9278
+ if (gaps.length === 0) continue;
9279
+ const leading = Math.min(...gaps);
9280
+ const group = [];
9281
+ for (const row of aligned) {
9282
+ const previous = group.at(-1);
9283
+ if (previous && (Math.abs(row.baseline - previous.baseline - leading) > Math.max(1, leading * 0.15) || /[-\u00ad\u0002]$/u.test(previous.element.text.trim())))
9284
+ break;
9285
+ group.push(row);
9286
+ }
9287
+ if (group.length < 2 || group.some((row) => blockedRows.has(row.element.id)))
9288
+ continue;
9289
+ const bounds = unionRects(group.map((row) => row.element.bounds));
9290
+ const rect = roundRect({
9291
+ x: first.x,
9292
+ y: bounds.y,
9293
+ width: Math.max(...group.map((row) => row.width)),
9294
+ height: bounds.height
9295
+ });
9296
+ const ids = new Set(group.map((row) => row.element.id));
9297
+ const firstIndex = Math.min(...group.map((row) => row.index));
9298
+ if (group.slice(0, -1).some(
9299
+ (row, index) => !continuesAtWrap(
9300
+ row,
9301
+ group[index + 1],
9302
+ rect.width,
9303
+ pdfium,
9304
+ measurer
9305
+ )
9306
+ ) || elements.some((element) => {
9307
+ if (ids.has(element.id) || !element.text?.trim()) return false;
9308
+ const b = element.bounds;
9309
+ const upright = uprightTexts.get(element.id);
9310
+ const sharesRow = upright && group.some((row) => isAdjacentText(row, upright));
9311
+ if (sharesRow) return true;
9312
+ const sharesColumn = b.x < rect.x + rect.width && b.x + b.width > rect.x;
9313
+ const edgeGap = Math.max(
9314
+ b.y - (rect.y + rect.height),
9315
+ rect.y - (b.y + b.height)
9316
+ );
9317
+ return sharesColumn && edgeGap <= leading * MAX_CONNECTED_EDGE_GAP;
9318
+ }))
9319
+ continue;
9320
+ if (elements.some(
9321
+ (element) => !ids.has(element.id) && intersects(rect, element.bounds) && !backgroundBehind(element, rect, records, firstIndex)
9322
+ ))
9323
+ continue;
9324
+ const id = `${first.element.id}:paragraph`;
9325
+ if (elementsById.has(id)) continue;
9326
+ let text = "";
9327
+ const members = group.map((row) => {
9328
+ if (text) text += " ";
9329
+ const start = text.length;
9330
+ text += row.element.text.trim();
9331
+ return { elementId: row.element.id, start, end: text.length };
9332
+ });
9333
+ if (text.length > 2e4) continue;
9334
+ const style = {
9335
+ ...first.element.textStyle,
9336
+ lineHeight: leading / first.element.textStyle.fontSize
9337
+ };
9338
+ const paragraph = {
9339
+ id,
9340
+ pageIndex,
9341
+ text,
9342
+ bounds: rect,
9343
+ textStyle: style,
9344
+ memberIds: [...ids],
9345
+ members
9346
+ };
9347
+ const target = {
9348
+ paragraph,
9349
+ spec: {
9350
+ kind: "paragraph",
9351
+ id,
9352
+ text,
9353
+ lines: group.map((row) => row.element.text.trim()),
9354
+ rect,
9355
+ style,
9356
+ baselineOffset: (first.baseline - rect.y) / style.fontSize
9357
+ },
9358
+ indexes: group.map((row) => row.index).sort((a, b) => a - b)
9359
+ };
9360
+ result3.set(id, target);
9361
+ for (const member of members) {
9362
+ result3.set(member.elementId, target);
9363
+ used.add(member.elementId);
9364
+ }
9365
+ }
9366
+ return result3;
9367
+ }
9368
+ function compatible(a, b) {
9369
+ return Math.abs(a.x - b.x) <= MAX_LEFT_ALIGNMENT_DELTA && sameTypography(a, b);
9370
+ }
9371
+ function sameTypography(a, b) {
9372
+ const x = a.element.textStyle;
9373
+ const y = b.element.textStyle;
9374
+ return a.font === b.font && x.fontSize === y.fontSize && x.color === y.color && x.bold === y.bold && x.italic === y.italic;
9375
+ }
9376
+ function rowsWithAdjacentText(rows, texts) {
9377
+ texts.sort((a, b) => a.baseline - b.baseline);
9378
+ const blocked = /* @__PURE__ */ new Set();
9379
+ for (const row of rows) {
9380
+ const tolerance = row.element.textStyle.fontSize * ADJACENT_BASELINE_EM;
9381
+ let low = 0;
9382
+ let high = texts.length;
9383
+ while (low < high) {
9384
+ const mid = low + high >>> 1;
9385
+ if (texts[mid].baseline < row.baseline - tolerance) low = mid + 1;
9386
+ else high = mid;
9387
+ }
9388
+ for (let at = low; at < texts.length; at += 1) {
9389
+ const other = texts[at];
9390
+ if (other.baseline > row.baseline + tolerance) break;
9391
+ if (isAdjacentText(row, other)) {
9392
+ blocked.add(row.element.id);
9393
+ break;
9394
+ }
9395
+ }
9396
+ }
9397
+ return blocked;
9398
+ }
9399
+ function isAdjacentText(row, other) {
9400
+ if (row.element.id === other.element.id) return false;
9401
+ const size = row.element.textStyle.fontSize;
9402
+ if (Math.abs(row.baseline - other.baseline) > size * ADJACENT_BASELINE_EM)
9403
+ return false;
9404
+ const a = row.element.bounds;
9405
+ const b = other.element.bounds;
9406
+ const gap = Math.max(b.x - (a.x + a.width), a.x - (b.x + b.width));
9407
+ return gap <= size * ADJACENT_TEXT_GAP_EM;
9408
+ }
9409
+ function continuesAtWrap(row, next, width, pdfium, measurer) {
9410
+ if (row.width >= width * MIN_CONTINUATION_FILL) return true;
9411
+ if (next.firstWordAdvance !== void 0)
9412
+ return row.width + next.firstWordAdvance > width + GLYPH_POSITION_PRECISION;
9413
+ const nextWord = next.element.text.trim().split(/\s/u)[0];
9414
+ if (!nextWord || !fontCanDraw(pdfium, row.fontHandle, ` ${nextWord}`))
9415
+ return false;
9416
+ const needed = measurer.advance(
9417
+ row.fontHandle,
9418
+ row.element.textStyle.fontSize,
9419
+ ` ${nextWord}`
9420
+ );
9421
+ return Number.isFinite(needed) && row.width + needed > width + GLYPH_POSITION_PRECISION;
9422
+ }
9423
+ function observedFirstWordAdvance(glyphs, text) {
9424
+ const prefix = /^\S+\s+/u.exec(text)?.[0];
9425
+ if (!prefix) return void 0;
9426
+ const count = [...prefix].length;
9427
+ const first = glyphs[0];
9428
+ const nextWord = glyphs[count];
9429
+ if (!first || !nextWord || glyphs.slice(0, count).map((glyph) => glyph.text).join("") !== prefix)
9430
+ return void 0;
9431
+ const advance2 = nextWord.x - first.x;
9432
+ return Number.isFinite(advance2) && advance2 > 0 ? advance2 : void 0;
9433
+ }
9434
+ function hasSupportedAdvances(glyphs, text, font, fontSize, measurer) {
9435
+ const characters = [...text.trimEnd()];
9436
+ const first = glyphs[0];
9437
+ if (!first || glyphs.length < characters.length) return false;
9438
+ const pairLimit = fontSize * MAX_PAIR_ADJUSTMENT_EM;
9439
+ let previousAdvance = 0;
9440
+ let cumulativeAdjustment = 0;
9441
+ let adjustedPairs = 0;
9442
+ let defaultPairs = 0;
9443
+ for (const [index, character] of characters.entries()) {
9444
+ const glyph = glyphs[index];
9445
+ if (!glyph || glyph.text !== character || !Number.isFinite(glyph.x) || !Number.isFinite(glyph.y) || Math.abs(glyph.y - first.y) > GLYPH_POSITION_PRECISION)
9446
+ return false;
9447
+ const previous = glyphs[index - 1];
9448
+ if (previous) {
9449
+ const adjustment = glyph.x - previous.x - previousAdvance;
9450
+ const changed2 = Math.abs(adjustment) > GLYPH_POSITION_PRECISION;
9451
+ if (/\s/u.test(previous.text + character)) {
9452
+ if (changed2) return false;
9453
+ } else if (changed2) {
9454
+ if (Math.abs(adjustment) > pairLimit) return false;
9455
+ adjustedPairs += 1;
9456
+ } else {
9457
+ defaultPairs += 1;
9458
+ }
9459
+ cumulativeAdjustment += adjustment;
9460
+ if (Math.abs(cumulativeAdjustment) > pairLimit) return false;
9461
+ }
9462
+ previousAdvance = measurer.advance(font, fontSize, character);
9463
+ if (!Number.isFinite(previousAdvance) || previousAdvance <= 0) return false;
9464
+ }
9465
+ return adjustedPairs === 0 || defaultPairs > adjustedPairs;
9466
+ }
9467
+ function intersects(a, b) {
9468
+ return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
9469
+ }
9470
+ function backgroundBehind(element, rect, records, first) {
9471
+ const b = element.bounds;
9472
+ return element.kind !== "text" && element.text === void 0 && records.findIndex((record) => record.id === element.id) < first && b.x <= rect.x && b.y <= rect.y && b.x + b.width >= rect.x + rect.width && b.y + b.height >= rect.y + rect.height;
9473
+ }
9474
+
9475
+ // src/edit/pdf/engine/paragraph-edit.ts
9476
+ var replaceParagraphText = {
9477
+ validate(operation2, context, issue) {
9478
+ const target = targetOf(operation2.target, context, issue);
9479
+ if (!target) return;
9480
+ const text = replacement(operation2, target.spec.text, issue);
9481
+ if (text === void 0 || !validateScript(text, issue)) return;
9482
+ if (text.length > 2e4) {
9483
+ issue(
9484
+ "/text",
9485
+ "max-length",
9486
+ "The complete paragraph cannot exceed 20000 characters"
9487
+ );
9488
+ return;
9489
+ }
9490
+ prepare(context, target, { ...target.spec, text }, issue);
9491
+ },
9492
+ apply(operation2, context) {
9493
+ const target = requireTarget(operation2.target, context);
9494
+ const text = replacement(operation2, target.spec.text);
9495
+ if (text === void 0)
9496
+ throw new ViewerError("invalid-operation", "Invalid paragraph range");
9497
+ return rebuild(context, target, { ...target.spec, text });
9498
+ }
9499
+ };
9500
+ var paragraphSetTextStyle = {
9501
+ validate(operation2, context, issue) {
9502
+ const target = targetOf(operation2.target, context, issue);
9503
+ if (!target) return;
9504
+ for (const field of [
9505
+ "fontFamily",
9506
+ "bold",
9507
+ "italic",
9508
+ "align",
9509
+ "lineHeight"
9510
+ ])
9511
+ if (operation2.style[field] !== void 0)
9512
+ issue(
9513
+ `/style/${field}`,
9514
+ "unsupported-style",
9515
+ `Only color and fontSize can change on imported paragraphs; ${field} cannot`
9516
+ );
9517
+ const spec = styled(target.spec, operation2);
9518
+ if (spec.style.fontSize !== target.spec.style.fontSize)
9519
+ prepare(context, target, spec, issue);
9520
+ },
9521
+ apply(operation2, context) {
9522
+ const target = requireTarget(operation2.target, context);
9523
+ const spec = styled(target.spec, operation2);
9524
+ if (spec.style.fontSize === target.spec.style.fontSize)
9525
+ return colorInPlace(context, target, spec);
9526
+ return rebuild(context, target, spec);
9527
+ }
9528
+ };
9529
+ function colorInPlace(context, target, spec) {
9530
+ const { pdfium } = context;
9531
+ const { lib } = pdfium;
9532
+ const color = parseColor(spec.style.color);
9533
+ const persisted = target.paragraph.memberIds.includes(spec.id);
9534
+ context.withPage(target.paragraph.pageIndex, (page) => {
9535
+ for (const index of target.indexes) {
9536
+ const object = lib.FPDFPage_GetObject(page, index);
9537
+ if (lib.FPDFPageObj_GetType(object) === OBJECT_TEXT)
9538
+ lib.FPDFPageObj_SetFillColor(object, ...color, 255);
9539
+ if (!persisted) continue;
9540
+ for (let at = 0; at < lib.FPDFPageObj_CountMarks(object); at += 1) {
9541
+ const mark = lib.FPDFPageObj_GetMark(object, at);
9542
+ const name = pdfium.readWideStringOut(
9543
+ (buffer, bytes2, out) => lib.FPDFPageObjMark_GetName(mark, buffer, bytes2, out)
9544
+ );
9545
+ if (name === MARK_NAME)
9546
+ lib.FPDFPageObjMark_SetStringParam(
9547
+ context.document,
9548
+ object,
9549
+ mark,
9550
+ MARK_PARAM,
9551
+ JSON.stringify(spec)
9552
+ );
9553
+ }
9554
+ context.spliceObjects(target.paragraph.pageIndex, index, 1, [
9555
+ { id: spec.id, type: lib.FPDFPageObj_GetType(object), mark: spec }
9556
+ ]);
8818
9557
  }
8819
- }
8820
- return requests;
8821
- }
8822
- function fontRequest(style, text) {
9558
+ });
8823
9559
  return {
8824
- family: style.fontFamily,
8825
- bold: style.bold,
8826
- italic: style.italic,
8827
- text
9560
+ createdIds: [],
9561
+ changedPages: [target.paragraph.pageIndex],
9562
+ warnings: []
8828
9563
  };
8829
9564
  }
8830
- function validateFont(request, context, issue) {
8831
- const problem = context.fonts.problem(request);
8832
- if (problem) issue(problem.path, problem.code, problem.message);
8833
- }
8834
- function placeUpright(pdfium, object, geometry, pageX, pageY) {
8835
- const [a, b, c, d] = ROTATIONS[geometry.rotation % 4];
8836
- const origin = pageToUser(geometry, pageX, pageY);
8837
- pdfium.lib.FPDFPageObj_Transform(object, a, b, c, d, origin.x, origin.y);
9565
+ function styled(spec, operation2) {
9566
+ return {
9567
+ ...spec,
9568
+ style: {
9569
+ ...spec.style,
9570
+ ...operation2.style.fontSize === void 0 ? {} : { fontSize: round(operation2.style.fontSize) },
9571
+ ...operation2.style.color === void 0 ? {} : { color: operation2.style.color }
9572
+ }
9573
+ };
8838
9574
  }
8839
- var ROTATIONS = [
8840
- [1, 0, 0, 1],
8841
- [0, 1, -1, 0],
8842
- [-1, 0, 0, -1],
8843
- [0, -1, 1, 0]
8844
- ];
8845
- function setText(pdfium, object, text) {
8846
- const wide = pdfium.writeWideString(text);
8847
- try {
8848
- pdfium.lib.FPDFText_SetText(object, wide);
8849
- } finally {
8850
- pdfium.free(wide);
9575
+ function targetOf(id, context, issue) {
9576
+ const target = context.paragraph(id);
9577
+ if (!target || target.paragraph.id !== id) {
9578
+ issue(
9579
+ "/target",
9580
+ "unsupported-target",
9581
+ "The target must be a resolved native paragraph id"
9582
+ );
9583
+ return void 0;
8851
9584
  }
9585
+ return target;
8852
9586
  }
8853
- function parseColor(color) {
8854
- return [1, 3, 5].map(
8855
- (offset2) => Number.parseInt(color.slice(offset2, offset2 + 2), 16)
9587
+ function requireTarget(id, context) {
9588
+ const target = targetOf(id, context, () => {
9589
+ });
9590
+ if (!target)
9591
+ throw new ViewerError(
9592
+ "invalid-operation",
9593
+ "The paragraph no longer exists"
9594
+ );
9595
+ return target;
9596
+ }
9597
+ function replacement(operation2, previous, issue = () => {
9598
+ }) {
9599
+ const range = operation2.range;
9600
+ if (!range) return operation2.text;
9601
+ const valid = [range.start, range.end].every(
9602
+ (position) => position.elementId === operation2.target && Number.isInteger(position.offset) && position.offset >= 0 && position.offset <= previous.length
8856
9603
  );
9604
+ if (!valid || range.start.offset > range.end.offset) {
9605
+ issue(
9606
+ "/range",
9607
+ "invalid-range",
9608
+ "The range must lie inside the paragraph's logical text"
9609
+ );
9610
+ return void 0;
9611
+ }
9612
+ return previous.slice(0, range.start.offset) + operation2.text + previous.slice(range.end.offset);
8857
9613
  }
8858
- function resolveStyle(style) {
9614
+ function fontFor(context, target, spec, page) {
9615
+ const { lib } = context.pdfium;
9616
+ const original = target.indexes.map((index) => lib.FPDFPage_GetObject(page, index)).find((object) => lib.FPDFPageObj_GetType(object) === OBJECT_TEXT);
9617
+ const native = original === void 0 ? void 0 : lib.FPDFTextObj_GetFont(original);
9618
+ if (native !== void 0 && fontCanDraw(context.pdfium, native, spec.text))
9619
+ return {
9620
+ handle: native,
9621
+ family: spec.style.fontFamily,
9622
+ substituted: false
9623
+ };
9624
+ const family = isStandardFamily(spec.style.fontFamily) || context.fonts.hasFamily(spec.style.fontFamily) ? spec.style.fontFamily : "Helvetica";
9625
+ const request = {
9626
+ family,
9627
+ bold: spec.style.bold,
9628
+ italic: spec.style.italic,
9629
+ text: spec.text
9630
+ };
9631
+ const problem = context.fonts.problem(request);
9632
+ if (problem) return { problem };
9633
+ const font = context.fonts.resolve(context.pdfium, context.document, request);
8859
9634
  return {
8860
- fontFamily: style.fontFamily ?? "Helvetica",
8861
- fontSize: style.fontSize ?? 12,
8862
- bold: style.bold ?? false,
8863
- italic: style.italic ?? false,
8864
- color: style.color ?? "#000000",
8865
- align: style.align ?? "left",
8866
- lineHeight: style.lineHeight ?? 1.2
9635
+ handle: font.handle,
9636
+ family: font.family,
9637
+ substituted: original !== void 0 || !isStandardFamily(spec.style.fontFamily),
9638
+ ...font.substitution ? { substitution: font.substitution } : {}
8867
9639
  };
8868
9640
  }
8869
- function definedFields(style) {
8870
- return Object.fromEntries(
8871
- Object.entries(style).filter(([, value]) => value !== void 0)
8872
- );
9641
+ function prepare(context, target, spec, issue) {
9642
+ return context.readPage(target.paragraph.pageIndex, (page) => {
9643
+ const font = fontFor(context, target, spec, page);
9644
+ if ("problem" in font) {
9645
+ issue(font.problem.path, font.problem.code, font.problem.message);
9646
+ return void 0;
9647
+ }
9648
+ const metrics = context.measurer.metrics(font.handle, spec.style.fontSize);
9649
+ const layout = layoutText({
9650
+ text: spec.text,
9651
+ width: spec.rect.width,
9652
+ height: Number.POSITIVE_INFINITY,
9653
+ fontSize: spec.style.fontSize,
9654
+ lineHeight: spec.style.lineHeight,
9655
+ align: "left",
9656
+ ascent: spec.baselineOffset * spec.style.fontSize,
9657
+ advance: (text) => context.measurer.advance(font.handle, spec.style.fontSize, text)
9658
+ });
9659
+ const last = layout.lines.at(-1);
9660
+ const height = spec.text.trim() && last ? last.baseline + metrics.descent : spec.rect.height;
9661
+ const rect = { ...spec.rect, height: Math.max(height, 1e-3) };
9662
+ const size = context.pageSize(target.paragraph.pageIndex);
9663
+ const members = new Set(target.paragraph.memberIds);
9664
+ const collision = context.pageElements(target.paragraph.pageIndex).some((element) => {
9665
+ if (members.has(element.id) || !intersects(rect, element.bounds))
9666
+ return false;
9667
+ const old = target.spec.rect;
9668
+ const b = element.bounds;
9669
+ const location = context.locate(element.id);
9670
+ const behind = location && location.indexes.every((index) => index < target.indexes[0]);
9671
+ return !(element.text === void 0 && behind && b.x <= old.x && b.y <= old.y && b.x + b.width >= rect.x + rect.width && b.y + b.height >= rect.y + rect.height);
9672
+ });
9673
+ if (collision || rect.x < 0 || rect.y < 0 || rect.x + rect.width > size.width + 0.01 || rect.y + rect.height > size.height + 0.01) {
9674
+ issue(
9675
+ "/text",
9676
+ "paragraph-overflow",
9677
+ "The paragraph would overlap neighboring content or leave the page"
9678
+ );
9679
+ return void 0;
9680
+ }
9681
+ const warnings = font.substituted ? [
9682
+ {
9683
+ code: "font-substitution",
9684
+ message: "substitution" in font && font.substitution ? font.substitution : `${spec.style.fontFamily} cannot draw the new paragraph; ${font.family} is used`,
9685
+ details: { elementId: spec.id }
9686
+ }
9687
+ ] : [];
9688
+ return {
9689
+ spec: {
9690
+ ...spec,
9691
+ rect,
9692
+ lines: layout.lines.map((line) => line.text).filter(Boolean),
9693
+ style: {
9694
+ ...spec.style,
9695
+ fontFamily: font.substituted ? font.family : spec.style.fontFamily
9696
+ }
9697
+ },
9698
+ layout,
9699
+ warnings
9700
+ };
9701
+ });
8873
9702
  }
8874
- function validateRect(rect, geometry, issue, path = "/rect") {
8875
- const size = displayedSize(geometry);
8876
- const tolerance = 0.01;
8877
- if (rect.x < -tolerance || rect.y < -tolerance || rect.x + rect.width > size.width + tolerance || rect.y + rect.height > size.height + tolerance)
8878
- issue(
8879
- path,
8880
- "range",
8881
- `The rectangle must lie within the ${size.width}\xD7${size.height} pt page`
9703
+ function rebuild(context, target, spec) {
9704
+ const issues = [];
9705
+ const prepared = prepare(
9706
+ context,
9707
+ target,
9708
+ spec,
9709
+ (path, code, message) => issues.push({ path, code, message })
9710
+ );
9711
+ if (!prepared)
9712
+ throw new ViewerError(
9713
+ "invalid-operation",
9714
+ "The paragraph cannot be changed",
9715
+ { details: { issues } }
8882
9716
  );
8883
- }
8884
-
8885
- // src/edit/pdf/engine/operations.ts
8886
- function issueCollector(operationIndex, issues) {
8887
- return (path, code, message) => issues.push({ operationIndex, path, code, message });
9717
+ const { pdfium } = context;
9718
+ const { lib } = pdfium;
9719
+ const pageIndex = target.paragraph.pageIndex;
9720
+ const geometry = context.geometry(pageIndex);
9721
+ const first = target.indexes[0];
9722
+ context.withPage(pageIndex, (page) => {
9723
+ const font = fontFor(context, target, spec, page);
9724
+ if ("problem" in font)
9725
+ throw new ViewerError("invalid-operation", font.problem.message);
9726
+ const objects = [];
9727
+ const [r, g, b] = parseColor(spec.style.color);
9728
+ if (!spec.text.trim()) {
9729
+ const rect = prepared.spec.rect;
9730
+ const origin = pageToUser(geometry, rect.x, rect.y + rect.height);
9731
+ const object = lib.FPDFPageObj_CreateNewRect(
9732
+ origin.x,
9733
+ origin.y,
9734
+ rect.width,
9735
+ rect.height
9736
+ );
9737
+ lib.FPDFPageObj_SetFillColor(object, 0, 0, 0, 0);
9738
+ lib.FPDFPath_SetDrawMode(object, 1, false);
9739
+ objects.push({ object, type: OBJECT_PATH });
9740
+ } else
9741
+ for (const line of prepared.layout.lines) {
9742
+ if (!line.text) continue;
9743
+ const object = lib.FPDFPageObj_CreateTextObj(
9744
+ context.document,
9745
+ font.handle,
9746
+ spec.style.fontSize
9747
+ );
9748
+ if (!object) {
9749
+ for (const made of objects) lib.FPDFPageObj_Destroy(made.object);
9750
+ throw new ViewerError(
9751
+ "edit-failed",
9752
+ "PDFium could not create paragraph text"
9753
+ );
9754
+ }
9755
+ setText(pdfium, object, line.text);
9756
+ lib.FPDFPageObj_SetFillColor(object, r, g, b, 255);
9757
+ placeUpright(
9758
+ pdfium,
9759
+ object,
9760
+ geometry,
9761
+ spec.rect.x + line.x,
9762
+ spec.rect.y + line.baseline
9763
+ );
9764
+ objects.push({ object, type: OBJECT_TEXT });
9765
+ }
9766
+ const params = JSON.stringify(prepared.spec);
9767
+ for (const { object } of objects) {
9768
+ const mark = lib.FPDFPageObj_AddMark(object, MARK_NAME);
9769
+ lib.FPDFPageObjMark_SetStringParam(
9770
+ context.document,
9771
+ object,
9772
+ mark,
9773
+ MARK_PARAM,
9774
+ params
9775
+ );
9776
+ }
9777
+ for (const index of [...target.indexes].reverse()) {
9778
+ const object = lib.FPDFPage_GetObject(page, index);
9779
+ lib.FPDFPage_RemoveObject(page, object);
9780
+ lib.FPDFPageObj_Destroy(object);
9781
+ context.spliceObjects(pageIndex, index, 1, []);
9782
+ }
9783
+ objects.forEach(
9784
+ ({ object }, at) => lib.FPDFPage_InsertObjectAtIndex(page, object, first + at)
9785
+ );
9786
+ const textPage = lib.FPDFText_LoadPage(page);
9787
+ try {
9788
+ const drawn = objects.filter(({ type }) => type === OBJECT_TEXT).map(
9789
+ ({ object }) => pdfium.readWideString(
9790
+ (buffer, bytes2) => lib.FPDFTextObj_GetText(object, textPage, buffer, bytes2)
9791
+ ).trimEnd()
9792
+ );
9793
+ if (drawn.length !== prepared.spec.lines.length || drawn.some((line, index) => line !== prepared.spec.lines[index]))
9794
+ throw new ViewerError(
9795
+ "edit-failed",
9796
+ "The native font could not encode the paragraph faithfully"
9797
+ );
9798
+ } finally {
9799
+ lib.FPDFText_ClosePage(textPage);
9800
+ }
9801
+ context.spliceObjects(
9802
+ pageIndex,
9803
+ first,
9804
+ 0,
9805
+ objects.map(({ type }) => ({ id: spec.id, type, mark: prepared.spec }))
9806
+ );
9807
+ });
9808
+ const promoted = !target.paragraph.memberIds.includes(spec.id);
9809
+ return {
9810
+ createdIds: [],
9811
+ removedIds: promoted ? target.paragraph.memberIds : [],
9812
+ ...promoted ? {
9813
+ textAnchorMigrations: target.paragraph.members.map((member) => ({
9814
+ sourceElementId: member.elementId,
9815
+ targetElementId: spec.id,
9816
+ sourceLength: member.end - member.start,
9817
+ targetOffset: member.start
9818
+ }))
9819
+ } : {},
9820
+ changedPages: [pageIndex],
9821
+ warnings: prepared.warnings
9822
+ };
8888
9823
  }
8889
9824
 
8890
9825
  // src/edit/pdf/engine/existing-text.ts
8891
9826
  var replaceText = {
8892
9827
  validate(operation2, context, issue) {
9828
+ if (context.paragraph(operation2.target)?.paragraph.id === operation2.target)
9829
+ return replaceParagraphText.validate(
9830
+ { ...operation2, op: "replaceParagraphText" },
9831
+ context,
9832
+ issue
9833
+ );
8893
9834
  const box = textBoxTarget(operation2.target, context);
8894
9835
  if (box) {
8895
9836
  const spliced = splice(box.spec.text, operation2, issue);
@@ -8918,6 +9859,11 @@ var replaceText = {
8918
9859
  if (problem) issue(problem.path, problem.code, problem.message);
8919
9860
  },
8920
9861
  apply(operation2, context) {
9862
+ if (context.paragraph(operation2.target)?.paragraph.id === operation2.target)
9863
+ return replaceParagraphText.apply(
9864
+ { ...operation2, op: "replaceParagraphText" },
9865
+ context
9866
+ );
8921
9867
  const box = textBoxTarget(operation2.target, context);
8922
9868
  if (box)
8923
9869
  return textBoxReplaceText.apply(
@@ -8968,6 +9914,8 @@ function spanOf(operation2, previous) {
8968
9914
  }
8969
9915
  var setTextStyle = {
8970
9916
  validate(operation2, context, issue) {
9917
+ if (context.paragraph(operation2.target)?.paragraph.id === operation2.target)
9918
+ return paragraphSetTextStyle.validate(operation2, context, issue);
8971
9919
  if (textBoxTarget(operation2.target, context))
8972
9920
  return textBoxSetTextStyle.validate(operation2, context, issue);
8973
9921
  if (!textTarget(operation2.target, context, issue)) return;
@@ -8986,6 +9934,8 @@ var setTextStyle = {
8986
9934
  );
8987
9935
  },
8988
9936
  apply(operation2, context) {
9937
+ if (context.paragraph(operation2.target)?.paragraph.id === operation2.target)
9938
+ return paragraphSetTextStyle.apply(operation2, context);
8989
9939
  if (textBoxTarget(operation2.target, context))
8990
9940
  return textBoxSetTextStyle.apply(operation2, context);
8991
9941
  const target = textTarget(operation2.target, context);
@@ -9026,66 +9976,19 @@ function textTarget(target, context, issue = () => {
9026
9976
  }
9027
9977
  return { location, element };
9028
9978
  }
9029
- function validateScript(text, issue) {
9030
- for (const character of text) {
9031
- const code = character.codePointAt(0);
9032
- if (code >= 1424 && code <= 2303 || code >= 64285 && code <= 65023 || code >= 65136 && code <= 65279) {
9033
- issue(
9034
- "/text",
9035
- "unsupported-script",
9036
- `Right-to-left text such as "${character}" cannot be edited yet`
9037
- );
9038
- return false;
9039
- }
9040
- }
9041
- return true;
9042
- }
9043
9979
  function canKeepFont(context, target, text) {
9044
- const { pdfium } = context;
9045
- const { lib } = pdfium;
9046
- return context.withPage(target.location.pageIndex, (page) => {
9047
- const object = lib.FPDFPage_GetObject(page, target.location.indexes[0]);
9048
- const font = lib.FPDFTextObj_GetFont(object);
9049
- if (!lib.FPDFFont_GetIsEmbedded(font)) {
9050
- const base = pdfium.readUtf8String(
9051
- (buffer, bytes2) => lib.FPDFFont_GetBaseFontName(font, buffer, bytes2)
9052
- );
9053
- return isStandardFamily(base.split(/[-,]/)[0] ?? "") && firstNonWinAnsi(text) === void 0;
9054
- }
9055
- const coverage = fontCoverage(pdfium, font);
9056
- if (!coverage) return false;
9057
- for (const character of text) {
9058
- const code = character.codePointAt(0);
9059
- if (code !== 10 && code !== 32 && !coverage.drawable(code))
9060
- return false;
9061
- }
9062
- return true;
9980
+ return context.readPage(target.location.pageIndex, (page) => {
9981
+ const object = context.pdfium.lib.FPDFPage_GetObject(
9982
+ page,
9983
+ target.location.indexes[0]
9984
+ );
9985
+ return fontCanRewrite(
9986
+ context.pdfium,
9987
+ context.pdfium.lib.FPDFTextObj_GetFont(object),
9988
+ text
9989
+ );
9063
9990
  });
9064
9991
  }
9065
- function fontCoverage(pdfium, font) {
9066
- const { lib } = pdfium;
9067
- const size = pdfium.readNumbers(
9068
- 1,
9069
- "i32",
9070
- ([out]) => lib.FPDFFont_GetFontData(font, 0, 0, out)
9071
- )?.[0];
9072
- if (!size) return void 0;
9073
- const buffer = pdfium.malloc(size);
9074
- try {
9075
- const out = pdfium.malloc(4);
9076
- try {
9077
- if (!lib.FPDFFont_GetFontData(font, buffer, size, out)) return void 0;
9078
- } finally {
9079
- pdfium.free(out);
9080
- }
9081
- const data = pdfium.readBytes(buffer, size);
9082
- const tag = String.fromCharCode(data[0], data[1], data[2], data[3]);
9083
- const truetype = data[0] === 0 && data[1] === 1 && data[2] === 0 && data[3] === 0 || tag === "true";
9084
- return truetype ? parseCmap(data) : void 0;
9085
- } finally {
9086
- pdfium.free(buffer);
9087
- }
9088
- }
9089
9992
  function replaceInPlace(context, target, text) {
9090
9993
  const { pdfium } = context;
9091
9994
  const { lib } = pdfium;
@@ -9351,6 +10254,14 @@ var moveElement = {
9351
10254
  validate(operation2, context, issue) {
9352
10255
  const target = anyTarget(operation2.target, context, issue);
9353
10256
  if (!target) return;
10257
+ if (target.element.kind === "paragraph") {
10258
+ issue(
10259
+ "/target",
10260
+ "unsupported-target",
10261
+ "Imported paragraphs cannot be moved"
10262
+ );
10263
+ return;
10264
+ }
9354
10265
  if (operation2.to === void 0 === (operation2.by === void 0)) {
9355
10266
  issue("", "one-of", "Give exactly one of `to` and `by`");
9356
10267
  return;
@@ -9402,8 +10313,12 @@ var resizeElement = {
9402
10313
  validate(operation2, context, issue) {
9403
10314
  const target = anyTarget(operation2.target, context, issue);
9404
10315
  if (!target) return;
9405
- if (target.element.kind === "table") {
9406
- issue("/target", "unsupported-target", "Tables cannot be resized");
10316
+ if (target.element.kind === "table" || target.element.kind === "paragraph") {
10317
+ issue(
10318
+ "/target",
10319
+ "unsupported-target",
10320
+ "Tables and imported paragraphs cannot be resized"
10321
+ );
9407
10322
  return;
9408
10323
  }
9409
10324
  validateRect(
@@ -9457,10 +10372,16 @@ var deleteElement = {
9457
10372
  },
9458
10373
  apply(operation2, context) {
9459
10374
  const { location } = anyTarget(operation2.target, context);
10375
+ const paragraph = context.paragraph(operation2.target)?.paragraph;
9460
10376
  removeObjects(context, location);
9461
10377
  return {
9462
10378
  ...changed(location, { overflow: false }),
9463
- removedIds: [operation2.target]
10379
+ removedIds: [
10380
+ .../* @__PURE__ */ new Set([
10381
+ operation2.target,
10382
+ ...paragraph?.id === operation2.target ? paragraph.memberIds : []
10383
+ ])
10384
+ ]
9464
10385
  };
9465
10386
  }
9466
10387
  };
@@ -10500,6 +11421,7 @@ function referenceOf(operation2) {
10500
11421
  return typeof target === "string" ? parseReference(target) : void 0;
10501
11422
  }
10502
11423
  var handlers = {
11424
+ replaceParagraphText,
10503
11425
  insertTextBox,
10504
11426
  replaceText,
10505
11427
  setTextStyle,
@@ -10622,6 +11544,7 @@ var PdfEditDocument = class {
10622
11544
  const createdIds = [];
10623
11545
  const createdByOperation = [];
10624
11546
  const removedIds = [];
11547
+ const textAnchorMigrations = [];
10625
11548
  const changedPages = /* @__PURE__ */ new Set();
10626
11549
  const warnings = [];
10627
11550
  if (this.#batches === 0 && (this.#signatures > 0 || this.#features.length))
@@ -10646,6 +11569,8 @@ var PdfEditDocument = class {
10646
11569
  createdIds.push(...result3.createdIds);
10647
11570
  createdByOperation[operationIndex] = [...result3.createdIds];
10648
11571
  removedIds.push(...result3.removedIds ?? []);
11572
+ for (const migration of result3.textAnchorMigrations ?? [])
11573
+ textAnchorMigrations.push({ ...migration, operationIndex });
10649
11574
  for (const pageIndex of result3.changedPages) changedPages.add(pageIndex);
10650
11575
  warnings.push(...result3.warnings);
10651
11576
  });
@@ -10653,6 +11578,7 @@ var PdfEditDocument = class {
10653
11578
  return {
10654
11579
  createdIds,
10655
11580
  removedIds,
11581
+ ...textAnchorMigrations.length ? { textAnchorMigrations } : {},
10656
11582
  changedPages: [...changedPages].sort((a, b) => a - b),
10657
11583
  pageCount: this.pageCount,
10658
11584
  warnings
@@ -10772,7 +11698,12 @@ var PdfEditDocument = class {
10772
11698
  getElement(id) {
10773
11699
  const pageIndex = this.#pageIndexOf(id);
10774
11700
  if (pageIndex === void 0) return void 0;
10775
- return this.#elementsOf(pageIndex).find((element) => element.id === id);
11701
+ const element = this.#elementsOf(pageIndex).find(
11702
+ (element2) => element2.id === id
11703
+ );
11704
+ if (element) return element;
11705
+ const target = this.#pages[pageIndex]?.paragraphs?.get(id);
11706
+ return target?.paragraph.id === id ? paragraphElement(target.paragraph) : void 0;
10776
11707
  }
10777
11708
  /** Elements under a point, top-most (drawn last) first. */
10778
11709
  elementsAt(pageIndex, point) {
@@ -10802,20 +11733,40 @@ var PdfEditDocument = class {
10802
11733
  });
10803
11734
  return targets;
10804
11735
  }
10805
- /** Lines, glyph boxes and styles of a text, text box or table element. */
11736
+ /** Resolves the canonical imported paragraph, when its rows can be grouped safely. */
11737
+ textParagraph(elementId) {
11738
+ return this.#paragraphOf(elementId)?.paragraph;
11739
+ }
11740
+ #paragraphOf(id) {
11741
+ const pageIndex = this.#pageIndexOf(id);
11742
+ if (pageIndex === void 0) return void 0;
11743
+ this.#elementsOf(pageIndex);
11744
+ return this.#pages[pageIndex]?.paragraphs?.get(id);
11745
+ }
11746
+ /** Lines, glyph boxes and styles of a text, text box, paragraph or table element. */
10806
11747
  textLayout(elementId) {
10807
11748
  const pageIndex = this.#pageIndexOf(elementId);
10808
11749
  if (pageIndex === void 0) return void 0;
10809
- return this.#scanText(pageIndex, (scan) => {
11750
+ const paragraph = this.#paragraphOf(elementId);
11751
+ return this.#scanText(pageIndex, (raw) => {
11752
+ const scan = paragraph?.paragraph.id === elementId ? this.#paragraphScan(raw, paragraph) : raw;
10810
11753
  const element = scan.elements.find((entry) => entry.id === elementId);
10811
- return element ? layoutOf(this.#pdfium, scan, element) : void 0;
11754
+ const layout = element ? layoutOf(this.#pdfium, scan, element) : void 0;
11755
+ return layout && paragraph?.paragraph.id === elementId ? {
11756
+ ...layout,
11757
+ lines: [...layout.lines].sort((a, b) => a.range.start.offset - b.range.start.offset).map((line) => ({
11758
+ ...line,
11759
+ fontSize: paragraph.paragraph.textStyle.fontSize
11760
+ }))
11761
+ } : layout;
10812
11762
  });
10813
11763
  }
10814
11764
  /** The browser face of the font a text or text box element is drawn in, see `TextFont`. */
10815
11765
  textFont(elementId) {
10816
11766
  const location = this.#locate(elementId);
10817
- const element = location && this.getElement(elementId);
10818
- if (!element || element.kind !== "text" && element.kind !== "textBox")
11767
+ if (!location) return void 0;
11768
+ const kind = this.getElement(elementId)?.kind ?? (this.#paragraphOf(elementId)?.paragraph.id === elementId ? "paragraph" : void 0);
11769
+ if (kind !== "text" && kind !== "textBox" && kind !== "paragraph")
10819
11770
  return void 0;
10820
11771
  const { lib } = this.#pdfium;
10821
11772
  return this.#withPage(location.pageIndex, (page) => {
@@ -10870,7 +11821,12 @@ var PdfEditDocument = class {
10870
11821
  "The rendered page exceeds maxDecodedPixels",
10871
11822
  { details: { actual: pixels, limit: this.#limits.maxDecodedPixels } }
10872
11823
  );
10873
- const wanted = new Set(elementIds);
11824
+ const wanted = new Set(
11825
+ elementIds.flatMap((id) => {
11826
+ const paragraph = this.#paragraphOf(id)?.paragraph;
11827
+ return paragraph?.id === id ? paragraph.memberIds : [id];
11828
+ })
11829
+ );
10874
11830
  const records = this.#objectsOf(pageIndex, page);
10875
11831
  const suppressed = [];
10876
11832
  records.forEach((record, index) => {
@@ -10952,9 +11908,14 @@ var PdfEditDocument = class {
10952
11908
  const pageIndex = this.#pageIndexOf(range.start.elementId);
10953
11909
  if (pageIndex === void 0 || pageIndex !== this.#pageIndexOf(range.end.elementId))
10954
11910
  return [];
11911
+ const paragraph = range.start.elementId === range.end.elementId ? this.#paragraphOf(range.start.elementId) : void 0;
10955
11912
  return this.#scanText(
10956
11913
  pageIndex,
10957
- (scan) => rectsOf(this.#pdfium, scan, range)
11914
+ (scan) => rectsOf(
11915
+ this.#pdfium,
11916
+ paragraph?.paragraph.id === range.start.elementId ? this.#paragraphScan(scan, paragraph) : scan,
11917
+ range
11918
+ )
10958
11919
  );
10959
11920
  }
10960
11921
  dispose() {
@@ -10985,10 +11946,14 @@ var PdfEditDocument = class {
10985
11946
  // part of the base, so a replay meets them and picks the same id.
10986
11947
  newId: (pageIndex, suffix = "") => `${this.#unusedId(pageIndex, `${this.#pages[pageIndex].key}:n${stateId}.${operationIndex}.${created++}`)}${suffix}`,
10987
11948
  withPage: (pageIndex, use) => this.#writePage(pageIndex, use),
11949
+ readPage: (pageIndex, use) => this.#withPage(pageIndex, use),
11950
+ paragraph: (id) => this.#paragraphOf(id),
11951
+ pageElements: (pageIndex) => this.#elementsOf(pageIndex),
10988
11952
  appendObjects: (pageIndex, records) => {
10989
11953
  const page = this.#pages[pageIndex];
10990
11954
  page.objects = [...page.objects ?? [], ...records];
10991
11955
  delete page.elements;
11956
+ delete page.paragraphs;
10992
11957
  },
10993
11958
  locate: (id) => this.#locate(id),
10994
11959
  element: (id) => this.getElement(id),
@@ -11019,6 +11984,7 @@ var PdfEditDocument = class {
11019
11984
  if (!page) return;
11020
11985
  delete page.geometry;
11021
11986
  delete page.elements;
11987
+ delete page.paragraphs;
11022
11988
  },
11023
11989
  spliceObjects: (pageIndex, start, count, records) => {
11024
11990
  const page = this.#pages[pageIndex];
@@ -11026,12 +11992,16 @@ var PdfEditDocument = class {
11026
11992
  objects.splice(start, count, ...records);
11027
11993
  page.objects = objects;
11028
11994
  delete page.elements;
11995
+ delete page.paragraphs;
11029
11996
  }
11030
11997
  };
11031
11998
  }
11032
11999
  /** Cached elements carry page indexes, which a structure change makes stale. */
11033
12000
  #forgetElements() {
11034
- for (const page of this.#pages) delete page.elements;
12001
+ for (const page of this.#pages) {
12002
+ delete page.elements;
12003
+ delete page.paragraphs;
12004
+ }
11035
12005
  }
11036
12006
  #locate(id) {
11037
12007
  const pageIndex = this.#pageIndexOf(id);
@@ -11045,7 +12015,14 @@ var PdfEditDocument = class {
11045
12015
  if (record2.id === id) indexes.push(index);
11046
12016
  });
11047
12017
  const record = records[indexes[0] ?? -1];
11048
- return record ? { pageIndex, indexes, record } : void 0;
12018
+ if (record) return { pageIndex, indexes, record };
12019
+ const paragraph = this.#paragraphOf(id);
12020
+ const first = paragraph && records[paragraph.indexes[0] ?? -1];
12021
+ return paragraph?.paragraph.id === id && first ? {
12022
+ pageIndex,
12023
+ indexes: paragraph.indexes,
12024
+ record: { ...first, id, mark: paragraph.spec }
12025
+ } : void 0;
11049
12026
  }
11050
12027
  /** Loads a page, lets `use` change it, regenerates its content stream. */
11051
12028
  #writePage(pageIndex, use) {
@@ -11067,6 +12044,7 @@ var PdfEditDocument = class {
11067
12044
  }
11068
12045
  );
11069
12046
  delete this.#pages[pageIndex].elements;
12047
+ delete this.#pages[pageIndex].paragraphs;
11070
12048
  return result3;
11071
12049
  } finally {
11072
12050
  lib.FPDF_ClosePage(page);
@@ -11093,7 +12071,22 @@ var PdfEditDocument = class {
11093
12071
  geometry,
11094
12072
  this.#objectsOf(pageIndex, page)
11095
12073
  );
11096
- record.elements = Object.freeze(elements);
12074
+ record.paragraphs = discoverParagraphs(
12075
+ this.#pdfium,
12076
+ page,
12077
+ pageIndex,
12078
+ geometry,
12079
+ this.#objectsOf(pageIndex, page),
12080
+ elements,
12081
+ textPage,
12082
+ this.#measurer
12083
+ );
12084
+ record.elements = Object.freeze(
12085
+ elements.map((element) => {
12086
+ const paragraph = record.paragraphs?.get(element.id)?.paragraph;
12087
+ return paragraph && paragraph.id !== element.id ? { ...element, textEditingTarget: paragraph.id } : element;
12088
+ })
12089
+ );
11097
12090
  return record.elements;
11098
12091
  });
11099
12092
  }
@@ -11206,6 +12199,27 @@ var PdfEditDocument = class {
11206
12199
  };
11207
12200
  return record.geometry;
11208
12201
  }
12202
+ #paragraphScan(scan, target) {
12203
+ const paragraph = target.paragraph;
12204
+ if (paragraph.memberIds.includes(paragraph.id)) return scan;
12205
+ const members = new Map(
12206
+ paragraph.members.map((member) => [member.elementId, member])
12207
+ );
12208
+ return {
12209
+ ...scan,
12210
+ elements: [
12211
+ ...scan.elements.filter((element) => !members.has(element.id)),
12212
+ paragraphElement(paragraph)
12213
+ ],
12214
+ offsets: scan.offsets.map((position) => {
12215
+ const member = position && members.get(position.elementId);
12216
+ return member && position ? {
12217
+ elementId: paragraph.id,
12218
+ offset: Math.min(member.end, member.start + position.offset)
12219
+ } : position;
12220
+ })
12221
+ };
12222
+ }
11209
12223
  /** Loads a page with its text page and the character-to-element mapping. */
11210
12224
  #scanText(pageIndex, use) {
11211
12225
  return this.#withPage(pageIndex, (page, textPage, geometry) => {
@@ -11460,6 +12474,8 @@ function createPdfEditHandler(host) {
11460
12474
  }
11461
12475
  case "edit-text-layout":
11462
12476
  return engine().textLayout(payload.id);
12477
+ case "edit-text-paragraph":
12478
+ return engine().textParagraph(payload.id);
11463
12479
  case "edit-position-at": {
11464
12480
  const { pageIndex, point } = payload;
11465
12481
  return engine().positionAt(pageIndex, point);