kordoc 3.5.0 → 3.5.1

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 (83) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +590 -590
  3. package/dist/{-GKNSTVEZ.js → -BNJMPTQ2.js} +5 -5
  4. package/dist/{chunk-X34YWRL5.cjs → chunk-DCZVOIEO.cjs} +1 -1
  5. package/dist/chunk-DCZVOIEO.cjs.map +1 -0
  6. package/dist/{chunk-AIQ3ISQU.js → chunk-GE43BE46.js} +1 -1
  7. package/dist/chunk-GE43BE46.js.map +1 -0
  8. package/dist/{chunk-GTCJQANY.js → chunk-GMPDZD7Q.js} +44 -36
  9. package/dist/chunk-GMPDZD7Q.js.map +1 -0
  10. package/dist/chunk-GS7T56RP.cjs.map +1 -1
  11. package/dist/{chunk-A4LSWJEC.js → chunk-IKAXUVSU.js} +16 -2
  12. package/dist/chunk-IKAXUVSU.js.map +1 -0
  13. package/dist/{chunk-CFQLK3LM.js → chunk-MEPHGCPQ.js} +1 -1
  14. package/dist/chunk-MEPHGCPQ.js.map +1 -0
  15. package/dist/{chunk-3TBUDJDE.js → chunk-MOL7MDBG.js} +1 -1
  16. package/dist/chunk-MOL7MDBG.js.map +1 -0
  17. package/dist/{chunk-AHDNELBR.js → chunk-OXOPDVNV.js} +16 -2
  18. package/dist/chunk-OXOPDVNV.js.map +1 -0
  19. package/dist/{chunk-4JLD2IT7.cjs → chunk-RWSGBXSA.cjs} +25 -11
  20. package/dist/chunk-RWSGBXSA.cjs.map +1 -0
  21. package/dist/cli.js +8 -8
  22. package/dist/cli.js.map +1 -1
  23. package/dist/{detect-NTR2FUEG.js → detect-RI2MQ33K.js} +2 -2
  24. package/dist/{formula-ZYUDQHD3.cjs → formula-5NKVS2LR.cjs} +1 -1
  25. package/dist/formula-5NKVS2LR.cjs.map +1 -0
  26. package/dist/{formula-KOQV353G.js → formula-JCNF43NE.js} +1 -1
  27. package/dist/formula-JCNF43NE.js.map +1 -0
  28. package/dist/{formula-MEDGYQXS.js → formula-RXVSQPXI.js} +1 -1
  29. package/dist/formula-RXVSQPXI.js.map +1 -0
  30. package/dist/index.cjs +181 -173
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.js +42 -34
  33. package/dist/index.js.map +1 -1
  34. package/dist/mcp.js +6 -6
  35. package/dist/mcp.js.map +1 -1
  36. package/dist/page-range-737B4EZW.js +8 -0
  37. package/dist/page-range-LNMXJU6W.js +7 -0
  38. package/dist/page-range-P7SDW6LR.cjs +8 -0
  39. package/dist/page-range-P7SDW6LR.cjs.map +1 -0
  40. package/dist/{parser-LBLPN3KY.js → parser-GHZMDUGG.js} +5 -5
  41. package/dist/parser-GHZMDUGG.js.map +1 -0
  42. package/dist/{parser-LMBDB7RM.js → parser-XXQAMYGV.js} +5 -5
  43. package/dist/parser-XXQAMYGV.js.map +1 -0
  44. package/dist/{parser-25ISHR7Q.cjs → parser-ZLFOUVR4.cjs} +18 -18
  45. package/dist/parser-ZLFOUVR4.cjs.map +1 -0
  46. package/dist/{provider-H4WWSPOV.js → provider-4ZJKV3DC.js} +1 -1
  47. package/dist/provider-4ZJKV3DC.js.map +1 -0
  48. package/dist/{provider-7H4CPZYS.js → provider-AKROB7WQ.js} +1 -1
  49. package/dist/provider-AKROB7WQ.js.map +1 -0
  50. package/dist/{provider-5K7O3Z2O.cjs → provider-G4C2V2PD.cjs} +1 -1
  51. package/dist/provider-G4C2V2PD.cjs.map +1 -0
  52. package/dist/{setup-QSJ2INNS.js → setup-57FB3LSP.js} +1 -1
  53. package/dist/setup-57FB3LSP.js.map +1 -0
  54. package/dist/{watch-77CYT54V.js → watch-IN3YN72W.js} +5 -5
  55. package/dist/watch-IN3YN72W.js.map +1 -0
  56. package/package.json +98 -98
  57. package/dist/chunk-3TBUDJDE.js.map +0 -1
  58. package/dist/chunk-4JLD2IT7.cjs.map +0 -1
  59. package/dist/chunk-A4LSWJEC.js.map +0 -1
  60. package/dist/chunk-AHDNELBR.js.map +0 -1
  61. package/dist/chunk-AIQ3ISQU.js.map +0 -1
  62. package/dist/chunk-CFQLK3LM.js.map +0 -1
  63. package/dist/chunk-GTCJQANY.js.map +0 -1
  64. package/dist/chunk-X34YWRL5.cjs.map +0 -1
  65. package/dist/formula-KOQV353G.js.map +0 -1
  66. package/dist/formula-MEDGYQXS.js.map +0 -1
  67. package/dist/formula-ZYUDQHD3.cjs.map +0 -1
  68. package/dist/page-range-CIRRJPXJ.js +0 -7
  69. package/dist/page-range-OF5I4PQY.js +0 -8
  70. package/dist/page-range-TPIRSA3J.cjs +0 -8
  71. package/dist/page-range-TPIRSA3J.cjs.map +0 -1
  72. package/dist/parser-25ISHR7Q.cjs.map +0 -1
  73. package/dist/parser-LBLPN3KY.js.map +0 -1
  74. package/dist/parser-LMBDB7RM.js.map +0 -1
  75. package/dist/provider-5K7O3Z2O.cjs.map +0 -1
  76. package/dist/provider-7H4CPZYS.js.map +0 -1
  77. package/dist/provider-H4WWSPOV.js.map +0 -1
  78. package/dist/setup-QSJ2INNS.js.map +0 -1
  79. package/dist/watch-77CYT54V.js.map +0 -1
  80. /package/dist/{-GKNSTVEZ.js.map → -BNJMPTQ2.js.map} +0 -0
  81. /package/dist/{detect-NTR2FUEG.js.map → detect-RI2MQ33K.js.map} +0 -0
  82. /package/dist/{page-range-CIRRJPXJ.js.map → page-range-737B4EZW.js.map} +0 -0
  83. /package/dist/{page-range-OF5I4PQY.js.map → page-range-LNMXJU6W.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/chris_gomdori/workspace/kordoc/dist/chunk-RWSGBXSA.cjs","../src/utils.ts","../src/shared/pua.ts","../src/table/builder.ts","../src/types.ts"],"names":[],"mappings":"AAAA;ACIO,IAAM,QAAA,EAAkB,KAAA,EAA4C,QAAA,EAAqB,WAAA;AAOzF,SAAS,aAAA,CAAc,GAAA,EAA0B;AACtD,EAAA,GAAA,CAAI,GAAA,CAAI,WAAA,IAAe,EAAA,GAAK,GAAA,CAAI,WAAA,IAAe,GAAA,CAAI,MAAA,CAAO,UAAA,EAAY;AACpE,IAAA,OAAO,GAAA,CAAI,MAAA;AAAA,EACb;AACA,EAAA,OAAO,GAAA,CAAI,MAAA,CAAO,KAAA,CAAM,GAAA,CAAI,UAAA,EAAY,GAAA,CAAI,WAAA,EAAa,GAAA,CAAI,UAAU,CAAA;AACzE;AAMO,IAAM,YAAA,EAAN,MAAA,QAA0B,MAAM;AAAA,EACrC,WAAA,CAAY,OAAA,EAAiB;AAC3B,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,KAAA,EAAO,aAAA;AAAA,EACd;AACF,CAAA;AAeO,SAAS,eAAA,CAAgB,IAAA,EAAuB;AACrD,EAAA,GAAA,CAAI,IAAA,CAAK,QAAA,CAAS,IAAM,CAAA,EAAG,OAAO,IAAA;AAClC,EAAA,MAAM,WAAA,EAAa,IAAA,CAAK,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA;AAC1C,EAAA,MAAM,SAAA,EAAW,UAAA,CAAW,KAAA,CAAM,GAAG,CAAA;AACrC,EAAA,OAAO,QAAA,CAAS,IAAA,CAAK,CAAA,CAAA,EAAA,GAAK,EAAA,IAAM,IAAI,EAAA,GAAK,UAAA,CAAW,UAAA,CAAW,GAAG,EAAA,GAAK,YAAA,CAAa,IAAA,CAAK,UAAU,CAAA;AACrG;AASO,SAAS,oBAAA,CAAqB,IAAA,EAA6B;AAChE,EAAA,GAAA,CAAI,CAAC,IAAA,EAAM,OAAO,IAAA;AAClB,EAAA,IAAI,WAAA,EAAa,IAAA,CAAK,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AAC5D,EAAA,GAAA,CAAI,eAAA,CAAgB,UAAU,CAAA,EAAG,OAAO,IAAA;AACxC,EAAA,GAAA,CAAI,sBAAA,CAAuB,IAAA,CAAK,UAAU,CAAA,EAAG,WAAA,EAAa,YAAA,EAAc,UAAA;AACxE,EAAA,OAAO,6BAAA,CAA8B,IAAA,CAAK,UAAU,EAAA,EAAI,WAAA,EAAa,IAAA;AACvE;AAGO,SAAS,mBAAA,CAAoB,CAAA,EAAW,CAAA,EAAmB;AAChE,EAAA,MAAM,GAAA,EAAK,MAAA,kCAAO,CAAA,qBAAE,KAAA,mBAAM,uBAAuB,CAAA,4BAAA,CAAI,CAAC,GAAA,UAAK,MAAA,CAAO,kBAAgB,CAAA;AAClF,EAAA,MAAM,GAAA,EAAK,MAAA,kCAAO,CAAA,qBAAE,KAAA,mBAAM,uBAAuB,CAAA,4BAAA,CAAI,CAAC,GAAA,UAAK,MAAA,CAAO,kBAAgB,CAAA;AAClF,EAAA,OAAO,GAAA,IAAO,GAAA,EAAK,CAAA,CAAE,aAAA,CAAc,CAAC,EAAA,EAAI,GAAA,EAAK,EAAA;AAC/C;AAQO,SAAS,eAAA,CACd,MAAA,EACA,oBAAA,EAAsB,IAAA,EAAM,KAAA,EAAO,IAAA,EACnC,WAAA,EAAa,GAAA,EACsC;AACnD,EAAA,IAAI;AACF,IAAA,MAAM,KAAA,EAAO,IAAI,QAAA,CAAS,MAAM,CAAA;AAChC,IAAA,MAAM,IAAA,EAAM,MAAA,CAAO,UAAA;AAEnB,IAAA,IAAI,WAAA,EAAa,CAAA,CAAA;AACjB,IAAA,IAAA,CAAA,IAAS,EAAA,EAAI,IAAA,EAAM,EAAA,EAAI,EAAA,GAAK,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,EAAM,KAAK,CAAA,EAAG,CAAA,EAAA,EAAK;AACzD,MAAA,GAAA,CAAI,IAAA,CAAK,SAAA,CAAU,CAAA,EAAG,IAAI,EAAA,IAAM,SAAA,EAAY;AAAE,QAAA,WAAA,EAAa,CAAA;AAAG,QAAA,KAAA;AAAA,MAAM;AAAA,IACtE;AACA,IAAA,GAAA,CAAI,WAAA,EAAa,CAAA,EAAG,OAAO,EAAE,iBAAA,EAAmB,CAAA,EAAG,UAAA,EAAY,EAAE,CAAA;AAEjE,IAAA,MAAM,WAAA,EAAa,IAAA,CAAK,SAAA,CAAU,WAAA,EAAa,EAAA,EAAI,IAAI,CAAA;AACvD,IAAA,GAAA,CAAI,WAAA,EAAa,UAAA,EAAY;AAC3B,MAAA,MAAM,IAAI,WAAA,CAAY,CAAA,4CAAA,EAAiB,UAAU,CAAA,eAAA,EAAQ,UAAU,CAAA,CAAA,CAAG,CAAA;AAAA,IACxE;AAEA,IAAA,MAAM,OAAA,EAAS,IAAA,CAAK,SAAA,CAAU,WAAA,EAAa,EAAA,EAAI,IAAI,CAAA;AACnD,IAAA,MAAM,SAAA,EAAW,IAAA,CAAK,SAAA,CAAU,WAAA,EAAa,EAAA,EAAI,IAAI,CAAA;AACrD,IAAA,GAAA,CAAI,SAAA,EAAW,OAAA,EAAS,GAAA,EAAK,OAAO,EAAE,iBAAA,EAAmB,CAAA,EAAG,WAAW,CAAA;AAEvE,IAAA,IAAI,kBAAA,EAAoB,CAAA;AACxB,IAAA,IAAI,IAAA,EAAM,QAAA;AACV,IAAA,IAAA,CAAA,IAAS,EAAA,EAAI,CAAA,EAAG,EAAA,EAAI,WAAA,GAAc,IAAA,EAAM,GAAA,GAAM,SAAA,EAAW,MAAA,EAAQ,CAAA,EAAA,EAAK;AACpE,MAAA,GAAA,CAAI,IAAA,CAAK,SAAA,CAAU,GAAA,EAAK,IAAI,EAAA,IAAM,QAAA,EAAY,KAAA;AAC9C,MAAA,kBAAA,GAAqB,IAAA,CAAK,SAAA,CAAU,IAAA,EAAM,EAAA,EAAI,IAAI,CAAA;AAClD,MAAA,MAAM,QAAA,EAAU,IAAA,CAAK,SAAA,CAAU,IAAA,EAAM,EAAA,EAAI,IAAI,CAAA;AAC7C,MAAA,MAAM,SAAA,EAAW,IAAA,CAAK,SAAA,CAAU,IAAA,EAAM,EAAA,EAAI,IAAI,CAAA;AAC9C,MAAA,MAAM,WAAA,EAAa,IAAA,CAAK,SAAA,CAAU,IAAA,EAAM,EAAA,EAAI,IAAI,CAAA;AAChD,MAAA,IAAA,GAAO,GAAA,EAAK,QAAA,EAAU,SAAA,EAAW,UAAA;AAAA,IACnC;AAEA,IAAA,GAAA,CAAI,kBAAA,EAAoB,mBAAA,EAAqB;AAC3C,MAAA,MAAM,IAAI,WAAA,CAAY,CAAA,kDAAA,EAAA,CAAmB,kBAAA,EAAoB,KAAA,EAAO,IAAA,CAAA,CAAM,OAAA,CAAQ,CAAC,CAAC,CAAA,iBAAA,EAAU,oBAAA,EAAsB,KAAA,EAAO,IAAI,CAAA,GAAA,CAAK,CAAA;AAAA,IACtI;AAEA,IAAA,OAAO,EAAE,iBAAA,EAAmB,WAAW,CAAA;AAAA,EACzC,EAAA,MAAA,CAAS,GAAA,EAAK;AACZ,IAAA,GAAA,CAAI,IAAA,WAAe,WAAA,EAAa,MAAM,GAAA;AACtC,IAAA,OAAO,EAAE,iBAAA,EAAmB,CAAA,EAAG,UAAA,EAAY,EAAE,CAAA;AAAA,EAC/C;AACF;AAGO,SAAS,QAAA,CAAS,GAAA,EAAqB;AAC5C,EAAA,OAAO,GAAA,CAAI,OAAA,CAAQ,wCAAA,EAA0C,EAAE,CAAA;AACjE;AAGA,IAAM,aAAA,EAAe,8BAAA;AACd,SAAS,YAAA,CAAa,IAAA,EAA6B;AACxD,EAAA,MAAM,QAAA,EAAU,IAAA,CAAK,IAAA,CAAK,CAAA;AAC1B,EAAA,GAAA,CAAI,CAAC,QAAA,GAAW,CAAC,YAAA,CAAa,IAAA,CAAK,OAAO,CAAA,EAAG,OAAO,IAAA;AACpD,EAAA,OAAO,OAAA;AACT;AAKO,SAAS,OAAA,CAAQ,GAAA,EAAuB;AAC7C,EAAA,IAAI,IAAA,EAAM,QAAA;AACV,EAAA,IAAA,CAAA,IAAS,EAAA,EAAI,CAAA,EAAG,EAAA,EAAI,GAAA,CAAI,MAAA,EAAQ,CAAA,EAAA,EAAK,GAAA,CAAI,GAAA,CAAI,CAAC,EAAA,EAAI,GAAA,EAAK,IAAA,EAAM,GAAA,CAAI,CAAC,CAAA;AAClE,EAAA,OAAO,GAAA;AACT;AAGO,SAAS,OAAA,CAAQ,GAAA,EAAuB;AAC7C,EAAA,IAAI,IAAA,EAAM,CAAA,QAAA;AACV,EAAA,IAAA,CAAA,IAAS,EAAA,EAAI,CAAA,EAAG,EAAA,EAAI,GAAA,CAAI,MAAA,EAAQ,CAAA,EAAA,EAAK,GAAA,CAAI,GAAA,CAAI,CAAC,EAAA,EAAI,GAAA,EAAK,IAAA,EAAM,GAAA,CAAI,CAAC,CAAA;AAClE,EAAA,OAAO,GAAA;AACT;AAOO,SAAS,aAAA,CAAc,GAAA,EAAyB;AACrD,EAAA,GAAA,CAAI,CAAA,CAAE,IAAA,WAAe,KAAA,CAAA,EAAQ,OAAO,aAAA;AACpC,EAAA,MAAM,IAAA,EAAM,GAAA,CAAI,OAAA;AAChB,EAAA,GAAA,CAAI,GAAA,CAAI,QAAA,CAAS,oBAAK,CAAA,EAAG,OAAO,WAAA;AAChC,EAAA,GAAA,CAAI,GAAA,CAAI,QAAA,CAAS,KAAK,CAAA,EAAG,OAAO,eAAA;AAChC,EAAA,GAAA,CAAI,GAAA,CAAI,QAAA,CAAS,UAAU,EAAA,GAAK,GAAA,CAAI,QAAA,CAAS,kDAAe,EAAA,GAAK,GAAA,CAAI,QAAA,CAAS,4CAAc,CAAA,EAAG,OAAO,UAAA;AACtG,EAAA,GAAA,CAAI,GAAA,CAAI,QAAA,CAAS,MAAM,EAAA,GAAK,GAAA,CAAI,QAAA,CAAS,2BAAO,EAAA,GAAK,GAAA,CAAI,QAAA,CAAS,2BAAO,CAAA,EAAG,OAAO,oBAAA;AACnF,EAAA,GAAA,CAAI,GAAA,CAAI,QAAA,CAAS,iCAAQ,CAAA,EAAG,OAAO,iBAAA;AACnC,EAAA,GAAA,CAAI,GAAA,CAAI,QAAA,CAAS,cAAI,EAAA,GAAA,CAAM,GAAA,CAAI,QAAA,CAAS,4BAAQ,EAAA,GAAK,GAAA,CAAI,QAAA,CAAS,cAAI,CAAA,CAAA,EAAI,OAAO,aAAA;AACjF,EAAA,GAAA,CAAI,GAAA,CAAI,QAAA,CAAS,0BAAM,EAAA,GAAK,GAAA,CAAI,QAAA,CAAS,kCAAS,CAAA,EAAG,OAAO,WAAA;AAC5D,EAAA,OAAO,aAAA;AACT;ADnEA;AACA;AE1FA,IAAM,eAAA,EAAyC;AAAA;AAAA,EAE7C,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA;AAAA,EAEN,GAAA,EAAM,MAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,MAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA;AAAA,EAEN,EAAA,EAAM,QAAA;AAAA;AAAA,EACN,EAAA,EAAM,QAAA;AAAA;AAAA,EACN,EAAA,EAAM,QAAA;AAAA;AAAA,EACN,EAAA,EAAM,QAAA;AAAA;AAAA;AAAA,EAEN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA;AAAA,EAEN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA,EACN,GAAA,EAAM,QAAA;AAAA;AAAA;AAAA,EAEN,EAAA,EAAM,QAAA;AAAA;AAAA,EACN,EAAA,EAAM,QAAA;AAAA;AAAA,EACN,EAAA,EAAM,QAAA;AAAA;AAAA,EACN,EAAA,EAAM,QAAA;AAAA;AAAA,EACN,EAAA,EAAM,QAAA;AAAA;AAAA,EACN,EAAA,EAAM,QAAA;AAAA;AAAA,EACN,EAAA,EAAM,QAAA;AAAA;AAAA,EACN,EAAA,EAAM;AAAA;AACR,CAAA;AAGA,IAAM,kBAAA,EAA4C;AAAA,EAChD,MAAA,EAAS,QAAA;AAAA;AAAA,EACT,MAAA,EAAS,MAAA;AAAA;AAAA,EACT,MAAA,EAAS,QAAA;AAAA;AAAA,EACT,MAAA,EAAS,QAAA;AAAA;AAAA,EACT,MAAA,EAAS,QAAA;AAAA;AAAA,EACT,MAAA,EAAS,QAAA;AAAA;AAAA,EACT,MAAA,EAAS;AAAA;AACX,CAAA;AAGO,SAAS,UAAA,CAAW,IAAA,EAAkC;AAC3D,EAAA,GAAA,CAAI,KAAA,GAAQ,MAAA,GAAU,KAAA,GAAQ,KAAA,EAAQ;AACpC,IAAA,OAAO,cAAA,CAAe,KAAA,EAAO,KAAM,CAAA;AAAA,EACrC;AACA,EAAA,GAAA,CAAI,KAAA,GAAQ,OAAA,GAAW,KAAA,GAAQ,MAAA,EAAS;AACtC,IAAA,OAAO,iBAAA,CAAkB,IAAI,CAAA;AAAA,EAC/B;AACA,EAAA,OAAO,KAAA,CAAA;AACT;AAYO,SAAS,UAAA,CAAW,IAAA,EAAsB;AAC/C,EAAA,IAAI,IAAA,EAAM,EAAA;AACV,EAAA,IAAA,CAAA,MAAW,GAAA,GAAM,IAAA,EAAM;AACrB,IAAA,MAAM,KAAA,EAAO,EAAA,CAAG,WAAA,CAAY,CAAC,CAAA;AAC7B,IAAA,IAAA,oBAAO,UAAA,CAAW,IAAI,CAAA,UAAK,IAAA;AAAA,EAC7B;AACA,EAAA,OAAO,GAAA;AACT;AFoIA;AACA;AG1OO,IAAM,SAAA,EAAW,GAAA;AAEjB,IAAM,SAAA,EAAW,GAAA;AAEjB,SAAS,UAAA,CAAW,IAAA,EAAgC;AACzD,EAAA,GAAA,CAAI,IAAA,CAAK,OAAA,EAAS,QAAA,EAAU,KAAA,EAAO,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,QAAQ,CAAA;AACzD,EAAA,MAAM,QAAA,EAAU,IAAA,CAAK,MAAA;AAGrB,EAAA,MAAM,QAAA,EAAU,IAAA,CAAK,IAAA,CAAK,CAAA,GAAA,EAAA,GAAO,GAAA,CAAI,IAAA,CAAK,CAAA,CAAA,EAAA,GAAK,CAAA,CAAE,QAAA,IAAY,KAAA,EAAA,GAAa,CAAA,CAAE,QAAA,IAAY,KAAA,CAAS,CAAC,CAAA;AAClG,EAAA,GAAA,CAAI,OAAA,EAAS,OAAO,gBAAA,CAAiB,IAAA,EAAM,OAAO,CAAA;AAGlD,EAAA,IAAI,QAAA,EAAU,CAAA;AACd,EAAA,MAAM,aAAA,EAA4B,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,QAAQ,CAAA,EAAG,CAAA,EAAA,GAAM,CAAC,CAAC,CAAA;AAE1E,EAAA,IAAA,CAAA,IAAS,OAAA,EAAS,CAAA,EAAG,OAAA,EAAS,OAAA,EAAS,MAAA,EAAA,EAAU;AAC/C,IAAA,IAAI,OAAA,EAAS,CAAA;AACb,IAAA,IAAA,CAAA,MAAW,KAAA,GAAQ,IAAA,CAAK,MAAM,CAAA,EAAG;AAC/B,MAAA,MAAA,CAAO,OAAA,EAAS,SAAA,GAAY,YAAA,CAAa,MAAM,CAAA,CAAE,MAAM,CAAA,EAAG,MAAA,EAAA;AAC1D,MAAA,GAAA,CAAI,OAAA,GAAU,QAAA,EAAU,KAAA;AAExB,MAAA,IAAA,CAAA,IAAS,EAAA,EAAI,MAAA,EAAQ,EAAA,EAAI,IAAA,CAAK,GAAA,CAAI,OAAA,EAAS,IAAA,CAAK,OAAA,EAAS,OAAO,CAAA,EAAG,CAAA,EAAA,EAAK;AACtE,QAAA,IAAA,CAAA,IAAS,EAAA,EAAI,MAAA,EAAQ,EAAA,EAAI,IAAA,CAAK,GAAA,CAAI,OAAA,EAAS,IAAA,CAAK,OAAA,EAAS,QAAQ,CAAA,EAAG,CAAA,EAAA,EAAK;AACvE,UAAA,YAAA,CAAa,CAAC,CAAA,CAAE,CAAC,EAAA,EAAI,IAAA;AAAA,QACvB;AAAA,MACF;AACA,MAAA,OAAA,GAAU,IAAA,CAAK,OAAA;AACf,MAAA,GAAA,CAAI,OAAA,EAAS,OAAA,EAAS,QAAA,EAAU,MAAA;AAAA,IAClC;AAAA,EACF;AAEA,EAAA,GAAA,CAAI,QAAA,IAAY,CAAA,EAAG,OAAO,EAAE,IAAA,EAAM,CAAA,EAAG,IAAA,EAAM,CAAA,EAAG,KAAA,EAAO,CAAC,CAAA,EAAG,SAAA,EAAW,MAAM,CAAA;AAG1E,EAAA,MAAM,KAAA,EAAmB,KAAA,CAAM,IAAA;AAAA,IAAK,EAAE,MAAA,EAAQ,QAAQ,CAAA;AAAA,IAAG,CAAA,EAAA,GACvD,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,QAAQ,CAAA,EAAG,CAAA,EAAA,GAAA,CAAO,EAAE,IAAA,EAAM,EAAA,EAAI,OAAA,EAAS,CAAA,EAAG,OAAA,EAAS,EAAE,CAAA,CAAE;AAAA,EAC9E,CAAA;AACA,EAAA,MAAM,SAAA,EAAwB,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,QAAQ,CAAA,EAAG,CAAA,EAAA,GAAM,KAAA,CAAM,OAAO,CAAA,CAAE,IAAA,CAAK,KAAK,CAAC,CAAA;AAE9F,EAAA,IAAA,CAAA,IAAS,OAAA,EAAS,CAAA,EAAG,OAAA,EAAS,OAAA,EAAS,MAAA,EAAA,EAAU;AAC/C,IAAA,IAAI,OAAA,EAAS,CAAA;AACb,IAAA,IAAI,QAAA,EAAU,CAAA;AAEd,IAAA,MAAA,CAAO,OAAA,EAAS,QAAA,GAAW,QAAA,EAAU,IAAA,CAAK,MAAM,CAAA,CAAE,MAAA,EAAQ;AACxD,MAAA,MAAA,CAAO,OAAA,EAAS,QAAA,GAAW,QAAA,CAAS,MAAM,CAAA,CAAE,MAAM,CAAA,EAAG,MAAA,EAAA;AACrD,MAAA,GAAA,CAAI,OAAA,GAAU,OAAA,EAAS,KAAA;AAEvB,MAAA,MAAM,KAAA,EAAO,IAAA,CAAK,MAAM,CAAA,CAAE,OAAO,CAAA;AACjC,MAAA,IAAA,CAAK,MAAM,CAAA,CAAE,MAAM,EAAA,EAAI;AAAA,QACrB,IAAA,EAAM,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,CAAA;AAAA,QACrB,OAAA,EAAS,IAAA,CAAK,OAAA;AAAA,QACd,OAAA,EAAS,IAAA,CAAK;AAAA,MAChB,CAAA;AAEA,MAAA,IAAA,CAAA,IAAS,EAAA,EAAI,MAAA,EAAQ,EAAA,EAAI,IAAA,CAAK,GAAA,CAAI,OAAA,EAAS,IAAA,CAAK,OAAA,EAAS,OAAO,CAAA,EAAG,CAAA,EAAA,EAAK;AACtE,QAAA,IAAA,CAAA,IAAS,EAAA,EAAI,MAAA,EAAQ,EAAA,EAAI,IAAA,CAAK,GAAA,CAAI,OAAA,EAAS,IAAA,CAAK,OAAA,EAAS,OAAO,CAAA,EAAG,CAAA,EAAA,EAAK;AACtE,UAAA,QAAA,CAAS,CAAC,CAAA,CAAE,CAAC,EAAA,EAAI,IAAA;AAAA,QACnB;AAAA,MACF;AAEA,MAAA,OAAA,GAAU,IAAA,CAAK,OAAA;AACf,MAAA,OAAA,EAAA;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,aAAA,CAAc,IAAA,EAAM,OAAA,EAAS,OAAO,CAAA;AAC7C;AAGA,SAAS,gBAAA,CAAiB,IAAA,EAAuB,OAAA,EAA0B;AAEzE,EAAA,IAAI,QAAA,EAAU,CAAA;AACd,EAAA,IAAA,CAAA,MAAW,IAAA,GAAO,IAAA,EAAM;AACtB,IAAA,IAAA,CAAA,MAAW,KAAA,GAAQ,GAAA,EAAK;AACtB,MAAA,MAAM,IAAA,EAAA,kBAAO,IAAA,CAAK,OAAA,UAAW,GAAA,EAAA,EAAK,IAAA,CAAK,OAAA;AACvC,MAAA,GAAA,CAAI,IAAA,EAAM,OAAA,EAAS,QAAA,EAAU,GAAA;AAAA,IAC/B;AAAA,EACF;AACA,EAAA,GAAA,CAAI,QAAA,EAAU,QAAA,EAAU,QAAA,EAAU,QAAA;AAClC,EAAA,GAAA,CAAI,QAAA,IAAY,CAAA,EAAG,OAAO,EAAE,IAAA,EAAM,CAAA,EAAG,IAAA,EAAM,CAAA,EAAG,KAAA,EAAO,CAAC,CAAA,EAAG,SAAA,EAAW,MAAM,CAAA;AAE1E,EAAA,MAAM,KAAA,EAAmB,KAAA,CAAM,IAAA;AAAA,IAAK,EAAE,MAAA,EAAQ,QAAQ,CAAA;AAAA,IAAG,CAAA,EAAA,GACvD,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,QAAQ,CAAA,EAAG,CAAA,EAAA,GAAA,CAAO,EAAE,IAAA,EAAM,EAAA,EAAI,OAAA,EAAS,CAAA,EAAG,OAAA,EAAS,EAAE,CAAA,CAAE;AAAA,EAC9E,CAAA;AAEA,EAAA,IAAA,CAAA,MAAW,IAAA,GAAO,IAAA,EAAM;AACtB,IAAA,IAAA,CAAA,MAAW,KAAA,GAAQ,GAAA,EAAK;AACtB,MAAA,MAAM,EAAA,mBAAI,IAAA,CAAK,OAAA,UAAW,GAAA;AAC1B,MAAA,MAAM,EAAA,mBAAI,IAAA,CAAK,OAAA,UAAW,GAAA;AAC1B,MAAA,GAAA,CAAI,EAAA,GAAK,QAAA,GAAW,EAAA,GAAK,QAAA,GAAW,EAAA,EAAI,EAAA,GAAK,EAAA,EAAI,CAAA,EAAG,QAAA;AAEpD,MAAA,IAAA,CAAK,CAAC,CAAA,CAAE,CAAC,EAAA,EAAI,EAAE,IAAA,EAAM,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,CAAA,EAAG,OAAA,EAAS,IAAA,CAAK,OAAA,EAAS,OAAA,EAAS,IAAA,CAAK,QAAQ,CAAA;AAGpF,MAAA,IAAA,CAAA,IAAS,GAAA,EAAK,CAAA,EAAG,GAAA,EAAK,IAAA,CAAK,OAAA,EAAS,EAAA,EAAA,EAAM;AACxC,QAAA,IAAA,CAAA,IAAS,GAAA,EAAK,CAAA,EAAG,GAAA,EAAK,IAAA,CAAK,OAAA,EAAS,EAAA,EAAA,EAAM;AACxC,UAAA,GAAA,CAAI,GAAA,IAAO,EAAA,GAAK,GAAA,IAAO,CAAA,EAAG,QAAA;AAC1B,UAAA,GAAA,CAAI,EAAA,EAAI,GAAA,EAAK,QAAA,GAAW,EAAA,EAAI,GAAA,EAAK,OAAA,EAAS;AACxC,YAAA,IAAA,CAAK,EAAA,EAAI,EAAE,CAAA,CAAE,EAAA,EAAI,EAAE,EAAA,EAAI,EAAE,IAAA,EAAM,EAAA,EAAI,OAAA,EAAS,CAAA,EAAG,OAAA,EAAS,EAAE,CAAA;AAAA,UAC5D;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,aAAA,CAAc,IAAA,EAAM,OAAA,EAAS,OAAO,CAAA;AAC7C;AAGA,SAAS,aAAA,CAAc,IAAA,EAAkB,OAAA,EAAiB,OAAA,EAA0B;AAClF,EAAA,IAAI,cAAA,EAAgB,OAAA;AACpB,EAAA,MAAA,CAAO,cAAA,EAAgB,CAAA,EAAG;AACxB,IAAA,MAAM,SAAA,EAAW,IAAA,CAAK,KAAA,CAAM,CAAA,GAAA,EAAA,GAAO,iBAAC,GAAA,qBAAI,cAAA,EAAgB,CAAC,CAAA,6BAAG,IAAA,+BAAM,IAAA,qBAAK,GAAC,CAAA;AACxE,IAAA,GAAA,CAAI,CAAC,QAAA,EAAU,KAAA;AACf,IAAA,aAAA,EAAA;AAAA,EACF;AACA,EAAA,GAAA,CAAI,cAAA,EAAgB,QAAA,GAAW,cAAA,EAAgB,CAAA,EAAG;AAChD,IAAA,MAAM,QAAA,EAAU,IAAA,CAAK,GAAA,CAAI,CAAA,GAAA,EAAA,GAAO,GAAA,CAAI,KAAA,CAAM,CAAA,EAAG,aAAa,CAAC,CAAA;AAC3D,IAAA,OAAO,EAAE,IAAA,EAAM,OAAA,EAAS,IAAA,EAAM,aAAA,EAAe,KAAA,EAAO,OAAA,EAAS,SAAA,EAAW,QAAA,EAAU,EAAE,CAAA;AAAA,EACtF;AACA,EAAA,OAAO,EAAE,IAAA,EAAM,OAAA,EAAS,IAAA,EAAM,OAAA,EAAS,KAAA,EAAO,IAAA,EAAM,SAAA,EAAW,QAAA,EAAU,EAAE,CAAA;AAC7E;AAEO,SAAS,kBAAA,CAAmB,IAAA,EAA+B;AAChE,EAAA,OAAO,IAAA,CACJ,GAAA;AAAA,IAAI,CAAA,GAAA,EAAA,GACH,GAAA,CACG,GAAA,CAAI,CAAA,CAAA,EAAA,GAAK,CAAA,CAAE,IAAA,CAAK,IAAA,CAAK,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,KAAK,CAAC,CAAA,CAChE,MAAA,CAAO,OAAO,CAAA,CACd,IAAA,CAAK,KAAK;AAAA,EACf,CAAA,CACC,MAAA,CAAO,OAAO,CAAA,CACd,IAAA,CAAK,IAAI,CAAA;AACd;AAGA,SAAS,SAAA,CAAU,IAAA,EAAsB;AAEvC,EAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM,KAAK,CAAA;AACjC;AAGA,IAAM,sBAAA,EAAwB,mOAAA;AAG9B,SAAS,YAAA,CAAa,IAAA,EAAsB;AAE1C,EAAA,IAAI,OAAA,EAAS,UAAA,CAAW,IAAI,CAAA,CAEzB,OAAA,CAAQ,yBAAA,EAA2B,EAAE,CAAA,CAErC,OAAA,CAAQ,qBAAA,EAAuB,EAAE,CAAA,CACjC,OAAA,CAAQ,MAAA,EAAQ,GAAG,CAAA,CACnB,IAAA,CAAK,CAAA;AAGR,EAAA,GAAA,CAAI,MAAA,CAAO,OAAA,GAAU,GAAA,GAAM,MAAA,CAAO,QAAA,CAAS,GAAG,CAAA,EAAG;AAC/C,IAAA,MAAM,OAAA,EAAS,MAAA,CAAO,KAAA,CAAM,GAAG,CAAA;AAE/B,IAAA,MAAM,sBAAA,EAAwB,MAAA,CAAO,MAAA,CAAO,CAAA,CAAA,EAAA,GAAK,CAAA,CAAE,OAAA,IAAW,EAAA,GAAK,8BAAA,CAA+B,IAAA,CAAK,CAAC,CAAC,CAAA,CAAE,MAAA;AAC3G,IAAA,GAAA,CAAI,MAAA,CAAO,OAAA,GAAU,EAAA,GAAK,sBAAA,EAAwB,MAAA,CAAO,OAAA,GAAU,GAAA,EAAK;AACtE,MAAA,OAAA,EAAS,MAAA,CAAO,IAAA,CAAK,EAAE,CAAA;AAAA,IACzB;AAAA,EACF;AACA,EAAA,OAAO,MAAA;AACT;AAOO,SAAS,mBAAA,CAAoB,MAAA,EAA8B;AAChE,EAAA,MAAM,OAAA,EAAoB,CAAC,CAAA;AAE3B,EAAA,IAAA,CAAA,MAAW,MAAA,GAAS,MAAA,EAAQ;AAC1B,IAAA,GAAA,CAAI,KAAA,CAAM,KAAA,IAAS,QAAA,GAAW,CAAC,KAAA,CAAM,KAAA,EAAO;AAC1C,MAAA,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA;AACjB,MAAA,QAAA;AAAA,IACF;AAEA,IAAA,MAAM,EAAE,IAAA,EAAM,OAAA,EAAS,IAAA,EAAM,OAAA,EAAS,MAAM,EAAA,EAAI,KAAA,CAAM,KAAA;AAGtD,IAAA,GAAA,CAAI,QAAA,IAAY,EAAA,GAAK,QAAA,IAAY,CAAA,EAAG;AAClC,MAAA,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA;AACjB,MAAA,QAAA;AAAA,IACF;AAGA,IAAA,GAAA,CAAI,QAAA,GAAW,CAAA,EAAG;AAChB,MAAA,IAAI,cAAA,EAAgB,CAAA;AACpB,MAAA,IAAI,aAAA,EAAe,CAAA;AACnB,MAAA,IAAA,CAAA,IAAS,EAAA,EAAI,CAAA,EAAG,EAAA,EAAI,OAAA,EAAS,CAAA,EAAA,EAAK;AAChC,QAAA,IAAA,CAAA,IAAS,EAAA,EAAI,CAAA,EAAG,EAAA,EAAI,OAAA,EAAS,CAAA,EAAA,EAAK;AAChC,UAAA,MAAM,EAAA,kBAAI,KAAA,uBAAM,CAAC,CAAA,8BAAA,CAAI,CAAC,CAAA,+BAAG,OAAA,GAAQ,EAAA;AACjC,UAAA,cAAA,GAAA,CAAkB,CAAA,CAAE,KAAA,CAAM,KAAK,EAAA,GAAK,CAAC,CAAA,CAAA,CAAG,MAAA;AACxC,UAAA,aAAA,GAAgB,CAAA,CAAE,MAAA;AAAA,QACpB;AAAA,MACF;AAKA,MAAA,GAAA,CAAI,QAAA,EAAU,EAAA,GAAA,CAAM,cAAA,EAAgB,EAAA,GAAM,QAAA,GAAW,EAAA,GAAK,aAAA,EAAe,GAAA,CAAA,EAAO;AAE9E,QAAA,IAAA,CAAA,IAAS,EAAA,EAAI,CAAA,EAAG,EAAA,EAAI,OAAA,EAAS,CAAA,EAAA,EAAK;AAChC,UAAA,IAAA,CAAA,IAAS,EAAA,EAAI,CAAA,EAAG,EAAA,EAAI,OAAA,EAAS,CAAA,EAAA,EAAK;AAChC,YAAA,MAAM,SAAA,kBAAW,KAAA,uBAAM,CAAC,CAAA,8BAAA,CAAI,CAAC,CAAA,+BAAG,IAAA,+BAAM,IAAA,qBAAK,GAAA;AAC3C,YAAA,GAAA,CAAI,CAAC,QAAA,EAAU,QAAA;AAEf,YAAA,IAAA,CAAA,MAAW,KAAA,GAAQ,QAAA,CAAS,KAAA,CAAM,IAAI,CAAA,EAAG;AACvC,cAAA,MAAM,QAAA,EAAU,IAAA,CAAK,IAAA,CAAK,CAAA;AAC1B,cAAA,GAAA,CAAI,CAAC,OAAA,EAAS,QAAA;AACd,cAAA,MAAA,CAAO,IAAA,CAAK,EAAE,IAAA,EAAM,WAAA,EAAa,IAAA,EAAM,OAAA,EAAS,UAAA,EAAY,KAAA,CAAM,WAAW,CAAC,CAAA;AAAA,YAChF;AAAA,UACF;AAAA,QACF;AACA,QAAA,QAAA;AAAA,MACF;AAAA,IACF;AAEA,IAAA,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA;AAAA,EACnB;AAEA,EAAA,OAAO,MAAA;AACT;AAEO,SAAS,gBAAA,CAAiB,MAAA,EAA2B;AAC1D,EAAA,MAAM,MAAA,EAAkB,CAAC,CAAA;AAEzB,EAAA,IAAA,CAAA,IAAS,EAAA,EAAI,CAAA,EAAG,EAAA,EAAI,MAAA,CAAO,MAAA,EAAQ,CAAA,EAAA,EAAK;AACtC,IAAA,MAAM,MAAA,EAAQ,MAAA,CAAO,CAAC,CAAA;AAGtB,IAAA,GAAA,CAAI,KAAA,CAAM,KAAA,IAAS,UAAA,GAAa,KAAA,CAAM,IAAA,EAAM;AAC1C,MAAA,MAAM,OAAA,EAAS,GAAA,CAAI,MAAA,CAAO,IAAA,CAAK,GAAA,CAAI,KAAA,CAAM,MAAA,GAAS,CAAA,EAAG,CAAC,CAAC,CAAA;AACvD,MAAA,MAAM,YAAA,EAAc,YAAA,CAAa,KAAA,CAAM,IAAI,CAAA;AAC3C,MAAA,GAAA,CAAI,WAAA,EAAa,KAAA,CAAM,IAAA,CAAK,EAAA,EAAI,CAAA,EAAA;AAChC,MAAA;AACF,IAAA;AAG8B,IAAA;AACK,MAAA;AACjC,MAAA;AACF,IAAA;AAGgC,IAAA;AACN,MAAA;AACxB,MAAA;AACF,IAAA;AAGmC,IAAA;AACH,MAAA;AACf,MAAA;AAEe,MAAA;AACG,MAAA;AACA,MAAA;AACb,MAAA;AACQ,QAAA;AACE,UAAA;AACC,UAAA;AAC7B,QAAA;AACF,MAAA;AACA,MAAA;AACF,IAAA;AAEkC,IAAA;AACF,MAAA;AACnB,MAAA;AAGmB,MAAA;AACE,QAAA;AACN,QAAA;AACO,UAAA;AAC7B,UAAA;AACK,QAAA;AACwB,UAAA;AAC/B,QAAA;AACA,QAAA;AACF,MAAA;AAE+B,MAAA;AACH,QAAA;AAC1B,QAAA;AACF,MAAA;AAGgB,MAAA;AACY,QAAA;AACI,QAAA;AAChC,MAAA;AAGwB,MAAA;AACA,QAAA;AACxB,MAAA;AAE8B,MAAA;AACN,IAAA;AAEM,MAAA;AACf,QAAA;AACf,MAAA;AAEyB,MAAA;AACM,QAAA;AACA,QAAA;AAC/B,MAAA;AACgC,MAAA;AACnB,MAAA;AACO,QAAA;AACL,QAAA;AACf,MAAA;AACF,IAAA;AACF,EAAA;AAE6B,EAAA;AAC/B;AAGiD;AAChB,EAAA;AACL,IAAA;AACO,MAAA;AAC/B,IAAA;AACF,EAAA;AACO,EAAA;AACT;AAGkD;AACjB,EAAA;AACL,IAAA;AACO,MAAA;AAC/B,IAAA;AACF,EAAA;AACO,EAAA;AACT;AAG6C;AAClB,EAAA;AAEX,IAAA;AACoB,MAAA;AAEI,QAAA;AACD,QAAA;AAC/B,MAAA;AACkC,MAAA;AACD,MAAA;AACL,MAAA;AAGlB,IAAA;AAChB,EAAA;AAC+B,EAAA;AACjC;AAEmD;AAC1C,EAAA;AACT;AAEiC;AACA,EAAA;AACL,IAAA;AACU,MAAA;AAClC,IAAA;AACF,EAAA;AACO,EAAA;AACT;AAG6C;AACP,EAAA;AACP,EAAA;AACK,EAAA;AAEA,EAAA;AACH,IAAA;AACF,IAAA;AACO,IAAA;AACL,MAAA;AACF,MAAA;AACd,MAAA;AAGgB,MAAA;AACE,QAAA;AACC,UAAA;AACE,UAAA;AAC9B,QAAA;AACF,MAAA;AAE+B,MAAA;AACN,MAAA;AACQ,MAAA;AACA,MAAA;AACF,MAAA;AACD,MAAA;AAChC,IAAA;AAC+B,IAAA;AACjC,EAAA;AAEqB,EAAA;AACC,EAAA;AACxB;AAEiD;AACjB,EAAA;AAEM,EAAA;AAIN,EAAA;AACJ,IAAA;AAC1B,EAAA;AAGoC,EAAA;AACC,IAAA;AACd,IAAA;AAGd,IAAA;AACuB,MAAA;AACL,MAAA;AACS,MAAA;AACE,MAAA;AACR,MAAA;AAGhB,IAAA;AACd,EAAA;AAGmC,EAAA;AAEnB,IAAA;AAGhB,EAAA;AAGiD,EAAA;AACpB,EAAA;AAEK,EAAA;AACE,IAAA;AACL,MAAA;AACF,MAAA;AACd,MAAA;AACe,MAAA;AAGC,MAAA;AACE,QAAA;AACC,UAAA;AACE,UAAA;AACA,YAAA;AAC5B,UAAA;AACF,QAAA;AACF,MAAA;AAEoB,MAAA;AACtB,IAAA;AACF,EAAA;AAMgC,EAAA;AACO,EAAA;AACH,EAAA;AACb,IAAA;AACU,IAAA;AACP,IAAA;AAIQ,IAAA;AACE,IAAA;AACA,IAAA;AACA,MAAA;AACd,MAAA;AAClB,MAAA;AACF,IAAA;AAGqB,IAAA;AACS,MAAA;AACP,MAAA;AACH,MAAA;AACpB,IAAA;AACmB,IAAA;AACrB,EAAA;AACqC,EAAA;AAED,EAAA;AAEd,EAAA;AACY,EAAA;AACD,EAAA;AACF,EAAA;AACK,IAAA;AACpC,EAAA;AACmB,EAAA;AACrB;AH0FuC;AACA;AIjOP;AACA;AACA;AJmOO;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/Users/chris_gomdori/workspace/kordoc/dist/chunk-RWSGBXSA.cjs","sourcesContent":[null,"/** kordoc 공용 유틸리티 */\n\n/** 빌드 타임에 tsup define으로 주입되는 버전 */\ndeclare const __KORDOC_VERSION__: string\nexport const VERSION: string = typeof __KORDOC_VERSION__ !== \"undefined\" ? __KORDOC_VERSION__ : \"0.0.0-dev\"\n\n/**\n * Node.js Buffer → ArrayBuffer 변환\n * pool Buffer의 공유 ArrayBuffer 문제를 안전하게 처리.\n * offset=0이고 전체 ArrayBuffer를 차지하면 복사 없이 직접 반환.\n */\nexport function toArrayBuffer(buf: Buffer): ArrayBuffer {\n if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {\n return buf.buffer as ArrayBuffer\n }\n return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) as ArrayBuffer\n}\n\n/**\n * kordoc 내부 에러 클래스 — 사용자에게 노출해도 안전한 메시지만 포함.\n * MCP 에러 정제에서 instanceof로 판별하여 allowlist 패턴 매칭 없이 안전하게 통과.\n */\nexport class KordocError extends Error {\n constructor(message: string) {\n super(message)\n this.name = \"KordocError\"\n }\n}\n\n/**\n * 에러 메시지 정제 — KordocError는 그대로, 나머지는 일반 메시지로 대체.\n * 파일시스템 경로, 스택 트레이스 등 내부 정보 노출 방지.\n */\nexport function sanitizeError(err: unknown): string {\n if (err instanceof KordocError) return err.message\n return \"문서 처리 중 오류가 발생했습니다\"\n}\n\n/**\n * ZIP 엔트리 경로의 경로 순회 여부 판별.\n * 백슬래시 정규화, .., 절대경로, Windows 드라이브 문자 모두 차단.\n */\nexport function isPathTraversal(name: string): boolean {\n if (name.includes(\"\\x00\")) return true\n const normalized = name.replace(/\\\\/g, \"/\")\n const segments = normalized.split(\"/\")\n return segments.some(s => s === \"..\") || normalized.startsWith(\"/\") || /^[A-Za-z]:/.test(normalized)\n}\n\n// ─── HWPX 섹션 href 해석 (parser/roundtrip 공용) ────────────────────\n\n/**\n * manifest href를 본문 섹션 경로(`Contents/sectionN.xml`)로 정규화.\n * 비본문 XML(header/script/settings)·path traversal·드라이브 경로는 null로 거른다.\n * 백슬래시 구분자를 슬래시로 통일해 parser와 roundtrip이 동일한 목록을 만든다.\n */\nexport function normalizeSectionHref(href: string): string | null {\n if (!href) return null\n let normalized = href.replace(/\\\\/g, \"/\").replace(/^\\/+/, \"\")\n if (isPathTraversal(normalized)) return null\n if (/^[Ss]ection\\d+\\.xml$/.test(normalized)) normalized = \"Contents/\" + normalized\n return /(?:^|\\/)[Ss]ection\\d+\\.xml$/.test(normalized) ? normalized : null\n}\n\n/** sectionN.xml을 N 숫자 순서로 정렬 (section10 > section2). */\nexport function compareSectionPaths(a: string, b: string): number {\n const ai = Number(a.match(/[Ss]ection(\\d+)\\.xml$/)?.[1] ?? Number.MAX_SAFE_INTEGER)\n const bi = Number(b.match(/[Ss]ection(\\d+)\\.xml$/)?.[1] ?? Number.MAX_SAFE_INTEGER)\n return ai === bi ? a.localeCompare(b) : ai - bi\n}\n\n// ─── ZIP 안전 로딩 (ZIP bomb 방지) ────────────────────\n\n/**\n * ZIP bomb 사전 검사 — Central Directory에서 비압축 합계와 엔트리 수 확인.\n * HWPX/XLSX/DOCX 등 모든 ZIP 기반 포맷에서 공통 사용.\n */\nexport function precheckZipSize(\n buffer: ArrayBuffer,\n maxUncompressedSize = 100 * 1024 * 1024,\n maxEntries = 500,\n): { totalUncompressed: number; entryCount: number } {\n try {\n const data = new DataView(buffer)\n const len = buffer.byteLength\n // EOCD 시그니처 역방향 스캔\n let eocdOffset = -1\n for (let i = len - 22; i >= Math.max(0, len - 65557); i--) {\n if (data.getUint32(i, true) === 0x06054b50) { eocdOffset = i; break }\n }\n if (eocdOffset < 0) return { totalUncompressed: 0, entryCount: 0 }\n\n const entryCount = data.getUint16(eocdOffset + 10, true)\n if (entryCount > maxEntries) {\n throw new KordocError(`ZIP 엔트리 수 초과: ${entryCount} (최대 ${maxEntries})`)\n }\n\n const cdSize = data.getUint32(eocdOffset + 12, true)\n const cdOffset = data.getUint32(eocdOffset + 16, true)\n if (cdOffset + cdSize > len) return { totalUncompressed: 0, entryCount }\n\n let totalUncompressed = 0\n let pos = cdOffset\n for (let i = 0; i < entryCount && pos + 46 <= cdOffset + cdSize; i++) {\n if (data.getUint32(pos, true) !== 0x02014b50) break\n totalUncompressed += data.getUint32(pos + 24, true)\n const nameLen = data.getUint16(pos + 28, true)\n const extraLen = data.getUint16(pos + 30, true)\n const commentLen = data.getUint16(pos + 32, true)\n pos += 46 + nameLen + extraLen + commentLen\n }\n\n if (totalUncompressed > maxUncompressedSize) {\n throw new KordocError(`ZIP 비압축 크기 초과: ${(totalUncompressed / 1024 / 1024).toFixed(1)}MB (최대 ${maxUncompressedSize / 1024 / 1024}MB)`)\n }\n\n return { totalUncompressed, entryCount }\n } catch (err) {\n if (err instanceof KordocError) throw err\n return { totalUncompressed: 0, entryCount: 0 }\n }\n}\n\n/** XXE/Billion Laughs 방지 — DOCTYPE 제거 (내부 DTD 서브셋 포함) */\nexport function stripDtd(xml: string): string {\n return xml.replace(/<!DOCTYPE\\s[^[>]*(\\[[\\s\\S]*?\\])?\\s*>/gi, \"\")\n}\n\n/** 하이퍼링크 URL 살균 — javascript: 등 XSS 위험 스킴 차단 */\nconst SAFE_HREF_RE = /^(?:https?:|mailto:|tel:|#)/i\nexport function sanitizeHref(href: string): string | null {\n const trimmed = href.trim()\n if (!trimmed || !SAFE_HREF_RE.test(trimmed)) return null\n return trimmed\n}\n\n// ─── 안전한 min/max (스택 오버플로 방지) ─────────────\n\n/** Math.min(...arr) 대체 — 대형 배열에서 스택 오버플로 방지 */\nexport function safeMin(arr: number[]): number {\n let min = Infinity\n for (let i = 0; i < arr.length; i++) if (arr[i] < min) min = arr[i]\n return min\n}\n\n/** Math.max(...arr) 대체 — 대형 배열에서 스택 오버플로 방지 */\nexport function safeMax(arr: number[]): number {\n let max = -Infinity\n for (let i = 0; i < arr.length; i++) if (arr[i] > max) max = arr[i]\n return max\n}\n\n// ─── 에러 분류 ──────────────────────────────────────\n\nimport type { ErrorCode } from \"./types.js\"\n\n/** 에러를 구조화된 ErrorCode로 분류 — KordocError 메시지 패턴 매칭 */\nexport function classifyError(err: unknown): ErrorCode {\n if (!(err instanceof Error)) return \"PARSE_ERROR\"\n const msg = err.message\n if (msg.includes(\"암호화\")) return \"ENCRYPTED\"\n if (msg.includes(\"DRM\")) return \"DRM_PROTECTED\"\n if (msg.includes(\"ZIP bomb\") || msg.includes(\"ZIP 비압축 크기 초과\") || msg.includes(\"ZIP 엔트리 수 초과\")) return \"ZIP_BOMB\"\n if (msg.includes(\"bomb\") || msg.includes(\"크기 초과\") || msg.includes(\"압축 해제\")) return \"DECOMPRESSION_BOMB\"\n if (msg.includes(\"이미지 기반\")) return \"IMAGE_BASED_PDF\"\n if (msg.includes(\"섹션\") && (msg.includes(\"찾을 수 없\") || msg.includes(\"없음\"))) return \"NO_SECTIONS\"\n if (msg.includes(\"시그니처\") || msg.includes(\"복구할 수 없\")) return \"CORRUPTED\"\n return \"PARSE_ERROR\"\n}\n","/**\n * 한컴 PUA(Private Use Area) 문자 → 유니코드 표준 문자 매핑.\n *\n * 한글(HWP)은 글머리표·문자표 기호를 Symbol/Wingdings 계열 PUA 코드포인트\n * (U+F020~U+F0FF)와 한컴 자체 Supplementary PUA-A 영역(U+F0000대)에 저장한다.\n * 매핑 없이 마크다운에 내보내면 빈 네모로 깨진다.\n *\n * 매핑 출처: rhwp (MIT, https://github.com/edwardkim/rhwp)\n * paragraph_layout.rs map_pua_bullet_char — 한컴 PDF 정답지 시각 검증 테이블.\n */\n\n/** BMP Symbol 영역 (U+F020~U+F0FF) 매핑 — 키는 (code - 0xF000) */\nconst BMP_SYMBOL_MAP: Record<number, string> = {\n // 도형/기호\n 0x6c: \"●\", // ●\n 0x6d: \"●\", // ● (그림자 원 근사)\n 0x6e: \"■\", // ■\n 0x6f: \"□\", // □\n 0x70: \"□\", // □ (굵은 흰 사각 근사)\n 0x71: \"□\", // □ (그림자 근사)\n 0x72: \"□\", // □ (그림자 근사)\n 0x73: \"⬧\", // ⬧\n 0x74: \"⧫\", // ⧫\n 0x75: \"◆\", // ◆\n 0x76: \"❖\", // ❖\n 0x77: \"⬥\", // ⬥\n // 체크/별/점\n 0x9e: \"·\", // ·\n 0x9f: \"•\", // •\n 0xa0: \"·\", // · (한컴 PDF 정답지 정합 — ▪ 아님)\n 0xa1: \"⚪\", // ⚪\n 0xa2: \"○\", // ○\n 0xa3: \"○\", // ○\n 0xa4: \"◉\", // ◉\n 0xa5: \"◎\", // ◎\n 0xa7: \"▪\", // ▪\n 0xa8: \"◻\", // ◻\n 0xaa: \"✦\", // ✦\n 0xab: \"★\", // ★\n 0xac: \"✶\", // ✶\n 0xad: \"✴\", // ✴\n 0xae: \"✹\", // ✹\n // 손 모양\n 0x45: \"☜\", // ☜\n 0x46: \"☞\", // ☞\n 0x47: \"☝\", // ☝\n 0x48: \"☟\", // ☟\n // 체크마크\n 0xfb: \"✗\", // ✗\n 0xfc: \"✔\", // ✔\n 0xfd: \"☒\", // ☒\n 0xfe: \"☑\", // ☑\n // 화살표\n 0xe8: \"➔\", // ➔ (heavy wide-headed — 한컴 PDF 정답지 정합)\n 0xef: \"⇦\", // ⇦\n 0xf0: \"⇨\", // ⇨\n 0xf1: \"⇧\", // ⇧\n 0xf2: \"⇩\", // ⇩\n // 기타\n 0x22: \"✂\", // ✂\n 0x36: \"⌛\", // ⌛\n 0x4a: \"☺\", // ☺\n 0x4e: \"☠\", // ☠\n 0x52: \"☼\", // ☼\n 0x54: \"❄\", // ❄\n 0x58: \"✠\", // ✠\n 0x59: \"✡\", // ✡\n}\n\n/** Supplementary PUA-A (U+F0000대) — 한컴 자체 영역 매핑 */\nconst SUPPLEMENTARY_MAP: Record<number, string> = {\n 0xf003b: \"↓\", // ↓\n 0xf02ef: \"·\", // ·\n 0xf0854: \"《\", // 《\n 0xf0855: \"》\", // 》\n 0xf00da: \"▸\", // ▸\n 0xf080f: \"━\", // ━\n 0xf0827: \"■\", // ■\n}\n\n/** 단일 코드포인트 매핑 — 매핑 없으면 원본 유지 */\nexport function mapPuaChar(code: number): string | undefined {\n if (code >= 0xf020 && code <= 0xf0ff) {\n return BMP_SYMBOL_MAP[code - 0xf000]\n }\n if (code >= 0xf0000 && code <= 0xf09ff) {\n return SUPPLEMENTARY_MAP[code]\n }\n return undefined\n}\n\n/** BMP PUA 영역 여부 (U+E000~U+F8FF) */\nexport function isBmpPua(code: number): boolean {\n return code >= 0xe000 && code <= 0xf8ff\n}\n\n/**\n * 문자열 내 한컴 PUA 문자를 표준 유니코드로 치환.\n * 매핑 없는 BMP PUA는 원본 유지(옛한글 한양PUA 가능성 — 제거하면 안 됨),\n * 매핑 없는 Supplementary PUA-A는 호출부의 기존 제거 로직에 위임한다.\n */\nexport function mapPuaText(text: string): string {\n let out = \"\"\n for (const ch of text) {\n const code = ch.codePointAt(0)!\n out += mapPuaChar(code) ?? ch\n }\n return out\n}\n","/** 2-pass colSpan/rowSpan 테이블 빌더 및 Markdown 변환 */\n\nimport type { CellContext, IRBlock, IRCell, IRTable } from \"../types.js\"\nimport { sanitizeHref } from \"../utils.js\"\nimport { mapPuaText } from \"../shared/pua.js\"\n\n/** 테이블 열 수 상한 — 한국 공공문서 기준 충분한 값 */\nexport const MAX_COLS = 200\n/** 테이블 행 수 상한 — 메모리 폭주 방지 */\nexport const MAX_ROWS = 10000\n\nexport function buildTable(rows: CellContext[][]): IRTable {\n if (rows.length > MAX_ROWS) rows = rows.slice(0, MAX_ROWS)\n const numRows = rows.length\n\n // colAddr/rowAddr가 있으면 직접 배치 (HWPX cellAddr, HWP5 colAddr/rowAddr)\n const hasAddr = rows.some(row => row.some(c => c.colAddr !== undefined && c.rowAddr !== undefined))\n if (hasAddr) return buildTableDirect(rows, numRows)\n\n // Pass 1: maxCols 계산 — 2D 배열 사용 (동적 확장)\n let maxCols = 0\n const tempOccupied: boolean[][] = Array.from({ length: numRows }, () => [])\n\n for (let rowIdx = 0; rowIdx < numRows; rowIdx++) {\n let colIdx = 0\n for (const cell of rows[rowIdx]) {\n while (colIdx < MAX_COLS && tempOccupied[rowIdx][colIdx]) colIdx++\n if (colIdx >= MAX_COLS) break\n\n for (let r = rowIdx; r < Math.min(rowIdx + cell.rowSpan, numRows); r++) {\n for (let c = colIdx; c < Math.min(colIdx + cell.colSpan, MAX_COLS); c++) {\n tempOccupied[r][c] = true\n }\n }\n colIdx += cell.colSpan\n if (colIdx > maxCols) maxCols = colIdx\n }\n }\n\n if (maxCols === 0) return { rows: 0, cols: 0, cells: [], hasHeader: false }\n\n // Pass 2: 실제 배치\n const grid: IRCell[][] = Array.from({ length: numRows }, () =>\n Array.from({ length: maxCols }, () => ({ text: \"\", colSpan: 1, rowSpan: 1 }))\n )\n const occupied: boolean[][] = Array.from({ length: numRows }, () => Array(maxCols).fill(false))\n\n for (let rowIdx = 0; rowIdx < numRows; rowIdx++) {\n let colIdx = 0\n let cellIdx = 0\n\n while (colIdx < maxCols && cellIdx < rows[rowIdx].length) {\n while (colIdx < maxCols && occupied[rowIdx][colIdx]) colIdx++\n if (colIdx >= maxCols) break\n\n const cell = rows[rowIdx][cellIdx]\n grid[rowIdx][colIdx] = {\n text: cell.text.trim(),\n colSpan: cell.colSpan,\n rowSpan: cell.rowSpan,\n }\n\n for (let r = rowIdx; r < Math.min(rowIdx + cell.rowSpan, numRows); r++) {\n for (let c = colIdx; c < Math.min(colIdx + cell.colSpan, maxCols); c++) {\n occupied[r][c] = true\n }\n }\n\n colIdx += cell.colSpan\n cellIdx++\n }\n }\n\n return trimAndReturn(grid, numRows, maxCols)\n}\n\n/** colAddr/rowAddr 절대 좌표 기반 직접 배치 */\nfunction buildTableDirect(rows: CellContext[][], numRows: number): IRTable {\n // 전체 셀에서 maxCols 계산 (MAX_COLS 상한 적용)\n let maxCols = 0\n for (const row of rows) {\n for (const cell of row) {\n const end = (cell.colAddr ?? 0) + cell.colSpan\n if (end > maxCols) maxCols = end\n }\n }\n if (maxCols > MAX_COLS) maxCols = MAX_COLS\n if (maxCols === 0) return { rows: 0, cols: 0, cells: [], hasHeader: false }\n\n const grid: IRCell[][] = Array.from({ length: numRows }, () =>\n Array.from({ length: maxCols }, () => ({ text: \"\", colSpan: 1, rowSpan: 1 }))\n )\n\n for (const row of rows) {\n for (const cell of row) {\n const r = cell.rowAddr ?? 0\n const c = cell.colAddr ?? 0\n if (r >= numRows || c >= maxCols || r < 0 || c < 0) continue\n\n grid[r][c] = { text: cell.text.trim(), colSpan: cell.colSpan, rowSpan: cell.rowSpan }\n\n // 병합 영역 마킹\n for (let dr = 0; dr < cell.rowSpan; dr++) {\n for (let dc = 0; dc < cell.colSpan; dc++) {\n if (dr === 0 && dc === 0) continue\n if (r + dr < numRows && c + dc < maxCols) {\n grid[r + dr][c + dc] = { text: \"\", colSpan: 1, rowSpan: 1 }\n }\n }\n }\n }\n }\n\n return trimAndReturn(grid, numRows, maxCols)\n}\n\n/** 빈 후행 열 제거 후 IRTable 반환 */\nfunction trimAndReturn(grid: IRCell[][], numRows: number, maxCols: number): IRTable {\n let effectiveCols = maxCols\n while (effectiveCols > 0) {\n const colEmpty = grid.every(row => !row[effectiveCols - 1]?.text?.trim())\n if (!colEmpty) break\n effectiveCols--\n }\n if (effectiveCols < maxCols && effectiveCols > 0) {\n const trimmed = grid.map(row => row.slice(0, effectiveCols))\n return { rows: numRows, cols: effectiveCols, cells: trimmed, hasHeader: numRows > 1 }\n }\n return { rows: numRows, cols: maxCols, cells: grid, hasHeader: numRows > 1 }\n}\n\nexport function convertTableToText(rows: CellContext[][]): string {\n return rows\n .map(row =>\n row\n .map(c => c.text.trim().replace(/\\n/g, \" \").replace(/\\|/g, \"\\\\|\"))\n .filter(Boolean)\n .join(\" / \")\n )\n .filter(Boolean)\n .join(\"\\n\")\n}\n\n/** 마크다운 GFM 특수문자 이스케이프 — remark-gfm 오해석 방지 */\nfunction escapeGfm(text: string): string {\n // ~ → \\~ (GFM strikethrough 방지)\n return text.replace(/~/g, \"\\\\~\")\n}\n\n/** HWP 자동생성 도형/개체 대체텍스트 정규식 — 한컴오피스가 삽입하는 모든 알려진 패턴 */\nconst HWP_SHAPE_ALT_TEXT_RE = /(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|이등변 삼각형|직각 삼각형|선|직선|곡선|화살표|굵은 화살표|이중 화살표|오각형|육각형|팔각형|별|[4-8]점별|십자|십자형|구름|구름형|마름모|도넛|평행사변형|사다리꼴|부채꼴|호|반원|물결|번개|하트|빗금|블록 화살표|수식|표|그림|개체|그리기\\s?개체|묶음\\s?개체|글상자|수식\\s?개체|OLE\\s?개체)\\s?입니다\\.?/g\n\n/** HWP PUA 특수문자 및 도형 대체텍스트 제거 — 모든 포맷 공통 */\nfunction sanitizeText(text: string): string {\n // 한컴 PUA → 표준 유니코드 매핑 (rhwp 검증 테이블) — 제거 regex보다 먼저 적용\n let result = mapPuaText(text)\n // Supplementary Private Use Area (U+F0000-U+FFFFD) — HWP 전용 기호 (매핑 안 된 잔여분)\n .replace(/[\\u{F0000}-\\u{FFFFD}]/gu, \"\")\n // HWP 도형/개체 자동생성 대체텍스트 제거\n .replace(HWP_SHAPE_ALT_TEXT_RE, \"\")\n .replace(/ +/g, \" \")\n .trim()\n // 균등배분 스페이스 정리 (\"현 장 대 응 단 장\" → \"현장대응단장\")\n // 짧은 텍스트(30자 이하)에서 70%+ 토큰이 한글 1글자면 균등배분으로 판단\n if (result.length <= 30 && result.includes(\" \")) {\n const tokens = result.split(\" \")\n // 한글 1글자 토큰만 카운트 — ASCII 특수문자(< > & 등)는 균등배분이 아님\n const koreanSingleCharCount = tokens.filter(t => t.length === 1 && /[\\uAC00-\\uD7AF\\u3131-\\u318E]/.test(t)).length\n if (tokens.length >= 3 && koreanSingleCharCount / tokens.length >= 0.7) {\n result = tokens.join(\"\")\n }\n }\n return result\n}\n\n/**\n * 레이아웃 테이블 감지 및 해체 — IRBlock 레벨에서 수행\n * 적은 행(≤3) + 셀 내 줄바꿈 다량 → table 블록을 paragraph 블록들로 분해\n * heading 감지 전에 호출해야 해체된 텍스트에 heading 감지 적용 가능\n */\nexport function flattenLayoutTables(blocks: IRBlock[]): IRBlock[] {\n const result: IRBlock[] = []\n\n for (const block of blocks) {\n if (block.type !== \"table\" || !block.table) {\n result.push(block)\n continue\n }\n\n const { rows: numRows, cols: numCols, cells } = block.table\n\n // 1x1 테이블은 기존 로직(tableToMarkdown)에서 처리\n if (numRows === 1 && numCols === 1) {\n result.push(block)\n continue\n }\n\n // 레이아웃 테이블 휴리스틱\n if (numRows <= 3) {\n let totalNewlines = 0\n let totalTextLen = 0\n for (let r = 0; r < numRows; r++) {\n for (let c = 0; c < numCols; c++) {\n const t = cells[r]?.[c]?.text || \"\"\n totalNewlines += (t.match(/\\n/g) || []).length\n totalTextLen += t.length\n }\n }\n\n // 레이아웃 테이블 판정: 많은 줄바꿈(>5), 또는 적은 행에 비해 총 텍스트 과다(>300)\n // 단, 열이 4개 이상이면 헤더-값 구조의 데이터 표일 가능성이 높아 해체하지 않는다\n // (실증: 2×10 모집프로그램 표가 문단으로 해체되어 헤더↔값 연결 파괴)\n if (numCols < 4 && (totalNewlines > 5 || (numRows <= 2 && totalTextLen > 300))) {\n // 레이아웃 테이블 → 각 셀을 paragraph 블록으로 분해\n for (let r = 0; r < numRows; r++) {\n for (let c = 0; c < numCols; c++) {\n const cellText = cells[r]?.[c]?.text?.trim()\n if (!cellText) continue\n // 셀 내 줄바꿈을 별도 paragraph로 분리\n for (const line of cellText.split(\"\\n\")) {\n const trimmed = line.trim()\n if (!trimmed) continue\n result.push({ type: \"paragraph\", text: trimmed, pageNumber: block.pageNumber })\n }\n }\n }\n continue\n }\n }\n\n result.push(block)\n }\n\n return result\n}\n\nexport function blocksToMarkdown(blocks: IRBlock[]): string {\n const lines: string[] = []\n\n for (let i = 0; i < blocks.length; i++) {\n const block = blocks[i]\n\n // 헤딩 블록\n if (block.type === \"heading\" && block.text) {\n const prefix = \"#\".repeat(Math.min(block.level || 2, 6))\n const headingText = sanitizeText(block.text)\n if (headingText) lines.push(\"\", `${prefix} ${headingText}`, \"\")\n continue\n }\n\n // 이미지 블록 — ![alt](filename) 참조\n if (block.type === \"image\" && block.text) {\n lines.push(\"\", `![image](${block.text})`, \"\")\n continue\n }\n\n // 구분선 블록\n if (block.type === \"separator\") {\n lines.push(\"\", \"---\", \"\")\n continue\n }\n\n // 리스트 블록\n if (block.type === \"list\" && block.text) {\n const listText = sanitizeText(block.text)\n if (!listText) continue\n // 텍스트가 이미 번호로 시작하면 그대로 출력 (원래 번호 보존)\n const alreadyNumbered = block.listType === \"ordered\" && /^\\d+\\.\\s/.test(listText)\n const prefix = alreadyNumbered ? \"\" : block.listType === \"ordered\" ? \"1. \" : \"- \"\n lines.push(`${prefix}${listText}`)\n if (block.children) {\n for (const child of block.children) {\n const childPrefix = child.listType === \"ordered\" ? \"1.\" : \"-\"\n lines.push(` ${childPrefix} ${child.text || \"\"}`)\n }\n }\n continue\n }\n\n if (block.type === \"paragraph\" && block.text) {\n let text = sanitizeText(block.text)\n if (!text) continue\n\n // 별표 패턴 (기존 호환)\n if (/^\\[별표\\s*\\d+/.test(text)) {\n const nextBlock = blocks[i + 1]\n if (nextBlock?.type === \"paragraph\" && nextBlock.text && /관련\\)?$/.test(nextBlock.text)) {\n lines.push(\"\", `## ${text} ${nextBlock.text}`, \"\")\n i++\n } else {\n lines.push(\"\", `## ${text}`, \"\")\n }\n continue\n }\n\n if (/^\\([^)]*조[^)]*관련\\)$/.test(text)) {\n lines.push(`*${text}*`, \"\")\n continue\n }\n\n // 하이퍼링크가 있으면 텍스트에 링크 삽입 (javascript: 등 위험 스킴 제거)\n if (block.href) {\n const href = sanitizeHref(block.href)\n if (href) text = `[${text}](${href})`\n }\n\n // 각주가 있으면 괄호로 인라인 삽입\n if (block.footnoteText) {\n text += ` (주: ${block.footnoteText})`\n }\n\n lines.push(escapeGfm(text), \"\")\n } else if (block.type === \"table\" && block.table) {\n // 테이블 앞에 빈 줄 보장 (마크다운 렌더링 필수)\n if (lines.length > 0 && lines[lines.length - 1] !== \"\") {\n lines.push(\"\")\n }\n // 표 캡션 — 표 위에 강조 문단으로 출력 (v3.0)\n if (block.table.caption) {\n const caption = sanitizeText(block.table.caption)\n if (caption) lines.push(`**${escapeGfm(caption)}**`, \"\")\n }\n const tableMd = tableToMarkdown(block.table)\n if (tableMd) {\n lines.push(tableMd)\n lines.push(\"\")\n }\n }\n }\n\n return lines.join(\"\\n\").trim()\n}\n\n/** 병합 셀 존재 여부 확인 */\nfunction hasMergedCells(table: IRTable): boolean {\n for (const row of table.cells) {\n for (const cell of row) {\n if (cell.colSpan > 1 || cell.rowSpan > 1) return true\n }\n }\n return false\n}\n\n/** 셀 내부에 중첩 표 블록 존재 여부 — v3.0 */\nfunction hasNestedTables(table: IRTable): boolean {\n for (const row of table.cells) {\n for (const cell of row) {\n if (cell.blocks?.some(b => b.type === \"table\" && b.table)) return true\n }\n }\n return false\n}\n\n/** 셀 내부 콘텐츠 → HTML — blocks(중첩표/다중문단) 있으면 구조 보존 재귀 렌더링 */\nfunction cellInnerHtml(cell: IRCell): string {\n if (cell.blocks?.length) {\n return cell.blocks\n .map(b => {\n if (b.type === \"table\" && b.table) {\n // 중첩표 캡션도 보존 — 표 위에 텍스트로\n const cap = b.table.caption ? sanitizeText(b.table.caption) : \"\"\n return (cap ? cap + \"<br>\" : \"\") + tableToHtml(b.table)\n }\n if (b.type === \"image\" && b.text) return `<img src=\"${b.text}\" alt=\"image\">`\n const t = sanitizeText(b.text ?? \"\")\n return t ? t.replace(/\\n/g, \"<br>\") : \"\"\n })\n .filter(Boolean)\n .join(\"<br>\")\n }\n return sanitizeText(cell.text).replace(/\\n/g, \"<br>\")\n}\n\nfunction containsInlineMath(text: string): boolean {\n return /(^|[^\\\\])\\$(?=\\S)(?:\\\\.|[^$\\n])+?\\S\\$/.test(text)\n}\n\nfunction tableContainsInlineMath(table: IRTable): boolean {\n for (const row of table.cells) {\n for (const cell of row) {\n if (containsInlineMath(cell.text)) return true\n }\n }\n return false\n}\n\n/** 병합 테이블 → HTML <table> 출력 (rowspan/colspan 보존) */\nfunction tableToHtml(table: IRTable): string {\n const { cells, rows: numRows, cols: numCols } = table\n const skip = new Set<string>()\n const lines: string[] = [\"<table>\"]\n\n for (let r = 0; r < numRows; r++) {\n const tag = r === 0 ? \"th\" : \"td\"\n const rowHtml: string[] = []\n for (let c = 0; c < numCols; c++) {\n if (skip.has(`${r},${c}`)) continue\n const cell = cells[r]?.[c]\n if (!cell) continue\n\n // 병합 영역 skip 마킹\n for (let dr = 0; dr < cell.rowSpan; dr++) {\n for (let dc = 0; dc < cell.colSpan; dc++) {\n if (dr === 0 && dc === 0) continue\n if (r + dr < numRows && c + dc < numCols) skip.add(`${r + dr},${c + dc}`)\n }\n }\n\n const text = cellInnerHtml(cell)\n const attrs: string[] = []\n if (cell.colSpan > 1) attrs.push(`colspan=\"${cell.colSpan}\"`)\n if (cell.rowSpan > 1) attrs.push(`rowspan=\"${cell.rowSpan}\"`)\n const attrStr = attrs.length ? \" \" + attrs.join(\" \") : \"\"\n rowHtml.push(`<${tag}${attrStr}>${text}</${tag}>`)\n }\n if (rowHtml.length) lines.push(`<tr>${rowHtml.join(\"\")}</tr>`)\n }\n\n lines.push(\"</table>\")\n return lines.join(\"\\n\")\n}\n\nfunction tableToMarkdown(table: IRTable): string {\n if (table.rows === 0 || table.cols === 0) return \"\"\n\n const { cells, rows: numRows, cols: numCols } = table\n\n // 병합 셀·중첩표가 있으면 HTML 테이블로 출력하되, 수식이 있으면 GFM 표로 출력한다.\n // 많은 Markdown 렌더러가 raw HTML table 내부의 $...$를 수식으로 다시 처리하지 않는다.\n if ((hasMergedCells(table) || hasNestedTables(table)) && !tableContainsInlineMath(table)) {\n return tableToHtml(table)\n }\n\n // 1행 1열 → 구조화된 텍스트 (빈 셀이면 스킵)\n if (numRows === 1 && numCols === 1) {\n const content = sanitizeText(cells[0][0].text)\n if (!content) return \"\"\n return content\n .split(/\\n/)\n .map(line => {\n const trimmed = line.trim()\n if (!trimmed) return \"\"\n if (/^\\d+\\.\\s/.test(trimmed)) return `**${escapeGfm(trimmed)}**`\n if (/^[가-힣]\\.\\s/.test(trimmed)) return ` ${escapeGfm(trimmed)}`\n return escapeGfm(trimmed)\n })\n .filter(Boolean)\n .join(\"\\n\")\n }\n\n // 1열 다행 테이블 → 각 행을 별도 라인으로 출력 (목록성 데이터)\n if (numCols === 1 && numRows >= 2) {\n return cells\n .map(row => escapeGfm(sanitizeText(row[0].text)).replace(/\\n/g, \" \"))\n .filter(Boolean)\n .join(\"\\n\")\n }\n\n // 병합 셀: 행/열 병합된 셀은 빈 칸으로\n const display: string[][] = Array.from({ length: numRows }, () => Array(numCols).fill(\"\"))\n const skip = new Set<string>()\n\n for (let r = 0; r < numRows; r++) {\n for (let c = 0; c < numCols; c++) {\n if (skip.has(`${r},${c}`)) continue\n const cell = cells[r]?.[c]\n if (!cell) continue\n display[r][c] = escapeGfm(sanitizeText(cell.text)).replace(/\\|/g, \"\\\\|\").replace(/\\n/g, \"<br>\")\n\n // colSpan/rowSpan: 병합된 열은 빈 칸으로 유지 (텍스트 중복 방지)\n for (let dr = 0; dr < cell.rowSpan; dr++) {\n for (let dc = 0; dc < cell.colSpan; dc++) {\n if (dr === 0 && dc === 0) continue\n if (r + dr < numRows && c + dc < numCols) {\n skip.add(`${r + dr},${c + dc}`)\n }\n }\n }\n // colSpan > 1이면 display 열 인덱스를 건너뜀\n c += cell.colSpan - 1\n }\n }\n\n // rowSpan 잔류 처리:\n // 1) 완전 빈 행 제거\n // 2) \"첫 열만 값, 나머지 빈\" 행 → 다음 데이터 행의 첫 열에 값을 전파\n // 단, colSpan으로 인한 빈 열(skip 셀)은 이 대상이 아님\n const uniqueRows: string[][] = []\n let pendingLabelRow: string[] | null = null\n for (let r = 0; r < display.length; r++) {\n const row = display[r]\n const isEmptyPlaceholder = row.every(cell => cell === \"\")\n if (isEmptyPlaceholder) continue\n\n // 첫 열만 값이 있고 나머지 모두 빈 행 → 다음 데이터 행의 첫 열에 전파\n // 단, colSpan으로 인한 빈 열(skip 셀)은 \"진짜 빈\"이 아니므로 제외\n const nonEmptyCols = row.filter(cell => cell !== \"\")\n const hasSkipInRow = row.some((_, c) => skip.has(`${r},${c}`))\n if (!hasSkipInRow && nonEmptyCols.length === 1 && row[0] !== \"\" && row.slice(1).every(c => c === \"\")) {\n if (pendingLabelRow) uniqueRows.push(pendingLabelRow) // 연속 보류 — 앞 행 소실 방지\n pendingLabelRow = row\n continue\n }\n\n // 보류한 첫 열 값을 현재 행의 빈 첫 열에 전파, 전파 불가면 보류 행 그대로 출력\n if (pendingLabelRow) {\n if (row[0] === \"\") row[0] = pendingLabelRow[0]\n else uniqueRows.push(pendingLabelRow)\n pendingLabelRow = null\n }\n uniqueRows.push(row)\n }\n if (pendingLabelRow) uniqueRows.push(pendingLabelRow) // 표 끝 보류 행 소실 방지\n\n if (uniqueRows.length === 0) return \"\"\n\n const md: string[] = []\n md.push(\"| \" + uniqueRows[0].join(\" | \") + \" |\")\n md.push(\"| \" + uniqueRows[0].map(() => \"---\").join(\" | \") + \" |\")\n for (let i = 1; i < uniqueRows.length; i++) {\n md.push(\"| \" + uniqueRows[i].join(\" | \") + \" |\")\n }\n return md.join(\"\\n\")\n}\n","/** kordoc 공통 타입 정의 */\n\n// ─── 중간 표현 (Intermediate Representation) ─────────\n\nexport interface CellContext {\n text: string\n colSpan: number\n rowSpan: number\n /** HWP5 셀 열 주소 (0-based) — 병합 테이블 배치용 */\n colAddr?: number\n /** HWP5 셀 행 주소 (0-based) — 병합 테이블 배치용 */\n rowAddr?: number\n}\n\n/** 블록 타입 — v2.0에서 heading, list, image, separator 추가 */\nexport type IRBlockType = \"paragraph\" | \"table\" | \"heading\" | \"list\" | \"image\" | \"separator\"\n\nexport interface IRBlock {\n type: IRBlockType\n text?: string\n table?: IRTable\n /** 헤딩 레벨 (1-6), type=\"heading\"일 때 사용 */\n level?: number\n /** 원본 페이지 번호 (1-based) */\n pageNumber?: number\n /** 바운딩 박스 — PDF에서만 제공 */\n bbox?: BoundingBox\n /** 텍스트 스타일 정보 (선택) */\n style?: InlineStyle\n /** 리스트 타입, type=\"list\"일 때 사용 */\n listType?: \"ordered\" | \"unordered\"\n /** 중첩 리스트 아이템 */\n children?: IRBlock[]\n /** 하이퍼링크 URL */\n href?: string\n /** 각주/미주 텍스트 (인라인 삽입용) */\n footnoteText?: string\n /** 이미지 데이터 (type=\"image\"일 때) */\n imageData?: ImageData\n}\n\n/** 추출된 이미지 바이너리 데이터 */\nexport interface ImageData {\n /** 이미지 바이너리 */\n data: Uint8Array\n /** MIME 타입 (image/png, image/jpeg, image/gif, image/bmp, image/wmf, image/emf) */\n mimeType: string\n /** 원본 파일명 (있는 경우) */\n filename?: string\n}\n\n/** 바운딩 박스 — PDF 포인트 단위 (72pt = 1인치) */\nexport interface BoundingBox {\n page: number\n x: number\n y: number\n width: number\n height: number\n}\n\n/** 인라인 텍스트 스타일 */\nexport interface InlineStyle {\n bold?: boolean\n italic?: boolean\n fontSize?: number\n fontName?: string\n}\n\nexport interface IRTable {\n rows: number\n cols: number\n cells: IRCell[][]\n /** 첫 행을 헤더로 렌더링할지 여부 (현재: rows > 1이면 true — 의미적 감지가 아닌 레이아웃 힌트) */\n hasHeader: boolean\n /** 표 캡션 (예: \"표 1. 부서별 예산\") — v3.0 */\n caption?: string\n}\n\nexport interface IRCell {\n text: string\n colSpan: number\n rowSpan: number\n /**\n * 셀 내부 블록 콘텐츠 — v3.0.\n * 중첩 표·이미지·다중 문단을 구조 그대로 보존한다.\n * blocks가 있으면 text는 blocks의 평탄화 텍스트(하위 호환용)다.\n */\n blocks?: IRBlock[]\n /** 제목 셀 여부 (HWP5 width_ref bit2 / HWPX header 속성) — v3.0 */\n isHeader?: boolean\n}\n\n// ─── 메타데이터 ─────────────────────────────────────\n\n/** 문서 메타데이터 — 각 포맷에서 추출 가능한 필드만 채워짐 */\nexport interface DocumentMetadata {\n /** 문서 제목 */\n title?: string\n /** 작성자 */\n author?: string\n /** 작성 프로그램 (예: \"한글 2020\", \"Adobe Acrobat\") */\n creator?: string\n /** 생성일시 (ISO 8601) */\n createdAt?: string\n /** 수정일시 (ISO 8601) */\n modifiedAt?: string\n /** 페이지/섹션 수 */\n pageCount?: number\n /** 문서 포맷 버전 (예: HWP \"5.1.0.1\") */\n version?: string\n /** 설명 */\n description?: string\n /** 키워드 */\n keywords?: string[]\n}\n\n// ─── 파싱 옵션 ──────────────────────────────────────\n\n/** 파싱 옵션 — parse() 함수에 전달 */\nexport interface ParseOptions {\n /**\n * 파싱할 페이지/섹션 범위 (1-based).\n * - 배열: [1, 2, 3]\n * - 문자열: \"1-3\", \"1,3,5-7\"\n *\n * PDF: 정확한 페이지 단위. HWP/HWPX: 섹션 단위 근사치.\n */\n pages?: number[] | string\n /** 이미지 기반 PDF용 OCR 프로바이더 (선택) */\n ocr?: OcrProvider\n /** 진행률 콜백 — current: 현재 페이지/섹션, total: 전체 수 */\n onProgress?: (current: number, total: number) => void\n /** PDF 머리글/바닥글 자동 제거 */\n removeHeaderFooter?: boolean\n /** 원본 파일 경로 (DRM COM fallback에 필요, 내부 전용) */\n filePath?: string\n /**\n * PDF 수식 OCR 활성화 (기본 false).\n *\n * 활성화 시 각 PDF 페이지를 이미지로 렌더링 → YOLOv8 기반 수식 영역 검출 →\n * TrOCR 기반 LaTeX 인식. 감지된 수식은 `$...$` (inline) / `$$...$$` (display) 로\n * 블록 텍스트에 삽입된다.\n *\n * 필수 optional 의존성: `onnxruntime-node`, `@huggingface/transformers`,\n * `@hyzyla/pdfium`, `sharp`. 미설치 시 parse 에 실패하지 않고 **경고만** 남기고\n * 수식 인식은 skip 한다 (일반 텍스트 추출은 정상 동작).\n *\n * 모델(~155MB) 은 첫 사용 시 HuggingFace 에서 자동 다운로드 되어\n * `~/.cache/kordoc/models/pix2text/` 에 SHA-256 검증과 함께 저장된다.\n */\n formulaOcr?: boolean\n}\n\n// ─── 파싱 경고 ──────────────────────────────────────\n\n/** 파싱 중 스킵/실패한 요소 보고 */\nexport interface ParseWarning {\n /** 관련 페이지 번호 (알 수 있는 경우) */\n page?: number\n /** 경고 메시지 */\n message: string\n /** 구조화된 경고 코드 */\n code: WarningCode\n}\n\nexport type WarningCode =\n | \"SKIPPED_IMAGE\"\n | \"SKIPPED_OLE\"\n | \"TRUNCATED_TABLE\"\n | \"OCR_FALLBACK\"\n | \"UNSUPPORTED_ELEMENT\"\n | \"BROKEN_ZIP_RECOVERY\"\n | \"HIDDEN_TEXT_FILTERED\"\n | \"MALFORMED_XML\"\n | \"PARTIAL_PARSE\"\n | \"LENIENT_CFB_RECOVERY\"\n | \"NEEDS_OCR\"\n\n/** 문서 구조 (헤딩 트리) */\nexport interface OutlineItem {\n level: number\n text: string\n pageNumber?: number\n}\n\n// ─── 에러 코드 ──────────────────────────────────────\n\n/** 구조화된 에러 코드 — 프로그래밍적 에러 핸들링용 */\nexport type ErrorCode =\n | \"EMPTY_INPUT\"\n | \"UNSUPPORTED_FORMAT\"\n | \"ENCRYPTED\"\n | \"DRM_PROTECTED\"\n | \"CORRUPTED\"\n | \"DECOMPRESSION_BOMB\"\n | \"ZIP_BOMB\"\n | \"IMAGE_BASED_PDF\"\n | \"NO_SECTIONS\"\n | \"PARSE_ERROR\"\n | \"MISSING_DEPENDENCY\"\n\n// ─── 파싱 결과 (discriminated union) ────────────────\n\nexport type FileType = \"hwpx\" | \"hwp\" | \"hwp3\" | \"hwpml\" | \"pdf\" | \"xlsx\" | \"xls\" | \"docx\" | \"unknown\"\n\ninterface ParseResultBase {\n fileType: FileType\n /** 페이지/섹션 수 — PDF: 실제 페이지 수, HWP/HWPX: 섹션 수, XLSX: 시트 수 */\n pageCount?: number\n /** 이미지 기반 PDF 여부 (텍스트 추출 불가) */\n isImageBased?: boolean\n}\n\nexport interface ParseSuccess extends ParseResultBase {\n success: true\n /** 추출된 마크다운 텍스트 */\n markdown: string\n /** 중간 표현 블록 (구조화된 데이터 접근용) */\n blocks: IRBlock[]\n /** 문서 메타데이터 */\n metadata?: DocumentMetadata\n /** 문서 구조 (헤딩 트리) — v2.0 */\n outline?: OutlineItem[]\n /** 파싱 중 발생한 경고 — v2.0 */\n warnings?: ParseWarning[]\n /** 추출된 이미지 목록 — 마크다운에서 파일명으로 참조됨 */\n images?: ExtractedImage[]\n /** 페이지별 텍스트 품질 신호 — PDF에서만 제공 */\n pageQuality?: PageQuality[]\n /** 문서 단위 품질 요약 — PDF에서만 제공 */\n qualitySummary?: DocumentQualitySummary\n}\n\n/** 페이지별 텍스트 품질 신호 (PDF 전용). 자세한 설명은 src/pdf/quality.ts */\nexport interface PageQuality {\n page: number\n textChars: number\n hangulRatio: number\n controlCharRatio: number\n replacementCharRatio: number\n puaRatio: number\n needsOcr: boolean\n ocrReason?: \"low_text\" | \"high_pua\" | \"high_control\" | \"high_replacement\"\n}\n\n/** 문서 단위 품질 요약 (PDF 전용). */\nexport interface DocumentQualitySummary {\n totalPages: number\n totalTextChars: number\n avgHangulRatio: number\n avgControlCharRatio: number\n avgReplacementCharRatio: number\n avgPuaRatio: number\n lowTextPageCount: number\n highPuaPageCount: number\n needsOcr: boolean\n ocrCandidatePages: number[]\n}\n\n/** 추출된 이미지 — ParseSuccess.images에 포함 */\nexport interface ExtractedImage {\n /** 마크다운에서 참조되는 파일명 (예: image_001.png) */\n filename: string\n /** 이미지 바이너리 */\n data: Uint8Array\n /** MIME 타입 */\n mimeType: string\n}\n\nexport interface ParseFailure extends ParseResultBase {\n success: false\n /** 오류 메시지 */\n error: string\n /** 구조화된 에러 코드 */\n code?: ErrorCode\n}\n\nexport type ParseResult = ParseSuccess | ParseFailure\n\n// ─── 문서 비교 (Diff) ───────────────────────────────\n\nexport type DiffChangeType = \"added\" | \"removed\" | \"modified\" | \"unchanged\"\n\nexport interface BlockDiff {\n type: DiffChangeType\n /** 원본 블록 (added이면 undefined) */\n before?: IRBlock\n /** 변경 후 블록 (removed이면 undefined) */\n after?: IRBlock\n /** modified 테이블의 셀 단위 diff */\n cellDiffs?: CellDiff[][]\n /** 유사도 (0-1) */\n similarity?: number\n}\n\nexport interface CellDiff {\n type: DiffChangeType\n before?: string\n after?: string\n}\n\nexport interface DiffResult {\n stats: { added: number; removed: number; modified: number; unchanged: number }\n diffs: BlockDiff[]\n}\n\n// ─── 라운드트립 패치 (v3.0) ─────────────────────────\n\n/** 패치 중 매핑 실패/미지원으로 건너뛴 항목 — silent 실패 금지 */\nexport interface PatchSkip {\n /** 건너뛴 사유 */\n reason: string\n /** 원본 쪽 내용 요약 (최대 80자) */\n before?: string\n /** 편집 쪽 내용 요약 (최대 80자) */\n after?: string\n}\n\n/** patchHwpx 옵션 */\nexport interface PatchOptions {\n /** 패치 후 재파싱 자동 검증 (기본 true) */\n verify?: boolean\n}\n\n/** patchHwpx / patchBlocks 결과 */\nexport interface PatchResult {\n success: boolean\n /** 패치된 HWPX (success=true) */\n data?: Uint8Array\n /** 적용된 변경 수 */\n applied: number\n /** 매핑 실패 항목 (이유 포함) */\n skipped: PatchSkip[]\n /**\n * 무손실 검증 (patchHwpx/patchHwp 전용): 패치본 재파싱 vs 편집 마크다운의\n * 잔차 diff — modified/added/removed가 0이어야 의도가 전부 반영된 것.\n * session.patchBlocks는 이 필드를 채우지 않는다 (changes 참조).\n */\n verification?: DiffResult\n /**\n * 변경 가시화 (session.patchBlocks 전용): 패치 전 → 후 문서 diff —\n * 적용된 편집 수만큼 modified가 나오는 것이 정상. verification과 의미가\n * 정반대이므로 혼용 금지.\n */\n changes?: DiffResult\n /** 실패 사유 (success=false) */\n error?: string\n}\n\n// ─── 양식 인식 ──────────────────────────────────────\n\nexport interface FormField {\n label: string\n value: string\n /** 0-based 소스 행 */\n row: number\n /** 0-based 소스 열 */\n col: number\n}\n\nexport interface FormResult {\n fields: FormField[]\n /** 양식 확신도 (0-1) */\n confidence: number\n}\n\n// ─── OCR 프로바이더 ─────────────────────────────────\n\n/** 사용자 제공 OCR 함수 — 페이지 이미지를 받아 텍스트 반환 */\nexport type OcrProvider = (\n pageImage: Uint8Array,\n pageNumber: number,\n mimeType: \"image/png\"\n) => Promise<string>\n\n// ─── Watch 모드 ─────────────────────────────────────\n\nexport interface WatchOptions {\n dir: string\n outDir?: string\n webhook?: string\n format?: \"markdown\" | \"json\"\n pages?: string\n silent?: boolean\n}\n\n// ─── 헤딩 감지 공통 임계값 ──────────────────────────\n\n/** 폰트 크기 비율 → heading level (전 파서 공통) */\nexport const HEADING_RATIO_H1 = 1.5\nexport const HEADING_RATIO_H2 = 1.3\nexport const HEADING_RATIO_H3 = 1.15\n\n// ─── 내부 파서 반환 타입 ─────────────────────────────\n\n/** 내부 파서가 index.ts에 반환하는 공통 타입 (HWP5/HWPX/PDF/XLSX/DOCX) */\nexport interface InternalParseResult {\n markdown: string\n blocks: IRBlock[]\n metadata?: DocumentMetadata\n outline?: OutlineItem[]\n warnings?: ParseWarning[]\n images?: ExtractedImage[]\n /** PDF 전용: 이미지 기반 PDF 여부 */\n isImageBased?: boolean\n /** PDF 전용: 페이지별 품질 신호 */\n pageQuality?: PageQuality[]\n /** PDF 전용: 문서 단위 품질 요약 */\n qualitySummary?: DocumentQualitySummary\n}\n"]}
package/dist/cli.js CHANGED
@@ -6,18 +6,18 @@ import {
6
6
  fillHwpx,
7
7
  markdownToHwpx,
8
8
  parse
9
- } from "./chunk-GTCJQANY.js";
9
+ } from "./chunk-GMPDZD7Q.js";
10
10
  import {
11
11
  detectFormat,
12
12
  detectZipFormat
13
- } from "./chunk-CFQLK3LM.js";
13
+ } from "./chunk-MEPHGCPQ.js";
14
14
  import {
15
15
  VERSION,
16
16
  blocksToMarkdown,
17
17
  sanitizeError,
18
18
  toArrayBuffer
19
- } from "./chunk-A4LSWJEC.js";
20
- import "./chunk-3TBUDJDE.js";
19
+ } from "./chunk-IKAXUVSU.js";
20
+ import "./chunk-MOL7MDBG.js";
21
21
 
22
22
  // src/cli.ts
23
23
  import { readFileSync, writeFileSync, mkdirSync, statSync } from "fs";
@@ -115,7 +115,7 @@ program.name("kordoc").description("\uBAA8\uB450 \uD30C\uC2F1\uD574\uBC84\uB9AC\
115
115
  }
116
116
  });
117
117
  program.command("watch <dir>").description("\uB514\uB809\uD1A0\uB9AC \uAC10\uC2DC \u2014 \uC0C8 \uBB38\uC11C \uC790\uB3D9 \uBCC0\uD658").option("--webhook <url>", "\uACB0\uACFC \uC804\uC1A1 \uC6F9\uD6C5 URL").option("-d, --out-dir <dir>", "\uBCC0\uD658 \uACB0\uACFC \uCD9C\uB825 \uB514\uB809\uD1A0\uB9AC").option("-p, --pages <range>", "\uD398\uC774\uC9C0/\uC139\uC158 \uBC94\uC704").option("--format <type>", "\uCD9C\uB825 \uD615\uC2DD: markdown \uB610\uB294 json", "markdown").option("--silent", "\uC9C4\uD589 \uBA54\uC2DC\uC9C0 \uC228\uAE30\uAE30").action(async (dir, opts) => {
118
- const { watchDirectory } = await import("./watch-77CYT54V.js");
118
+ const { watchDirectory } = await import("./watch-IN3YN72W.js");
119
119
  await watchDirectory({
120
120
  dir,
121
121
  outDir: opts.outDir,
@@ -266,7 +266,7 @@ program.command("fill <template>").description("\uC11C\uC2DD \uBB38\uC11C\uC758
266
266
  });
267
267
  program.command("patch <original> <edited>").description("\uC11C\uC2DD \uBCF4\uC874 \uB77C\uC6B4\uB4DC\uD2B8\uB9BD \uD328\uCE58 \u2014 \uD3B8\uC9D1\uB41C \uB9C8\uD06C\uB2E4\uC6B4\uC744 \uC6D0\uBCF8 HWPX/HWP\uC5D0 in-place \uBC18\uC601 (kordoc patch \uC6D0\uBCF8.hwpx \uD3B8\uC9D1.md -o \uCD9C\uB825.hwpx)").option("-o, --output <path>", "\uCD9C\uB825 \uACBD\uB85C (\uAE30\uBCF8: <\uC6D0\uBCF8>.patched.hwpx|.hwp)").option("--no-verify", "\uD328\uCE58 \uD6C4 \uC7AC\uD30C\uC2F1 \uC790\uB3D9 \uAC80\uC99D \uC0DD\uB7B5").option("--silent", "\uC9C4\uD589 \uBA54\uC2DC\uC9C0 \uC228\uAE30\uAE30").action(async (original, edited, opts) => {
268
268
  try {
269
- const { patchHwpx, patchHwp, detectFormat: detectFormat2 } = await import("./-GKNSTVEZ.js");
269
+ const { patchHwpx, patchHwp, detectFormat: detectFormat2 } = await import("./-BNJMPTQ2.js");
270
270
  const rootOpts = program.opts();
271
271
  const output = opts.output ?? rootOpts.output;
272
272
  const silent = opts.silent ?? rootOpts.silent;
@@ -358,12 +358,12 @@ program.command("mcp").description("MCP \uC11C\uBC84 \uC2E4\uD589 (Claude / Curs
358
358
  await import("./mcp.js");
359
359
  });
360
360
  program.command("setup").description("\uB300\uD654\uD615 \uC124\uCE58 \uB9C8\uBC95\uC0AC \u2014 AI \uD074\uB77C\uC774\uC5B8\uD2B8 \uC790\uB3D9 \uB4F1\uB85D (Mac/Win/Linux)").action(async () => {
361
- const { runSetup } = await import("./setup-QSJ2INNS.js");
361
+ const { runSetup } = await import("./setup-57FB3LSP.js");
362
362
  await runSetup();
363
363
  });
364
364
  program.command("check-formula-models").description("PDF \uC218\uC2DD OCR \uBAA8\uB378(MFD + MFR + tokenizer, ~155MB) \uC0C1\uD0DC \uD655\uC778 \u2014 \uC5C6\uAC70\uB098 SHA \uBD88\uC77C\uCE58\uBA74 \uB2E4\uC6B4\uB85C\uB4DC").option("--status-only", "\uC0C1\uD0DC\uB9CC JSON \uC73C\uB85C \uCD9C\uB825 (\uB2E4\uC6B4\uB85C\uB4DC \uC548 \uD568)").action(async (opts) => {
365
365
  try {
366
- const { getFormulaModelStatus, ensureFormulaModels, getFormulaModelsDir } = await import("./formula-KOQV353G.js");
366
+ const { getFormulaModelStatus, ensureFormulaModels, getFormulaModelsDir } = await import("./formula-JCNF43NE.js");
367
367
  const dir = getFormulaModelsDir();
368
368
  if (opts.statusOnly) {
369
369
  const status = await getFormulaModelStatus();
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["/** kordoc CLI — 모두 파싱해버리겠다 */\r\n\r\nimport { readFileSync, writeFileSync, mkdirSync, statSync } from \"fs\"\r\nimport { basename, dirname, resolve, extname } from \"path\"\r\nimport { Command } from \"commander\"\r\nimport { parse, detectFormat, detectZipFormat, fillFormFields, extractFormFields, blocksToMarkdown, markdownToHwpx, fillHwpx, PRESET_ALIAS } from \"./index.js\"\r\nimport type { ParseOptions } from \"./types.js\"\r\nimport { VERSION, toArrayBuffer, sanitizeError } from \"./utils.js\"\r\n\r\nconst program = new Command()\r\n\r\nprogram\r\n .name(\"kordoc\")\r\n .description(\"모두 파싱해버리겠다 — HWP, HWPX, PDF, XLSX, DOCX → Markdown\")\r\n .version(VERSION)\r\n .argument(\"<files...>\", \"변환할 파일 경로 (HWP, HWPX, PDF, XLSX, DOCX)\")\r\n .option(\"-o, --output <path>\", \"출력 파일 경로 (단일 파일 시)\")\r\n .option(\"-d, --out-dir <dir>\", \"출력 디렉토리 (다중 파일 시)\")\r\n .option(\"-p, --pages <range>\", \"페이지/섹션 범위 (예: 1-3, 1,3,5)\")\r\n .option(\"--format <type>\", \"출력 형식: markdown (기본) 또는 json\", \"markdown\")\r\n .option(\"--no-header-footer\", \"PDF 머리글/바닥글 자동 제거\")\r\n .option(\"--formula-ocr\", \"PDF 수식 OCR 활성화 (MFD+MFR ONNX, 첫 사용 시 모델 ~155MB 자동 다운로드)\")\r\n .option(\"--silent\", \"진행 메시지 숨기기\")\r\n .action(async (files: string[], opts) => {\r\n const validFormats = [\"markdown\", \"json\"]\r\n if (!validFormats.includes(opts.format)) {\r\n process.stderr.write(`[kordoc] 지원하지 않는 형식: ${opts.format} (markdown 또는 json)\\n`)\r\n process.exit(1)\r\n }\r\n for (let fi = 0; fi < files.length; fi++) {\r\n const filePath = files[fi]\r\n const absPath = resolve(filePath)\r\n const fileName = basename(absPath)\r\n const filePrefix = files.length > 1 ? `[${fi + 1}/${files.length}] ` : \"\"\r\n\r\n try {\r\n const fileSize = statSync(absPath).size\r\n if (fileSize > 500 * 1024 * 1024) {\r\n process.stderr.write(`\\n[kordoc] SKIP: ${fileName} — 파일이 너무 큽니다 (${(fileSize / 1024 / 1024).toFixed(1)}MB)\\n`)\r\n process.exitCode = 1\r\n continue\r\n }\r\n const buffer = readFileSync(absPath)\r\n const arrayBuffer = toArrayBuffer(buffer)\r\n const format = detectFormat(arrayBuffer)\r\n\r\n if (!opts.silent) {\r\n process.stderr.write(`[kordoc] ${filePrefix}${fileName} (${format}) ...`)\r\n }\r\n\r\n const parseOptions: ParseOptions = { filePath: absPath }\r\n if (opts.pages) parseOptions.pages = opts.pages as string\r\n if (opts.headerFooter === false) parseOptions.removeHeaderFooter = false\r\n if (opts.formulaOcr) parseOptions.formulaOcr = true\r\n if (!opts.silent) {\r\n parseOptions.onProgress = (current: number, total: number) => {\r\n process.stderr.write(`\\r[kordoc] ${filePrefix}${fileName} (${format}) [${current}/${total}]`)\r\n }\r\n }\r\n const result = await parse(arrayBuffer, parseOptions)\r\n\r\n if (!result.success) {\r\n process.stderr.write(` FAIL\\n`)\r\n process.stderr.write(` → ${result.error}\\n`)\r\n process.exitCode = 1\r\n continue\r\n }\r\n\r\n if (!opts.silent) process.stderr.write(` OK\\n`)\r\n\r\n let markdown = result.markdown\r\n // --out-dir 시 이미지 참조 경로에 images/ 접두사 추가\r\n if (opts.outDir && result.images?.length) {\r\n markdown = markdown.replace(/!\\[image\\]\\(image_/g, \"![image](images/image_\")\r\n }\r\n const output = opts.format === \"json\"\r\n ? JSON.stringify(result, (_key, value) =>\r\n value instanceof Uint8Array ? Buffer.from(value).toString(\"base64\") : value\r\n , 2)\r\n : markdown\r\n\r\n // 이미지 저장 (--out-dir 또는 --output 시)\r\n const saveImages = (dir: string) => {\r\n if (!result.images?.length) return\r\n const imgDir = resolve(dir, \"images\")\r\n mkdirSync(imgDir, { recursive: true })\r\n for (const img of result.images) {\r\n writeFileSync(resolve(imgDir, img.filename), img.data)\r\n }\r\n if (!opts.silent) process.stderr.write(` → ${result.images.length}개 이미지 → ${imgDir}\\n`)\r\n }\r\n\r\n if (opts.output && files.length === 1) {\r\n writeFileSync(opts.output, output, \"utf-8\")\r\n if (!opts.silent) process.stderr.write(` → ${opts.output}\\n`)\r\n saveImages(resolve(opts.output, \"..\"))\r\n } else if (opts.outDir) {\r\n mkdirSync(opts.outDir, { recursive: true })\r\n const outExt = opts.format === \"json\" ? \".json\" : \".md\"\r\n const outPath = resolve(opts.outDir, fileName.replace(/\\.[^.]+$/, outExt))\r\n writeFileSync(outPath, output, \"utf-8\")\r\n if (!opts.silent) process.stderr.write(` → ${outPath}\\n`)\r\n saveImages(opts.outDir)\r\n } else {\r\n process.stdout.write(output + \"\\n\")\r\n }\r\n } catch (err) {\r\n process.stderr.write(`\\n[kordoc] ERROR: ${fileName} — ${sanitizeError(err)}\\n`)\r\n process.exitCode = 1\r\n }\r\n }\r\n })\r\n\r\nprogram\r\n .command(\"watch <dir>\")\r\n .description(\"디렉토리 감시 — 새 문서 자동 변환\")\r\n .option(\"--webhook <url>\", \"결과 전송 웹훅 URL\")\r\n .option(\"-d, --out-dir <dir>\", \"변환 결과 출력 디렉토리\")\r\n .option(\"-p, --pages <range>\", \"페이지/섹션 범위\")\r\n .option(\"--format <type>\", \"출력 형식: markdown 또는 json\", \"markdown\")\r\n .option(\"--silent\", \"진행 메시지 숨기기\")\r\n .action(async (dir: string, opts) => {\r\n const { watchDirectory } = await import(\"./watch.js\")\r\n await watchDirectory({\r\n dir,\r\n outDir: opts.outDir,\r\n webhook: opts.webhook,\r\n format: opts.format,\r\n pages: opts.pages,\r\n silent: opts.silent,\r\n })\r\n })\r\n\r\nprogram\r\n .command(\"fill <template>\")\r\n .description(\"서식 문서의 빈칸을 채워서 출력 — kordoc fill 신청서.hwpx -f '성명=홍길동,전화=010-1234-5678' -o 결과.hwpx\")\r\n .option(\"-f, --fields <pairs>\", \"채울 필드 (key=value 쉼표 구분 또는 JSON)\")\r\n .option(\"-j, --json <path>\", \"채울 필드 JSON 파일 경로\")\r\n .option(\"-o, --output <path>\", \"출력 파일 경로 (확장자로 포맷 결정: .md, .hwpx)\")\r\n .option(\"--format <type>\", \"출력 포맷: hwpx-preserve (기본, 원본 스타일 보존), hwpx, markdown\", \"hwpx-preserve\")\r\n .option(\"--dry-run\", \"채우지 않고 서식 필드 목록만 출력\")\r\n .option(\"--silent\", \"진행 메시지 숨기기\")\r\n .action(async (template: string, opts) => {\r\n try {\r\n const absPath = resolve(template)\r\n const fileSize = statSync(absPath).size\r\n if (fileSize > 500 * 1024 * 1024) {\r\n process.stderr.write(`[kordoc] 파일이 너무 큽니다 (${(fileSize / 1024 / 1024).toFixed(1)}MB)\\n`)\r\n process.exit(1)\r\n }\r\n\r\n const buffer = readFileSync(absPath)\r\n const arrayBuffer = toArrayBuffer(buffer)\r\n\r\n if (!opts.silent) process.stderr.write(`[kordoc] ${basename(absPath)} 파싱 중...\\n`)\r\n\r\n // --dry-run: 필드 목록만 출력\r\n if (opts.dryRun) {\r\n const result = await parse(arrayBuffer)\r\n if (!result.success) {\r\n process.stderr.write(`[kordoc] 파싱 실패: ${result.error}\\n`)\r\n process.exit(1)\r\n }\r\n const formInfo = extractFormFields(result.blocks)\r\n if (formInfo.fields.length === 0) {\r\n process.stderr.write(`[kordoc] 서식 필드를 찾을 수 없습니다.\\n`)\r\n process.exit(1)\r\n }\r\n process.stdout.write(JSON.stringify(formInfo, null, 2) + \"\\n\")\r\n return\r\n }\r\n\r\n // 필드 값 파싱\r\n let values: Record<string, string> = {}\r\n if (opts.json) {\r\n const jsonPath = resolve(opts.json)\r\n const jsonContent = readFileSync(jsonPath, \"utf-8\")\r\n values = JSON.parse(jsonContent)\r\n } else if (opts.fields) {\r\n const fieldsStr: string = opts.fields\r\n if (fieldsStr.startsWith(\"{\")) {\r\n values = JSON.parse(fieldsStr)\r\n } else {\r\n // \"key1=value1,key2=value2\" 파싱 — 값에 쉼표가 있을 수 있으므로\r\n // '=' 앞의 키를 기준으로 분리 (쉼표+한글/영문+= 패턴)\r\n const pairs = fieldsStr.split(/,(?=[가-힣A-Za-z][가-힣A-Za-z\\s]*=)/)\r\n for (const pair of pairs) {\r\n const eqIdx = pair.indexOf(\"=\")\r\n if (eqIdx > 0) {\r\n const key = pair.slice(0, eqIdx).trim()\r\n const val = pair.slice(eqIdx + 1).trim()\r\n values[key] = val\r\n }\r\n }\r\n }\r\n } else {\r\n process.stderr.write(`[kordoc] 채울 필드를 지정해주세요 (-f 또는 -j 옵션)\\n`)\r\n process.exit(1)\r\n }\r\n\r\n // 출력 포맷 결정\r\n let outputFormat = opts.format as string\r\n if (opts.output) {\r\n const ext = extname(opts.output).toLowerCase()\r\n if (ext === \".hwpx\") outputFormat = outputFormat === \"markdown\" ? \"hwpx-preserve\" : outputFormat\r\n else if (ext === \".md\") outputFormat = \"markdown\"\r\n }\r\n\r\n // ─── hwpx-preserve: 원본 ZIP 직접 수정 ───\r\n if (outputFormat === \"hwpx-preserve\") {\r\n const format = detectFormat(arrayBuffer)\r\n let isHwpx = format === \"hwpx\"\r\n if (isHwpx) {\r\n const zipFormat = await detectZipFormat(arrayBuffer)\r\n isHwpx = zipFormat === \"hwpx\"\r\n }\r\n if (!isHwpx) {\r\n if (!opts.silent) process.stderr.write(`[kordoc] HWPX가 아니므로 hwpx 모드로 전환합니다\\n`)\r\n outputFormat = \"hwpx\"\r\n } else {\r\n const hwpxResult = await fillHwpx(arrayBuffer, values)\r\n if (!opts.silent) {\r\n process.stderr.write(`[kordoc] ${hwpxResult.filled.length}개 필드 채움 (원본 스타일 보존)\\n`)\r\n if (hwpxResult.unmatched.length > 0) {\r\n process.stderr.write(`[kordoc] ⚠️ 매칭 실패: ${hwpxResult.unmatched.join(\", \")}\\n`)\r\n }\r\n }\r\n if (opts.output) {\r\n mkdirSync(dirname(resolve(opts.output)), { recursive: true })\r\n writeFileSync(resolve(opts.output), Buffer.from(hwpxResult.buffer))\r\n if (!opts.silent) process.stderr.write(`[kordoc] → ${resolve(opts.output)}\\n`)\r\n } else {\r\n process.stdout.write(Buffer.from(hwpxResult.buffer))\r\n }\r\n return\r\n }\r\n }\r\n\r\n // ─── 일반 경로: parse → fill → output ───\r\n const result = await parse(arrayBuffer)\r\n if (!result.success) {\r\n process.stderr.write(`[kordoc] 파싱 실패: ${result.error}\\n`)\r\n process.exit(1)\r\n }\r\n\r\n const formInfo = extractFormFields(result.blocks)\r\n if (!opts.silent) {\r\n process.stderr.write(`[kordoc] 서식 필드 ${formInfo.fields.length}개 감지 (확신도 ${(formInfo.confidence * 100).toFixed(0)}%)\\n`)\r\n }\r\n\r\n const fillResult = fillFormFields(result.blocks, values)\r\n if (!opts.silent) {\r\n process.stderr.write(`[kordoc] ${fillResult.filled.length}개 필드 채움\\n`)\r\n if (fillResult.unmatched.length > 0) {\r\n process.stderr.write(`[kordoc] ⚠️ 매칭 실패: ${fillResult.unmatched.join(\", \")}\\n`)\r\n }\r\n }\r\n\r\n const markdown = blocksToMarkdown(fillResult.blocks)\r\n\r\n if (outputFormat === \"hwpx\") {\r\n const hwpxBuffer = await markdownToHwpx(markdown)\r\n if (opts.output) {\r\n mkdirSync(dirname(resolve(opts.output)), { recursive: true })\r\n writeFileSync(resolve(opts.output), Buffer.from(hwpxBuffer))\r\n if (!opts.silent) process.stderr.write(`[kordoc] → ${resolve(opts.output)}\\n`)\r\n } else {\r\n process.stdout.write(Buffer.from(hwpxBuffer))\r\n }\r\n } else {\r\n if (opts.output) {\r\n mkdirSync(dirname(resolve(opts.output)), { recursive: true })\r\n writeFileSync(resolve(opts.output), markdown, \"utf-8\")\r\n if (!opts.silent) process.stderr.write(`[kordoc] → ${resolve(opts.output)}\\n`)\r\n } else {\r\n process.stdout.write(markdown + \"\\n\")\r\n }\r\n }\r\n } catch (err) {\r\n process.stderr.write(`[kordoc] 오류: ${sanitizeError(err)}\\n`)\r\n process.exit(1)\r\n }\r\n })\r\n\r\nprogram\r\n .command(\"patch <original> <edited>\")\r\n .description(\"서식 보존 라운드트립 패치 — 편집된 마크다운을 원본 HWPX/HWP에 in-place 반영 (kordoc patch 원본.hwpx 편집.md -o 출력.hwpx)\")\r\n .option(\"-o, --output <path>\", \"출력 경로 (기본: <원본>.patched.hwpx|.hwp)\")\r\n .option(\"--no-verify\", \"패치 후 재파싱 자동 검증 생략\")\r\n .option(\"--silent\", \"진행 메시지 숨기기\")\r\n .action(async (original: string, edited: string, opts) => {\r\n try {\r\n const { patchHwpx, patchHwp, detectFormat } = await import(\"./index.js\")\r\n // 루트 커맨드의 동명 옵션(-o/--silent)이 서브커맨드 옵션을 가로채는 commander 동작 보완\r\n const rootOpts = program.opts()\r\n const output: string | undefined = opts.output ?? rootOpts.output\r\n const silent: boolean = opts.silent ?? rootOpts.silent\r\n const originalBuf = new Uint8Array(readFileSync(resolve(original)))\r\n const editedMarkdown = readFileSync(resolve(edited), \"utf-8\")\r\n\r\n const format = detectFormat(originalBuf.buffer as ArrayBuffer)\r\n const result = format === \"hwp\"\r\n ? await patchHwp(originalBuf, editedMarkdown, { verify: opts.verify !== false })\r\n : await patchHwpx(originalBuf, editedMarkdown, { verify: opts.verify !== false })\r\n if (!result.success || !result.data) {\r\n process.stderr.write(`[kordoc] 패치 실패: ${result.error ?? \"알 수 없는 오류\"}\\n`)\r\n process.exit(1)\r\n }\r\n\r\n const ext = format === \"hwp\" ? \".hwp\" : \".hwpx\"\r\n const outPath = resolve(output ?? original.replace(/\\.hwpx?$/i, \"\") + \".patched\" + ext)\r\n mkdirSync(dirname(outPath), { recursive: true })\r\n writeFileSync(outPath, result.data)\r\n\r\n if (!silent) {\r\n process.stderr.write(`[kordoc] ${result.applied}개 변경 적용 (원본 서식 보존) → ${outPath}\\n`)\r\n for (const s of result.skipped) {\r\n process.stderr.write(`[kordoc] ⚠️ SKIP: ${s.reason}${s.before ? ` | ${s.before}` : \"\"}\\n`)\r\n }\r\n if (result.verification) {\r\n const v = result.verification.stats\r\n const residual = v.added + v.removed + v.modified\r\n process.stderr.write(residual === 0\r\n ? `[kordoc] ✓ 검증: 편집 마크다운과 재파싱 결과 완전 일치 (${v.unchanged}블록)\\n`\r\n : `[kordoc] ⚠️ 검증 잔차: 수정 ${v.modified}, 추가 ${v.added}, 삭제 ${v.removed} (미지원 변경은 skip 목록 참조)\\n`)\r\n }\r\n }\r\n if (result.skipped.length > 0) process.exitCode = 2\r\n } catch (err) {\r\n process.stderr.write(`[kordoc] 오류: ${sanitizeError(err)}\\n`)\r\n process.exit(1)\r\n }\r\n })\r\n\r\n// 공문서 프리셋 별칭(한글/영문) → 내부 preset 키 — gongmun.ts와 공용(PRESET_ALIAS)\r\n\r\nprogram\r\n .command(\"generate <markdown>\")\r\n .alias(\"gen\")\r\n .description(\"마크다운 → 공문서 HWPX 생성 — kordoc generate 보고서.md -o 보고서.hwpx --preset 보고서 (markdown에 '-' 지정 시 stdin)\")\r\n .option(\"-o, --output <path>\", \"출력 HWPX 경로 (기본: <입력>.hwpx)\")\r\n .option(\"--preset <name>\", \"공문서 프리셋: 기안문(official)·보고서(report)·계획서(plan)·통지(notice)·회의록(minutes)\", \"기안문\")\r\n .option(\"--font <type>\", \"본문 글꼴: myeongjo(함초롬바탕) 또는 gothic(맑은 고딕)\")\r\n .option(\"--pt <size>\", \"본문 글자 크기(pt)\")\r\n .option(\"--line-spacing <percent>\", \"본문 줄간격(%)\")\r\n .option(\"--plain\", \"공문서 모드 끄기 (범용 마크다운 변환)\")\r\n .option(\"--silent\", \"진행 메시지 숨기기\")\r\n .action(async (markdown: string, opts) => {\r\n try {\r\n const rootOpts = program.opts()\r\n const output: string | undefined = opts.output ?? rootOpts.output\r\n const silent: boolean = opts.silent ?? rootOpts.silent\r\n\r\n // 입력: '-' 이면 stdin, 아니면 파일\r\n let md: string\r\n let baseName = \"document\"\r\n if (markdown === \"-\") {\r\n md = readFileSync(0, \"utf-8\")\r\n } else {\r\n const inPath = resolve(markdown)\r\n md = readFileSync(inPath, \"utf-8\")\r\n baseName = basename(inPath).replace(/\\.(md|markdown|txt)$/i, \"\")\r\n }\r\n\r\n // 공문서 옵션 구성\r\n let gongmun: import(\"./index.js\").GongmunOptions | undefined\r\n if (!opts.plain) {\r\n const preset = PRESET_ALIAS[String(opts.preset).trim()]\r\n if (!preset) {\r\n process.stderr.write(`[kordoc] 알 수 없는 프리셋: ${opts.preset} (기안문/보고서/계획서/통지/회의록)\\n`)\r\n process.exit(1)\r\n }\r\n gongmun = { preset }\r\n if (opts.font) {\r\n if (opts.font !== \"myeongjo\" && opts.font !== \"gothic\") {\r\n process.stderr.write(`[kordoc] --font 은 myeongjo 또는 gothic\\n`)\r\n process.exit(1)\r\n }\r\n gongmun.bodyFont = opts.font\r\n }\r\n if (opts.pt) gongmun.bodyPt = Number(opts.pt)\r\n if (opts.lineSpacing) gongmun.lineSpacing = Number(opts.lineSpacing)\r\n }\r\n\r\n const buf = await markdownToHwpx(md, gongmun ? { gongmun } : undefined)\r\n const outPath = resolve(output ?? (markdown === \"-\" ? `${baseName}.hwpx` : markdown.replace(/\\.(md|markdown|txt)$/i, \"\") + \".hwpx\"))\r\n mkdirSync(dirname(outPath), { recursive: true })\r\n writeFileSync(outPath, Buffer.from(buf))\r\n\r\n if (!silent) {\r\n const mode = gongmun ? `공문서:${gongmun.preset}` : \"범용\"\r\n process.stderr.write(`[kordoc] HWPX 생성 (${mode}) → ${outPath}\\n`)\r\n }\r\n } catch (err) {\r\n process.stderr.write(`[kordoc] 오류: ${sanitizeError(err)}\\n`)\r\n process.exit(1)\r\n }\r\n })\r\n\r\nprogram\r\n .command(\"mcp\")\r\n .description(\"MCP 서버 실행 (Claude / Cursor / Windsurf 연동)\")\r\n .action(async () => {\r\n await import(\"./mcp.js\")\r\n })\r\n\r\nprogram\r\n .command(\"setup\")\r\n .description(\"대화형 설치 마법사 — AI 클라이언트 자동 등록 (Mac/Win/Linux)\")\r\n .action(async () => {\r\n const { runSetup } = await import(\"./setup.js\")\r\n await runSetup()\r\n })\r\n\r\nprogram\r\n .command(\"check-formula-models\")\r\n .description(\"PDF 수식 OCR 모델(MFD + MFR + tokenizer, ~155MB) 상태 확인 — 없거나 SHA 불일치면 다운로드\")\r\n .option(\"--status-only\", \"상태만 JSON 으로 출력 (다운로드 안 함)\")\r\n .action(async (opts) => {\r\n try {\r\n const { getFormulaModelStatus, ensureFormulaModels, getFormulaModelsDir } = await import(\r\n \"./pdf/formula/index.js\"\r\n )\r\n const dir = getFormulaModelsDir()\r\n if (opts.statusOnly) {\r\n const status = await getFormulaModelStatus()\r\n process.stdout.write(\r\n JSON.stringify(\r\n {\r\n modelsDir: dir,\r\n allReady: status.every((s) => s.verified),\r\n models: status.map((s) => ({\r\n name: s.spec.name,\r\n filename: s.spec.filename,\r\n sizeMb: s.spec.sizeMb,\r\n exists: s.exists,\r\n verified: s.verified,\r\n invalidReason: s.invalidReason,\r\n path: s.localPath,\r\n })),\r\n },\r\n null,\r\n 2,\r\n ) + \"\\n\",\r\n )\r\n return\r\n }\r\n process.stderr.write(`[kordoc-formula] 캐시 디렉토리: ${dir}\\n`)\r\n await ensureFormulaModels((p) => {\r\n if (p.phase === \"download\" && p.total) {\r\n const pct = Math.floor((p.downloaded / p.total) * 100)\r\n process.stderr.write(\r\n `\\r[kordoc-formula] ${p.spec.name} ${pct}% (${(p.downloaded / 1024 / 1024).toFixed(1)}/${(p.total / 1024 / 1024).toFixed(1)}MB)`,\r\n )\r\n if (p.downloaded >= p.total) process.stderr.write(\"\\n\")\r\n } else if (p.phase === \"verify\") {\r\n process.stderr.write(`[kordoc-formula] ${p.spec.name} SHA-256 검증 중...\\n`)\r\n } else if (p.phase === \"done\") {\r\n process.stderr.write(`[kordoc-formula] ${p.spec.name} 준비 완료\\n`)\r\n } else if (p.phase === \"skip\") {\r\n process.stderr.write(`[kordoc-formula] ${p.spec.name} 이미 존재 (skip)\\n`)\r\n }\r\n })\r\n process.stdout.write(\"ok\\n\")\r\n } catch (err) {\r\n process.stderr.write(`[kordoc] 수식 모델 준비 실패: ${sanitizeError(err)}\\n`)\r\n process.exit(1)\r\n }\r\n })\r\n\r\nprogram.parse()\r\n"],"mappings":"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","names":["result","formInfo","detectFormat"]}
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["/** kordoc CLI — 모두 파싱해버리겠다 */\n\nimport { readFileSync, writeFileSync, mkdirSync, statSync } from \"fs\"\nimport { basename, dirname, resolve, extname } from \"path\"\nimport { Command } from \"commander\"\nimport { parse, detectFormat, detectZipFormat, fillFormFields, extractFormFields, blocksToMarkdown, markdownToHwpx, fillHwpx, PRESET_ALIAS } from \"./index.js\"\nimport type { ParseOptions } from \"./types.js\"\nimport { VERSION, toArrayBuffer, sanitizeError } from \"./utils.js\"\n\nconst program = new Command()\n\nprogram\n .name(\"kordoc\")\n .description(\"모두 파싱해버리겠다 — HWP, HWPX, PDF, XLSX, DOCX → Markdown\")\n .version(VERSION)\n .argument(\"<files...>\", \"변환할 파일 경로 (HWP, HWPX, PDF, XLSX, DOCX)\")\n .option(\"-o, --output <path>\", \"출력 파일 경로 (단일 파일 시)\")\n .option(\"-d, --out-dir <dir>\", \"출력 디렉토리 (다중 파일 시)\")\n .option(\"-p, --pages <range>\", \"페이지/섹션 범위 (예: 1-3, 1,3,5)\")\n .option(\"--format <type>\", \"출력 형식: markdown (기본) 또는 json\", \"markdown\")\n .option(\"--no-header-footer\", \"PDF 머리글/바닥글 자동 제거\")\n .option(\"--formula-ocr\", \"PDF 수식 OCR 활성화 (MFD+MFR ONNX, 첫 사용 시 모델 ~155MB 자동 다운로드)\")\n .option(\"--silent\", \"진행 메시지 숨기기\")\n .action(async (files: string[], opts) => {\n const validFormats = [\"markdown\", \"json\"]\n if (!validFormats.includes(opts.format)) {\n process.stderr.write(`[kordoc] 지원하지 않는 형식: ${opts.format} (markdown 또는 json)\\n`)\n process.exit(1)\n }\n for (let fi = 0; fi < files.length; fi++) {\n const filePath = files[fi]\n const absPath = resolve(filePath)\n const fileName = basename(absPath)\n const filePrefix = files.length > 1 ? `[${fi + 1}/${files.length}] ` : \"\"\n\n try {\n const fileSize = statSync(absPath).size\n if (fileSize > 500 * 1024 * 1024) {\n process.stderr.write(`\\n[kordoc] SKIP: ${fileName} — 파일이 너무 큽니다 (${(fileSize / 1024 / 1024).toFixed(1)}MB)\\n`)\n process.exitCode = 1\n continue\n }\n const buffer = readFileSync(absPath)\n const arrayBuffer = toArrayBuffer(buffer)\n const format = detectFormat(arrayBuffer)\n\n if (!opts.silent) {\n process.stderr.write(`[kordoc] ${filePrefix}${fileName} (${format}) ...`)\n }\n\n const parseOptions: ParseOptions = { filePath: absPath }\n if (opts.pages) parseOptions.pages = opts.pages as string\n if (opts.headerFooter === false) parseOptions.removeHeaderFooter = false\n if (opts.formulaOcr) parseOptions.formulaOcr = true\n if (!opts.silent) {\n parseOptions.onProgress = (current: number, total: number) => {\n process.stderr.write(`\\r[kordoc] ${filePrefix}${fileName} (${format}) [${current}/${total}]`)\n }\n }\n const result = await parse(arrayBuffer, parseOptions)\n\n if (!result.success) {\n process.stderr.write(` FAIL\\n`)\n process.stderr.write(` → ${result.error}\\n`)\n process.exitCode = 1\n continue\n }\n\n if (!opts.silent) process.stderr.write(` OK\\n`)\n\n let markdown = result.markdown\n // --out-dir 시 이미지 참조 경로에 images/ 접두사 추가\n if (opts.outDir && result.images?.length) {\n markdown = markdown.replace(/!\\[image\\]\\(image_/g, \"![image](images/image_\")\n }\n const output = opts.format === \"json\"\n ? JSON.stringify(result, (_key, value) =>\n value instanceof Uint8Array ? Buffer.from(value).toString(\"base64\") : value\n , 2)\n : markdown\n\n // 이미지 저장 (--out-dir 또는 --output 시)\n const saveImages = (dir: string) => {\n if (!result.images?.length) return\n const imgDir = resolve(dir, \"images\")\n mkdirSync(imgDir, { recursive: true })\n for (const img of result.images) {\n writeFileSync(resolve(imgDir, img.filename), img.data)\n }\n if (!opts.silent) process.stderr.write(` → ${result.images.length}개 이미지 → ${imgDir}\\n`)\n }\n\n if (opts.output && files.length === 1) {\n writeFileSync(opts.output, output, \"utf-8\")\n if (!opts.silent) process.stderr.write(` → ${opts.output}\\n`)\n saveImages(resolve(opts.output, \"..\"))\n } else if (opts.outDir) {\n mkdirSync(opts.outDir, { recursive: true })\n const outExt = opts.format === \"json\" ? \".json\" : \".md\"\n const outPath = resolve(opts.outDir, fileName.replace(/\\.[^.]+$/, outExt))\n writeFileSync(outPath, output, \"utf-8\")\n if (!opts.silent) process.stderr.write(` → ${outPath}\\n`)\n saveImages(opts.outDir)\n } else {\n process.stdout.write(output + \"\\n\")\n }\n } catch (err) {\n process.stderr.write(`\\n[kordoc] ERROR: ${fileName} — ${sanitizeError(err)}\\n`)\n process.exitCode = 1\n }\n }\n })\n\nprogram\n .command(\"watch <dir>\")\n .description(\"디렉토리 감시 — 새 문서 자동 변환\")\n .option(\"--webhook <url>\", \"결과 전송 웹훅 URL\")\n .option(\"-d, --out-dir <dir>\", \"변환 결과 출력 디렉토리\")\n .option(\"-p, --pages <range>\", \"페이지/섹션 범위\")\n .option(\"--format <type>\", \"출력 형식: markdown 또는 json\", \"markdown\")\n .option(\"--silent\", \"진행 메시지 숨기기\")\n .action(async (dir: string, opts) => {\n const { watchDirectory } = await import(\"./watch.js\")\n await watchDirectory({\n dir,\n outDir: opts.outDir,\n webhook: opts.webhook,\n format: opts.format,\n pages: opts.pages,\n silent: opts.silent,\n })\n })\n\nprogram\n .command(\"fill <template>\")\n .description(\"서식 문서의 빈칸을 채워서 출력 — kordoc fill 신청서.hwpx -f '성명=홍길동,전화=010-1234-5678' -o 결과.hwpx\")\n .option(\"-f, --fields <pairs>\", \"채울 필드 (key=value 쉼표 구분 또는 JSON)\")\n .option(\"-j, --json <path>\", \"채울 필드 JSON 파일 경로\")\n .option(\"-o, --output <path>\", \"출력 파일 경로 (확장자로 포맷 결정: .md, .hwpx)\")\n .option(\"--format <type>\", \"출력 포맷: hwpx-preserve (기본, 원본 스타일 보존), hwpx, markdown\", \"hwpx-preserve\")\n .option(\"--dry-run\", \"채우지 않고 서식 필드 목록만 출력\")\n .option(\"--silent\", \"진행 메시지 숨기기\")\n .action(async (template: string, opts) => {\n try {\n const absPath = resolve(template)\n const fileSize = statSync(absPath).size\n if (fileSize > 500 * 1024 * 1024) {\n process.stderr.write(`[kordoc] 파일이 너무 큽니다 (${(fileSize / 1024 / 1024).toFixed(1)}MB)\\n`)\n process.exit(1)\n }\n\n const buffer = readFileSync(absPath)\n const arrayBuffer = toArrayBuffer(buffer)\n\n if (!opts.silent) process.stderr.write(`[kordoc] ${basename(absPath)} 파싱 중...\\n`)\n\n // --dry-run: 필드 목록만 출력\n if (opts.dryRun) {\n const result = await parse(arrayBuffer)\n if (!result.success) {\n process.stderr.write(`[kordoc] 파싱 실패: ${result.error}\\n`)\n process.exit(1)\n }\n const formInfo = extractFormFields(result.blocks)\n if (formInfo.fields.length === 0) {\n process.stderr.write(`[kordoc] 서식 필드를 찾을 수 없습니다.\\n`)\n process.exit(1)\n }\n process.stdout.write(JSON.stringify(formInfo, null, 2) + \"\\n\")\n return\n }\n\n // 필드 값 파싱\n let values: Record<string, string> = {}\n if (opts.json) {\n const jsonPath = resolve(opts.json)\n const jsonContent = readFileSync(jsonPath, \"utf-8\")\n values = JSON.parse(jsonContent)\n } else if (opts.fields) {\n const fieldsStr: string = opts.fields\n if (fieldsStr.startsWith(\"{\")) {\n values = JSON.parse(fieldsStr)\n } else {\n // \"key1=value1,key2=value2\" 파싱 — 값에 쉼표가 있을 수 있으므로\n // '=' 앞의 키를 기준으로 분리 (쉼표+한글/영문+= 패턴)\n const pairs = fieldsStr.split(/,(?=[가-힣A-Za-z][가-힣A-Za-z\\s]*=)/)\n for (const pair of pairs) {\n const eqIdx = pair.indexOf(\"=\")\n if (eqIdx > 0) {\n const key = pair.slice(0, eqIdx).trim()\n const val = pair.slice(eqIdx + 1).trim()\n values[key] = val\n }\n }\n }\n } else {\n process.stderr.write(`[kordoc] 채울 필드를 지정해주세요 (-f 또는 -j 옵션)\\n`)\n process.exit(1)\n }\n\n // 출력 포맷 결정\n let outputFormat = opts.format as string\n if (opts.output) {\n const ext = extname(opts.output).toLowerCase()\n if (ext === \".hwpx\") outputFormat = outputFormat === \"markdown\" ? \"hwpx-preserve\" : outputFormat\n else if (ext === \".md\") outputFormat = \"markdown\"\n }\n\n // ─── hwpx-preserve: 원본 ZIP 직접 수정 ───\n if (outputFormat === \"hwpx-preserve\") {\n const format = detectFormat(arrayBuffer)\n let isHwpx = format === \"hwpx\"\n if (isHwpx) {\n const zipFormat = await detectZipFormat(arrayBuffer)\n isHwpx = zipFormat === \"hwpx\"\n }\n if (!isHwpx) {\n if (!opts.silent) process.stderr.write(`[kordoc] HWPX가 아니므로 hwpx 모드로 전환합니다\\n`)\n outputFormat = \"hwpx\"\n } else {\n const hwpxResult = await fillHwpx(arrayBuffer, values)\n if (!opts.silent) {\n process.stderr.write(`[kordoc] ${hwpxResult.filled.length}개 필드 채움 (원본 스타일 보존)\\n`)\n if (hwpxResult.unmatched.length > 0) {\n process.stderr.write(`[kordoc] ⚠️ 매칭 실패: ${hwpxResult.unmatched.join(\", \")}\\n`)\n }\n }\n if (opts.output) {\n mkdirSync(dirname(resolve(opts.output)), { recursive: true })\n writeFileSync(resolve(opts.output), Buffer.from(hwpxResult.buffer))\n if (!opts.silent) process.stderr.write(`[kordoc] → ${resolve(opts.output)}\\n`)\n } else {\n process.stdout.write(Buffer.from(hwpxResult.buffer))\n }\n return\n }\n }\n\n // ─── 일반 경로: parse → fill → output ───\n const result = await parse(arrayBuffer)\n if (!result.success) {\n process.stderr.write(`[kordoc] 파싱 실패: ${result.error}\\n`)\n process.exit(1)\n }\n\n const formInfo = extractFormFields(result.blocks)\n if (!opts.silent) {\n process.stderr.write(`[kordoc] 서식 필드 ${formInfo.fields.length}개 감지 (확신도 ${(formInfo.confidence * 100).toFixed(0)}%)\\n`)\n }\n\n const fillResult = fillFormFields(result.blocks, values)\n if (!opts.silent) {\n process.stderr.write(`[kordoc] ${fillResult.filled.length}개 필드 채움\\n`)\n if (fillResult.unmatched.length > 0) {\n process.stderr.write(`[kordoc] ⚠️ 매칭 실패: ${fillResult.unmatched.join(\", \")}\\n`)\n }\n }\n\n const markdown = blocksToMarkdown(fillResult.blocks)\n\n if (outputFormat === \"hwpx\") {\n const hwpxBuffer = await markdownToHwpx(markdown)\n if (opts.output) {\n mkdirSync(dirname(resolve(opts.output)), { recursive: true })\n writeFileSync(resolve(opts.output), Buffer.from(hwpxBuffer))\n if (!opts.silent) process.stderr.write(`[kordoc] → ${resolve(opts.output)}\\n`)\n } else {\n process.stdout.write(Buffer.from(hwpxBuffer))\n }\n } else {\n if (opts.output) {\n mkdirSync(dirname(resolve(opts.output)), { recursive: true })\n writeFileSync(resolve(opts.output), markdown, \"utf-8\")\n if (!opts.silent) process.stderr.write(`[kordoc] → ${resolve(opts.output)}\\n`)\n } else {\n process.stdout.write(markdown + \"\\n\")\n }\n }\n } catch (err) {\n process.stderr.write(`[kordoc] 오류: ${sanitizeError(err)}\\n`)\n process.exit(1)\n }\n })\n\nprogram\n .command(\"patch <original> <edited>\")\n .description(\"서식 보존 라운드트립 패치 — 편집된 마크다운을 원본 HWPX/HWP에 in-place 반영 (kordoc patch 원본.hwpx 편집.md -o 출력.hwpx)\")\n .option(\"-o, --output <path>\", \"출력 경로 (기본: <원본>.patched.hwpx|.hwp)\")\n .option(\"--no-verify\", \"패치 후 재파싱 자동 검증 생략\")\n .option(\"--silent\", \"진행 메시지 숨기기\")\n .action(async (original: string, edited: string, opts) => {\n try {\n const { patchHwpx, patchHwp, detectFormat } = await import(\"./index.js\")\n // 루트 커맨드의 동명 옵션(-o/--silent)이 서브커맨드 옵션을 가로채는 commander 동작 보완\n const rootOpts = program.opts()\n const output: string | undefined = opts.output ?? rootOpts.output\n const silent: boolean = opts.silent ?? rootOpts.silent\n const originalBuf = new Uint8Array(readFileSync(resolve(original)))\n const editedMarkdown = readFileSync(resolve(edited), \"utf-8\")\n\n const format = detectFormat(originalBuf.buffer as ArrayBuffer)\n const result = format === \"hwp\"\n ? await patchHwp(originalBuf, editedMarkdown, { verify: opts.verify !== false })\n : await patchHwpx(originalBuf, editedMarkdown, { verify: opts.verify !== false })\n if (!result.success || !result.data) {\n process.stderr.write(`[kordoc] 패치 실패: ${result.error ?? \"알 수 없는 오류\"}\\n`)\n process.exit(1)\n }\n\n const ext = format === \"hwp\" ? \".hwp\" : \".hwpx\"\n const outPath = resolve(output ?? original.replace(/\\.hwpx?$/i, \"\") + \".patched\" + ext)\n mkdirSync(dirname(outPath), { recursive: true })\n writeFileSync(outPath, result.data)\n\n if (!silent) {\n process.stderr.write(`[kordoc] ${result.applied}개 변경 적용 (원본 서식 보존) → ${outPath}\\n`)\n for (const s of result.skipped) {\n process.stderr.write(`[kordoc] ⚠️ SKIP: ${s.reason}${s.before ? ` | ${s.before}` : \"\"}\\n`)\n }\n if (result.verification) {\n const v = result.verification.stats\n const residual = v.added + v.removed + v.modified\n process.stderr.write(residual === 0\n ? `[kordoc] ✓ 검증: 편집 마크다운과 재파싱 결과 완전 일치 (${v.unchanged}블록)\\n`\n : `[kordoc] ⚠️ 검증 잔차: 수정 ${v.modified}, 추가 ${v.added}, 삭제 ${v.removed} (미지원 변경은 skip 목록 참조)\\n`)\n }\n }\n if (result.skipped.length > 0) process.exitCode = 2\n } catch (err) {\n process.stderr.write(`[kordoc] 오류: ${sanitizeError(err)}\\n`)\n process.exit(1)\n }\n })\n\n// 공문서 프리셋 별칭(한글/영문) → 내부 preset 키 — gongmun.ts와 공용(PRESET_ALIAS)\n\nprogram\n .command(\"generate <markdown>\")\n .alias(\"gen\")\n .description(\"마크다운 → 공문서 HWPX 생성 — kordoc generate 보고서.md -o 보고서.hwpx --preset 보고서 (markdown에 '-' 지정 시 stdin)\")\n .option(\"-o, --output <path>\", \"출력 HWPX 경로 (기본: <입력>.hwpx)\")\n .option(\"--preset <name>\", \"공문서 프리셋: 기안문(official)·보고서(report)·계획서(plan)·통지(notice)·회의록(minutes)\", \"기안문\")\n .option(\"--font <type>\", \"본문 글꼴: myeongjo(함초롬바탕) 또는 gothic(맑은 고딕)\")\n .option(\"--pt <size>\", \"본문 글자 크기(pt)\")\n .option(\"--line-spacing <percent>\", \"본문 줄간격(%)\")\n .option(\"--plain\", \"공문서 모드 끄기 (범용 마크다운 변환)\")\n .option(\"--silent\", \"진행 메시지 숨기기\")\n .action(async (markdown: string, opts) => {\n try {\n const rootOpts = program.opts()\n const output: string | undefined = opts.output ?? rootOpts.output\n const silent: boolean = opts.silent ?? rootOpts.silent\n\n // 입력: '-' 이면 stdin, 아니면 파일\n let md: string\n let baseName = \"document\"\n if (markdown === \"-\") {\n md = readFileSync(0, \"utf-8\")\n } else {\n const inPath = resolve(markdown)\n md = readFileSync(inPath, \"utf-8\")\n baseName = basename(inPath).replace(/\\.(md|markdown|txt)$/i, \"\")\n }\n\n // 공문서 옵션 구성\n let gongmun: import(\"./index.js\").GongmunOptions | undefined\n if (!opts.plain) {\n const preset = PRESET_ALIAS[String(opts.preset).trim()]\n if (!preset) {\n process.stderr.write(`[kordoc] 알 수 없는 프리셋: ${opts.preset} (기안문/보고서/계획서/통지/회의록)\\n`)\n process.exit(1)\n }\n gongmun = { preset }\n if (opts.font) {\n if (opts.font !== \"myeongjo\" && opts.font !== \"gothic\") {\n process.stderr.write(`[kordoc] --font 은 myeongjo 또는 gothic\\n`)\n process.exit(1)\n }\n gongmun.bodyFont = opts.font\n }\n if (opts.pt) gongmun.bodyPt = Number(opts.pt)\n if (opts.lineSpacing) gongmun.lineSpacing = Number(opts.lineSpacing)\n }\n\n const buf = await markdownToHwpx(md, gongmun ? { gongmun } : undefined)\n const outPath = resolve(output ?? (markdown === \"-\" ? `${baseName}.hwpx` : markdown.replace(/\\.(md|markdown|txt)$/i, \"\") + \".hwpx\"))\n mkdirSync(dirname(outPath), { recursive: true })\n writeFileSync(outPath, Buffer.from(buf))\n\n if (!silent) {\n const mode = gongmun ? `공문서:${gongmun.preset}` : \"범용\"\n process.stderr.write(`[kordoc] HWPX 생성 (${mode}) → ${outPath}\\n`)\n }\n } catch (err) {\n process.stderr.write(`[kordoc] 오류: ${sanitizeError(err)}\\n`)\n process.exit(1)\n }\n })\n\nprogram\n .command(\"mcp\")\n .description(\"MCP 서버 실행 (Claude / Cursor / Windsurf 연동)\")\n .action(async () => {\n await import(\"./mcp.js\")\n })\n\nprogram\n .command(\"setup\")\n .description(\"대화형 설치 마법사 — AI 클라이언트 자동 등록 (Mac/Win/Linux)\")\n .action(async () => {\n const { runSetup } = await import(\"./setup.js\")\n await runSetup()\n })\n\nprogram\n .command(\"check-formula-models\")\n .description(\"PDF 수식 OCR 모델(MFD + MFR + tokenizer, ~155MB) 상태 확인 — 없거나 SHA 불일치면 다운로드\")\n .option(\"--status-only\", \"상태만 JSON 으로 출력 (다운로드 안 함)\")\n .action(async (opts) => {\n try {\n const { getFormulaModelStatus, ensureFormulaModels, getFormulaModelsDir } = await import(\n \"./pdf/formula/index.js\"\n )\n const dir = getFormulaModelsDir()\n if (opts.statusOnly) {\n const status = await getFormulaModelStatus()\n process.stdout.write(\n JSON.stringify(\n {\n modelsDir: dir,\n allReady: status.every((s) => s.verified),\n models: status.map((s) => ({\n name: s.spec.name,\n filename: s.spec.filename,\n sizeMb: s.spec.sizeMb,\n exists: s.exists,\n verified: s.verified,\n invalidReason: s.invalidReason,\n path: s.localPath,\n })),\n },\n null,\n 2,\n ) + \"\\n\",\n )\n return\n }\n process.stderr.write(`[kordoc-formula] 캐시 디렉토리: ${dir}\\n`)\n await ensureFormulaModels((p) => {\n if (p.phase === \"download\" && p.total) {\n const pct = Math.floor((p.downloaded / p.total) * 100)\n process.stderr.write(\n `\\r[kordoc-formula] ${p.spec.name} ${pct}% (${(p.downloaded / 1024 / 1024).toFixed(1)}/${(p.total / 1024 / 1024).toFixed(1)}MB)`,\n )\n if (p.downloaded >= p.total) process.stderr.write(\"\\n\")\n } else if (p.phase === \"verify\") {\n process.stderr.write(`[kordoc-formula] ${p.spec.name} SHA-256 검증 중...\\n`)\n } else if (p.phase === \"done\") {\n process.stderr.write(`[kordoc-formula] ${p.spec.name} 준비 완료\\n`)\n } else if (p.phase === \"skip\") {\n process.stderr.write(`[kordoc-formula] ${p.spec.name} 이미 존재 (skip)\\n`)\n }\n })\n process.stdout.write(\"ok\\n\")\n } catch (err) {\n process.stderr.write(`[kordoc] 수식 모델 준비 실패: ${sanitizeError(err)}\\n`)\n process.exit(1)\n }\n })\n\nprogram.parse()\n"],"mappings":"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","names":["result","formInfo","detectFormat"]}
@@ -9,7 +9,7 @@ import {
9
9
  isOldHwpFile,
10
10
  isPdfFile,
11
11
  isZipFile
12
- } from "./chunk-CFQLK3LM.js";
12
+ } from "./chunk-MEPHGCPQ.js";
13
13
  export {
14
14
  detectFormat,
15
15
  detectOle2Format,
@@ -21,4 +21,4 @@ export {
21
21
  isPdfFile,
22
22
  isZipFile
23
23
  };
24
- //# sourceMappingURL=detect-NTR2FUEG.js.map
24
+ //# sourceMappingURL=detect-RI2MQ33K.js.map
@@ -1150,4 +1150,4 @@ function bgraToRgba(bgra) {
1150
1150
 
1151
1151
 
1152
1152
  exports.ALL_FORMULA_MODELS = ALL_FORMULA_MODELS; exports.FormulaPipeline = FormulaPipeline; exports.MFD_MODEL = MFD_MODEL; exports.MFR_DECODER_MODEL = MFR_DECODER_MODEL; exports.MFR_ENCODER_MODEL = MFR_ENCODER_MODEL; exports.MFR_TOKENIZER = MFR_TOKENIZER; exports.ensureFormulaModels = ensureFormulaModels; exports.ensureSingleModel = ensureSingleModel; exports.getFormulaModelStatus = getFormulaModelStatus; exports.getFormulaModelsDir = getFormulaModelsDir; exports.postProcessLatex = postProcessLatex;
1153
- //# sourceMappingURL=formula-ZYUDQHD3.cjs.map
1153
+ //# sourceMappingURL=formula-5NKVS2LR.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/chris_gomdori/workspace/kordoc/dist/formula-5NKVS2LR.cjs","../src/pdf/formula/models.ts","../src/pdf/formula/postprocess.ts","../src/pdf/formula/detector.ts","../src/pdf/formula/recognizer.ts","../src/pdf/formula/pipeline.ts"],"names":[],"mappings":"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","file":"/Users/chris_gomdori/workspace/kordoc/dist/formula-5NKVS2LR.cjs","sourcesContent":[null,"/**\n * 수식 OCR 모델 자동 다운로드 + SHA-256 검증\n *\n * 캐시 위치: `~/.cache/kordoc/models/pix2text/`\n * 총 ~155MB (MFD 44MB + MFR encoder 87MB + MFR decoder 30MB + tokenizer 40KB)\n *\n * HF URL + SHA 는 Docufinder archive/rust-formula-ocr-wip 에서 검증 완료됨.\n * 모델 버전 갱신 시 URL + SHA 를 함께 갱신해야 한다.\n */\n\nimport { createHash } from \"crypto\"\nimport { createReadStream } from \"fs\"\nimport { mkdir, stat, unlink, rename } from \"fs/promises\"\nimport { createWriteStream } from \"fs\"\nimport { homedir } from \"os\"\nimport { join, dirname } from \"path\"\nimport { pipeline } from \"stream/promises\"\nimport { Readable } from \"stream\"\n\nexport interface ModelSpec {\n name: string\n filename: string\n url: string\n sha256: string\n sizeMb: number\n}\n\n/** 모델 스펙 (SHA-256 은 실제 다운로드로 검증됨 — 변경 금지) */\nexport const MFD_MODEL: ModelSpec = {\n name: \"Pix2Text MFD\",\n filename: \"mfd.onnx\",\n url: \"https://huggingface.co/breezedeus/pix2text-mfd/resolve/main/mfd-v20240618.onnx\",\n sha256: \"51a8854743b17ae654729af8db82a630c1ccfa06debf4856c8b28055f87d02c1\",\n sizeMb: 42,\n}\n\nexport const MFR_ENCODER_MODEL: ModelSpec = {\n name: \"Pix2Text MFR encoder\",\n filename: \"encoder_model.onnx\",\n url: \"https://huggingface.co/breezedeus/pix2text-mfr/resolve/main/encoder_model.onnx\",\n sha256: \"bd8d5c322792e9ec45793af5569e9748f82a3d728a9e00213dbfc56c1486f37d\",\n sizeMb: 87,\n}\n\nexport const MFR_DECODER_MODEL: ModelSpec = {\n name: \"Pix2Text MFR decoder\",\n filename: \"decoder_model.onnx\",\n url: \"https://huggingface.co/breezedeus/pix2text-mfr/resolve/main/decoder_model.onnx\",\n sha256: \"fd0f92d7a012f3dae41e1ac79421aea0ea888b5a66cb3f9a004e424f82f3daed\",\n sizeMb: 30,\n}\n\nexport const MFR_TOKENIZER: ModelSpec = {\n name: \"Pix2Text MFR tokenizer\",\n filename: \"tokenizer.json\",\n url: \"https://huggingface.co/breezedeus/pix2text-mfr/resolve/main/tokenizer.json\",\n sha256: \"3e2ab757277d22639bec28c9d7972e352d3d1dba223051fa674002dc5ab64df3\",\n sizeMb: 1,\n}\n\nexport const ALL_FORMULA_MODELS: ReadonlyArray<ModelSpec> = [\n MFD_MODEL,\n MFR_ENCODER_MODEL,\n MFR_DECODER_MODEL,\n MFR_TOKENIZER,\n]\n\n/**\n * 환경 변수 `KORDOC_MODEL_CACHE` 가 설정되면 해당 경로를 우선 사용.\n * 없으면 `~/.cache/kordoc/models/pix2text/`.\n */\nexport function getFormulaModelsDir(): string {\n const override = process.env.KORDOC_MODEL_CACHE\n if (override && override.trim()) {\n return join(override, \"pix2text\")\n }\n return join(homedir(), \".cache\", \"kordoc\", \"models\", \"pix2text\")\n}\n\nexport interface ModelStatus {\n spec: ModelSpec\n localPath: string\n exists: boolean\n /** SHA 검증까지 끝났는가 (exists && valid) */\n verified: boolean\n /** 검증 실패 시 이유 */\n invalidReason?: string\n}\n\n/** 모든 수식 모델의 존재/유효성 상태 반환 (다운로드 없이 확인만) */\nexport async function getFormulaModelStatus(): Promise<ModelStatus[]> {\n const dir = getFormulaModelsDir()\n const result: ModelStatus[] = []\n for (const spec of ALL_FORMULA_MODELS) {\n const localPath = join(dir, spec.filename)\n let exists = false\n try {\n const s = await stat(localPath)\n exists = s.isFile() && s.size > 0\n } catch {\n exists = false\n }\n if (!exists) {\n result.push({ spec, localPath, exists: false, verified: false })\n continue\n }\n try {\n const actual = await sha256OfFile(localPath)\n if (actual === spec.sha256) {\n result.push({ spec, localPath, exists: true, verified: true })\n } else {\n result.push({\n spec,\n localPath,\n exists: true,\n verified: false,\n invalidReason: `SHA256 mismatch: expected ${spec.sha256}, got ${actual}`,\n })\n }\n } catch (e) {\n result.push({\n spec,\n localPath,\n exists: true,\n verified: false,\n invalidReason: `SHA compute failed: ${(e as Error).message}`,\n })\n }\n }\n return result\n}\n\nexport interface DownloadProgress {\n spec: ModelSpec\n /** 현재까지 다운로드한 바이트 */\n downloaded: number\n /** 전체 바이트 (알 수 있으면) */\n total: number | null\n /** \"download\" | \"verify\" | \"done\" | \"skip\" */\n phase: \"download\" | \"verify\" | \"done\" | \"skip\" | \"error\"\n message?: string\n}\n\nexport type ProgressHandler = (p: DownloadProgress) => void\n\n/**\n * 필요한 모든 수식 모델을 다운로드/검증한다.\n * - 이미 있고 SHA 일치 → skip\n * - 없음 → 다운로드 후 SHA 검증. 실패 시 파일 삭제.\n */\nexport async function ensureFormulaModels(onProgress?: ProgressHandler): Promise<void> {\n const dir = getFormulaModelsDir()\n await mkdir(dir, { recursive: true })\n\n for (const spec of ALL_FORMULA_MODELS) {\n const localPath = join(dir, spec.filename)\n\n if (await isExistingValid(localPath, spec.sha256)) {\n onProgress?.({\n spec,\n downloaded: 0,\n total: null,\n phase: \"skip\",\n message: \"이미 존재 + SHA 일치\",\n })\n continue\n }\n\n // 기존 파일 있지만 SHA 불일치 → 삭제\n try {\n await unlink(localPath)\n } catch {\n // 없을 수 있음\n }\n\n await downloadToFile(spec, localPath, onProgress)\n }\n}\n\n/** 단일 모델만 확인/다운로드 (진단 UI 에서 개별 상태 새로고침용) */\nexport async function ensureSingleModel(spec: ModelSpec, onProgress?: ProgressHandler): Promise<void> {\n const dir = getFormulaModelsDir()\n await mkdir(dir, { recursive: true })\n const localPath = join(dir, spec.filename)\n if (await isExistingValid(localPath, spec.sha256)) {\n onProgress?.({ spec, downloaded: 0, total: null, phase: \"skip\" })\n return\n }\n try {\n await unlink(localPath)\n } catch {}\n await downloadToFile(spec, localPath, onProgress)\n}\n\nasync function isExistingValid(localPath: string, sha256Expected: string): Promise<boolean> {\n try {\n const s = await stat(localPath)\n if (!s.isFile() || s.size === 0) return false\n } catch {\n return false\n }\n try {\n const actual = await sha256OfFile(localPath)\n return actual === sha256Expected\n } catch {\n return false\n }\n}\n\nasync function downloadToFile(\n spec: ModelSpec,\n localPath: string,\n onProgress?: ProgressHandler,\n): Promise<void> {\n // 먼저 .part 로 받고 검증 후 rename — 중단된 다운로드가 \"정상 파일\"로 오인되는 걸 방지\n const partPath = `${localPath}.part`\n await mkdir(dirname(localPath), { recursive: true })\n\n const resp = await fetch(spec.url, {\n headers: {\n // HF CDN 은 UA 없으면 가끔 403 을 뱉는다\n \"User-Agent\": \"kordoc-formula-ocr/1.0 (+https://github.com/chrisryugj/kordoc)\",\n },\n })\n if (!resp.ok || !resp.body) {\n throw new Error(\n `${spec.name} 다운로드 실패: HTTP ${resp.status} ${resp.statusText} (${spec.url})`,\n )\n }\n\n const lenHeader = resp.headers.get(\"content-length\")\n const total = lenHeader ? Number.parseInt(lenHeader, 10) : null\n let downloaded = 0\n\n const ws = createWriteStream(partPath)\n try {\n const reader = Readable.fromWeb(resp.body as unknown as import(\"stream/web\").ReadableStream)\n reader.on(\"data\", (chunk: Buffer | Uint8Array) => {\n downloaded += chunk.length\n onProgress?.({\n spec,\n downloaded,\n total,\n phase: \"download\",\n })\n })\n await pipeline(reader, ws)\n } catch (e) {\n try {\n await unlink(partPath)\n } catch {}\n throw new Error(`${spec.name} 스트리밍 실패: ${(e as Error).message}`)\n }\n\n onProgress?.({\n spec,\n downloaded,\n total,\n phase: \"verify\",\n })\n\n // SHA 검증\n let actual: string\n try {\n actual = await sha256OfFile(partPath)\n } catch (e) {\n try {\n await unlink(partPath)\n } catch {}\n throw new Error(`${spec.name} SHA 계산 실패: ${(e as Error).message}`)\n }\n\n if (actual !== spec.sha256) {\n try {\n await unlink(partPath)\n } catch {}\n throw new Error(\n `${spec.name} SHA256 mismatch: expected ${spec.sha256}, got ${actual} — 모델 URL 이 오염되었거나 전송 중 손상되었습니다.`,\n )\n }\n\n await rename(partPath, localPath)\n onProgress?.({\n spec,\n downloaded,\n total,\n phase: \"done\",\n })\n}\n\nasync function sha256OfFile(p: string): Promise<string> {\n const h = createHash(\"sha256\")\n const stream = createReadStream(p)\n await pipeline(stream, async function* (src) {\n for await (const chunk of src) {\n h.update(chunk)\n // yield 안 함 (소비만)\n }\n })\n return h.digest(\"hex\")\n}\n","/**\n * LaTeX 후처리 — Pix2Text `latex_ocr.py` 의 post_process 간소화 + 자체 필터\n *\n * 1) 후행 whitespace command 제거 (\\, \\: \\; \\! \\quad \\qquad \\enspace \\thinspace \\ )\n * 2) 연속 공백 → 1개\n * 3) 빈 그룹 반복 제거 (^{} _{} \\hat{} \\bar{} \\vec{} 등)\n * 4) \\cmd 뒤 영문자 공백 분리 (\\cdotd → \\cdot d, \\timesd → \\times d)\n * 5) isTrivialFormula — 다이어그램 단일 글자/반복/장식 오탐 제거\n */\n\nconst TRAILING_WHITESPACE_CMDS = [\n \"\\\\,\",\n \"\\\\:\",\n \"\\\\;\",\n \"\\\\!\",\n \"\\\\ \",\n \"\\\\quad\",\n \"\\\\qquad\",\n \"\\\\enspace\",\n \"\\\\thinspace\",\n] as const\n\nexport function postProcessLatex(latex: string): string {\n let s = stripTrailingWhitespace(latex)\n s = collapseSpaces(s)\n for (let i = 0; i < 10; i++) {\n const next = stripEmptyGroups(s)\n if (next === s) break\n s = next\n }\n s = fixLatexSpacing(s)\n s = normalizeFormulaSpacing(s)\n s = s.trim()\n // trivial 이면 빈 문자열 반환 — 상위(pipeline/parser) 에서 latex.trim() 비었을 때 skip.\n if (isTrivialFormula(s)) return \"\"\n return s\n}\n\nexport function stripTrailingWhitespace(s: string): string {\n let t = s\n // 바깥 loop: 한 번 제거 후 다시 체크 (중첩 \"\\\\, \\\\quad\" 같은 경우)\n for (;;) {\n const trimmed = t.replace(/[\\s]+$/, \"\")\n let changed = false\n for (const p of TRAILING_WHITESPACE_CMDS) {\n if (trimmed.endsWith(p)) {\n t = trimmed.slice(0, trimmed.length - p.length)\n changed = true\n break\n }\n }\n if (!changed) return trimmed\n }\n}\n\nexport function collapseSpaces(s: string): string {\n let out = \"\"\n let prevSpace = false\n for (const c of s) {\n if (/\\s/.test(c)) {\n if (!prevSpace) {\n out += \" \"\n prevSpace = true\n }\n } else {\n out += c\n prevSpace = false\n }\n }\n return out\n}\n\n/**\n * 빈 그룹 `{}` 또는 `{\\s+}` 를 찾아 선행하는 `^`, `_`, 또는 `\\cmd` 와 함께 제거.\n */\nexport function stripEmptyGroups(s: string): string {\n let out = \"\"\n let i = 0\n const bytes = s\n while (i < bytes.length) {\n const ch = bytes[i]\n if (ch === \"{\") {\n // 빈 { \\s* } 스캔\n let j = i + 1\n while (j < bytes.length && /\\s/.test(bytes[j])) j++\n if (j < bytes.length && bytes[j] === \"}\") {\n // 앞쪽 공백 제거\n while (out.endsWith(\" \") || out.endsWith(\"\\t\")) {\n out = out.slice(0, -1)\n }\n if (out.endsWith(\"^\") || out.endsWith(\"_\")) {\n out = out.slice(0, -1)\n } else {\n // \\cmd 형태인지 탐색\n let k = out.length\n while (k > 0 && /[A-Za-z]/.test(out[k - 1])) k--\n if (k > 0 && out[k - 1] === \"\\\\\" && k < out.length) {\n out = out.slice(0, k - 1)\n }\n // 아니면 아무것도 안 함 — 빈 {} 만 제거\n }\n i = j + 1\n continue\n }\n }\n out += ch\n i++\n }\n return out\n}\n\n/**\n * LaTeX 명령어 화이트리스트 — 공백 누락 감지에 사용.\n * MFR 이 `\\cdot d` 를 `\\cdotd` 로 합쳐 출력할 때, 어느 지점에서 명령어가 끝나는지\n * 알려진 커맨드명으로 판단한다. 완전한 리스트는 아니며 OCR 에서 실제 관찰된 케이스 중심.\n */\nconst KNOWN_LATEX_CMDS: ReadonlySet<string> = new Set([\n // 연산자\n \"cdot\", \"cdots\", \"ldots\", \"dots\", \"vdots\", \"ddots\",\n \"times\", \"div\", \"pm\", \"mp\", \"ast\", \"star\", \"circ\", \"bullet\",\n \"oplus\", \"ominus\", \"otimes\", \"odot\",\n // 관계\n \"approx\", \"equiv\", \"neq\", \"ne\", \"sim\", \"simeq\", \"cong\",\n \"leq\", \"geq\", \"le\", \"ge\", \"ll\", \"gg\", \"prec\", \"succ\", \"preceq\", \"succeq\",\n \"propto\", \"parallel\", \"perp\",\n // 집합/논리\n \"in\", \"notin\", \"ni\", \"subset\", \"supset\", \"subseteq\", \"supseteq\",\n \"cap\", \"cup\", \"bigcap\", \"bigcup\", \"emptyset\", \"varnothing\",\n \"forall\", \"exists\", \"nexists\", \"neg\", \"lnot\", \"land\", \"lor\", \"vee\", \"wedge\",\n // 그리스 소문자\n \"alpha\", \"beta\", \"gamma\", \"delta\", \"epsilon\", \"varepsilon\",\n \"zeta\", \"eta\", \"theta\", \"vartheta\", \"iota\", \"kappa\", \"lambda\",\n \"mu\", \"nu\", \"xi\", \"omicron\", \"pi\", \"varpi\", \"rho\", \"varrho\",\n \"sigma\", \"varsigma\", \"tau\", \"upsilon\", \"phi\", \"varphi\",\n \"chi\", \"psi\", \"omega\",\n // 그리스 대문자\n \"Gamma\", \"Delta\", \"Theta\", \"Lambda\", \"Xi\", \"Pi\", \"Sigma\",\n \"Upsilon\", \"Phi\", \"Psi\", \"Omega\",\n // 화살표\n \"to\", \"gets\", \"mapsto\", \"rightarrow\", \"leftarrow\", \"leftrightarrow\",\n \"Rightarrow\", \"Leftarrow\", \"Leftrightarrow\", \"uparrow\", \"downarrow\",\n \"longrightarrow\", \"longleftarrow\", \"longmapsto\",\n // 큰 연산자\n \"sum\", \"prod\", \"coprod\", \"int\", \"iint\", \"iiint\", \"oint\", \"bigoplus\", \"bigotimes\",\n // 함수명\n \"sin\", \"cos\", \"tan\", \"sec\", \"csc\", \"cot\",\n \"arcsin\", \"arccos\", \"arctan\", \"sinh\", \"cosh\", \"tanh\",\n \"log\", \"ln\", \"lg\", \"exp\", \"lim\", \"liminf\", \"limsup\",\n \"sup\", \"inf\", \"max\", \"min\", \"arg\", \"det\", \"dim\", \"gcd\", \"deg\", \"hom\", \"ker\", \"mod\",\n // 특수 기호/수식\n \"infty\", \"partial\", \"nabla\", \"prime\", \"aleph\", \"ell\", \"hbar\", \"Re\", \"Im\",\n \"top\", \"bot\", \"angle\", \"vdash\", \"dashv\",\n // 기타\n \"left\", \"right\", \"big\", \"Big\", \"bigg\", \"Bigg\",\n])\n\n/**\n * `\\cmd` 뒤에 영문자가 바로 붙으면 공백 분리 (e.g. `\\cdotd` → `\\cdot d`, `\\timesd_{k}` → `\\times d_{k}`).\n *\n * MFR 이 좁은 영역에서 가끔 `\\cdot` 과 다음 변수 사이 공백을 누락. 그대로 두면 LaTeX 파서가\n * `\\cdotd` 를 undefined command 로 처리 (KaTeX: 에러 / TeX: 정의되지 않은 제어 시퀀스).\n * 알려진 LaTeX 명령어 prefix 중 가장 긴 것을 찾아 분할하면 렌더 안정성 확보.\n *\n * `\\cmd{..}` (영문자 뒤 `{`) 는 인자 포함 형태로 간주해 분리하지 않음.\n */\nexport function fixLatexSpacing(s: string): string {\n let out = \"\"\n let i = 0\n while (i < s.length) {\n if (s[i] === \"\\\\\" && i + 1 < s.length && /[A-Za-z]/.test(s[i + 1])) {\n // \\cmd 최장일치\n let j = i + 1\n while (j < s.length && /[A-Za-z]/.test(s[j])) j++\n const full = s.slice(i + 1, j) // 명령어 이름 (앞 \\ 제거)\n const nextChar = j < s.length ? s[j] : \"\"\n\n // \\cmd{..} 형태 — 인자로 이어지므로 분리하지 않음\n if (nextChar === \"{\") {\n out += \"\\\\\" + full\n i = j\n continue\n }\n\n // 자체가 알려진 명령어이거나, 긴 prefix 가 없으면 그대로\n let splitAt = full.length\n if (!KNOWN_LATEX_CMDS.has(full) && full.length >= 3) {\n // 뒤에서부터 prefix 길이를 줄이며 가장 긴 known cmd 찾기\n for (let len = full.length - 1; len >= 2; len--) {\n if (KNOWN_LATEX_CMDS.has(full.slice(0, len))) {\n splitAt = len\n break\n }\n }\n }\n\n out += \"\\\\\" + full.slice(0, splitAt)\n if (splitAt < full.length) {\n out += \" \" + full.slice(splitAt)\n }\n i = j\n } else {\n out += s[i]\n i++\n }\n }\n return out\n}\n\n/**\n * trivial(noise) 수식 판정.\n *\n * true 인 케이스 (MFD 오탐 주범):\n * 1) 공백/중괄호 제거 후 2자 이하 (e.g. `O`, `a`, `.`, `n`)\n * 2) 단일 `\\cmd` (e.g. `\\imath`, `\\pi`, `\\eta`, `\\sigma`, `\\theta`, `\\emptyset`, `\\varPi`)\n * 3) 단일 `\\mathrm{..}` / `\\textrm{..}` / `\\operatorname{..}` 류 + 짧은 내용 (e.g. `\\mathrm{fcloc}`)\n * 4) 동일 토큰이 3회 이상 + 전체의 50% 이상 (e.g. `\\pm \\pm \\pm \\pm`, `\\cap \\exists \\exists \\rceil`)\n *\n * false 유지 (의미 있는 수식):\n * `O(1)`, `d_{k}=64`, `\\sqrt{d_{k}}`, `PE_{pos+k}`, 등\n */\nexport function isTrivialFormula(s: string): boolean {\n const t = s.trim()\n if (t.length === 0) return true\n\n // 1) 공백/중괄호 제거 후 길이 ≤ 2\n const stripped = t.replace(/[\\s{}]/g, \"\")\n if (stripped.length <= 2) return true\n\n // 2) 단일 \\cmd (e.g. \\imath, \\varPi, \\pi)\n if (/^\\\\[A-Za-z]+$/.test(t)) return true\n\n // 3) 단일 \\mathrm{..} / \\textrm{..} / \\operatorname{..} / \\mathit{..} / \\mathbf{..} / \\mathcal{..} + 짧은 내용\n if (\n /^\\\\(?:mathrm|textrm|text|operatorname|mathit|mathbf|mathcal|mathsf|mathtt)\\{[A-Za-z]{1,6}\\}$/.test(\n t,\n )\n )\n return true\n\n const tokens = tokenizeLatex(t)\n\n // 4) 반복 토큰 dominant (e.g. \"\\\\pm \\\\pm \\\\pm \\\\pm\")\n if (tokens.length >= 3) {\n const freq = new Map<string, number>()\n for (const tok of tokens) freq.set(tok, (freq.get(tok) ?? 0) + 1)\n let maxCount = 0\n for (const c of freq.values()) if (c > maxCount) maxCount = c\n if (maxCount >= 3 && maxCount / tokens.length >= 0.5) return true\n }\n\n // 5) 심볼만 구성된 짧은 식 — 토큰 2~4개이고 연산자/숫자 없음 (e.g. \"\\\\cap \\\\exists \\\\exists \\\\rceil\")\n // 의미 있는 수식은 대개 `=` 또는 숫자가 포함됨. 허용되는 연산자: = + - / * < > 숫자\n if (tokens.length >= 2 && tokens.length <= 4) {\n const hasOpOrNum = tokens.some((tok) =>\n /^[=+\\-/*<>]$/.test(tok) || /^[0-9]$/.test(tok),\n )\n if (!hasOpOrNum) return true\n }\n\n // 6) substring 반복 패턴 — 동일 5~15자 substring 이 3회 이상 + 커버리지 60%+\n // (e.g. \"\\\\alpha_{1}=\\\\alpha_{2}=\\\\alpha_{3}=...\" 의 `\\\\alpha_{` 반복,\n // \"c_{c}^{\\\\prime}\\\\phi_{c}^{\\\\prime}=c_{c}^{\\\\prime}...\" 의 `c_{c}^{\\\\prime}` 반복)\n if (hasHighRepetition(t)) return true\n\n // 7) MFR placeholder 인 `\\square` 포함 — 인식 실패 영역을 `\\square` 로 대체하는 특성\n // 정당한 수식에 `\\square` 가 나오는 경우는 거의 없음 (대부분 `\\Box`, `\\blacksquare` 등 사용)\n if (t.includes(\"\\\\square\")) return true\n\n // 8) 단독 숫자/실수 (e.g. \"$1.0$\", \"$42$\", \"$-3.14$\")\n // 공백/중괄호/백슬래시 제거 후 순수 숫자/부호/소수점만 남으면 trivial\n if (/^[-+]?\\d+\\.?\\d*$/.test(t.replace(/[\\s{}\\\\]/g, \"\"))) return true\n\n // 9) `(X)(X)` 또는 `{X}{X}` 연속 중복 — 동일 그룹 반복 (e.g. \"$C(T_{2})(T_{2})$\")\n // 한 단계 중첩 허용: `{k_{c}}` 같이 내부에 `{...}` 하나 포함 가능.\n if (/(\\([^()]{2,15}\\))\\s*\\1/.test(t)) return true\n if (/(\\{(?:[^{}]|\\{[^{}]*\\})+\\})\\s*\\1/.test(t)) return true\n\n // 10) 동일 인자 반복 — 괄호 인자 중 쉼표 구분 3개 중 2개 이상 동일 (e.g. `C(\\tau_{2},\\mu^{\\prime},\\mu^{\\prime})`)\n // 완전 일치 인자가 2회+ 반복이면 OCR 반복 오류로 간주\n const argMatch = t.match(/^[A-Za-z\\\\][A-Za-z]*\\(([^()]+)\\)$/)\n if (argMatch) {\n const args = argMatch[1].split(\",\").map((a) => a.trim())\n if (args.length >= 2) {\n const freq = new Map<string, number>()\n for (const a of args) if (a) freq.set(a, (freq.get(a) ?? 0) + 1)\n for (const [, c] of freq) {\n if (c >= 2 && c / args.length >= 0.5) return true\n }\n }\n }\n\n // 11) X/X 또는 \\frac{X}{X} — 분자=분모 = 1, 의미 없는 수식\n // 수식 **어디에나** 나타나면 trivial (e.g. \"$k<\\\\frac{\\\\eta}{\\\\eta}$\" 도 포함)\n // 정당한 수식에서 $\\\\frac{a}{a}$ 를 쓰는 경우는 거의 없음 (대수적으로 1).\n if (/\\\\frac\\{([^{}]+)\\}\\{\\1\\}/.test(t)) return true\n // X/X 꼴 (단일 변수 또는 \\cmd)\n if (/(\\\\[A-Za-z]+|\\b[A-Za-z])\\s*\\/\\s*\\1\\b/.test(t)) return true\n\n // 12) `\\begin{matrix}` 내부 `\\cdots` 여러 번 — MFR 이 다이어그램/표를 matrix 로 오인식\n // 본문 수식의 matrix 는 구체적 원소가 있어 `\\cdots` 가 2회 이상 나오는 경우 거의 없음\n if (/\\\\begin\\{(?:matrix|pmatrix|bmatrix|vmatrix)\\}/.test(t)) {\n const cdotsCount = (t.match(/\\\\cdots/g) ?? []).length\n if (cdotsCount >= 2) return true\n }\n\n // 13) 다이어그램 텍스트 레이블 — `\\mathrm{word}` 가 2회+ 포함된 짧은 식 (토큰 수 ≤ 12)\n // 또는 `[a-z]{1,3}_\\{\\mathrm{word}\\}` 꼴의 비정상 변수명 (e.g. `cl_{\\mathrm{model}}`)\n if (tokens.length <= 12) {\n const mathrmCount = (t.match(/\\\\mathrm\\{/g) ?? []).length\n if (mathrmCount >= 2) {\n // 연산자/등호/숫자가 거의 없어 \"단순 레이블\" 로 보이는지 추가 확인\n const hasRealMath = /[=+\\-*/<>^]/.test(t) && /\\d/.test(t)\n if (!hasRealMath) return true\n }\n }\n // 비정상 변수명: 2-3자 영문 접두 + _{\\mathrm{긴단어}} 단독 (e.g. `cl_{\\mathrm{model}}`, `to_{\\mathrm{max}}`)\n // 정상 변수명은 보통 단일 문자 (`d_{\\mathrm{model}}`, `W_{i}`) — 2자 이상이면 다이어그램 오인식\n if (/^[a-zA-Z]{2,3}_\\{\\\\mathrm\\{[a-zA-Z]{3,}\\}\\}$/.test(t)) return true\n\n // 14) `\\mathrm{word}` + 이항 연산자 + single 요소 꼴 — 다이어그램 \"word → value\" 레이블\n // (e.g. `\\mathrm{to}-\\infty`, `\\mathrm{to}+\\infty`)\n if (/^\\\\mathrm\\{[a-z]{2,}\\}[-+][-+]?(?:\\\\[a-zA-Z]+|[a-zA-Z0-9])$/.test(t)) return true\n\n // 15) `\\mathsf`, `\\mathtt`, `\\texttt` 는 본문 수식에 거의 쓰이지 않고 다이어그램 타이포그래피 오인식이 대부분\n // — 포함만으로 trivial (e.g. `\\mathtt{Welgnt}`, `\\mathsf{Tr}_{s}`, `\\texttt{wg}`)\n if (/\\\\(?:mathsf|mathtt|texttt)\\{/.test(t)) return true\n\n // 16) `\\begin{aligned}` 이지만 `=` 없음 — 정당한 aligned 는 항상 등호 포함 (여러 줄 equation)\n // 등호 없으면 OCR garbage 조합\n if (/\\\\begin\\{aligned\\}/.test(t) && !t.includes(\"=\")) return true\n\n // 17) `\\begin{matrix}` + `\\downarrow` 2회+ — 네트워크 architecture 다이어그램 오인식\n if (/\\\\begin\\{matrix\\}/.test(t) && (t.match(/\\\\downarrow/g) ?? []).length >= 2) return true\n\n return false\n}\n\n/**\n * substring 반복 탐지 — 동일한 N자 substring 이 3회 이상 나타나고\n * 그 반복 총 길이가 전체의 60% 이상이면 true.\n *\n * 5~15자 범위만 검사 (짧으면 정상 수식의 `^{2}` 같은 패턴도 걸림, 길면 노이즈 거의 없음).\n * 영문자 포함 substring 만 검사 (구두점만 반복은 정상 수식에도 많음).\n *\n * 정당한 수식은 다양한 구조로 거의 겹치지 않아 false-positive 낮음.\n */\nexport function hasHighRepetition(s: string): boolean {\n if (s.length < 15) return false\n\n for (let len = 5; len <= 15; len++) {\n // substring 길이 × 3회가 전체보다 길면 의미 없음\n if (len * 3 > s.length) break\n const seen = new Map<string, number>()\n for (let i = 0; i <= s.length - len; i++) {\n const sub = s.slice(i, i + len)\n if (!/[a-zA-Z]/.test(sub)) continue\n seen.set(sub, (seen.get(sub) ?? 0) + 1)\n }\n for (const [, count] of seen) {\n if (count < 3) continue\n // 겹치지 않는 점유율 추정: count*len (overlapping 포함이라 약간 과대)\n // 보수적 판정 위해 최소 3회 + 60% 커버리지\n if ((count * len) / s.length >= 0.6) return true\n }\n }\n return false\n}\n\n/**\n * LaTeX 를 단순 토큰 배열로 분할 — `\\cmd` 는 하나의 토큰, 그 외는 non-space 단일 문자.\n * isTrivialFormula 의 반복 감지용.\n */\nfunction tokenizeLatex(s: string): string[] {\n const result: string[] = []\n let i = 0\n while (i < s.length) {\n const c = s[i]\n if (c === \"\\\\\") {\n let j = i + 1\n while (j < s.length && /[A-Za-z]/.test(s[j])) j++\n if (j === i + 1 && j < s.length) j++ // `\\\\` 또는 `\\{` 같은 단일 escape\n result.push(s.slice(i, j))\n i = j\n } else if (/\\s/.test(c)) {\n i++\n } else {\n result.push(c)\n i++\n }\n }\n return result\n}\n\n/**\n * MFR tokenizer 가 LaTeX 토큰 사이에 삽입하는 과도한 공백을 정리.\n *\n * 예: `\\mathrm { m o d d }` → `\\mathrm{modd}`, `6 4` → `64`, `( Q, K, V )` → `(Q,K,V)`\n *\n * 규칙: 공백을 앞뒤 토큰 기준으로 유지/제거 결정.\n * - 직전이 `\\cmd` 이고 직후가 영문자 → **유지** (e.g. `\\cdot d`, `\\alpha b` — 공백 없으면 greedy 로 `\\cdotd` 가 명령어로 해석될 수 있음)\n * - 그 외 → **제거** (숫자/괄호/중괄호/첨자/구두점 주변 공백은 LaTeX 의미를 바꾸지 않음)\n *\n * `fixLatexSpacing` 이후에 호출되는 것을 전제 — 먼저 공백 누락을 복원한 뒤 과도한 공백을 걷어냄.\n */\nexport function normalizeFormulaSpacing(s: string): string {\n // `\\cmd`, 단일 escape, 공백, 일반 문자 단위로 토큰화 (공백도 토큰으로 유지)\n const tokens: string[] = []\n let i = 0\n while (i < s.length) {\n const c = s[i]\n if (c === \"\\\\\") {\n let j = i + 1\n while (j < s.length && /[A-Za-z]/.test(s[j])) j++\n if (j === i + 1 && j < s.length) j++ // 단일 escape (`\\\\`, `\\{` 등)\n tokens.push(s.slice(i, j))\n i = j\n } else if (/\\s/.test(c)) {\n tokens.push(\" \")\n i++\n } else {\n tokens.push(c)\n i++\n }\n }\n\n const out: string[] = []\n for (let k = 0; k < tokens.length; k++) {\n if (tokens[k] !== \" \") {\n out.push(tokens[k])\n continue\n }\n // 공백 토큰 — 유지 여부 결정\n // 연속 공백은 나중에 병합 (아래 로직은 앞뒤 non-space 토큰 기준)\n let prev = \"\"\n for (let p = k - 1; p >= 0; p--) {\n if (tokens[p] !== \" \") {\n prev = tokens[p]\n break\n }\n }\n let next = \"\"\n for (let q = k + 1; q < tokens.length; q++) {\n if (tokens[q] !== \" \") {\n next = tokens[q]\n break\n }\n }\n const prevIsCmd = /^\\\\[A-Za-z]+$/.test(prev)\n const nextIsAlpha = /^[A-Za-z]$/.test(next)\n if (prevIsCmd && nextIsAlpha) {\n // 앞 cmd 와 뒤 변수 분리용 공백 (연속 공백이더라도 하나만 남김)\n if (out.length === 0 || out[out.length - 1] !== \" \") {\n out.push(\" \")\n }\n }\n // 그 외 공백은 drop\n }\n\n // 앞/뒤 공백 제거\n while (out.length > 0 && out[0] === \" \") out.shift()\n while (out.length > 0 && out[out.length - 1] === \" \") out.pop()\n\n return out.join(\"\")\n}\n","/**\n * MFD (Mathematical Formula Detection) — YOLOv8 기반 수식 영역 검출\n *\n * 모델: breezedeus/pix2text-mfd (YOLOv8 small, imgsz=768, stride=32)\n * 출력: [1, 6, 12096] — (cx, cy, w, h, class0_score, class1_score) × anchors\n * - class 0 = embedding (inline 수식)\n * - class 1 = isolated (display 수식)\n *\n * 전처리: letterbox (긴 변 비율 유지 + 회색 패딩 114)\n * 후처리: conf ≥ 0.25 → class-per-class NMS (IoU ≥ 0.45)\n */\n\nimport type { InferenceSession, Tensor } from \"onnxruntime-node\"\nimport type { FormulaKind, PixelFrame, FormulaRegion } from \"./types.js\"\n\nconst MFD_IMG_SIZE = 768\nconst MFD_NUM_CLASSES = 2\nconst MFD_CHANNELS = 4 + MFD_NUM_CLASSES\n/** 기본 confidence 임계값 (Pix2Text 원본 0.25 — 누락은 적지만 noise 발생) */\nconst MFD_CONF_INLINE = 0.30\n/** display 수식 임계값 — diagram/figure 오탐 감소 목적으로 inline 보다 높게 */\nconst MFD_CONF_DISPLAY = 0.40\nconst MFD_IOU_THRESHOLD = 0.45\n/** 최소 bbox 면적 (px²) — 이보다 작으면 OCR noise 가능성 높음 */\nconst MFD_MIN_AREA = 80\nconst PAD_VALUE = 114 / 255\n\ninterface RawDetection {\n x1: number\n y1: number\n x2: number\n y2: number\n kind: FormulaKind\n score: number\n}\n\n/**\n * 단일 페이지 이미지에서 수식 영역 검출 (인식 없음 — recognizer 로 넘김).\n * bbox 는 원본 이미지 좌표계.\n */\nexport async function detectFormulaRegions(\n session: InferenceSession,\n frame: PixelFrame,\n ort: typeof import(\"onnxruntime-node\"),\n): Promise<Omit<FormulaRegion, \"latex\">[]> {\n const { scale, padX, padY, tensor } = letterbox(frame, MFD_IMG_SIZE)\n\n const input = new ort.Tensor(\"float32\", tensor, [1, 3, MFD_IMG_SIZE, MFD_IMG_SIZE])\n const feeds: Record<string, Tensor> = { images: input }\n const outputs = await session.run(feeds)\n\n const firstKey = Object.keys(outputs)[0]\n const out = outputs[firstKey]\n if (!out || out.type !== \"float32\") {\n throw new Error(\"MFD 출력 없음 또는 dtype 불일치\")\n }\n\n const outDims = out.dims\n if (outDims.length !== 3) {\n throw new Error(`MFD 출력 차원 예상 3, 실제 ${outDims.length}: [${outDims.join(\",\")}]`)\n }\n const channels = outDims[1]\n const anchors = outDims[2]\n if (channels !== MFD_CHANNELS) {\n throw new Error(`MFD 채널 수 예상 ${MFD_CHANNELS}, 실제 ${channels}`)\n }\n if (anchors <= 0) return []\n\n const data = out.data as Float32Array\n const candidates: RawDetection[] = []\n\n for (let a = 0; a < anchors; a++) {\n const cx = data[a]\n const cy = data[anchors + a]\n const w = data[2 * anchors + a]\n const h = data[3 * anchors + a]\n\n let bestCls = 0\n let bestScore = 0\n for (let c = 0; c < MFD_NUM_CLASSES; c++) {\n const s = data[(4 + c) * anchors + a]\n if (s > bestScore) {\n bestScore = s\n bestCls = c\n }\n }\n // 클래스별 임계값 — display 쪽은 diagram/figure 오탐 감소 목적으로 inline 보다 엄격.\n const threshold = bestCls === 1 ? MFD_CONF_DISPLAY : MFD_CONF_INLINE\n if (bestScore < threshold) continue\n\n // letterbox → 원본 좌표\n let x1 = (cx - w / 2 - padX) / scale\n let y1 = (cy - h / 2 - padY) / scale\n let x2 = (cx + w / 2 - padX) / scale\n let y2 = (cy + h / 2 - padY) / scale\n\n x1 = clamp(x1, 0, frame.width - 1)\n y1 = clamp(y1, 0, frame.height - 1)\n x2 = clamp(x2, 0, frame.width - 1)\n y2 = clamp(y2, 0, frame.height - 1)\n\n if (x2 - x1 < 2 || y2 - y1 < 2) continue\n // 너무 작은 bbox (픽셀 단위 noise 로 보이는 영역) 제외\n if ((x2 - x1) * (y2 - y1) < MFD_MIN_AREA) continue\n\n candidates.push({\n x1,\n y1,\n x2,\n y2,\n kind: bestCls === 1 ? \"display\" : \"inline\",\n score: bestScore,\n })\n }\n\n // 클래스별 NMS (혼합 클래스 bbox 간 중복은 허용 — Pix2Text 원본 동작)\n const kept: RawDetection[] = []\n for (const kind of [\"inline\", \"display\"] as FormulaKind[]) {\n const subset = candidates.filter((c) => c.kind === kind)\n kept.push(...nms(subset, MFD_IOU_THRESHOLD))\n }\n\n // 읽기 순서 정렬 (위→아래, 왼→오른쪽)\n kept.sort((a, b) => a.y1 - b.y1 || a.x1 - b.x1)\n\n return kept.map((d) => ({\n bbox: { x1: d.x1, y1: d.y1, x2: d.x2, y2: d.y2 },\n kind: d.kind,\n score: d.score,\n }))\n}\n\n/**\n * YOLOv8 letterbox 전처리 — RGBA 프레임을 target×target float32 CHW tensor 로.\n *\n * - 긴 변 기준으로 비율 유지 리사이즈\n * - 가운데 패딩 (회색 114)\n * - /255 정규화\n * - CHW 레이아웃\n */\nexport function letterbox(\n frame: PixelFrame,\n target: number,\n): { scale: number; padX: number; padY: number; tensor: Float32Array } {\n const w = frame.width\n const h = frame.height\n const scale = Math.min(target / w, target / h)\n const newW = Math.max(1, Math.round(w * scale))\n const newH = Math.max(1, Math.round(h * scale))\n const padX = (target - newW) / 2\n const padY = (target - newH) / 2\n const offX = Math.floor(padX)\n const offY = Math.floor(padY)\n\n const ts = target\n const tensor = new Float32Array(3 * ts * ts)\n tensor.fill(PAD_VALUE)\n\n // RGBA 원본 → 먼저 nearest-neighbor resize 한 뒤 복사.\n // (sharp 로 정확한 bilinear 가능하지만, 여기선 이미 렌더링된 bitmap 이 충분히 해상도 높음 —\n // 결과는 동일한 수준으로 수식 박스 위치 맞춤. Rust 구현도 Triangle 사용.)\n const src = frame.data\n const srcW = frame.width\n const srcH = frame.height\n\n for (let y = 0; y < newH; y++) {\n const sy = Math.min(srcH - 1, Math.floor((y + 0.5) / newH * srcH))\n for (let x = 0; x < newW; x++) {\n const sx = Math.min(srcW - 1, Math.floor((x + 0.5) / newW * srcW))\n const srcIdx = (sy * srcW + sx) * 4\n const r = src[srcIdx]\n const g = src[srcIdx + 1]\n const b = src[srcIdx + 2]\n\n const tx = x + offX\n const ty = y + offY\n const idx = ty * ts + tx\n tensor[idx] = r / 255\n tensor[ts * ts + idx] = g / 255\n tensor[2 * ts * ts + idx] = b / 255\n }\n }\n\n return { scale, padX, padY, tensor }\n}\n\nfunction nms(cands: RawDetection[], iouThreshold: number): RawDetection[] {\n const sorted = [...cands].sort((a, b) => b.score - a.score)\n const kept: RawDetection[] = []\n for (const cand of sorted) {\n let keep = true\n for (const k of kept) {\n if (iou(cand, k) > iouThreshold) {\n keep = false\n break\n }\n }\n if (keep) kept.push(cand)\n }\n return kept\n}\n\nfunction iou(\n a: { x1: number; y1: number; x2: number; y2: number },\n b: { x1: number; y1: number; x2: number; y2: number },\n): number {\n const x1 = Math.max(a.x1, b.x1)\n const y1 = Math.max(a.y1, b.y1)\n const x2 = Math.min(a.x2, b.x2)\n const y2 = Math.min(a.y2, b.y2)\n const interW = Math.max(0, x2 - x1)\n const interH = Math.max(0, y2 - y1)\n const inter = interW * interH\n const areaA = Math.max(0, a.x2 - a.x1) * Math.max(0, a.y2 - a.y1)\n const areaB = Math.max(0, b.x2 - b.x1) * Math.max(0, b.y2 - b.y1)\n const union = areaA + areaB - inter\n return union <= 0 ? 0 : inter / union\n}\n\nfunction clamp(v: number, lo: number, hi: number): number {\n if (v < lo) return lo\n if (v > hi) return hi\n return v\n}\n\n// 테스트 용도로만 export\nexport const __internal = { nms, iou, letterbox }\n","/**\n * MFR (Mathematical Formula Recognition) — DeiT encoder + TrOCR decoder\n *\n * 모델: breezedeus/pix2text-mfr\n * - encoder: DeiT distilled, input 384×384 → output [1, 578, 384]\n * - decoder: TrOCR (no KV cache), vocab=1200, EOS=2\n *\n * 전처리 (DeiT): Catmull-Rom resize → /255 → (x - 0.5) / 0.5 == x*2 - 1\n *\n * 디코딩: greedy, start=[2], stop at 2 또는 max_new_tokens=256\n * decoder 는 매 스텝 전체 시퀀스를 재계산 (no KV cache — 모델 고정)\n */\n\nimport type { InferenceSession, Tensor } from \"onnxruntime-node\"\nimport type { PreTrainedTokenizer } from \"@huggingface/transformers\"\nimport { postProcessLatex } from \"./postprocess.js\"\nimport type { PixelFrame } from \"./types.js\"\n\nconst MFR_IMG_SIZE = 384\nconst MFR_ENC_HIDDEN = 384\nconst MFR_MAX_NEW_TOKENS = 256\nconst MFR_EOS_ID = 2\nconst MFR_PAD_ID = 0\n\nexport interface RecognizeDeps {\n encoder: InferenceSession\n decoder: InferenceSession\n tokenizer: PreTrainedTokenizer\n ort: typeof import(\"onnxruntime-node\")\n}\n\n/**\n * 단일 수식 crop 을 LaTeX 로 인식.\n * 입력 frame 은 이미 해당 영역만 잘린 RGBA 이미지이어야 한다 (recognizer 가 resize 수행).\n */\nexport async function recognizeFormula(\n deps: RecognizeDeps,\n crop: PixelFrame,\n): Promise<string> {\n const tensor = deitPreprocess(crop, MFR_IMG_SIZE)\n const { ort, encoder, decoder, tokenizer } = deps\n\n const pixelInput = new ort.Tensor(\"float32\", tensor, [1, 3, MFR_IMG_SIZE, MFR_IMG_SIZE])\n const encOut = await encoder.run({ pixel_values: pixelInput })\n\n const encKey =\n Object.keys(encOut).find((k) => k.includes(\"hidden\")) ?? Object.keys(encOut)[0]\n const encTensor = encOut[encKey]\n if (!encTensor || encTensor.type !== \"float32\") {\n throw new Error(\"MFR encoder 출력 없음\")\n }\n const encDims = encTensor.dims\n if (encDims.length !== 3) {\n throw new Error(`MFR encoder 차원 예상 3, 실제 ${encDims.length}`)\n }\n const encSeq = encDims[1]\n const encHidden = encDims[2]\n if (encHidden !== MFR_ENC_HIDDEN) {\n throw new Error(`MFR encoder hidden 예상 ${MFR_ENC_HIDDEN}, 실제 ${encHidden}`)\n }\n const encData = encTensor.data as Float32Array\n\n // greedy decode\n const tokens: number[] = [MFR_EOS_ID] // decoder_start_token_id\n\n for (let step = 0; step < MFR_MAX_NEW_TOKENS; step++) {\n const seqLen = tokens.length\n const idsArr = BigInt64Array.from(tokens.map((t) => BigInt(t)))\n const idsTensor = new ort.Tensor(\"int64\", idsArr, [1, seqLen])\n\n // encoder_hidden_states 는 매 스텝 동일 — 복사 필요 (Tensor 는 buffer 소유)\n const hidCopy = new Float32Array(encData)\n const hidTensor = new ort.Tensor(\"float32\", hidCopy, [1, encSeq, encHidden])\n\n const decOut = await decoder.run({\n input_ids: idsTensor,\n encoder_hidden_states: hidTensor,\n })\n\n const logitKey =\n Object.keys(decOut).find((k) => k.includes(\"logit\")) ?? Object.keys(decOut)[0]\n const logitsTensor = decOut[logitKey]\n if (!logitsTensor || logitsTensor.type !== \"float32\") {\n throw new Error(\"MFR decoder logits 없음\")\n }\n const dims = logitsTensor.dims\n if (dims.length !== 3) {\n throw new Error(`MFR decoder 차원 예상 3, 실제 ${dims.length}`)\n }\n const decSeq = dims[1]\n const vocab = dims[2]\n const logitsData = logitsTensor.data as Float32Array\n\n // 마지막 step logits 만\n const lastOffset = (decSeq - 1) * vocab\n let bestId = 0\n let bestVal = -Infinity\n for (let v = 0; v < vocab; v++) {\n const val = logitsData[lastOffset + v]\n if (val > bestVal) {\n bestVal = val\n bestId = v\n }\n }\n tokens.push(bestId)\n if (bestId === MFR_EOS_ID) break\n }\n\n // 첫 start 토큰 제외 + EOS/PAD 제거\n const body: number[] = []\n for (let i = 1; i < tokens.length; i++) {\n const t = tokens[i]\n if (t === MFR_EOS_ID) break\n if (t === MFR_PAD_ID) continue\n if (t < 0) continue\n body.push(t)\n }\n\n // Transformers.js tokenizer.decode 는 skip_special_tokens=true 기본\n const raw = tokenizer.decode(body, { skip_special_tokens: true })\n return postProcessLatex(raw)\n}\n\n/**\n * DeiT preprocess:\n * resize to 384×384 (bilinear)\n * (pixel/255 - 0.5) / 0.5 == pixel/127.5 - 1\n * CHW layout\n */\nexport function deitPreprocess(crop: PixelFrame, target: number): Float32Array {\n const ts = target\n const out = new Float32Array(3 * ts * ts)\n const { data: src, width: srcW, height: srcH } = crop\n\n // 단순 nearest (이미 수식 crop 은 300DPI 근방이라 충분). 필요시 sharp bilinear 로 교체.\n for (let y = 0; y < ts; y++) {\n const sy = Math.min(srcH - 1, Math.max(0, Math.floor(((y + 0.5) / ts) * srcH)))\n for (let x = 0; x < ts; x++) {\n const sx = Math.min(srcW - 1, Math.max(0, Math.floor(((x + 0.5) / ts) * srcW)))\n const srcIdx = (sy * srcW + sx) * 4\n const r = src[srcIdx]\n const g = src[srcIdx + 1]\n const b = src[srcIdx + 2]\n\n const idx = y * ts + x\n out[idx] = r / 127.5 - 1\n out[ts * ts + idx] = g / 127.5 - 1\n out[2 * ts * ts + idx] = b / 127.5 - 1\n }\n }\n return out\n}\n","/**\n * 수식 OCR 파이프라인 — PDFium 렌더 + MFD + MFR 통합\n *\n * 한 번 생성된 FormulaPipeline 인스턴스는 ONNX 세션을 재사용해 여러 PDF 에 적용 가능.\n * Docufinder 는 CLI 프로세스 단위로 새로 띄우기 때문에 세션 재사용은 PDF 내부 페이지 루프에만 적용됨.\n */\n\nimport type { PDFiumLibrary, PDFiumDocument } from \"@hyzyla/pdfium\"\nimport type { InferenceSession } from \"onnxruntime-node\"\nimport type { PreTrainedTokenizer } from \"@huggingface/transformers\"\n\n/** sharp 은 CommonJS `export =` 형태라 타입을 직접 import 하기 까다로움. 함수형 시그니처만 사용. */\ntype SharpFactory = (\n input: Uint8Array | Buffer,\n options?: { raw?: { width: number; height: number; channels: number } },\n) => {\n extract(region: { left: number; top: number; width: number; height: number }): {\n raw(): { toBuffer(): Promise<Buffer> }\n }\n}\n\nimport { detectFormulaRegions } from \"./detector.js\"\nimport { recognizeFormula } from \"./recognizer.js\"\nimport {\n getFormulaModelsDir,\n MFD_MODEL,\n MFR_ENCODER_MODEL,\n MFR_DECODER_MODEL,\n MFR_TOKENIZER,\n} from \"./models.js\"\nimport type { FormulaRegion, PixelFrame } from \"./types.js\"\nimport { join } from \"path\"\n\n/** PDF 페이지 렌더 해상도 (scale=2 → 약 144 DPI). 수식 인식에는 이 이상은 과함. */\nconst RENDER_SCALE = 2\n\nexport interface FormulaPipelineOptions {\n /** 수식 인식 페이지 스케일 (기본 2). 값을 올리면 작은 수식 인식률 ↑, 속도 ↓. */\n scale?: number\n /** 페이지별 최대 수식 수 (안전 상한). 기본 50. */\n maxRegionsPerPage?: number\n /** 페이지 하나당 타임아웃 (ms, 기본 60000 = 1분). 초과 시 해당 페이지 skip. */\n pageTimeoutMs?: number\n}\n\nexport interface PageFormulaResult {\n pageNumber: number\n /** 페이지 전체 렌더 이미지의 가로/세로 (원본 좌표계, scale 반영됨) */\n renderedWidth: number\n renderedHeight: number\n /** 원본 PDF 포인트 기준 너비/높이 (pdfjs 와 좌표 매핑용) */\n pdfWidth: number\n pdfHeight: number\n regions: FormulaRegion[]\n}\n\n/**\n * 수식 OCR 실행 컨텍스트. 처음 호출 시 무겁게 초기화 (ONNX 세션 로드),\n * 이후 runOnBuffer / runOnPages 를 여러 번 호출해 재사용 가능.\n *\n * 모든 의존성(onnxruntime-node, @huggingface/transformers, @hyzyla/pdfium, sharp)은\n * optional 이므로 dynamic import. 미설치 시 초기화에서 명확한 에러 메시지 반환.\n */\nexport class FormulaPipeline {\n private mfd: InferenceSession\n private encoder: InferenceSession\n private decoder: InferenceSession\n private tokenizer: PreTrainedTokenizer\n private ort: typeof import(\"onnxruntime-node\")\n private sharp: SharpFactory\n private pdfium: PDFiumLibrary\n private opts: Required<FormulaPipelineOptions>\n\n private constructor(parts: {\n mfd: InferenceSession\n encoder: InferenceSession\n decoder: InferenceSession\n tokenizer: PreTrainedTokenizer\n ort: typeof import(\"onnxruntime-node\")\n sharp: SharpFactory\n pdfium: PDFiumLibrary\n opts: Required<FormulaPipelineOptions>\n }) {\n this.mfd = parts.mfd\n this.encoder = parts.encoder\n this.decoder = parts.decoder\n this.tokenizer = parts.tokenizer\n this.ort = parts.ort\n this.sharp = parts.sharp\n this.pdfium = parts.pdfium\n this.opts = parts.opts\n }\n\n /**\n * 수식 OCR 엔진 초기화. 모델 파일이 로컬에 없으면 즉시 실패 — 호출자가\n * `ensureFormulaModels()` 를 먼저 돌려야 한다.\n */\n static async create(options?: FormulaPipelineOptions): Promise<FormulaPipeline> {\n const opts: Required<FormulaPipelineOptions> = {\n scale: options?.scale ?? RENDER_SCALE,\n maxRegionsPerPage: options?.maxRegionsPerPage ?? 50,\n pageTimeoutMs: options?.pageTimeoutMs ?? 60_000,\n }\n\n const [ortMod, sharpModRaw, hfMod, pdfiumMod] = await Promise.all([\n tryImport<typeof import(\"onnxruntime-node\")>(\n \"onnxruntime-node\",\n () => import(\"onnxruntime-node\"),\n ),\n tryImport<{ default?: SharpFactory } & SharpFactory>(\n \"sharp\",\n () => import(\"sharp\") as unknown as Promise<{ default?: SharpFactory } & SharpFactory>,\n ),\n tryImport<typeof import(\"@huggingface/transformers\")>(\n \"@huggingface/transformers\",\n () => import(\"@huggingface/transformers\"),\n ),\n tryImport<typeof import(\"@hyzyla/pdfium\")>(\n \"@hyzyla/pdfium\",\n () => import(\"@hyzyla/pdfium\"),\n ),\n ])\n // CJS `export =` + ESM interop → default 속성에 래핑될 때도, 안 될 때도 있음.\n const sharpAny = sharpModRaw as { default?: SharpFactory } | SharpFactory\n const sharpMod: SharpFactory =\n typeof sharpAny === \"function\"\n ? sharpAny\n : (sharpAny.default ?? (sharpAny as unknown as SharpFactory))\n\n const modelsDir = getFormulaModelsDir()\n const mfdPath = join(modelsDir, MFD_MODEL.filename)\n const encPath = join(modelsDir, MFR_ENCODER_MODEL.filename)\n const decPath = join(modelsDir, MFR_DECODER_MODEL.filename)\n const tokPath = join(modelsDir, MFR_TOKENIZER.filename)\n\n const sessionOpts: import(\"onnxruntime-node\").InferenceSession.SessionOptions = {\n graphOptimizationLevel: \"all\",\n executionProviders: [\"cpu\"],\n }\n\n // 각 세션은 I/O 바운드 아님 → 병렬 로드로 1~2초 단축\n const [mfd, encoder, decoder] = await Promise.all([\n ortMod.InferenceSession.create(mfdPath, sessionOpts),\n ortMod.InferenceSession.create(encPath, sessionOpts),\n ortMod.InferenceSession.create(decPath, sessionOpts),\n ])\n\n // Transformers.js 의 PreTrainedTokenizer 를 로컬 tokenizer.json 으로 로드.\n // AutoTokenizer.from_pretrained 는 HF hub 접근을 시도하므로 우회.\n const { readFile } = await import(\"fs/promises\")\n const tokenizerJson = JSON.parse(await readFile(tokPath, \"utf-8\"))\n const PretrainedCtor = hfMod.PreTrainedTokenizer as unknown as new (\n json: unknown,\n config: Record<string, unknown>,\n ) => PreTrainedTokenizer\n const tokenizer = new PretrainedCtor(tokenizerJson, {})\n\n const pdfium = await pdfiumMod.PDFiumLibrary.init()\n\n return new FormulaPipeline({\n mfd,\n encoder,\n decoder,\n tokenizer,\n ort: ortMod,\n sharp: sharpMod,\n pdfium,\n opts,\n })\n }\n\n /** 리소스 해제 — 더 이상 사용하지 않을 때 호출. */\n async destroy(): Promise<void> {\n // onnxruntime-node InferenceSession 은 release() 없음 (GC 의존).\n try {\n this.pdfium.destroy()\n } catch {\n // ignore\n }\n }\n\n /**\n * PDF 버퍼를 열어 페이지별 수식 영역을 인식한다.\n * 실패한 페이지는 skip (에러 전파 없음 — 로그만).\n *\n * @param pageFilter null 이면 전체 페이지. Set 이면 1-based 페이지 번호 일치만.\n */\n async runOnBuffer(\n buffer: ArrayBuffer | Uint8Array,\n pageFilter: Set<number> | null = null,\n onPageProgress?: (pageNumber: number, total: number) => void,\n ): Promise<PageFormulaResult[]> {\n const view = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer)\n const doc: PDFiumDocument = await this.pdfium.loadDocument(view)\n try {\n const pages: PageFormulaResult[] = []\n let pageIdx = 0\n for (const page of doc.pages()) {\n pageIdx++\n if (pageFilter && !pageFilter.has(page.number)) continue\n\n onPageProgress?.(page.number, doc.getPageCount())\n\n try {\n const result = await withTimeout(\n this.processPage(page.number, page),\n this.opts.pageTimeoutMs,\n `formula page ${page.number} timed out after ${this.opts.pageTimeoutMs}ms`,\n )\n if (result) pages.push(result)\n } catch (e) {\n // 페이지 단위 실패는 조용히 넘어간다 — 전체 PDF 파싱 실패 방지.\n process.stderr.write(\n `[kordoc-formula] page ${page.number} skipped: ${(e as Error).message}\\n`,\n )\n }\n }\n return pages\n } finally {\n doc.destroy()\n }\n }\n\n private async processPage(\n pageNumber: number,\n page: import(\"@hyzyla/pdfium\").PDFiumPage,\n ): Promise<PageFormulaResult | null> {\n const { originalWidth: pdfWidth, originalHeight: pdfHeight } = page.getOriginalSize()\n\n const sharpCtor = this.sharp\n const rendered = await page.render({\n scale: this.opts.scale,\n render: async ({ data, width, height }) => {\n // 임시로 PNG 로 인코딩하지 않고 raw 그대로 반환 — 바로 쓸 거라서.\n return data\n },\n })\n // rendered.data 는 BGRA — sharp 로 RGBA 로 변환\n const { data: bgra, width: rw, height: rh } = rendered\n const rgba = bgraToRgba(bgra)\n\n const pageFrame: PixelFrame = { width: rw, height: rh, data: rgba }\n\n // 1) 수식 영역 검출\n const regions0 = await detectFormulaRegions(this.mfd, pageFrame, this.ort)\n if (regions0.length === 0) {\n return { pageNumber, renderedWidth: rw, renderedHeight: rh, pdfWidth, pdfHeight, regions: [] }\n }\n\n const capped = regions0.slice(0, this.opts.maxRegionsPerPage)\n const regions: FormulaRegion[] = []\n\n // 2) 각 영역 crop → 인식\n for (const r of capped) {\n const x1 = Math.floor(Math.max(0, r.bbox.x1))\n const y1 = Math.floor(Math.max(0, r.bbox.y1))\n const x2 = Math.ceil(Math.min(rw, r.bbox.x2))\n const y2 = Math.ceil(Math.min(rh, r.bbox.y2))\n const cw = x2 - x1\n const ch = y2 - y1\n if (cw < 4 || ch < 4) continue\n\n // sharp 로 raw RGBA 에서 crop → raw RGBA 반환\n const cropRgba = await sharpCtor(rgba, {\n raw: { width: rw, height: rh, channels: 4 },\n })\n .extract({ left: x1, top: y1, width: cw, height: ch })\n .raw()\n .toBuffer()\n\n const cropFrame: PixelFrame = { width: cw, height: ch, data: new Uint8Array(cropRgba) }\n\n let latex = \"\"\n try {\n latex = await recognizeFormula(\n {\n encoder: this.encoder,\n decoder: this.decoder,\n tokenizer: this.tokenizer,\n ort: this.ort,\n },\n cropFrame,\n )\n } catch (e) {\n process.stderr.write(\n `[kordoc-formula] recognize failed at page ${pageNumber} ${JSON.stringify(r.bbox)}: ${(e as Error).message}\\n`,\n )\n latex = \"\"\n }\n\n regions.push({ ...r, latex })\n }\n\n return {\n pageNumber,\n renderedWidth: rw,\n renderedHeight: rh,\n pdfWidth,\n pdfHeight,\n regions,\n }\n }\n}\n\nasync function tryImport<T>(name: string, loader: () => Promise<T>): Promise<T> {\n try {\n return await loader()\n } catch (e) {\n throw new Error(\n `수식 OCR 을 사용하려면 optional dependency '${name}' 이 필요합니다. ` +\n `\\`npm install ${name}\\` 후 다시 실행하세요. 원인: ${(e as Error).message}`,\n )\n }\n}\n\nasync function withTimeout<T>(promise: Promise<T>, ms: number, msg: string): Promise<T> {\n let timer: NodeJS.Timeout | undefined\n try {\n return await Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error(msg)), ms)\n }),\n ])\n } finally {\n if (timer) clearTimeout(timer)\n }\n}\n\n/** pdfium 의 BGRA 버퍼를 RGBA 로 변환 (Red ↔ Blue 스왑). */\nfunction bgraToRgba(bgra: Uint8Array): Uint8Array {\n const out = new Uint8Array(bgra.length)\n for (let i = 0; i < bgra.length; i += 4) {\n out[i] = bgra[i + 2] // R ← B\n out[i + 1] = bgra[i + 1] // G ← G\n out[i + 2] = bgra[i] // B ← R\n out[i + 3] = bgra[i + 3] // A ← A\n }\n return out\n}\n"]}
@@ -1150,4 +1150,4 @@ export {
1150
1150
  getFormulaModelsDir,
1151
1151
  postProcessLatex
1152
1152
  };
1153
- //# sourceMappingURL=formula-KOQV353G.js.map
1153
+ //# sourceMappingURL=formula-JCNF43NE.js.map