pretext-pdf 0.5.2 → 0.8.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 (57) hide show
  1. package/CHANGELOG.md +462 -351
  2. package/README.md +749 -568
  3. package/dist/assets.d.ts +5 -0
  4. package/dist/assets.d.ts.map +1 -1
  5. package/dist/assets.js +248 -43
  6. package/dist/assets.js.map +1 -1
  7. package/dist/errors.d.ts +1 -1
  8. package/dist/errors.d.ts.map +1 -1
  9. package/dist/errors.js.map +1 -1
  10. package/dist/fonts.d.ts.map +1 -1
  11. package/dist/fonts.js +67 -8
  12. package/dist/fonts.js.map +1 -1
  13. package/dist/index.d.ts +29 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +35 -2
  16. package/dist/index.js.map +1 -1
  17. package/dist/markdown.d.ts +28 -0
  18. package/dist/markdown.d.ts.map +1 -0
  19. package/dist/markdown.js +222 -0
  20. package/dist/markdown.js.map +1 -0
  21. package/dist/measure-blocks.d.ts.map +1 -1
  22. package/dist/measure-blocks.js +347 -62
  23. package/dist/measure-blocks.js.map +1 -1
  24. package/dist/measure-text.d.ts.map +1 -1
  25. package/dist/measure-text.js +1 -8
  26. package/dist/measure-text.js.map +1 -1
  27. package/dist/measure.d.ts.map +1 -1
  28. package/dist/measure.js +13 -21
  29. package/dist/measure.js.map +1 -1
  30. package/dist/render-blocks.d.ts +4 -1
  31. package/dist/render-blocks.d.ts.map +1 -1
  32. package/dist/render-blocks.js +227 -105
  33. package/dist/render-blocks.js.map +1 -1
  34. package/dist/render-extras.d.ts.map +1 -1
  35. package/dist/render-extras.js +72 -71
  36. package/dist/render-extras.js.map +1 -1
  37. package/dist/render-utils.d.ts +9 -2
  38. package/dist/render-utils.d.ts.map +1 -1
  39. package/dist/render-utils.js +24 -13
  40. package/dist/render-utils.js.map +1 -1
  41. package/dist/render.d.ts.map +1 -1
  42. package/dist/render.js +27 -3
  43. package/dist/render.js.map +1 -1
  44. package/dist/rich-text.d.ts +0 -4
  45. package/dist/rich-text.d.ts.map +1 -1
  46. package/dist/rich-text.js +15 -9
  47. package/dist/rich-text.js.map +1 -1
  48. package/dist/templates.d.ts +79 -0
  49. package/dist/templates.d.ts.map +1 -0
  50. package/dist/templates.js +201 -0
  51. package/dist/templates.js.map +1 -0
  52. package/dist/types.d.ts +139 -5
  53. package/dist/types.d.ts.map +1 -1
  54. package/dist/validate.d.ts.map +1 -1
  55. package/dist/validate.js +241 -28
  56. package/dist/validate.js.map +1 -1
  57. package/package.json +175 -130
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAa7C;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,QAAgB,EAChB,UAA2B,EAAE;IAE7B,IAAI,YAA2D,CAAA;IAC/D,IAAI,CAAC;QACH,YAAY,GAAG,MAAM,MAAM,CAAC,QAAkB,CAAwB,CAAA;IACxE,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,eAAe,CACvB,sBAAsB,EACtB,iFAAiF,CAClF,CAAA;IACH,CAAC;IAED,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;IAClD,MAAM,QAAQ,GAAqB,EAAE,CAAA;IAErC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,SAAS,GAAG,YAAY,CAAC,KAAc,EAAE,OAAO,CAAC,CAAA;QACvD,IAAI,SAAS,KAAK,IAAI;YAAE,SAAQ;QAChC,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,CAAA;;YACpD,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IAC/B,CAAC;IAED,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,iFAAiF;AAEjF,SAAS,YAAY,CACnB,KAAY,EACZ,OAAwB;IAExB,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,SAAS,CAAC,CAAC,OAAO,cAAc,CAAC,KAAuB,CAAC,CAAA;QAC9D,KAAK,WAAW,CAAC,CAAC,OAAO,gBAAgB,CAAC,KAAyB,EAAE,OAAO,CAAC,CAAA;QAC7E,KAAK,MAAM,CAAC,CAAC,OAAO,WAAW,CAAC,KAAoB,CAAC,CAAA;QACrD,KAAK,MAAM,CAAC,CAAC,OAAO,WAAW,CAAC,KAAoB,EAAE,OAAO,CAAC,CAAA;QAC9D,KAAK,YAAY,CAAC,CAAC,OAAO,iBAAiB,CAAC,KAA0B,CAAC,CAAA;QACvE,KAAK,IAAI,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAkC,CAAA;QAChE,KAAK,OAAO,CAAC,CAAC,OAAO,IAAI,CAAA;QACzB,KAAK,MAAM,CAAC,CAAC,OAAO,IAAI,CAAA;QACxB,OAAO,CAAC,CAAC,OAAO,IAAI,CAAA;IACtB,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,KAAqB;IAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,CAAkB,CAAA;IACpE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,gBAAgB,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAA;AAC/E,CAAC;AAED,SAAS,gBAAgB,CACvB,KAAuB,EACvB,OAAwB;IAExB,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,EAAE,CAAA;IAEjC,IAAI,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,MAAM,EAAE,GAAqB,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAA;QAClF,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS;YAAE,EAAE,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAA;QACxE,OAAO,EAAE,CAAA;IACX,CAAC;IAED,MAAM,KAAK,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAA;IACzC,MAAM,EAAE,GAAyB,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,CAAA;IAClE,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS;QAAE,EAAE,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAA;IACxE,OAAO,EAAE,CAAA;AACX,CAAC;AAED,SAAS,WAAW,CAAC,KAAkB;IACrC,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW;QAC9C,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;KACtD,CAAA;AACH,CAAC;AAED,SAAS,eAAe,CAAC,IAAqB;IAC5C,IAAI,IAAI,GAAG,EAAE,CAAA;IACb,MAAM,WAAW,GAAe,EAAE,CAAA;IAElC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChC,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC1B,MAAM,CAAC,GAAG,KAAoB,CAAA;YAC9B,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QACxD,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACjC,MAAM,MAAM,GAAG,KAAoB,CAAA;YACnC,KAAK,MAAM,UAAU,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACtC,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,mBAAmB,CAAC,UAAU,CAAC,EAAE,CAAC,CAAA;YAC7D,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAa,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,CAAA;IAC9C,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,CAAC,KAAK,GAAG,WAAW,CAAA;IACtD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,SAAS,mBAAmB,CAAC,IAAqB;IAChD,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChC,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC1B,MAAM,CAAC,GAAG,KAAoB,CAAA;YAC9B,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QACvD,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAA;AAClB,CAAC;AAED,SAAS,WAAW,CAAC,KAAkB,EAAE,OAAwB;IAC/D,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;QAC3B,OAAO;YACL,IAAI,EAAE,MAAM;YACZ,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,UAAU,EAAE,OAAO,CAAC,cAAc;YAClC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9B,CAAA;IACrB,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAA6B,CAAA;AAC3E,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAwB;IACjD,IAAI,IAAI,GAAG,EAAE,CAAA;IACb,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YAC7B,IAAI,CAAC,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBAC3B,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,gBAAgB,CAAE,CAAsB,CAAC,MAAM,IAAI,EAAE,CAAC,CAAA;YACpF,CAAC;QACH,CAAC;IACH,CAAC;IACD,IAAI,CAAC,IAAI;QAAE,IAAI,GAAG,KAAK,CAAC,IAAI,CAAA;IAC5B,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAA;AACrC,CAAC;AAED,iFAAiF;AAEjF,SAAS,cAAc,CAAC,MAAe;IACrC,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;AAC7G,CAAC;AAED,SAAS,gBAAgB,CAAC,MAAe;IACvC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;QACpB,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM;YAAE,OAAQ,CAAiB,CAAC,IAAI,CAAA;QACrD,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,gBAAgB,CAAE,CAAmB,CAAC,MAAM,IAAI,EAAE,CAAC,CAAA;QACnF,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI;YAAE,OAAO,gBAAgB,CAAE,CAAe,CAAC,MAAM,IAAI,EAAE,CAAC,CAAA;QAC3E,IAAI,CAAC,CAAC,IAAI,KAAK,UAAU;YAAE,OAAQ,CAAqB,CAAC,IAAI,CAAA;QAC7D,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM;YAAE,OAAO,gBAAgB,CAAE,CAAiB,CAAC,MAAM,IAAI,EAAE,CAAC,CAAA;QAC/E,IAAI,CAAC,CAAC,IAAI,KAAK,KAAK;YAAE,OAAO,gBAAgB,CAAG,CAAsC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAA;QACpG,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAQ,CAAmB,CAAC,IAAI,CAAA;QACzD,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI;YAAE,OAAO,GAAG,CAAA;QAC/B,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO;YAAE,OAAQ,CAAkB,CAAC,IAAI,IAAI,EAAE,CAAA;QAC7D,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO;YAAE,OAAO,GAAG,CAAA;QAClC,OAAO,EAAE,CAAA;IACX,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AACb,CAAC;AAED,SAAS,mBAAmB,CAAC,MAAe;IAC1C,MAAM,KAAK,GAAiB,EAAE,CAAA;IAE9B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC1B,MAAM,IAAI,GAAI,KAAqB,CAAC,IAAI,CAAA;YACxC,IAAI,IAAI;gBAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAA;QAChC,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACnC,MAAM,CAAC,GAAG,KAAsB,CAAA;YAChC,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,IAAI,EAAE,CAAA;YAC5B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAE,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBACpD,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAG,KAAK,CAAC,CAAC,CAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAA;YACvE,CAAC;iBAAM,CAAC;gBACN,KAAK,MAAM,CAAC,IAAI,mBAAmB,CAAC,KAAK,CAAC;oBAAE,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAA;YACnF,CAAC;QACH,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YAC/B,MAAM,CAAC,GAAG,KAAkB,CAAA;YAC5B,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,IAAI,EAAE,CAAA;YAC5B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAE,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBACpD,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAG,KAAK,CAAC,CAAC,CAAiB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAA;YAC3E,CAAC;iBAAM,CAAC;gBACN,KAAK,MAAM,CAAC,IAAI,mBAAmB,CAAC,KAAK,CAAC;oBAAE,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAA;YACvF,CAAC;QACH,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YACrC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAG,KAAyB,CAAC,IAAI,EAAE,CAAC,CAAA;QACvD,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACjC,MAAM,CAAC,GAAG,KAAoB,CAAA;YAC9B,MAAM,IAAI,GAAG,gBAAgB,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAA;YAC7C,IAAI,IAAI;gBAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;QAC9C,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;YAChC,MAAM,KAAK,GAAK,KAA0C,CAAC,MAAM,IAAI,EAAE,CAAA;YACvE,KAAK,MAAM,CAAC,IAAI,mBAAmB,CAAC,KAAK,CAAC;gBAAE,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAA;QACvF,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACnC,MAAM,IAAI,GAAI,KAAuB,CAAC,IAAI,CAAA;YAC1C,IAAI,IAAI;gBAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAA;QAChC,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YAC/B,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAA;QAC3B,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAClC,MAAM,OAAO,GAAI,KAAsB,CAAC,IAAI,CAAA;YAC5C,IAAI,OAAO;gBAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAA;QAC5C,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAClC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAA;QAC3B,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"measure-blocks.d.ts","sourceRoot":"","sources":["../src/measure-blocks.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,cAAc,EAAE,aAAa,EAE7B,QAAQ,EAAE,SAAS,EAAgB,WAAW,EAC/C,MAAM,YAAY,CAAA;AAInB,OAAO,EAAe,cAAc,EAAmC,MAAM,mBAAmB,CAAA;AAuBhG,wBAAsB,YAAY,CAChC,OAAO,EAAE,cAAc,EACvB,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,WAAW,EAChB,cAAc,CAAC,EAAE,cAAc,GAC9B,OAAO,CAAC,aAAa,GAAG,aAAa,EAAE,CAAC,CAwe1C;AAMD,2DAA2D;AAC3D,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,OAAO,YAAY,EAAE,YAAY,EAC1C,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,aAAa,CAAC,CAqExB;AAID,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,OAAO,YAAY,EAAE,YAAY,EAC1C,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,MAAM,EACzB,GAAG,EAAE,OAAO,YAAY,EAAE,WAAW,GACpC,OAAO,CAAC,aAAa,CAAC,CAuExB;AAID,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,OAAO,YAAY,EAAE,iBAAiB,EAC/C,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,MAAM,EACzB,GAAG,EAAE,WAAW,EAChB,cAAc,CAAC,EAAE,cAAc,GAC9B,OAAO,CAAC,aAAa,CAAC,CA8GxB;AAuSD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,SAAS,EAAE,EACpB,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,aAAa,CAAC,EAAE,MAAM,EAAE,GACvB,MAAM,EAAE,CA4EV;AAgED;;;GAGG"}
1
+ {"version":3,"file":"measure-blocks.d.ts","sourceRoot":"","sources":["../src/measure-blocks.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,cAAc,EAAE,aAAa,EAE7B,QAAQ,EAAE,SAAS,EAAgB,WAAW,EAC/C,MAAM,YAAY,CAAA;AAInB,OAAO,EAAe,cAAc,EAAmC,MAAM,mBAAmB,CAAA;AAwBhG,wBAAsB,YAAY,CAChC,OAAO,EAAE,cAAc,EACvB,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,WAAW,EAChB,cAAc,CAAC,EAAE,cAAc,GAC9B,OAAO,CAAC,aAAa,GAAG,aAAa,EAAE,CAAC,CAqf1C;AAMD,2DAA2D;AAC3D,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,OAAO,YAAY,EAAE,YAAY,EAC1C,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,aAAa,CAAC,CAqExB;AAID,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,OAAO,YAAY,EAAE,YAAY,EAC1C,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,MAAM,EACzB,GAAG,EAAE,OAAO,YAAY,EAAE,WAAW,GACpC,OAAO,CAAC,aAAa,CAAC,CAiFxB;AAID,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,OAAO,YAAY,EAAE,iBAAiB,EAC/C,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,MAAM,EACzB,GAAG,EAAE,WAAW,EAChB,cAAc,CAAC,EAAE,cAAc,GAC9B,OAAO,CAAC,aAAa,CAAC,CA8GxB;AAmaD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,SAAS,EAAE,EACpB,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,aAAa,CAAC,EAAE,MAAM,EAAE,GACvB,MAAM,EAAE,CA4EV;AA8ND;;;GAGG"}
@@ -6,6 +6,7 @@ import { PretextPdfError } from './errors.js';
6
6
  import { measureRichText } from './rich-text.js';
7
7
  import { buildFontKey } from './measure.js';
8
8
  import { measureText, getPretext, detectAndReorderRTL } from './measure-text.js';
9
+ import { LINE_HEIGHT_BODY, LINE_HEIGHT_COMPACT } from './render-utils.js';
9
10
  /** Heading level size multipliers and defaults */
10
11
  const HEADING_DEFAULTS = {
11
12
  1: { sizeMultiplier: 2.0, fontWeight: 700, spaceAfter: 16, spaceBefore: 28 },
@@ -68,7 +69,7 @@ export async function measureBlock(element, contentWidth, doc, hyphenatorOpts) {
68
69
  case 'form-field': {
69
70
  const el = element;
70
71
  const fs = el.fontSize ?? baseFontSize;
71
- const labelHeight = el.label ? fs * 1.5 + 4 : 0;
72
+ const labelHeight = el.label ? fs * LINE_HEIGHT_BODY + 4 : 0;
72
73
  let fieldHeight = el.height;
73
74
  if (!fieldHeight) {
74
75
  if (el.fieldType === 'text' && el.multiline)
@@ -97,7 +98,7 @@ export async function measureBlock(element, contentWidth, doc, hyphenatorOpts) {
97
98
  // smallCaps renders at 80% of fontSize — measure at the same size to avoid
98
99
  // overestimating block height and wasting vertical space
99
100
  const effectiveFontSize = element.smallCaps === true ? fontSize * 0.8 : fontSize;
100
- const lineHeight = element.lineHeight ?? doc.defaultParagraphStyle?.lineHeight ?? doc.defaultLineHeight ?? (effectiveFontSize * 1.5);
101
+ const lineHeight = element.lineHeight ?? doc.defaultParagraphStyle?.lineHeight ?? doc.defaultLineHeight ?? (effectiveFontSize * LINE_HEIGHT_BODY);
101
102
  const fontFamily = element.fontFamily ?? doc.defaultParagraphStyle?.fontFamily ?? baseFont;
102
103
  const fontWeight = element.fontWeight ?? doc.defaultParagraphStyle?.fontWeight ?? 400;
103
104
  const fontKey = buildFontKey(fontFamily, fontWeight, 'normal');
@@ -108,8 +109,8 @@ export async function measureBlock(element, contentWidth, doc, hyphenatorOpts) {
108
109
  // Multi-column layout
109
110
  let computedColumnWidth = contentWidth;
110
111
  if (columns > 1) {
111
- if (columns > 20) {
112
- throw new PretextPdfError('VALIDATION_ERROR', `columns must be 1–20, got ${columns}`);
112
+ if (columns > 6) {
113
+ throw new PretextPdfError('VALIDATION_ERROR', `columns must be 1–6, got ${columns}`);
113
114
  }
114
115
  computedColumnWidth = (contentWidth - (columns - 1) * columnGap) / columns;
115
116
  if (computedColumnWidth < 50) {
@@ -177,7 +178,7 @@ export async function measureBlock(element, contentWidth, doc, hyphenatorOpts) {
177
178
  const fontSize = element.fontSize ?? (baseHeadingFontSize * defaults.sizeMultiplier);
178
179
  // smallCaps renders at 80% — measure at effective size
179
180
  const effectiveFontSize = element.smallCaps === true ? fontSize * 0.8 : fontSize;
180
- const lineHeight = element.lineHeight ?? doc.defaultParagraphStyle?.lineHeight ?? doc.defaultLineHeight ?? (effectiveFontSize * 1.4);
181
+ const lineHeight = element.lineHeight ?? doc.defaultParagraphStyle?.lineHeight ?? doc.defaultLineHeight ?? (effectiveFontSize * LINE_HEIGHT_COMPACT);
181
182
  const fontFamily = element.fontFamily ?? doc.defaultParagraphStyle?.fontFamily ?? baseFont;
182
183
  const fontWeight = element.fontWeight ?? doc.defaultParagraphStyle?.fontWeight ?? defaults.fontWeight;
183
184
  const fontKey = buildFontKey(fontFamily, fontWeight, 'normal');
@@ -243,7 +244,7 @@ export async function measureBlock(element, contentWidth, doc, hyphenatorOpts) {
243
244
  const fullText = element.spans.map(s => s.text).join('');
244
245
  const { isRTL } = await detectAndReorderRTL(fullText, element.dir);
245
246
  const fontSize = element.fontSize ?? baseFontSize;
246
- const lineHeight = element.lineHeight ?? doc.defaultLineHeight ?? (fontSize * 1.5);
247
+ const lineHeight = element.lineHeight ?? doc.defaultLineHeight ?? (fontSize * LINE_HEIGHT_BODY);
247
248
  // 'justify' uses left alignment for measurement (justify is rendering-only)
248
249
  const alignRaw = element.align ?? 'left';
249
250
  const align = alignRaw === 'justify' ? 'left' : alignRaw;
@@ -307,7 +308,7 @@ export async function measureBlock(element, contentWidth, doc, hyphenatorOpts) {
307
308
  }
308
309
  case 'code': {
309
310
  const fontSize = element.fontSize ?? Math.max(baseFontSize - 2, 8);
310
- const lineHeight = element.lineHeight ?? (fontSize * 1.4);
311
+ const lineHeight = element.lineHeight ?? (fontSize * LINE_HEIGHT_COMPACT);
311
312
  const padding = element.padding ?? 8;
312
313
  // Text area is narrower by padding on both sides
313
314
  const textWidth = contentWidth - 2 * padding;
@@ -316,6 +317,11 @@ export async function measureBlock(element, contentWidth, doc, hyphenatorOpts) {
316
317
  const lines = await measureText(element.text, fontSize, element.fontFamily, 400, Math.max(textWidth, 1), lineHeight);
317
318
  // height = lines * lineHeight + padding top + padding bottom
318
319
  const height = (lines.length || 1) * lineHeight + 2 * padding;
320
+ // Syntax highlighting: tokenize if language is set
321
+ let codeHighlightTokens;
322
+ if (element.language) {
323
+ codeHighlightTokens = await tokenizeCodeForHighlighting(element.text, element.language, element.color ?? '#24292f', lines.length, element.highlightTheme);
324
+ }
319
325
  return {
320
326
  element,
321
327
  height,
@@ -326,6 +332,7 @@ export async function measureBlock(element, contentWidth, doc, hyphenatorOpts) {
326
332
  spaceAfter: element.spaceAfter ?? 12,
327
333
  spaceBefore: element.spaceBefore ?? 12,
328
334
  codePadding: padding,
335
+ ...(codeHighlightTokens ? { codeHighlightTokens } : {}),
329
336
  isRTL: false, // NEW (Phase 7F): Code blocks always LTR
330
337
  };
331
338
  }
@@ -333,7 +340,7 @@ export async function measureBlock(element, contentWidth, doc, hyphenatorOpts) {
333
340
  // NEW (Phase 7F): Detect and reorder RTL text
334
341
  const { visual: visualText, isRTL, logical: logicalText } = await detectAndReorderRTL(element.text, element.dir);
335
342
  const fontSize = element.fontSize ?? baseFontSize;
336
- const lineHeight = element.lineHeight ?? doc.defaultLineHeight ?? (fontSize * 1.5);
343
+ const lineHeight = element.lineHeight ?? doc.defaultLineHeight ?? (fontSize * LINE_HEIGHT_BODY);
337
344
  const fontFamily = element.fontFamily ?? baseFont;
338
345
  const fontWeight = element.fontWeight ?? 400;
339
346
  const fontStyle = element.fontStyle ?? 'normal';
@@ -366,7 +373,7 @@ export async function measureBlock(element, contentWidth, doc, hyphenatorOpts) {
366
373
  case 'callout': {
367
374
  const el = element;
368
375
  const fs = el.fontSize ?? baseFontSize;
369
- const lh = el.lineHeight ?? (fs * 1.5);
376
+ const lh = el.lineHeight ?? (fs * LINE_HEIGHT_BODY);
370
377
  const ph = el.paddingH ?? el.padding ?? 16;
371
378
  const pv = el.paddingV ?? el.padding ?? 10;
372
379
  const family = el.fontFamily ?? baseFont;
@@ -378,7 +385,7 @@ export async function measureBlock(element, contentWidth, doc, hyphenatorOpts) {
378
385
  // Measure title height (one line assumed, bold)
379
386
  let titleHeight = 0;
380
387
  if (el.title) {
381
- titleHeight = fs * 1.4 + 4; // 1.4 line height + 4pt separator
388
+ titleHeight = fs * LINE_HEIGHT_COMPACT + 4; // compact line height + 4pt separator
382
389
  }
383
390
  // Measure content text
384
391
  const innerWidth = contentWidth - ph * 2;
@@ -421,7 +428,7 @@ export async function measureBlock(element, contentWidth, doc, hyphenatorOpts) {
421
428
  const fn = element;
422
429
  const baseFontSize = doc.defaultFontSize ?? 12;
423
430
  const fontSize = fn.fontSize ?? Math.max(8, baseFontSize - 2);
424
- const lineHeight = fontSize * 1.5;
431
+ const lineHeight = fontSize * LINE_HEIGHT_BODY;
425
432
  const fontFamily = fn.fontFamily ?? doc.defaultFont ?? 'Inter';
426
433
  const fontKey = buildFontKey(fontFamily, 400, 'normal');
427
434
  // Measure the def text with a 20pt left indent (for the number prefix space)
@@ -534,16 +541,25 @@ export async function measureFloatImageBlock(element, imageKey, imageMap, conten
534
541
  const imageBlock = await measureImageWithKey(syntheticEl, imageKey, imageMap, floatWidth, pageContentHeight);
535
542
  const imageRenderWidth = imageBlock.imageData.renderWidth;
536
543
  const imageRenderHeight = imageBlock.imageData.renderHeight;
537
- // Measure the float text
544
+ // Measure the float text (plain or rich)
538
545
  const fontSize = element.floatFontSize ?? doc.defaultFontSize ?? 12;
539
- const lineHeight = fontSize * 1.5;
546
+ const lineHeight = fontSize * LINE_HEIGHT_BODY;
540
547
  const fontFamily = element.floatFontFamily ?? doc.defaultFont ?? 'Inter';
541
548
  const fontKey = buildFontKey(fontFamily, 400, 'normal');
542
- const textLines = await measureText(element.floatText, fontSize, fontFamily, 400, textColWidth, lineHeight, undefined);
549
+ let textLines = [];
550
+ let richFloatLines;
551
+ if (element.floatSpans) {
552
+ richFloatLines = await measureRichText(element.floatSpans, fontSize, lineHeight, textColWidth, 'left', doc);
553
+ }
554
+ else {
555
+ textLines = await measureText(element.floatText, fontSize, fontFamily, 400, textColWidth, lineHeight, undefined);
556
+ }
543
557
  // Column X positions
544
558
  const imageColX = element.float === 'left' ? 0 : textColWidth + floatGap;
545
559
  const textColX = element.float === 'left' ? floatWidth + floatGap : 0;
546
- const textHeight = textLines.length * lineHeight;
560
+ const textHeight = richFloatLines
561
+ ? richFloatLines.reduce((sum, l) => sum + l.lineHeight, 0)
562
+ : textLines.length * lineHeight;
547
563
  const compositeHeight = Math.max(imageRenderHeight, textHeight);
548
564
  return {
549
565
  element,
@@ -562,6 +578,7 @@ export async function measureFloatImageBlock(element, imageKey, imageMap, conten
562
578
  textColX,
563
579
  textColWidth,
564
580
  textLines,
581
+ ...(richFloatLines ? { richFloatLines } : {}),
565
582
  textFontKey: fontKey,
566
583
  textFontSize: fontSize,
567
584
  textLineHeight: lineHeight,
@@ -600,7 +617,7 @@ export async function measureFloatGroup(element, imageKey, imageMap, contentWidt
600
617
  const blocks = Array.isArray(measuredEl) ? measuredEl : [measuredEl];
601
618
  for (const block of blocks) {
602
619
  const fontSize = block.fontSize || baseFontSize;
603
- const lineHeight = block.lineHeight || (fontSize * 1.5);
620
+ const lineHeight = block.lineHeight || (fontSize * LINE_HEIGHT_BODY);
604
621
  // Extract text from lines or rich-lines
605
622
  let lines = [];
606
623
  if (block.richLines && block.richLines.length > 0) {
@@ -663,12 +680,12 @@ export async function measureFloatGroup(element, imageKey, imageMap, contentWidt
663
680
  async function measureList(element, contentWidth, doc, baseFontSize, hyphenatorOpts) {
664
681
  const baseFontFamily = doc.defaultFont ?? 'Inter';
665
682
  const fontSize = element.fontSize ?? baseFontSize;
666
- const lineHeight = element.lineHeight ?? doc.defaultLineHeight ?? (fontSize * 1.5);
683
+ const lineHeight = element.lineHeight ?? doc.defaultLineHeight ?? (fontSize * LINE_HEIGHT_BODY);
667
684
  const indent = element.indent ?? 20;
668
685
  const itemSpaceAfter = element.itemSpaceAfter ?? 4;
669
686
  const fontKey = buildFontKey(baseFontFamily, 400, 'normal');
670
687
  const blocks = [];
671
- // Flatten items and nested items
688
+ // Flatten items and nested items (up to 2 levels deep)
672
689
  const nestedStyle = element.nestedNumberingStyle ?? 'continue';
673
690
  let orderedIndex = 1;
674
691
  const allItems = [];
@@ -679,8 +696,8 @@ async function measureList(element, contentWidth, doc, baseFontSize, hyphenatorO
679
696
  ? `${orderedIndex}.`
680
697
  : (element.marker ?? '•');
681
698
  orderedIndex++;
682
- allItems.push({ text: item.text, marker, isNested: false, isFirstInList: isFirst, fontWeight: item.fontWeight ?? 400 });
683
- // Nested items (1 level deep)
699
+ allItems.push({ text: item.text, marker, depth: 0, isFirstInList: isFirst, fontWeight: item.fontWeight ?? 400 });
700
+ // Nested items (depth 1)
684
701
  if (item.items && item.items.length > 0) {
685
702
  // 'restart': nested ordered items count from 1, parent counter unaffected
686
703
  // 'continue': nested items share the parent counter (existing behavior)
@@ -689,11 +706,25 @@ async function measureList(element, contentWidth, doc, baseFontSize, hyphenatorO
689
706
  const nested = item.items[ni];
690
707
  const nestedMarker = element.style === 'ordered'
691
708
  ? `${nestedIndex}.`
692
- : '◦'; // hollow bullet for nested unordered
709
+ : '◦'; // hollow bullet for depth-1 unordered
693
710
  nestedIndex++;
694
711
  if (nestedStyle === 'continue')
695
712
  orderedIndex++;
696
- allItems.push({ text: nested.text, marker: nestedMarker, isNested: true, isFirstInList: false, fontWeight: nested.fontWeight ?? 400 });
713
+ allItems.push({ text: nested.text, marker: nestedMarker, depth: 1, isFirstInList: false, fontWeight: nested.fontWeight ?? 400 });
714
+ // Nested items (depth 2)
715
+ if (nested.items && nested.items.length > 0) {
716
+ let deepIndex = nestedStyle === 'restart' ? 1 : nestedIndex;
717
+ for (let di = 0; di < nested.items.length; di++) {
718
+ const deep = nested.items[di];
719
+ const deepMarker = element.style === 'ordered'
720
+ ? `${deepIndex}.`
721
+ : '▪'; // small filled square for depth-2 unordered
722
+ deepIndex++;
723
+ if (nestedStyle === 'continue')
724
+ orderedIndex++;
725
+ allItems.push({ text: deep.text, marker: deepMarker, depth: 2, isFirstInList: false, fontWeight: deep.fontWeight ?? 400 });
726
+ }
727
+ }
697
728
  }
698
729
  }
699
730
  }
@@ -717,8 +748,8 @@ async function measureList(element, contentWidth, doc, baseFontSize, hyphenatorO
717
748
  for (let i = 0; i < allItems.length; i++) {
718
749
  const item = allItems[i];
719
750
  const isLast = i === allItems.length - 1;
720
- const nestedIndent = item.isNested ? indent + markerWidth : indent;
721
- const nestedTextWidth = item.isNested ? textWidth - markerWidth : textWidth;
751
+ const nestedIndent = indent + item.depth * markerWidth;
752
+ const nestedTextWidth = textWidth - item.depth * markerWidth;
722
753
  const lines = await measureText(item.text, fontSize, baseFontFamily, item.fontWeight, nestedTextWidth, lineHeight, hyphenatorOpts);
723
754
  const listItemData = {
724
755
  marker: item.marker,
@@ -745,25 +776,49 @@ async function measureList(element, contentWidth, doc, baseFontSize, hyphenatorO
745
776
  }
746
777
  return blocks;
747
778
  }
748
- // ─── Table measurement ────────────────────────────────────────────────────────
779
+ /** Build a map from "rowIdx,colIdx" → span origin info for all positions occupied by a rowspan cell from an earlier row. */
780
+ function buildSpanGrid(rows) {
781
+ const grid = new Map();
782
+ for (let ri = 0; ri < rows.length; ri++) {
783
+ let ci = 0;
784
+ for (const cell of rows[ri].cells) {
785
+ while (grid.has(`${ri},${ci}`))
786
+ ci++;
787
+ const cs = cell.colspan ?? 1;
788
+ const rs = cell.rowspan ?? 1;
789
+ for (let r2 = ri + 1; r2 < ri + rs; r2++) {
790
+ for (let c2 = ci; c2 < ci + cs; c2++) {
791
+ grid.set(`${r2},${c2}`, { originRowIdx: ri, originColStart: ci, colspan: cs, rowspan: rs });
792
+ }
793
+ }
794
+ ci += cs;
795
+ }
796
+ }
797
+ return grid;
798
+ }
749
799
  async function measureTable(element, contentWidth, doc, baseFontSize, hyphenatorOpts) {
750
800
  const baseFontFamily = doc.defaultFont ?? 'Inter';
751
801
  const fontSize = element.fontSize ?? baseFontSize;
752
- const lineHeight = doc.defaultLineHeight ?? (fontSize * 1.5);
802
+ const lineHeight = doc.defaultLineHeight ?? (fontSize * LINE_HEIGHT_BODY);
753
803
  const cellPaddingH = element.cellPaddingH ?? 8;
754
804
  const cellPaddingV = element.cellPaddingV ?? 6;
755
805
  const borderWidth = element.borderWidth ?? 0.5;
756
806
  const borderColor = element.borderColor ?? '#cccccc';
757
807
  const headerBgColor = element.headerBgColor ?? '#f5f5f5';
808
+ // Build span occupancy grid (needed for correct colStart tracking in all passes)
809
+ const spanGrid = buildSpanGrid(element.rows);
758
810
  // Pre-pass: measure natural widths for 'auto' columns — run all in parallel
759
811
  const hasAutoColumns = element.columns.some(c => c.width === 'auto');
760
812
  let naturalWidths;
761
813
  if (hasAutoColumns) {
762
814
  naturalWidths = new Array(element.columns.length).fill(0);
763
815
  const jobs = [];
764
- for (const row of element.rows) {
816
+ for (let rowIdx = 0; rowIdx < element.rows.length; rowIdx++) {
817
+ const row = element.rows[rowIdx];
765
818
  let colIdx = 0;
766
819
  for (const cell of row.cells) {
820
+ while (spanGrid.has(`${rowIdx},${colIdx}`))
821
+ colIdx++;
767
822
  const cs = cell.colspan ?? 1;
768
823
  if (element.columns[colIdx]?.width === 'auto') {
769
824
  jobs.push({
@@ -798,10 +853,14 @@ async function measureTable(element, contentWidth, doc, baseFontSize, hyphenator
798
853
  ? element.headerRows
799
854
  : element.rows.filter(r => r.isHeader).length;
800
855
  const allCellMeta = [];
801
- for (const row of element.rows) {
856
+ for (let rowIdx = 0; rowIdx < element.rows.length; rowIdx++) {
857
+ const row = element.rows[rowIdx];
802
858
  let colStart = 0;
803
859
  for (const cell of row.cells) {
860
+ while (spanGrid.has(`${rowIdx},${colStart}`))
861
+ colStart++;
804
862
  const cs = cell.colspan ?? 1;
863
+ const rs = cell.rowspan ?? 1;
805
864
  const col = element.columns[colStart];
806
865
  let mergedWidth = 0;
807
866
  for (let si = colStart; si < colStart + cs && si < columnWidths.length; si++) {
@@ -812,11 +871,11 @@ async function measureTable(element, contentWidth, doc, baseFontSize, hyphenator
812
871
  const fontWeight = (cell.fontWeight ?? (row.isHeader ? 700 : 400));
813
872
  const fontFamily = cell.fontFamily ?? baseFontFamily;
814
873
  const cellFontSize = cell.fontSize ?? fontSize;
815
- const cellLineHeight = doc.defaultLineHeight ?? (cellFontSize * 1.5);
874
+ const cellLineHeight = doc.defaultLineHeight ?? (cellFontSize * LINE_HEIGHT_BODY);
816
875
  const cellFontKey = buildFontKey(fontFamily, fontWeight, 'normal');
817
876
  const textWidth = mergedWidth - 2 * cellPaddingH - borderWidth;
818
- const cellDir = (cell.dir ?? 'auto');
819
- allCellMeta.push({ cell, row, cs, col, mergedWidth, fontWeight, fontFamily, cellFontSize, cellLineHeight, cellFontKey, textWidth, cellDir });
877
+ const cellDir = (cell.dir ?? element.dir ?? 'auto');
878
+ allCellMeta.push({ cell, row, rowIdx, colStart, cs, rs, col, mergedWidth, fontWeight, fontFamily, cellFontSize, cellLineHeight, cellFontKey, textWidth, cellDir });
820
879
  colStart += cs;
821
880
  }
822
881
  }
@@ -826,39 +885,110 @@ async function measureTable(element, contentWidth, doc, baseFontSize, hyphenator
826
885
  const lines = await measureText(cellVisualText, m.cellFontSize, m.fontFamily, m.fontWeight, Math.max(m.textWidth, 1), m.cellLineHeight, hyphenatorOpts);
827
886
  return { cellVisualText, cellIsRTL, lines };
828
887
  }));
829
- // Step 3: Reassemble into measuredRows
888
+ const cellByKey = new Map();
889
+ for (let i = 0; i < allCellMeta.length; i++) {
890
+ const m = allCellMeta[i];
891
+ cellByKey.set(`${m.rowIdx},${m.colStart}`, { meta: m, result: cellResults[i] });
892
+ }
893
+ // Sub-pass 3a: Compute raw row heights from non-spanning cells only
894
+ const rowHeights = new Array(element.rows.length).fill(0);
895
+ for (const { meta: m, result } of cellByKey.values()) {
896
+ if (m.rs === 1) {
897
+ const cellContentHeight = Math.max(result.lines.length, 1) * m.cellLineHeight;
898
+ rowHeights[m.rowIdx] = Math.max(rowHeights[m.rowIdx], cellContentHeight);
899
+ }
900
+ }
901
+ for (let ri = 0; ri < rowHeights.length; ri++) {
902
+ rowHeights[ri] = (rowHeights[ri] ?? 0) + 2 * cellPaddingV;
903
+ }
904
+ // Sub-pass 3b: Expand last spanned row if spanning cell needs more space
905
+ for (const { meta: m, result } of cellByKey.values()) {
906
+ if (m.rs > 1) {
907
+ const cellContentHeight = Math.max(result.lines.length, 1) * m.cellLineHeight + 2 * cellPaddingV;
908
+ let spanHeight = 0;
909
+ for (let r2 = m.rowIdx; r2 < m.rowIdx + m.rs && r2 < rowHeights.length; r2++) {
910
+ spanHeight += rowHeights[r2];
911
+ }
912
+ if (cellContentHeight > spanHeight) {
913
+ const lastRowIdx = Math.min(m.rowIdx + m.rs - 1, rowHeights.length - 1);
914
+ rowHeights[lastRowIdx] += cellContentHeight - spanHeight;
915
+ }
916
+ }
917
+ }
918
+ // Sub-pass 3c: Build measuredRows, inserting placeholder cells for rowspan continuations
830
919
  const measuredRows = [];
831
- let cellIdx = 0;
832
- for (const row of element.rows) {
920
+ const numColumns = element.columns.length;
921
+ for (let rowIdx = 0; rowIdx < element.rows.length; rowIdx++) {
922
+ const row = element.rows[rowIdx];
923
+ const rowHeight = rowHeights[rowIdx];
833
924
  const measuredCells = [];
834
- let maxCellHeight = 0;
835
- for (const _ of row.cells) {
836
- const m = allCellMeta[cellIdx];
837
- const { cellIsRTL, lines } = cellResults[cellIdx];
838
- const cellContentHeight = Math.max(lines.length, 1) * m.cellLineHeight;
839
- maxCellHeight = Math.max(maxCellHeight, cellContentHeight);
840
- const align = m.cell.align ?? m.col.align ?? (cellIsRTL ? 'right' : 'left');
841
- const measuredCell = {
842
- lines,
843
- fontSize: m.cellFontSize,
844
- lineHeight: m.cellLineHeight,
845
- fontKey: m.cellFontKey,
846
- fontFamily: m.fontFamily,
847
- align,
848
- color: m.cell.color ?? '#000000',
849
- colspan: m.cs,
850
- mergedWidth: m.mergedWidth,
851
- isRTL: cellIsRTL,
852
- ...(m.cell.tabularNumbers !== undefined && { tabularNumbers: m.cell.tabularNumbers }),
853
- };
854
- if (m.cell.bgColor !== undefined)
855
- measuredCell.bgColor = m.cell.bgColor;
856
- measuredCells.push(measuredCell);
857
- cellIdx++;
925
+ let hasRowspan = false;
926
+ let colCursor = 0;
927
+ while (colCursor < numColumns) {
928
+ const spanEntry = spanGrid.get(`${rowIdx},${colCursor}`);
929
+ if (spanEntry) {
930
+ // Only insert ONE placeholder per span group (at the leftmost column of the span)
931
+ if (colCursor === spanEntry.originColStart) {
932
+ const originCell = cellByKey.get(`${spanEntry.originRowIdx},${spanEntry.originColStart}`);
933
+ const pw = originCell?.meta.mergedWidth ?? (columnWidths[colCursor] ?? 0);
934
+ measuredCells.push({
935
+ lines: [], fontSize: 0, lineHeight: 0, fontKey: '', fontFamily: '',
936
+ align: 'left', color: '#000000',
937
+ colspan: spanEntry.colspan, mergedWidth: pw,
938
+ isSpanPlaceholder: true,
939
+ });
940
+ colCursor += spanEntry.colspan;
941
+ }
942
+ else {
943
+ // Mid-span column not at origin advance past the full span group
944
+ colCursor += spanEntry.colspan;
945
+ }
946
+ }
947
+ else {
948
+ const cellAtCol = cellByKey.get(`${rowIdx},${colCursor}`);
949
+ if (!cellAtCol) {
950
+ colCursor++;
951
+ continue;
952
+ }
953
+ const { meta: m, result: { cellIsRTL, lines } } = cellAtCol;
954
+ let spanHeight;
955
+ if (m.rs > 1) {
956
+ spanHeight = 0;
957
+ for (let r2 = rowIdx; r2 < rowIdx + m.rs && r2 < rowHeights.length; r2++) {
958
+ spanHeight += rowHeights[r2];
959
+ }
960
+ hasRowspan = true;
961
+ }
962
+ const align = m.cell.align ?? m.col.align ?? (cellIsRTL ? 'right' : 'left');
963
+ const measuredCell = {
964
+ lines,
965
+ fontSize: m.cellFontSize,
966
+ lineHeight: m.cellLineHeight,
967
+ fontKey: m.cellFontKey,
968
+ fontFamily: m.fontFamily,
969
+ align,
970
+ color: m.cell.color ?? '#000000',
971
+ colspan: m.cs,
972
+ mergedWidth: m.mergedWidth,
973
+ isRTL: cellIsRTL,
974
+ ...(m.cell.tabularNumbers !== undefined && { tabularNumbers: m.cell.tabularNumbers }),
975
+ ...(m.rs > 1 ? { rowspan: m.rs } : {}),
976
+ ...(spanHeight !== undefined ? { spanHeight } : {}),
977
+ };
978
+ if (m.cell.bgColor !== undefined)
979
+ measuredCell.bgColor = m.cell.bgColor;
980
+ measuredCells.push(measuredCell);
981
+ colCursor += m.cs;
982
+ }
858
983
  }
859
- const rowHeight = maxCellHeight + 2 * cellPaddingV;
860
- const activeBoundaries = computeActiveBoundaries(row.cells, element.columns.length);
861
- measuredRows.push({ cells: measuredCells, height: rowHeight, isHeader: row.isHeader ?? false, activeBoundaries });
984
+ const activeBoundaries = computeActiveBoundaries(measuredCells, numColumns);
985
+ measuredRows.push({
986
+ cells: measuredCells,
987
+ height: rowHeight,
988
+ isHeader: row.isHeader ?? false,
989
+ activeBoundaries,
990
+ ...(hasRowspan ? { hasRowspan: true } : {}),
991
+ });
862
992
  }
863
993
  // Header rows are the first N rows
864
994
  const headerRows = measuredRows.slice(0, headerRowCount);
@@ -1007,10 +1137,165 @@ async function measureNaturalTextWidth(text, fontSize, fontFamily, fontWeight) {
1007
1137
  // Use a very large width to prevent wrapping; also handle multi-line text (\n)
1008
1138
  // by taking the max line width across all lines
1009
1139
  const prepared = prepareWithSegments(text, fontString, { whiteSpace: 'pre-wrap' });
1010
- const result = layoutWithLines(prepared, 99999, fontSize * 1.5);
1140
+ const result = layoutWithLines(prepared, 99999, fontSize * LINE_HEIGHT_BODY);
1011
1141
  const lines = result.lines ?? [];
1012
1142
  return lines.reduce((max, line) => Math.max(max, line.width), 0);
1013
1143
  }
1144
+ // ─── Syntax highlighting ──────────────────────────────────────────────────────
1145
+ /** Default GitHub-light-inspired highlight theme colors */
1146
+ const DEFAULT_HIGHLIGHT_THEME = {
1147
+ keyword: '#cf222e',
1148
+ string: '#0a3069',
1149
+ comment: '#6e7781',
1150
+ number: '#0550ae',
1151
+ function: '#8250df',
1152
+ title: '#8250df',
1153
+ built_in: '#0550ae',
1154
+ literal: '#0550ae',
1155
+ type: '#953800',
1156
+ meta: '#cf222e',
1157
+ attr: '#0550ae',
1158
+ name: '#0550ae',
1159
+ params: '#24292f',
1160
+ punctuation: '#24292f',
1161
+ operator: '#24292f',
1162
+ regexp: '#0a3069',
1163
+ variable: '#953800',
1164
+ property: '#0550ae',
1165
+ tag: '#116329',
1166
+ selector: '#116329',
1167
+ subst: '#24292f',
1168
+ 'template-tag': '#cf222e',
1169
+ 'template-string': '#0a3069',
1170
+ symbol: '#0550ae',
1171
+ addition: '#116329',
1172
+ deletion: '#cf222e',
1173
+ section: '#0550ae',
1174
+ };
1175
+ /** Cached highlight.js module (loaded once, reused across code blocks) */
1176
+ let _hljsCache = null;
1177
+ let _hljsLoadAttempted = false;
1178
+ /**
1179
+ * Tokenize source code into per-line colored spans using highlight.js.
1180
+ * Returns undefined if highlight.js is not installed (renderer falls back to plain text).
1181
+ */
1182
+ async function tokenizeCodeForHighlighting(text, language, defaultColor, measuredLineCount, customTheme) {
1183
+ if (!_hljsLoadAttempted) {
1184
+ _hljsLoadAttempted = true;
1185
+ try {
1186
+ const mod = await import('highlight.js');
1187
+ _hljsCache = mod.default ?? mod;
1188
+ }
1189
+ catch { /* not installed */ }
1190
+ }
1191
+ if (!_hljsCache)
1192
+ return undefined;
1193
+ const hljs = _hljsCache;
1194
+ const theme = { ...DEFAULT_HIGHLIGHT_THEME };
1195
+ if (customTheme) {
1196
+ for (const [k, v] of Object.entries(customTheme)) {
1197
+ if (v !== undefined)
1198
+ theme[k] = v;
1199
+ }
1200
+ }
1201
+ let highlighted;
1202
+ try {
1203
+ const result = language === 'auto'
1204
+ ? hljs.highlightAuto(text)
1205
+ : hljs.highlight(text, { language });
1206
+ highlighted = result.value;
1207
+ }
1208
+ catch {
1209
+ return undefined;
1210
+ }
1211
+ const tokens = parseHighlightHtml(highlighted, defaultColor, theme);
1212
+ // Safety check: tokenizer splits on \n but the layout engine may wrap long lines.
1213
+ // If line counts don't match, the colors would be applied to the wrong lines.
1214
+ if (tokens.length !== measuredLineCount)
1215
+ return undefined;
1216
+ return tokens;
1217
+ }
1218
+ /**
1219
+ * Parse highlight.js HTML into per-line token arrays.
1220
+ * Handles nested spans (e.g. string interpolation) by tracking a color stack.
1221
+ */
1222
+ function parseHighlightHtml(html, defaultColor, theme) {
1223
+ const lines = [[]];
1224
+ const colorStack = [defaultColor];
1225
+ let i = 0;
1226
+ while (i < html.length) {
1227
+ if (html[i] === '<') {
1228
+ const closeTag = html.indexOf('>', i);
1229
+ if (closeTag === -1)
1230
+ break;
1231
+ const tag = html.slice(i, closeTag + 1);
1232
+ if (tag.startsWith('<span')) {
1233
+ // Extract class: <span class="hljs-keyword"> or <span class="hljs-template-string">
1234
+ const classMatch = tag.match(/class="hljs-([\w-]+)"/);
1235
+ const cls = classMatch ? classMatch[1] : '';
1236
+ colorStack.push(theme[cls] ?? defaultColor);
1237
+ }
1238
+ else if (tag === '</span>') {
1239
+ if (colorStack.length > 1)
1240
+ colorStack.pop();
1241
+ }
1242
+ i = closeTag + 1;
1243
+ }
1244
+ else if (html[i] === '&') {
1245
+ // HTML entities: named (&amp;), hex (&#x3D;), decimal (&#96;)
1246
+ const semi = html.indexOf(';', i);
1247
+ if (semi !== -1 && semi - i < 10) {
1248
+ const entity = html.slice(i, semi + 1);
1249
+ let ch;
1250
+ if (entity === '&amp;')
1251
+ ch = '&';
1252
+ else if (entity === '&lt;')
1253
+ ch = '<';
1254
+ else if (entity === '&gt;')
1255
+ ch = '>';
1256
+ else if (entity === '&quot;')
1257
+ ch = '"';
1258
+ else if (entity === '&#x27;' || entity === '&apos;')
1259
+ ch = "'";
1260
+ else if (entity.startsWith('&#x'))
1261
+ ch = String.fromCodePoint(parseInt(entity.slice(3, -1), 16));
1262
+ else if (entity.startsWith('&#'))
1263
+ ch = String.fromCodePoint(parseInt(entity.slice(2, -1), 10));
1264
+ else
1265
+ ch = entity; // unknown named entity — keep as-is
1266
+ lines[lines.length - 1].push({ text: ch, color: colorStack[colorStack.length - 1] });
1267
+ i = semi + 1;
1268
+ }
1269
+ else {
1270
+ lines[lines.length - 1].push({ text: '&', color: colorStack[colorStack.length - 1] });
1271
+ i++;
1272
+ }
1273
+ }
1274
+ else if (html[i] === '\n') {
1275
+ lines.push([]);
1276
+ i++;
1277
+ }
1278
+ else {
1279
+ // Regular text — accumulate consecutive chars with same color
1280
+ const color = colorStack[colorStack.length - 1];
1281
+ let end = i + 1;
1282
+ while (end < html.length && html[end] !== '<' && html[end] !== '&' && html[end] !== '\n')
1283
+ end++;
1284
+ lines[lines.length - 1].push({ text: html.slice(i, end), color });
1285
+ i = end;
1286
+ }
1287
+ }
1288
+ // Merge adjacent tokens with the same color on each line (fewer drawText calls)
1289
+ for (const line of lines) {
1290
+ for (let j = line.length - 1; j > 0; j--) {
1291
+ if (line[j].color === line[j - 1].color) {
1292
+ line[j - 1].text += line[j].text;
1293
+ line.splice(j, 1);
1294
+ }
1295
+ }
1296
+ }
1297
+ return lines;
1298
+ }
1014
1299
  // ─── Text measurement (shared by all text-bearing elements) ──────────────────
1015
1300
  /**
1016
1301
  * Measure text with automatic word hyphenation (Liang's algorithm via hypher).