pagyra-js 0.0.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 (540) hide show
  1. package/.eslintrc.json +30 -0
  2. package/CHANGELOG.md +13 -0
  3. package/README.md +275 -0
  4. package/UA_Styles_Chromium.md +93 -0
  5. package/_ext/woff2_conversion/brotli-decode.d.ts +139 -0
  6. package/_ext/woff2_conversion/brotli-encode.d.ts +129 -0
  7. package/_ext/woff2_conversion/brotli-port.d.ts +12 -0
  8. package/_ext/woff2_conversion/brotli-shared-dictionary.d.ts +25 -0
  9. package/_ext/woff2_conversion/brotli-types.d.ts +15 -0
  10. package/_ext/woff2_conversion/woff2-common.d.ts +37 -0
  11. package/_ext/woff2_conversion/woff2-decode.d.ts +32 -0
  12. package/_ext/woff2_conversion/woff2-encode.d.ts +31 -0
  13. package/_ext/woff2_conversion/woff2-output.d.ts +39 -0
  14. package/_ext/woff2_original_cpp/brotli/brotli.c +1559 -0
  15. package/_ext/woff2_original_cpp/brotli/brotli.md +116 -0
  16. package/_ext/woff2_original_cpp/brotli/decode.h +409 -0
  17. package/_ext/woff2_original_cpp/brotli/encode.h +505 -0
  18. package/_ext/woff2_original_cpp/brotli/port.h +302 -0
  19. package/_ext/woff2_original_cpp/brotli/shared_dictionary.h +100 -0
  20. package/_ext/woff2_original_cpp/brotli/types.h +83 -0
  21. package/_ext/woff2_original_cpp/cmake/FindBrotliDec.cmake +35 -0
  22. package/_ext/woff2_original_cpp/cmake/FindBrotliEnc.cmake +35 -0
  23. package/_ext/woff2_original_cpp/include/woff2/decode.h +36 -0
  24. package/_ext/woff2_original_cpp/include/woff2/encode.h +43 -0
  25. package/_ext/woff2_original_cpp/include/woff2/output.h +86 -0
  26. package/_ext/woff2_original_cpp/src/buffer.h +164 -0
  27. package/_ext/woff2_original_cpp/src/convert_woff2ttf_fuzzer.cc +13 -0
  28. package/_ext/woff2_original_cpp/src/convert_woff2ttf_fuzzer_new_entry.cc +12 -0
  29. package/_ext/woff2_original_cpp/src/file.h +30 -0
  30. package/_ext/woff2_original_cpp/src/font.cc +400 -0
  31. package/_ext/woff2_original_cpp/src/font.h +105 -0
  32. package/_ext/woff2_original_cpp/src/glyph.cc +383 -0
  33. package/_ext/woff2_original_cpp/src/glyph.h +71 -0
  34. package/_ext/woff2_original_cpp/src/normalize.cc +314 -0
  35. package/_ext/woff2_original_cpp/src/normalize.h +39 -0
  36. package/_ext/woff2_original_cpp/src/port.h +66 -0
  37. package/_ext/woff2_original_cpp/src/round.h +27 -0
  38. package/_ext/woff2_original_cpp/src/store_bytes.h +55 -0
  39. package/_ext/woff2_original_cpp/src/table_tags.cc +82 -0
  40. package/_ext/woff2_original_cpp/src/table_tags.h +30 -0
  41. package/_ext/woff2_original_cpp/src/transform.cc +430 -0
  42. package/_ext/woff2_original_cpp/src/transform.h +26 -0
  43. package/_ext/woff2_original_cpp/src/variable_length.cc +129 -0
  44. package/_ext/woff2_original_cpp/src/variable_length.h +30 -0
  45. package/_ext/woff2_original_cpp/src/woff2_common.cc +50 -0
  46. package/_ext/woff2_original_cpp/src/woff2_common.h +64 -0
  47. package/_ext/woff2_original_cpp/src/woff2_compress.cc +43 -0
  48. package/_ext/woff2_original_cpp/src/woff2_dec.cc +1398 -0
  49. package/_ext/woff2_original_cpp/src/woff2_decompress.cc +41 -0
  50. package/_ext/woff2_original_cpp/src/woff2_enc.cc +458 -0
  51. package/_ext/woff2_original_cpp/src/woff2_info.cc +142 -0
  52. package/_ext/woff2_original_cpp/src/woff2_out.cc +63 -0
  53. package/assets/fonts/ttf/arimo/Arimo-Bold.ttf +0 -0
  54. package/assets/fonts/ttf/arimo/Arimo-BoldItalic.ttf +0 -0
  55. package/assets/fonts/ttf/arimo/Arimo-Italic.ttf +0 -0
  56. package/assets/fonts/ttf/arimo/Arimo-Regular.ttf +0 -0
  57. package/assets/fonts/ttf/cinzeldecorative/CinzelDecorative-Black.ttf +0 -0
  58. package/assets/fonts/ttf/cinzeldecorative/CinzelDecorative-Bold.ttf +0 -0
  59. package/assets/fonts/ttf/cinzeldecorative/CinzelDecorative-Regular.ttf +0 -0
  60. package/assets/fonts/ttf/dejavu/DejaVuSans.ttf +0 -0
  61. package/assets/fonts/ttf/firecode/FiraCode-Bold.ttf +0 -0
  62. package/assets/fonts/ttf/firecode/FiraCode-Light.ttf +0 -0
  63. package/assets/fonts/ttf/firecode/FiraCode-Medium.ttf +0 -0
  64. package/assets/fonts/ttf/firecode/FiraCode-Regular.ttf +0 -0
  65. package/assets/fonts/ttf/firecode/FiraCode-SemiBold.ttf +0 -0
  66. package/assets/fonts/ttf/notoemoji/NotoEmoji-Bold.ttf +0 -0
  67. package/assets/fonts/ttf/notoemoji/NotoEmoji-Light.ttf +0 -0
  68. package/assets/fonts/ttf/notoemoji/NotoEmoji-Medium.ttf +0 -0
  69. package/assets/fonts/ttf/notoemoji/NotoEmoji-Regular.ttf +0 -0
  70. package/assets/fonts/ttf/notoemoji/NotoEmoji-SemiBold.ttf +0 -0
  71. package/assets/fonts/ttf/notosans/NotoSans-Regular.ttf +0 -0
  72. package/assets/fonts/ttf/roboto/Roboto-Bold.ttf +0 -0
  73. package/assets/fonts/ttf/roboto/Roboto-BoldItalic.ttf +0 -0
  74. package/assets/fonts/ttf/roboto/Roboto-Italic.ttf +0 -0
  75. package/assets/fonts/ttf/roboto/Roboto-Regular.ttf +0 -0
  76. package/assets/fonts/ttf/stixtwomath/STIXTwoMath-Regular.ttf +0 -0
  77. package/assets/fonts/ttf/tinos/Tinos-Bold.ttf +0 -0
  78. package/assets/fonts/ttf/tinos/Tinos-BoldItalic.ttf +0 -0
  79. package/assets/fonts/ttf/tinos/Tinos-Italic.ttf +0 -0
  80. package/assets/fonts/ttf/tinos/Tinos-Regular.ttf +0 -0
  81. package/assets/fonts/woff/lato/lato-latin-400-italic.woff +0 -0
  82. package/assets/fonts/woff/lato/lato-latin-400-normal.woff +0 -0
  83. package/assets/fonts/woff/lato/lato-latin-700-italic.woff +0 -0
  84. package/assets/fonts/woff/lato/lato-latin-700-normal.woff +0 -0
  85. package/assets/fonts/woff2/caveat/Caveat-Bold.woff2 +0 -0
  86. package/assets/fonts/woff2/caveat/Caveat-Regular.woff2 +0 -0
  87. package/assets/fonts/woff2/lato/lato-latin-400-italic.woff2 +0 -0
  88. package/assets/fonts/woff2/lato/lato-latin-400-normal.woff2 +0 -0
  89. package/assets/fonts/woff2/lato/lato-latin-700-italic.woff2 +0 -0
  90. package/assets/fonts/woff2/lato/lato-latin-700-normal.woff2 +0 -0
  91. package/docs/AGENTS.md +288 -0
  92. package/docs/BACKGROUND-REPEAT-IMPLEMENTATION.md +127 -0
  93. package/docs/BACKGROUND-REPEAT-REFERENCE.md +127 -0
  94. package/docs/BACKGROUND-REPEAT-SPACE-ROUND.md +164 -0
  95. package/docs/css-properties-support.md +256 -0
  96. package/docs/src_modules_table.md +172 -0
  97. package/docs/text-overlap-fix.md +85 -0
  98. package/docs/text-overlap-investigation.md +27 -0
  99. package/eslint.config.js +36 -0
  100. package/glyph_measure.htm +1458 -0
  101. package/package.json +50 -0
  102. package/playground/browser-entry.ts +2 -0
  103. package/playground/exports/background-text-debug.pdf +0 -0
  104. package/playground/public/app.js +875 -0
  105. package/playground/public/assets/1.webp +0 -0
  106. package/playground/public/examples/accents-test.html +24 -0
  107. package/playground/public/examples/advanced-selectors-demo.html +118 -0
  108. package/playground/public/examples/background-advanced-showcase.html +82 -0
  109. package/playground/public/examples/background-clip-text.html +36 -0
  110. package/playground/public/examples/background-origin-showcase.html +137 -0
  111. package/playground/public/examples/background-position-showcase.html +83 -0
  112. package/playground/public/examples/background-repeat-showcase.html +83 -0
  113. package/playground/public/examples/background-repeat-space-round.html +348 -0
  114. package/playground/public/examples/background-size-showcase.html +82 -0
  115. package/playground/public/examples/background-text-debug.html +18 -0
  116. package/playground/public/examples/baseline-test.html +24 -0
  117. package/playground/public/examples/bold-showcase.html +150 -0
  118. package/playground/public/examples/bold-strike-example.html +12 -0
  119. package/playground/public/examples/border-collapse-test.html +23 -0
  120. package/playground/public/examples/centered-shadow-div.html +72 -0
  121. package/playground/public/examples/css-variables.html +50 -0
  122. package/playground/public/examples/debug-accents.html +11 -0
  123. package/playground/public/examples/debug-text-overlap.html +46 -0
  124. package/playground/public/examples/flex-gap-column.html +130 -0
  125. package/playground/public/examples/flex-gap-row.html +137 -0
  126. package/playground/public/examples/flex-padding-test.html +29 -0
  127. package/playground/public/examples/flexbox-text-test.html +193 -0
  128. package/playground/public/examples/fonts-demo.html +126 -0
  129. package/playground/public/examples/footer-example.html +4 -0
  130. package/playground/public/examples/gradient-text.html +54 -0
  131. package/playground/public/examples/grid-gap-demo.html +156 -0
  132. package/playground/public/examples/header-example.html +4 -0
  133. package/playground/public/examples/header-footer-example.html +27 -0
  134. package/playground/public/examples/image-showcase.html +33 -0
  135. package/playground/public/examples/justify-text.html +22 -0
  136. package/playground/public/examples/linear-gradient-example.html +38 -0
  137. package/playground/public/examples/lorem-span.html +14 -0
  138. package/playground/public/examples/margin-block-showcase.html +21 -0
  139. package/playground/public/examples/margin-inline-showcase.html +21 -0
  140. package/playground/public/examples/monthly-summary.html +95 -0
  141. package/playground/public/examples/multi-page-lorem.html +190 -0
  142. package/playground/public/examples/opacity-debug.html +39 -0
  143. package/playground/public/examples/opacity-example.html +70 -0
  144. package/playground/public/examples/png-image-example.html +13 -0
  145. package/playground/public/examples/red-rectangle.html +18 -0
  146. package/playground/public/examples/repro.html +24 -0
  147. package/playground/public/examples/rounded-borders-test.html +24 -0
  148. package/playground/public/examples/simple-list.html +89 -0
  149. package/playground/public/examples/simple-svg.html +37 -0
  150. package/playground/public/examples/simple-table.html +52 -0
  151. package/playground/public/examples/skew-div.html +138 -0
  152. package/playground/public/examples/skew-text.html +21 -0
  153. package/playground/public/examples/starter-report.css +51 -0
  154. package/playground/public/examples/starter-report.html +23 -0
  155. package/playground/public/examples/svg-aspect-ratio-showcase.html +116 -0
  156. package/playground/public/examples/svg-gradients-linear.html +28 -0
  157. package/playground/public/examples/svg-gradients-radial.html +29 -0
  158. package/playground/public/examples/svg-gradients-showcase.html +66 -0
  159. package/playground/public/examples/svg-image-path-test.html +43 -0
  160. package/playground/public/examples/svg-images-clipping.html +27 -0
  161. package/playground/public/examples/svg-path-gallery.html +118 -0
  162. package/playground/public/examples/svg-radial-transform-demo.html +78 -0
  163. package/playground/public/examples/svg-transform-stack.html +103 -0
  164. package/playground/public/examples/svg-transforms-demo.html +127 -0
  165. package/playground/public/examples/table-merge-test.html +34 -0
  166. package/playground/public/examples/text-decoration-showcase.html +138 -0
  167. package/playground/public/examples/text-indent-showcase.html +137 -0
  168. package/playground/public/examples/text-shadow-example.html +29 -0
  169. package/playground/public/examples/very-complex-css.html +293 -0
  170. package/playground/public/examples/webp-example.html +13 -0
  171. package/playground/public/examples/z-index-demo.html +93 -0
  172. package/playground/public/examples.json +240 -0
  173. package/playground/public/images/dice.png +0 -0
  174. package/playground/public/images/duck.jpg +0 -0
  175. package/playground/public/index.html +149 -0
  176. package/playground/public/mode.js +1 -0
  177. package/playground/public/styles.css +382 -0
  178. package/playground/public/tmp-h2-debug.html +33 -0
  179. package/playground/public/tmp-italic-debug.html +32 -0
  180. package/playground/public/vendor/codemirror/codemirror.min.css +1 -0
  181. package/playground/public/vendor/codemirror/codemirror.min.js +1 -0
  182. package/playground/public/vendor/codemirror/css.min.js +1 -0
  183. package/playground/public/vendor/codemirror/darcula.min.css +1 -0
  184. package/playground/public/vendor/codemirror/htmlmixed.min.js +1 -0
  185. package/playground/public/vendor/codemirror/javascript.min.js +1 -0
  186. package/playground/public/vendor/codemirror/xml.min.js +1 -0
  187. package/playground/public/vendor/pagyra-playground-browser.js +165966 -0
  188. package/playground/public/vendor/pagyra-playground-browser.js.map +7 -0
  189. package/playground/server.d.ts +1 -0
  190. package/playground/server.js +68 -0
  191. package/playground/server.ts +128 -0
  192. package/scripts/browser-build.ts +101 -0
  193. package/scripts/build-browser-bundle.ts +52 -0
  194. package/scripts/glyph-comparison/simulate.ts +744 -0
  195. package/scripts/playground-browser-server.ts +57 -0
  196. package/scripts/probe-roboto.ts +6 -0
  197. package/scripts/render-playground-example.ts +121 -0
  198. package/scripts/run-glyph-atlas-tuner-runner.mjs +113 -0
  199. package/scripts/run-glyph-atlas-tuner.ts +141 -0
  200. package/scripts/top-ts-files.ps1 +39 -0
  201. package/scripts/top-ts-files.sh +37 -0
  202. package/scripts/woff2_info.ps1 +132 -0
  203. package/src/browser-entry.ts +14 -0
  204. package/src/compression/adler32.ts +45 -0
  205. package/src/compression/brotli/brotli.ts +463 -0
  206. package/src/compression/brotli/index.ts +15 -0
  207. package/src/compression/brotli/transform.ts +184 -0
  208. package/src/compression/brotli/types.ts +58 -0
  209. package/src/compression/brotli/utils.ts +157 -0
  210. package/src/compression/brotli/vendor/bit_reader.js +124 -0
  211. package/src/compression/brotli/vendor/context.js +250 -0
  212. package/src/compression/brotli/vendor/decode.d.ts +2 -0
  213. package/src/compression/brotli/vendor/decode.js +938 -0
  214. package/src/compression/brotli/vendor/dictionary-data.js +9469 -0
  215. package/src/compression/brotli/vendor/dictionary.js +36 -0
  216. package/src/compression/brotli/vendor/huffman.js +123 -0
  217. package/src/compression/brotli/vendor/package.json +3 -0
  218. package/src/compression/brotli/vendor/prefix.js +60 -0
  219. package/src/compression/brotli/vendor/streams.js +31 -0
  220. package/src/compression/brotli/vendor/transform.js +247 -0
  221. package/src/compression/brotli/vendor-decode.d.ts +4 -0
  222. package/src/compression/brotli/woff2-glyf-transform.ts +623 -0
  223. package/src/compression/decompress.ts +16 -0
  224. package/src/compression/deflate.ts +295 -0
  225. package/src/compression/index.ts +4 -0
  226. package/src/compression/types.ts +26 -0
  227. package/src/compression/utils.ts +107 -0
  228. package/src/core.ts +18 -0
  229. package/src/css/apply-declarations.ts +86 -0
  230. package/src/css/background-types.ts +65 -0
  231. package/src/css/browser-defaults.ts +16 -0
  232. package/src/css/clip-path-types.ts +13 -0
  233. package/src/css/compute-style.ts +494 -0
  234. package/src/css/css-unit-resolver.ts +65 -0
  235. package/src/css/custom-properties.ts +215 -0
  236. package/src/css/enums.ts +127 -0
  237. package/src/css/font-face-parser.ts +233 -0
  238. package/src/css/font-weight.ts +65 -0
  239. package/src/css/inline-style-parser.ts +27 -0
  240. package/src/css/layout-property-resolver.ts +75 -0
  241. package/src/css/length.ts +141 -0
  242. package/src/css/line-height.ts +96 -0
  243. package/src/css/named-colors.ts +150 -0
  244. package/src/css/parsers/background-parser-extended.ts +111 -0
  245. package/src/css/parsers/background-parser.ts +456 -0
  246. package/src/css/parsers/border-block-parser.ts +26 -0
  247. package/src/css/parsers/border-inline-parser.ts +26 -0
  248. package/src/css/parsers/border-parser-extended.ts +256 -0
  249. package/src/css/parsers/border-parser.ts +175 -0
  250. package/src/css/parsers/box-shadow-parser.ts +106 -0
  251. package/src/css/parsers/clip-path-parser.ts +92 -0
  252. package/src/css/parsers/color-parser.ts +14 -0
  253. package/src/css/parsers/dimension-parser.ts +117 -0
  254. package/src/css/parsers/display-flex-parser.ts +59 -0
  255. package/src/css/parsers/flex-parser.ts +144 -0
  256. package/src/css/parsers/font-parser.ts +40 -0
  257. package/src/css/parsers/gradient-parser.ts +366 -0
  258. package/src/css/parsers/grid-parser-extended.ts +55 -0
  259. package/src/css/parsers/grid-parser.ts +218 -0
  260. package/src/css/parsers/length-parser.ts +95 -0
  261. package/src/css/parsers/list-style-parser.ts +39 -0
  262. package/src/css/parsers/margin-block-parser.ts +12 -0
  263. package/src/css/parsers/margin-inline-parser.ts +12 -0
  264. package/src/css/parsers/margin-parser.ts +30 -0
  265. package/src/css/parsers/opacity-parser.ts +32 -0
  266. package/src/css/parsers/overflow-wrap-parser.ts +38 -0
  267. package/src/css/parsers/padding-block-parser.ts +12 -0
  268. package/src/css/parsers/padding-inline-parser.ts +12 -0
  269. package/src/css/parsers/padding-parser.ts +30 -0
  270. package/src/css/parsers/position-parser.ts +75 -0
  271. package/src/css/parsers/register-parsers.ts +302 -0
  272. package/src/css/parsers/registry.ts +18 -0
  273. package/src/css/parsers/text-parser-extended.ts +144 -0
  274. package/src/css/parsers/text-parser.ts +25 -0
  275. package/src/css/parsers/text-shadow-parser.ts +94 -0
  276. package/src/css/properties/box-model.ts +82 -0
  277. package/src/css/properties/flexbox.ts +44 -0
  278. package/src/css/properties/gap.ts +14 -0
  279. package/src/css/properties/grid.ts +94 -0
  280. package/src/css/properties/layout.ts +59 -0
  281. package/src/css/properties/misc.ts +44 -0
  282. package/src/css/properties/typography.ts +71 -0
  283. package/src/css/properties/visual.ts +68 -0
  284. package/src/css/selectors/matcher.ts +219 -0
  285. package/src/css/selectors/parser.ts +163 -0
  286. package/src/css/selectors/simple-key.ts +31 -0
  287. package/src/css/selectors/specificity.ts +41 -0
  288. package/src/css/selectors/types.ts +31 -0
  289. package/src/css/shorthands/border-shorthand.ts +68 -0
  290. package/src/css/shorthands/box-shorthand.ts +33 -0
  291. package/src/css/style-inheritance.ts +50 -0
  292. package/src/css/style.ts +402 -0
  293. package/src/css/ua-defaults/base-defaults.ts +266 -0
  294. package/src/css/ua-defaults/browser-defaults.ts +134 -0
  295. package/src/css/ua-defaults/element-defaults.ts +374 -0
  296. package/src/css/ua-defaults/types.ts +43 -0
  297. package/src/css/unit-conversion.ts +24 -0
  298. package/src/css/utils.ts +108 -0
  299. package/src/css/viewport.ts +17 -0
  300. package/src/debug/audit.ts +20 -0
  301. package/src/debug/ids.ts +13 -0
  302. package/src/debug/log.js +28 -0
  303. package/src/debug/log.ts +52 -0
  304. package/src/debug/tree.ts +57 -0
  305. package/src/dom/node.ts +133 -0
  306. package/src/environment/browser-environment.ts +78 -0
  307. package/src/environment/environment.ts +35 -0
  308. package/src/environment/global.ts +13 -0
  309. package/src/environment/node-environment.browser.ts +28 -0
  310. package/src/environment/node-environment.ts +64 -0
  311. package/src/fonts/detector.ts +28 -0
  312. package/src/fonts/engines/ttf-engine.ts +28 -0
  313. package/src/fonts/engines/woff-engine.ts +38 -0
  314. package/src/fonts/engines/woff2-engine.ts +41 -0
  315. package/src/fonts/extractors/metrics-extractor.ts +362 -0
  316. package/src/fonts/font-registry-resolver.ts +132 -0
  317. package/src/fonts/index.ts +3 -0
  318. package/src/fonts/orchestrator.ts +92 -0
  319. package/src/fonts/parsers/base-parser.ts +23 -0
  320. package/src/fonts/types.ts +85 -0
  321. package/src/fonts/utils/ttf-reconstructor.ts +120 -0
  322. package/src/fonts/woff/decoder.ts +105 -0
  323. package/src/fonts/woff2/buffer.ts +106 -0
  324. package/src/fonts/woff2/decoder.ts +981 -0
  325. package/src/geometry/box.ts +48 -0
  326. package/src/geometry/matrix.ts +59 -0
  327. package/src/html/css/parse-css.ts +85 -0
  328. package/src/html/dom-converter.ts +433 -0
  329. package/src/html/image-converter.ts +200 -0
  330. package/src/html-to-pdf.ts +410 -0
  331. package/src/image/base-decoder.ts +149 -0
  332. package/src/image/image-service.ts +188 -0
  333. package/src/image/jpeg-decoder.ts +73 -0
  334. package/src/image/png-decoder.ts +550 -0
  335. package/src/image/types.ts +20 -0
  336. package/src/image/webp-decoder.ts +242 -0
  337. package/src/image/webp-huffman.ts +218 -0
  338. package/src/image/webp-riff-parser.ts +54 -0
  339. package/src/image/webp-vp8l-decoder.ts +199 -0
  340. package/src/index.ts +35 -0
  341. package/src/layout/context/float-context.ts +62 -0
  342. package/src/layout/context/layout-environment.ts +29 -0
  343. package/src/layout/debug.ts +18 -0
  344. package/src/layout/inline/bounding-box-calculator.ts +132 -0
  345. package/src/layout/inline/font-baseline-calculator.ts +76 -0
  346. package/src/layout/inline/inline-utils.ts +94 -0
  347. package/src/layout/inline/layout.ts +285 -0
  348. package/src/layout/inline/line_breaker.ts +109 -0
  349. package/src/layout/inline/measurement.ts +144 -0
  350. package/src/layout/inline/run-placer.ts +139 -0
  351. package/src/layout/inline/text-alignment.ts +70 -0
  352. package/src/layout/inline/tokenizer.ts +195 -0
  353. package/src/layout/inline/types.ts +76 -0
  354. package/src/layout/pipeline/context-factory.ts +16 -0
  355. package/src/layout/pipeline/default-engine.ts +24 -0
  356. package/src/layout/pipeline/engine.ts +59 -0
  357. package/src/layout/pipeline/layout-tree.ts +13 -0
  358. package/src/layout/pipeline/out-of-flow-manager.ts +73 -0
  359. package/src/layout/pipeline/strategy.ts +12 -0
  360. package/src/layout/pipeline/text-metrics-initializer.ts +13 -0
  361. package/src/layout/strategies/block.ts +236 -0
  362. package/src/layout/strategies/display-none.ts +14 -0
  363. package/src/layout/strategies/fallback.ts +15 -0
  364. package/src/layout/strategies/flex.ts +477 -0
  365. package/src/layout/strategies/fragmentation.ts +17 -0
  366. package/src/layout/strategies/grid.ts +247 -0
  367. package/src/layout/strategies/image.ts +342 -0
  368. package/src/layout/strategies/inline.ts +128 -0
  369. package/src/layout/strategies/table.ts +595 -0
  370. package/src/layout/table/cell_layout.ts +31 -0
  371. package/src/layout/table/diagnostics.ts +19 -0
  372. package/src/layout/text-run.ts +42 -0
  373. package/src/layout/utils/content-measurer.ts +117 -0
  374. package/src/layout/utils/display-utils.ts +24 -0
  375. package/src/layout/utils/floats.ts +98 -0
  376. package/src/layout/utils/gap-calculator.ts +167 -0
  377. package/src/layout/utils/inline-formatter.ts +31 -0
  378. package/src/layout/utils/inline-formatting.ts +9 -0
  379. package/src/layout/utils/margin.ts +140 -0
  380. package/src/layout/utils/node-math.ts +237 -0
  381. package/src/layout/utils/overflow.ts +14 -0
  382. package/src/layout/utils/sizing.ts +12 -0
  383. package/src/layout/utils/text-metrics.ts +361 -0
  384. package/src/logging/debug.ts +58 -0
  385. package/src/pdf/font/base14/widths-courier-bold.ts +159 -0
  386. package/src/pdf/font/base14/widths-courier.ts +159 -0
  387. package/src/pdf/font/base14/widths-helvetica-bold.ts +158 -0
  388. package/src/pdf/font/base14/widths-helvetica.ts +158 -0
  389. package/src/pdf/font/base14/widths-times-bold.ts +158 -0
  390. package/src/pdf/font/base14/widths-times-roman.ts +158 -0
  391. package/src/pdf/font/base14/widths-types.ts +25 -0
  392. package/src/pdf/font/base14-widths.ts +32 -0
  393. package/src/pdf/font/blur.ts +81 -0
  394. package/src/pdf/font/builtin-fonts.browser.ts +262 -0
  395. package/src/pdf/font/builtin-fonts.ts +126 -0
  396. package/src/pdf/font/composite-glyph-parser.ts +242 -0
  397. package/src/pdf/font/embedder.ts +395 -0
  398. package/src/pdf/font/font-config.ts +190 -0
  399. package/src/pdf/font/font-registry.ts +263 -0
  400. package/src/pdf/font/font-subset.ts +258 -0
  401. package/src/pdf/font/glyph-atlas-maxrects.ts +305 -0
  402. package/src/pdf/font/glyph-atlas-tuner.ts +98 -0
  403. package/src/pdf/font/glyph-atlas.ts +226 -0
  404. package/src/pdf/font/glyph-cache.ts +127 -0
  405. package/src/pdf/font/loca-reader.ts +109 -0
  406. package/src/pdf/font/managers/font-resource-manager.ts +73 -0
  407. package/src/pdf/font/managers/subset-resource-manager.ts +164 -0
  408. package/src/pdf/font/rasterizer.ts +270 -0
  409. package/src/pdf/font/resolvers/base-font-mapper.ts +77 -0
  410. package/src/pdf/font/resolvers/family-resolver.ts +33 -0
  411. package/src/pdf/font/resolvers/weight-style-applicator.ts +63 -0
  412. package/src/pdf/font/simple-glyph-parser.ts +289 -0
  413. package/src/pdf/font/to-unicode.ts +109 -0
  414. package/src/pdf/font/transformation-matrix.ts +136 -0
  415. package/src/pdf/font/ttf-cmap.ts +180 -0
  416. package/src/pdf/font/ttf-global-metrics.ts +58 -0
  417. package/src/pdf/font/ttf-glyf.ts +125 -0
  418. package/src/pdf/font/ttf-glyph-metrics.ts +43 -0
  419. package/src/pdf/font/ttf-lite.ts +269 -0
  420. package/src/pdf/font/ttf-table-parser.ts +132 -0
  421. package/src/pdf/font/ttf-table-provider.ts +61 -0
  422. package/src/pdf/font/widths.ts +79 -0
  423. package/src/pdf/font-subset/font-registry.ts +127 -0
  424. package/src/pdf/header-footer-layout.ts +153 -0
  425. package/src/pdf/header-footer-painter.ts +209 -0
  426. package/src/pdf/header-footer-renderer.ts +357 -0
  427. package/src/pdf/header-footer-tokens.ts +55 -0
  428. package/src/pdf/header-footer.ts +25 -0
  429. package/src/pdf/layout-tree-builder.ts +261 -0
  430. package/src/pdf/page-painter.ts +241 -0
  431. package/src/pdf/pagination.ts +155 -0
  432. package/src/pdf/primitives/pdf-builder.ts +378 -0
  433. package/src/pdf/primitives/pdf-bytes.ts +40 -0
  434. package/src/pdf/primitives/pdf-document.ts +108 -0
  435. package/src/pdf/primitives/pdf-reference-manager.ts +47 -0
  436. package/src/pdf/primitives/pdf-resource-registries.ts +255 -0
  437. package/src/pdf/primitives/pdf-serializers.ts +194 -0
  438. package/src/pdf/primitives/pdf-types.ts +73 -0
  439. package/src/pdf/render.ts +210 -0
  440. package/src/pdf/renderer/box-painter.ts +236 -0
  441. package/src/pdf/renderer/page-paint.ts +102 -0
  442. package/src/pdf/renderer/paint-box-shadows.ts +218 -0
  443. package/src/pdf/renderer/radius.ts +58 -0
  444. package/src/pdf/renderers/graphics-state-manager.ts +40 -0
  445. package/src/pdf/renderers/image-renderer.ts +127 -0
  446. package/src/pdf/renderers/radius-utils.ts +80 -0
  447. package/src/pdf/renderers/rectangle-renderer.ts +129 -0
  448. package/src/pdf/renderers/rounded-rect-path.ts +120 -0
  449. package/src/pdf/renderers/shape-renderer.ts +563 -0
  450. package/src/pdf/renderers/shape-utils.ts +194 -0
  451. package/src/pdf/renderers/text-decoration-renderer.ts +313 -0
  452. package/src/pdf/renderers/text-encoder.ts +41 -0
  453. package/src/pdf/renderers/text-font-resolver.ts +75 -0
  454. package/src/pdf/renderers/text-renderer-utils.ts +28 -0
  455. package/src/pdf/renderers/text-renderer.ts +391 -0
  456. package/src/pdf/renderers/text-shadow-renderer.ts +300 -0
  457. package/src/pdf/shading/gradient-service.ts +525 -0
  458. package/src/pdf/shading/index.ts +1 -0
  459. package/src/pdf/stacking/build-stacking-contexts.ts +93 -0
  460. package/src/pdf/stacking/resolve-paint-order.ts +157 -0
  461. package/src/pdf/stacking/types.ts +40 -0
  462. package/src/pdf/svg/aspect-ratio.ts +81 -0
  463. package/src/pdf/svg/coordinate-mapper.ts +81 -0
  464. package/src/pdf/svg/geometry-builder.ts +45 -0
  465. package/src/pdf/svg/render-svg.ts +296 -0
  466. package/src/pdf/svg/shape-renderer.ts +463 -0
  467. package/src/pdf/svg/style-computer.ts +246 -0
  468. package/src/pdf/transform-adapter.ts +26 -0
  469. package/src/pdf/types.ts +377 -0
  470. package/src/pdf/utils/background-layer-resolver.ts +439 -0
  471. package/src/pdf/utils/background-tiles.ts +192 -0
  472. package/src/pdf/utils/border-dashes.ts +109 -0
  473. package/src/pdf/utils/border-radius-utils.ts +86 -0
  474. package/src/pdf/utils/box-dimensions-utils.ts +47 -0
  475. package/src/pdf/utils/clip-path-resolver.ts +50 -0
  476. package/src/pdf/utils/clipping-path-builder.ts +190 -0
  477. package/src/pdf/utils/color-utils.ts +102 -0
  478. package/src/pdf/utils/coordinate-transformer.ts +30 -0
  479. package/src/pdf/utils/drop-shadow-raster.ts +233 -0
  480. package/src/pdf/utils/encoding.ts +98 -0
  481. package/src/pdf/utils/glyph-atlas-registrar.ts +13 -0
  482. package/src/pdf/utils/glyph-run-renderer.ts +129 -0
  483. package/src/pdf/utils/image-command-partitioner.ts +28 -0
  484. package/src/pdf/utils/image-matrix-builder.ts +31 -0
  485. package/src/pdf/utils/image-utils.ts +26 -0
  486. package/src/pdf/utils/list-utils.ts +194 -0
  487. package/src/pdf/utils/node-text-run-factory.ts +202 -0
  488. package/src/pdf/utils/page-resource-registrar.ts +46 -0
  489. package/src/pdf/utils/result-combiner.ts +102 -0
  490. package/src/pdf/utils/shadow-utils.ts +127 -0
  491. package/src/pdf/utils/text-alignment-resolver.ts +76 -0
  492. package/src/pdf/utils/text-decoration-utils.ts +64 -0
  493. package/src/pdf/utils/text-layout-adjuster.ts +185 -0
  494. package/src/pdf/utils/text-utils.ts +193 -0
  495. package/src/pdf/utils/transform-scope-manager.ts +69 -0
  496. package/src/render/offset.ts +170 -0
  497. package/src/shim/empty.ts +2 -0
  498. package/src/shim/fs-empty.ts +5 -0
  499. package/src/shim/url-empty.ts +7 -0
  500. package/src/shim/zlib-empty.ts +9 -0
  501. package/src/style/shorthands/index.ts +19 -0
  502. package/src/style/ua/defaults.ts +69 -0
  503. package/src/svg/index.ts +4 -0
  504. package/src/svg/parser-registry.ts +71 -0
  505. package/src/svg/parser.ts +486 -0
  506. package/src/svg/path-data.ts +515 -0
  507. package/src/svg/types.ts +194 -0
  508. package/src/text/line-breaker.ts +321 -0
  509. package/src/text/text-transform.ts +43 -0
  510. package/src/text/text.ts +33 -0
  511. package/src/transform/css-parser.ts +95 -0
  512. package/src/types/fonts.ts +62 -0
  513. package/src/types/public.ts +19 -0
  514. package/src/units/page-utils.ts +58 -0
  515. package/src/units/units.ts +50 -0
  516. package/src/utils/base64.ts +24 -0
  517. package/test-output.txt +79 -0
  518. package/tests/css/background-parser.spec.ts +14 -0
  519. package/tests/css/clip-path-parser.spec.ts +66 -0
  520. package/tests/environment/path-resolution.spec.ts +104 -0
  521. package/tests/helpers/ai-layout-diagnostics.ts +141 -0
  522. package/tests/helpers/render-utils.ts +52 -0
  523. package/tests/helpers/text-geometry.ts +56 -0
  524. package/tests/layout/custom-properties.test.ts +38 -0
  525. package/tests/layout/gap-calculator.spec.ts +196 -0
  526. package/tests/layout/inline-background-alignment.spec.ts +93 -0
  527. package/tests/layout/inline-fragments.spec.ts +26 -0
  528. package/tests/layout/run-placer-baseline.spec.ts +108 -0
  529. package/tests/pdf/alignments.spec.ts +26 -0
  530. package/tests/pdf/background-clip.spec.ts +57 -0
  531. package/tests/pdf/background-repeat-space-round.spec.ts +35 -0
  532. package/tests/pdf/background-repeat.spec.ts +137 -0
  533. package/tests/pdf/border-radius.spec.ts +151 -0
  534. package/tests/pdf/clip-path.spec.ts +92 -0
  535. package/tests/pdf/radial-gradient.spec.ts +50 -0
  536. package/tests/pdf/svg-stroke-dash.spec.ts +81 -0
  537. package/tests/pdf/text-transform-matrix.spec.ts +43 -0
  538. package/tsconfig.json +17 -0
  539. package/types/fonts.js +10 -0
  540. package/vitest.config.ts +9 -0
@@ -0,0 +1,1458 @@
1
+ <!DOCTYPE html>
2
+ <html lang="pt-BR">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>Debugger de Parágrafo Justificado v2</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
+ <link href="https://fonts.googleapis.com/css2?family=Arimo:ital,wght@0,400;0,700;1,400;1,700&family=Tinos:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet" />
10
+ <style>
11
+ :root {
12
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
13
+ color-scheme: dark light;
14
+ }
15
+
16
+ body {
17
+ margin: 0;
18
+ padding: 0;
19
+ background: #0f172a;
20
+ color: #e5e7eb;
21
+ min-height: 100vh;
22
+ display: flex;
23
+ flex-direction: column;
24
+ }
25
+
26
+ header {
27
+ padding: 1rem 1.5rem;
28
+ background: #020617;
29
+ border-bottom: 1px solid rgba(148, 163, 184, 0.3);
30
+ display: flex;
31
+ align-items: center;
32
+ justify-content: space-between;
33
+ gap: 1rem;
34
+ flex-wrap: wrap;
35
+ }
36
+
37
+ header h1 {
38
+ margin: 0;
39
+ font-size: 1.1rem;
40
+ font-weight: 600;
41
+ letter-spacing: 0.03em;
42
+ }
43
+
44
+ .header-badges {
45
+ display: flex;
46
+ gap: 0.5rem;
47
+ align-items: center;
48
+ }
49
+
50
+ header span.label {
51
+ font-size: 0.75rem;
52
+ padding: 0.1rem 0.5rem;
53
+ border-radius: 999px;
54
+ border: 1px solid rgba(148, 163, 184, 0.5);
55
+ text-transform: uppercase;
56
+ letter-spacing: 0.08em;
57
+ color: #94a3b8;
58
+ }
59
+
60
+ .zoom-warning {
61
+ font-size: 0.7rem;
62
+ padding: 0.15rem 0.5rem;
63
+ border-radius: 999px;
64
+ background: rgba(251, 191, 36, 0.2);
65
+ border: 1px solid rgba(251, 191, 36, 0.6);
66
+ color: #fbbf24;
67
+ display: none;
68
+ }
69
+
70
+ .zoom-warning.visible {
71
+ display: inline-block;
72
+ }
73
+
74
+ main {
75
+ flex: 1;
76
+ display: grid;
77
+ grid-template-columns: minmax(0, 1.1fr) minmax(0, 1.4fr);
78
+ gap: 1px;
79
+ background: #020617;
80
+ min-height: 0;
81
+ }
82
+
83
+ @media (max-width: 900px) {
84
+ main {
85
+ grid-template-columns: minmax(0, 1fr);
86
+ grid-template-rows: auto auto;
87
+ }
88
+ }
89
+
90
+ section {
91
+ padding: 1rem 1.25rem;
92
+ background: radial-gradient(circle at top left, rgba(148, 163, 184, 0.15), #020617 55%);
93
+ min-height: 0;
94
+ display: flex;
95
+ flex-direction: column;
96
+ gap: 0.75rem;
97
+ }
98
+
99
+ h2 {
100
+ margin: 0;
101
+ font-size: 0.9rem;
102
+ text-transform: uppercase;
103
+ letter-spacing: 0.1em;
104
+ color: #9ca3af;
105
+ }
106
+
107
+ .section-header {
108
+ display: flex;
109
+ align-items: baseline;
110
+ justify-content: space-between;
111
+ gap: 0.5rem;
112
+ }
113
+
114
+ .section-header small {
115
+ font-size: 0.75rem;
116
+ color: #6b7280;
117
+ }
118
+
119
+ textarea {
120
+ width: 100%;
121
+ min-height: 140px;
122
+ resize: vertical;
123
+ border-radius: 0.75rem;
124
+ border: 1px solid rgba(148, 163, 184, 0.5);
125
+ background: rgba(15, 23, 42, 0.9);
126
+ color: #e5e7eb;
127
+ padding: 0.75rem 0.85rem;
128
+ font-size: 0.9rem;
129
+ line-height: 1.5;
130
+ outline: none;
131
+ }
132
+
133
+ textarea:focus {
134
+ border-color: #38bdf8;
135
+ box-shadow: 0 0 0 1px rgba(56, 189, 248, 0.3);
136
+ }
137
+
138
+ .controls-row {
139
+ display: flex;
140
+ flex-wrap: wrap;
141
+ gap: 0.75rem;
142
+ align-items: center;
143
+ justify-content: space-between;
144
+ margin-top: 0.5rem;
145
+ }
146
+
147
+ .controls-group {
148
+ display: flex;
149
+ flex-wrap: wrap;
150
+ gap: 0.75rem;
151
+ align-items: center;
152
+ }
153
+
154
+ label.inline {
155
+ font-size: 0.8rem;
156
+ color: #9ca3af;
157
+ display: inline-flex;
158
+ align-items: center;
159
+ gap: 0.25rem;
160
+ }
161
+
162
+ input[type="range"] {
163
+ accent-color: #38bdf8;
164
+ }
165
+
166
+ select, input[type="number"] {
167
+ background: rgba(15, 23, 42, 0.9);
168
+ border: 1px solid rgba(148, 163, 184, 0.5);
169
+ color: #e5e7eb;
170
+ border-radius: 999px;
171
+ padding: 0.2rem 0.6rem;
172
+ font-size: 0.8rem;
173
+ outline: none;
174
+ }
175
+
176
+ button {
177
+ border-radius: 999px;
178
+ background: linear-gradient(135deg, #0ea5e9, #22c55e);
179
+ border: none;
180
+ color: white;
181
+ font-size: 0.85rem;
182
+ font-weight: 600;
183
+ padding: 0.45rem 1rem;
184
+ display: inline-flex;
185
+ align-items: center;
186
+ gap: 0.35rem;
187
+ cursor: pointer;
188
+ box-shadow: 0 10px 25px rgba(15, 118, 110, 0.4);
189
+ white-space: nowrap;
190
+ }
191
+
192
+ button span.icon {
193
+ font-size: 1rem;
194
+ }
195
+
196
+ button:active {
197
+ transform: translateY(1px);
198
+ box-shadow: 0 4px 15px rgba(15, 118, 110, 0.4);
199
+ }
200
+
201
+ button.secondary {
202
+ background: linear-gradient(135deg, #6366f1, #8b5cf6);
203
+ }
204
+
205
+ .preview-shell {
206
+ border-radius: 0.9rem;
207
+ border: 1px solid rgba(148, 163, 184, 0.5);
208
+ background: radial-gradient(circle at top, rgba(148, 163, 184, 0.15), rgba(15, 23, 42, 0.95));
209
+ padding: 0.75rem;
210
+ display: flex;
211
+ flex-direction: column;
212
+ gap: 0.4rem;
213
+ min-height: 200px;
214
+ }
215
+
216
+ .preview-meta {
217
+ display: flex;
218
+ justify-content: space-between;
219
+ align-items: baseline;
220
+ font-size: 0.75rem;
221
+ color: #9ca3af;
222
+ flex-wrap: wrap;
223
+ gap: 0.5rem;
224
+ }
225
+
226
+ .preview-container {
227
+ position: relative;
228
+ border-radius: 0.7rem;
229
+ background: #f9fafb;
230
+ color: #111827;
231
+ padding: 1rem;
232
+ min-height: 120px;
233
+ overflow: hidden;
234
+ }
235
+
236
+ .preview-container p {
237
+ margin: 0;
238
+ }
239
+
240
+ #preview {
241
+ text-align: justify;
242
+ font-size: 16px;
243
+ }
244
+
245
+ .hint {
246
+ font-size: 0.75rem;
247
+ color: #64748b;
248
+ }
249
+
250
+ .results-grid {
251
+ display: grid;
252
+ grid-template-rows: auto auto minmax(0, 1fr);
253
+ gap: 0.75rem;
254
+ min-height: 0;
255
+ }
256
+
257
+ .results-summary {
258
+ display: flex;
259
+ flex-wrap: wrap;
260
+ align-items: center;
261
+ justify-content: space-between;
262
+ gap: 0.75rem;
263
+ font-size: 0.8rem;
264
+ color: #9ca3af;
265
+ }
266
+
267
+ .pill {
268
+ padding: 0.2rem 0.6rem;
269
+ border-radius: 999px;
270
+ border: 1px solid rgba(148, 163, 184, 0.5);
271
+ font-size: 0.75rem;
272
+ }
273
+
274
+ .pill.auto {
275
+ background: rgba(34, 197, 94, 0.2);
276
+ border-color: rgba(34, 197, 94, 0.6);
277
+ color: #22c55e;
278
+ }
279
+
280
+ .lines-output {
281
+ background: rgba(15, 23, 42, 0.9);
282
+ border-radius: 0.75rem;
283
+ border: 1px solid rgba(148, 163, 184, 0.5);
284
+ padding: 0.6rem 0.75rem;
285
+ font-size: 0.78rem;
286
+ max-height: 120px;
287
+ overflow: auto;
288
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
289
+ white-space: pre-wrap;
290
+ }
291
+
292
+ .table-shell {
293
+ border-radius: 0.75rem;
294
+ border: 1px solid rgba(148, 163, 184, 0.5);
295
+ background: rgba(15, 23, 42, 0.9);
296
+ padding: 0.4rem 0.3rem 0.3rem;
297
+ min-height: 0;
298
+ display: flex;
299
+ flex-direction: column;
300
+ }
301
+
302
+ .table-shell h3 {
303
+ margin: 0 0 0.25rem;
304
+ font-size: 0.8rem;
305
+ text-transform: uppercase;
306
+ letter-spacing: 0.08em;
307
+ color: #9ca3af;
308
+ padding: 0 0.4rem;
309
+ }
310
+
311
+ .table-container {
312
+ flex: 1;
313
+ min-height: 0;
314
+ max-height: 260px;
315
+ overflow: auto;
316
+ }
317
+
318
+ table {
319
+ width: 100%;
320
+ border-collapse: collapse;
321
+ font-size: 0.74rem;
322
+ table-layout: fixed;
323
+ }
324
+
325
+ thead {
326
+ position: sticky;
327
+ top: 0;
328
+ background: rgba(15, 23, 42, 1);
329
+ z-index: 1;
330
+ }
331
+
332
+ th, td {
333
+ padding: 0.25rem 0.4rem;
334
+ border-bottom: 1px solid rgba(31, 41, 55, 0.9);
335
+ text-align: left;
336
+ white-space: nowrap;
337
+ overflow: hidden;
338
+ text-overflow: ellipsis;
339
+ }
340
+
341
+ th {
342
+ font-weight: 500;
343
+ color: #9ca3af;
344
+ }
345
+
346
+ tr:nth-child(even) td {
347
+ background: rgba(15, 23, 42, 0.7);
348
+ }
349
+
350
+ .char-cell {
351
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
352
+ }
353
+
354
+ .char-cell span {
355
+ padding: 0.05rem 0.3rem;
356
+ border-radius: 999px;
357
+ background: rgba(148, 163, 184, 0.18);
358
+ border: 1px solid rgba(148, 163, 184, 0.4);
359
+ display: inline-block;
360
+ }
361
+
362
+ .footnote {
363
+ margin-top: 0.25rem;
364
+ font-size: 0.7rem;
365
+ color: #6b7280;
366
+ padding: 0 0.3rem;
367
+ }
368
+
369
+ .advanced-panel {
370
+ background: rgba(15, 23, 42, 0.6);
371
+ border: 1px solid rgba(148, 163, 184, 0.3);
372
+ border-radius: 0.5rem;
373
+ padding: 0.5rem 0.75rem;
374
+ margin-top: 0.25rem;
375
+ }
376
+
377
+ .advanced-panel summary {
378
+ cursor: pointer;
379
+ font-size: 0.75rem;
380
+ color: #9ca3af;
381
+ user-select: none;
382
+ }
383
+
384
+ .advanced-panel[open] summary {
385
+ margin-bottom: 0.5rem;
386
+ }
387
+
388
+ .advanced-controls {
389
+ display: flex;
390
+ flex-wrap: wrap;
391
+ gap: 0.75rem;
392
+ align-items: center;
393
+ }
394
+
395
+ .info-box {
396
+ background: rgba(59, 130, 246, 0.1);
397
+ border: 1px solid rgba(59, 130, 246, 0.4);
398
+ border-radius: 0.5rem;
399
+ padding: 0.5rem 0.75rem;
400
+ font-size: 0.75rem;
401
+ color: #93c5fd;
402
+ margin-top: 0.5rem;
403
+ }
404
+
405
+ .info-box code {
406
+ background: rgba(0,0,0,0.3);
407
+ padding: 0.1rem 0.3rem;
408
+ border-radius: 3px;
409
+ }
410
+ </style>
411
+ </head>
412
+ <body>
413
+ <header>
414
+ <h1>Debugger de Parágrafo Justificado v2</h1>
415
+ <div class="header-badges">
416
+ <span class="label">Layout inspector</span>
417
+ <span class="zoom-warning" id="zoomWarning">⚠️ Zoom: <span id="zoomLevel">100</span>%</span>
418
+ </div>
419
+ </header>
420
+
421
+ <main>
422
+ <section aria-label="Entrada de texto e opções">
423
+ <div class="section-header">
424
+ <h2>Texto</h2>
425
+ <small>Digite, cole ou edite o parágrafo que você quer inspecionar.</small>
426
+ </div>
427
+
428
+ <textarea id="sourceText"></textarea>
429
+
430
+ <div class="controls-row">
431
+ <div class="controls-group">
432
+ <label class="inline">
433
+ Tamanho
434
+ <input type="range" id="fontSize" min="10" max="32" value="16" />
435
+ <span id="fontSizeValue">16px</span>
436
+ </label>
437
+
438
+ <label class="inline">
439
+ Alinhamento
440
+ <select id="textAlign">
441
+ <option value="justify" selected>justify</option>
442
+ <option value="left">left</option>
443
+ <option value="right">right</option>
444
+ <option value="center">center</option>
445
+ </select>
446
+ </label>
447
+
448
+ <label class="inline">
449
+ Fonte
450
+ <select id="fontFamily">
451
+ <option value="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif">Sistema</option>
452
+ <option value="Georgia, 'Times New Roman', serif">Serif</option>
453
+ <option value="'Tinos', Georgia, 'Times New Roman', serif">Tinos (Google)</option>
454
+ <option value="'Arimo', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif">Arimo (Google)</option>
455
+ <option value="'Times New Roman', Times, serif">Times</option>
456
+ <option value="'Courier New', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace">Mono</option>
457
+ </select>
458
+ </label>
459
+
460
+ <label class="inline">
461
+ Direção
462
+ <select id="textDirection">
463
+ <option value="ltr" selected>LTR</option>
464
+ <option value="rtl">RTL</option>
465
+ </select>
466
+ </label>
467
+ </div>
468
+
469
+ <div class="controls-group">
470
+ <label class="inline">
471
+ <input type="checkbox" id="showOverlay" checked />
472
+ Overlay
473
+ </label>
474
+ <button id="analyzeBtn" type="button">
475
+ <span class="icon">🔍</span>
476
+ Analisar layout
477
+ </button>
478
+ <button id="exportBtn" type="button" class="secondary">
479
+ <span class="icon">📦</span>
480
+ Exportar JSON
481
+ </button>
482
+ </div>
483
+ </div>
484
+
485
+ <!-- Painel Avançado para resolver os 3 problemas -->
486
+ <details class="advanced-panel">
487
+ <summary>⚙️ Configurações Avançadas (Precisão, Multi-Rect, Threshold)</summary>
488
+ <div class="advanced-controls">
489
+ <!-- PROBLEMA 1: Subpixel Precision -->
490
+ <label class="inline">
491
+ Precisão
492
+ <select id="precisionMode">
493
+ <option value="full">Completa (subpixel)</option>
494
+ <option value="rounded" selected>Arredondada (2 decimais)</option>
495
+ <option value="integer">Inteiro (px)</option>
496
+ </select>
497
+ </label>
498
+
499
+ <label class="inline">
500
+ Coords
501
+ <select id="coordMode">
502
+ <option value="viewport" selected>Viewport (absolutas)</option>
503
+ <option value="relative">Relativas ao container</option>
504
+ </select>
505
+ </label>
506
+
507
+ <!-- PROBLEMA 2: Multi-Rect Handling -->
508
+ <label class="inline">
509
+ Estratégia Rect
510
+ <select id="rectStrategy">
511
+ <option value="widest" selected>Mais largo</option>
512
+ <option value="first">Primeiro</option>
513
+ <option value="largest">Maior área</option>
514
+ <option value="union">União (bounding box)</option>
515
+ <option value="all">Todos (múltiplos)</option>
516
+ </select>
517
+ </label>
518
+
519
+ <!-- PROBLEMA 3: Line Threshold Sensitivity -->
520
+ <label class="inline">
521
+ Threshold
522
+ <select id="thresholdMode">
523
+ <option value="auto" selected>Auto (baseado em line-height)</option>
524
+ <option value="manual">Manual</option>
525
+ <option value="adaptive">Adaptativo (clustering)</option>
526
+ </select>
527
+ </label>
528
+
529
+ <label class="inline" id="manualThresholdLabel" style="display:none;">
530
+ Valor
531
+ <input type="number" id="lineThreshold" value="2" min="0" max="20" step="0.5" style="width:3.5rem" />px
532
+ </label>
533
+ </div>
534
+
535
+ <div class="info-box" id="thresholdInfo">
536
+ ℹ️ <strong>Auto-threshold</strong>: Calculado como <code>lineHeight × 0.3</code>.
537
+ Valor atual: <span id="calculatedThreshold">--</span>px
538
+ </div>
539
+ </details>
540
+
541
+ <p class="hint">Dica: Para texto RTL ou fontes exóticas, ajuste a estratégia de rect e o threshold nas configurações avançadas.</p>
542
+ </section>
543
+
544
+ <section aria-label="Preview e resultados">
545
+ <div class="section-header">
546
+ <h2>Preview &amp; resultados</h2>
547
+ <small>O overlay reflete o layout final renderizado pelo browser.</small>
548
+ </div>
549
+
550
+ <div class="preview-shell">
551
+ <div class="preview-meta">
552
+ <span class="pill">Preview do parágrafo</span>
553
+ <span class="pill auto" id="autoThresholdBadge" style="display:none;">Auto: <span id="autoThresholdValue">--</span>px</span>
554
+ <span class="hint">O texto abaixo é o que está sendo medido.</span>
555
+ </div>
556
+ <div class="preview-container">
557
+ <p id="preview"></p>
558
+ </div>
559
+ </div>
560
+
561
+ <div class="results-grid">
562
+ <div class="results-summary">
563
+ <div>
564
+ <span id="summaryLines">0 linhas</span> ·
565
+ <span id="summaryGlyphs">0 glifos</span> ·
566
+ <span id="summaryRects">0 rects</span>
567
+ </div>
568
+ <div class="hint">Espaços aparecem como <code>␣</code> na tabela.</div>
569
+ </div>
570
+
571
+ <div class="lines-output" id="linesOutput"></div>
572
+
573
+ <div class="table-shell">
574
+ <h3>Glifos medidos</h3>
575
+ <div class="table-container">
576
+ <table>
577
+ <thead>
578
+ <tr>
579
+ <th>#</th>
580
+ <th>Char</th>
581
+ <th>Linha</th>
582
+ <th>X</th>
583
+ <th>Y</th>
584
+ <th>Largura</th>
585
+ <th>Altura</th>
586
+ <th>Rects</th>
587
+ </tr>
588
+ </thead>
589
+ <tbody id="glyphTableBody"></tbody>
590
+ </table>
591
+ </div>
592
+ <div class="footnote">
593
+ Os valores de X/Y dependem do modo de coordenadas selecionado.
594
+ <span id="coordModeNote">(Viewport)</span>
595
+ </div>
596
+ </div>
597
+ </div>
598
+ </section>
599
+ </main>
600
+
601
+ <script>
602
+ // =========================================
603
+ // CORE DE DEBUG v2 - COM SOLUÇÕES PARA:
604
+ // 1. Subpixel Precision
605
+ // 2. Multi-Rect Handling
606
+ // 3. Line Threshold Sensitivity
607
+ // =========================================
608
+
609
+ (function () {
610
+ // -------------------------
611
+ // SOLUÇÃO 1: Detecção de Zoom
612
+ // -------------------------
613
+ function getZoomLevel() {
614
+ // Método 1: devicePixelRatio vs zoom visual
615
+ const dpr = window.devicePixelRatio || 1;
616
+
617
+ // Método 2: comparar tamanho computado com esperado
618
+ const testEl = document.createElement('div');
619
+ testEl.style.cssText = 'position:absolute;width:100px;height:100px;top:-9999px;left:-9999px;';
620
+ document.body.appendChild(testEl);
621
+ const computedWidth = testEl.getBoundingClientRect().width;
622
+ document.body.removeChild(testEl);
623
+
624
+ // Zoom aproximado baseado no DPR e tamanho renderizado
625
+ const zoomFromSize = computedWidth / 100;
626
+
627
+ // Retorna o mais provável
628
+ return Math.round(zoomFromSize * 100);
629
+ }
630
+
631
+ function monitorZoom(callback) {
632
+ let lastZoom = getZoomLevel();
633
+
634
+ const check = () => {
635
+ const currentZoom = getZoomLevel();
636
+ if (currentZoom !== lastZoom) {
637
+ lastZoom = currentZoom;
638
+ callback(currentZoom);
639
+ }
640
+ };
641
+
642
+ window.addEventListener('resize', check);
643
+
644
+ // Também verifica periodicamente (alguns zooms não disparam resize)
645
+ setInterval(check, 1000);
646
+
647
+ return lastZoom;
648
+ }
649
+
650
+ // -------------------------
651
+ // SOLUÇÃO 2: Estratégias de Multi-Rect
652
+ // -------------------------
653
+ const RectStrategies = {
654
+ // Estratégia original: escolhe o mais largo
655
+ widest: function(rects) {
656
+ if (!rects.length) return null;
657
+ let chosen = rects[0];
658
+ for (let i = 1; i < rects.length; i++) {
659
+ if (rects[i].width > chosen.width) {
660
+ chosen = rects[i];
661
+ }
662
+ }
663
+ return [chosen];
664
+ },
665
+
666
+ // Primeiro rect (útil para LTR simples)
667
+ first: function(rects) {
668
+ return rects.length ? [rects[0]] : null;
669
+ },
670
+
671
+ // Maior área (melhor para glifos fragmentados)
672
+ largest: function(rects) {
673
+ if (!rects.length) return null;
674
+ let chosen = rects[0];
675
+ let maxArea = chosen.width * chosen.height;
676
+ for (let i = 1; i < rects.length; i++) {
677
+ const area = rects[i].width * rects[i].height;
678
+ if (area > maxArea) {
679
+ chosen = rects[i];
680
+ maxArea = area;
681
+ }
682
+ }
683
+ return [chosen];
684
+ },
685
+
686
+ // União: bounding box de todos os rects
687
+ union: function(rects) {
688
+ if (!rects.length) return null;
689
+
690
+ let minX = Infinity, minY = Infinity;
691
+ let maxX = -Infinity, maxY = -Infinity;
692
+
693
+ for (const r of rects) {
694
+ minX = Math.min(minX, r.left);
695
+ minY = Math.min(minY, r.top);
696
+ maxX = Math.max(maxX, r.right);
697
+ maxY = Math.max(maxY, r.bottom);
698
+ }
699
+
700
+ return [{
701
+ left: minX,
702
+ top: minY,
703
+ right: maxX,
704
+ bottom: maxY,
705
+ width: maxX - minX,
706
+ height: maxY - minY,
707
+ x: minX,
708
+ y: minY
709
+ }];
710
+ },
711
+
712
+ // Todos: retorna todos os rects (para análise completa)
713
+ all: function(rects) {
714
+ return rects.length ? Array.from(rects) : null;
715
+ }
716
+ };
717
+
718
+ // -------------------------
719
+ // SOLUÇÃO 3: Threshold Adaptativo
720
+ // -------------------------
721
+ const ThresholdStrategies = {
722
+ // Manual: usa valor fixo
723
+ manual: function(glyphs, manualValue) {
724
+ return {
725
+ threshold: manualValue,
726
+ method: 'manual',
727
+ confidence: 1.0
728
+ };
729
+ },
730
+
731
+ // Auto: baseado em line-height computada
732
+ auto: function(glyphs, _, paragraph) {
733
+ if (!paragraph) return { threshold: 2, method: 'auto-fallback', confidence: 0.5 };
734
+
735
+ const style = window.getComputedStyle(paragraph);
736
+ const lineHeight = parseFloat(style.lineHeight);
737
+ const fontSize = parseFloat(style.fontSize);
738
+
739
+ // Se lineHeight é 'normal', estima como 1.2 × fontSize
740
+ const effectiveLineHeight = isNaN(lineHeight) ? fontSize * 1.2 : lineHeight;
741
+
742
+ // Threshold = 30% da line-height (permite variação de baseline)
743
+ const threshold = effectiveLineHeight * 0.3;
744
+
745
+ return {
746
+ threshold: Math.max(1, threshold),
747
+ method: 'auto',
748
+ lineHeight: effectiveLineHeight,
749
+ fontSize: fontSize,
750
+ confidence: 0.9
751
+ };
752
+ },
753
+
754
+ // Adaptativo: usa clustering baseado em gaps naturais de Y
755
+ adaptive: function(glyphs) {
756
+ if (glyphs.length < 2) {
757
+ return { threshold: 2, method: 'adaptive-fallback', confidence: 0.5 };
758
+ }
759
+
760
+ // Ordena por Y
761
+ const sortedY = glyphs.map(g => g.y).sort((a, b) => a - b);
762
+
763
+ // Calcula gaps entre valores Y consecutivos
764
+ const gaps = [];
765
+ for (let i = 1; i < sortedY.length; i++) {
766
+ gaps.push(sortedY[i] - sortedY[i - 1]);
767
+ }
768
+
769
+ // Filtra gaps muito pequenos (mesmo glifo) e muito grandes (outliers)
770
+ const avgHeight = glyphs.reduce((s, g) => s + g.height, 0) / glyphs.length;
771
+ const significantGaps = gaps.filter(g => g > avgHeight * 0.1 && g < avgHeight * 3);
772
+
773
+ if (!significantGaps.length) {
774
+ return { threshold: avgHeight * 0.3, method: 'adaptive-height', confidence: 0.7 };
775
+ }
776
+
777
+ // Encontra o gap que melhor separa "mesma linha" de "nova linha"
778
+ // usando o "cotovelo" na distribuição
779
+ significantGaps.sort((a, b) => a - b);
780
+
781
+ let bestThreshold = significantGaps[0];
782
+ let maxJump = 0;
783
+
784
+ for (let i = 1; i < significantGaps.length; i++) {
785
+ const jump = significantGaps[i] - significantGaps[i - 1];
786
+ if (jump > maxJump) {
787
+ maxJump = jump;
788
+ // Threshold = ponto médio do maior salto
789
+ bestThreshold = (significantGaps[i - 1] + significantGaps[i]) / 2;
790
+ }
791
+ }
792
+
793
+ return {
794
+ threshold: Math.max(1, bestThreshold),
795
+ method: 'adaptive',
796
+ gapsAnalyzed: significantGaps.length,
797
+ confidence: 0.85
798
+ };
799
+ }
800
+ };
801
+
802
+ // -------------------------
803
+ // Utilitários de Precisão
804
+ // -------------------------
805
+ function formatNumber(n, precision) {
806
+ switch (precision) {
807
+ case 'full': return n;
808
+ case 'integer': return Math.round(n);
809
+ case 'rounded':
810
+ default: return Math.round(n * 100) / 100;
811
+ }
812
+ }
813
+
814
+ // -------------------------
815
+ // Funções Auxiliares
816
+ // -------------------------
817
+ function getTextNodes(root) {
818
+ const walker = document.createTreeWalker(
819
+ root,
820
+ NodeFilter.SHOW_TEXT,
821
+ {
822
+ acceptNode(node) {
823
+ if (!node.nodeValue || !node.nodeValue.trim()) {
824
+ return NodeFilter.FILTER_REJECT;
825
+ }
826
+ return NodeFilter.FILTER_ACCEPT;
827
+ },
828
+ }
829
+ );
830
+
831
+ const nodes = [];
832
+ while (walker.nextNode()) {
833
+ nodes.push(walker.currentNode);
834
+ }
835
+ return nodes;
836
+ }
837
+
838
+ function segmentText(text) {
839
+ if (window.Intl && typeof Intl.Segmenter === "function") {
840
+ const seg = new Intl.Segmenter(undefined, { granularity: "grapheme" });
841
+ const it = seg.segment(text);
842
+ const result = [];
843
+ for (const s of it) {
844
+ result.push({
845
+ segment: s.segment,
846
+ start: s.index,
847
+ end: s.index + s.segment.length,
848
+ });
849
+ }
850
+ return result;
851
+ }
852
+
853
+ const result = [];
854
+ for (let i = 0; i < text.length; i++) {
855
+ result.push({ segment: text[i], start: i, end: i + 1 });
856
+ }
857
+ return result;
858
+ }
859
+
860
+ function isRTL(element) {
861
+ const style = window.getComputedStyle(element);
862
+ return style.direction === 'rtl';
863
+ }
864
+
865
+ // -------------------------
866
+ // Overlay Melhorado
867
+ // -------------------------
868
+ function attachOverlay(paragraph, glyphs, lines, options) {
869
+ if (window.__paragraphDebugOverlay && window.__paragraphDebugOverlay.parentNode) {
870
+ window.__paragraphDebugOverlay.parentNode.removeChild(window.__paragraphDebugOverlay);
871
+ }
872
+
873
+ const paraRect = paragraph.getBoundingClientRect();
874
+ const scrollX = window.scrollX || window.pageXOffset || 0;
875
+ const scrollY = window.scrollY || window.pageYOffset || 0;
876
+
877
+ const overlay = document.createElement("div");
878
+ overlay.style.position = "absolute";
879
+ overlay.style.left = paraRect.left + scrollX + "px";
880
+ overlay.style.top = paraRect.top + scrollY + "px";
881
+ overlay.style.width = paraRect.width + "px";
882
+ overlay.style.height = paraRect.height + "px";
883
+ overlay.style.pointerEvents = "none";
884
+ overlay.style.zIndex = 999999;
885
+ overlay.style.boxSizing = "border-box";
886
+ overlay.style.outline = "1px dashed rgba(15,23,42,0.3)";
887
+
888
+ const overlayLeft = paraRect.left + scrollX;
889
+ const overlayTop = paraRect.top + scrollY;
890
+
891
+ // Desenha glifos
892
+ glyphs.forEach((g) => {
893
+ // Se estratégia "all", pode ter múltiplos rects por glifo
894
+ const rectsToRender = g.allRects || [g];
895
+
896
+ rectsToRender.forEach((rect, i) => {
897
+ const box = document.createElement("div");
898
+ box.style.position = "absolute";
899
+ const innerLeft = (rect.pageX || g.pageX) - overlayLeft;
900
+ const innerTop = (rect.pageY || g.pageY) - overlayTop;
901
+ box.style.left = innerLeft + "px";
902
+ box.style.top = innerTop + "px";
903
+ box.style.width = (rect.width || g.width) + "px";
904
+ box.style.height = (rect.height || g.height) + "px";
905
+
906
+ // Cor diferente para múltiplos rects
907
+ const color = rectsToRender.length > 1
908
+ ? `rgba(${100 + i * 50}, 113, 113, 0.7)`
909
+ : "rgba(248,113,113,0.7)";
910
+ box.style.border = `1px solid ${color}`;
911
+ box.style.boxSizing = "border-box";
912
+ box.style.pointerEvents = "none";
913
+ box.title = `${g.text} (idx ${g.globalIndex}, linha ${g.line}${rectsToRender.length > 1 ? `, rect ${i+1}/${rectsToRender.length}` : ''})`;
914
+ overlay.appendChild(box);
915
+ });
916
+ });
917
+
918
+ // Desenha linhas
919
+ lines.forEach((line) => {
920
+ const lineBox = document.createElement("div");
921
+ lineBox.style.position = "absolute";
922
+ const innerLeft = line.xPage - overlayLeft;
923
+ const innerTop = line.yPage - overlayTop;
924
+ lineBox.style.left = innerLeft + "px";
925
+ lineBox.style.top = innerTop + "px";
926
+ lineBox.style.width = line.width + "px";
927
+ lineBox.style.height = line.height + "px";
928
+ lineBox.style.border = "1px dashed rgba(59,130,246,0.8)";
929
+ lineBox.style.boxSizing = "border-box";
930
+ lineBox.style.pointerEvents = "none";
931
+ lineBox.style.fontSize = "10px";
932
+ lineBox.style.lineHeight = "10px";
933
+ lineBox.style.color = "rgba(59,130,246,0.8)";
934
+ lineBox.style.textAlign = "right";
935
+ lineBox.style.paddingRight = "2px";
936
+ lineBox.textContent = `L${line.line}`;
937
+ overlay.appendChild(lineBox);
938
+ });
939
+
940
+ document.body.appendChild(overlay);
941
+ window.__paragraphDebugOverlay = overlay;
942
+ return overlay;
943
+ }
944
+
945
+ function clearOverlay() {
946
+ if (window.__paragraphDebugOverlay && window.__paragraphDebugOverlay.parentNode) {
947
+ window.__paragraphDebugOverlay.parentNode.removeChild(window.__paragraphDebugOverlay);
948
+ }
949
+ window.__paragraphDebugOverlay = null;
950
+ }
951
+
952
+ // -------------------------
953
+ // Função Principal MELHORADA
954
+ // -------------------------
955
+ function debugJustifiedParagraph(paragraph, options) {
956
+ if (!paragraph || !(paragraph instanceof HTMLElement)) {
957
+ throw new Error("Passe um elemento <p> válido para debugJustifiedParagraph(paragraph, options)");
958
+ }
959
+
960
+ const opts = Object.assign({
961
+ overlay: false,
962
+ lineThreshold: 2,
963
+ // Novas opções
964
+ thresholdMode: 'auto', // 'manual', 'auto', 'adaptive'
965
+ rectStrategy: 'widest', // 'widest', 'first', 'largest', 'union', 'all'
966
+ precision: 'rounded', // 'full', 'rounded', 'integer'
967
+ coordMode: 'viewport' // 'viewport', 'relative'
968
+ }, options || {});
969
+
970
+ const textNodes = getTextNodes(paragraph);
971
+ if (!textNodes.length) {
972
+ return { glyphs: [], lines: [], overlay: null, meta: {} };
973
+ }
974
+
975
+ const range = document.createRange();
976
+ const glyphs = [];
977
+ let globalIndex = 0;
978
+ let totalRects = 0;
979
+
980
+ // Detecta RTL para ordenação correta
981
+ const rtl = isRTL(paragraph);
982
+ const paraRect = paragraph.getBoundingClientRect();
983
+
984
+ const scrollX = window.scrollX || window.pageXOffset || 0;
985
+ const scrollY = window.scrollY || window.pageYOffset || 0;
986
+
987
+ textNodes.forEach((node, nodeIndex) => {
988
+ const text = node.data;
989
+ const segments = segmentText(text);
990
+
991
+ segments.forEach((seg) => {
992
+ range.setStart(node, seg.start);
993
+ range.setEnd(node, seg.end);
994
+
995
+ const rects = range.getClientRects();
996
+ if (!rects.length) return;
997
+
998
+ totalRects += rects.length;
999
+
1000
+ // SOLUÇÃO 2: Aplica estratégia de seleção de rects
1001
+ const strategy = RectStrategies[opts.rectStrategy] || RectStrategies.widest;
1002
+ const selectedRects = strategy(rects);
1003
+
1004
+ if (!selectedRects || !selectedRects.length) return;
1005
+
1006
+ // Usa o primeiro rect selecionado como principal
1007
+ const chosen = selectedRects[0];
1008
+
1009
+ // SOLUÇÃO 1: Calcula coordenadas conforme modo
1010
+ let x, y, pageX, pageY;
1011
+
1012
+ if (opts.coordMode === 'relative') {
1013
+ x = chosen.left - paraRect.left;
1014
+ y = chosen.top - paraRect.top;
1015
+ pageX = x;
1016
+ pageY = y;
1017
+ } else {
1018
+ x = chosen.left;
1019
+ y = chosen.top;
1020
+ pageX = chosen.left + scrollX;
1021
+ pageY = chosen.top + scrollY;
1022
+ }
1023
+
1024
+ const glyphData = {
1025
+ globalIndex: globalIndex++,
1026
+ nodeIndex,
1027
+ node,
1028
+ text: seg.segment,
1029
+ startOffset: seg.start,
1030
+ endOffset: seg.end,
1031
+ x: formatNumber(x, opts.precision),
1032
+ y: formatNumber(y, opts.precision),
1033
+ width: formatNumber(chosen.width, opts.precision),
1034
+ height: formatNumber(chosen.height, opts.precision),
1035
+ pageX: formatNumber(pageX, opts.precision),
1036
+ pageY: formatNumber(pageY, opts.precision),
1037
+ line: null,
1038
+ rectCount: rects.length,
1039
+ // Armazena todos os rects se estratégia "all"
1040
+ allRects: opts.rectStrategy === 'all' ? selectedRects.map(r => ({
1041
+ x: formatNumber(opts.coordMode === 'relative' ? r.left - paraRect.left : r.left, opts.precision),
1042
+ y: formatNumber(opts.coordMode === 'relative' ? r.top - paraRect.top : r.top, opts.precision),
1043
+ width: formatNumber(r.width, opts.precision),
1044
+ height: formatNumber(r.height, opts.precision),
1045
+ pageX: formatNumber(r.left + scrollX, opts.precision),
1046
+ pageY: formatNumber(r.top + scrollY, opts.precision)
1047
+ })) : null,
1048
+ // Dados brutos para análise
1049
+ _rawY: chosen.top
1050
+ };
1051
+
1052
+ glyphs.push(glyphData);
1053
+ });
1054
+ });
1055
+
1056
+ range.detach?.();
1057
+
1058
+ if (!glyphs.length) {
1059
+ return { glyphs: [], lines: [], overlay: null, meta: {} };
1060
+ }
1061
+
1062
+ // Ordena: para RTL, inverte a ordenação X
1063
+ glyphs.sort((a, b) => {
1064
+ const yDiff = a._rawY - b._rawY;
1065
+ if (Math.abs(yDiff) > 1) return yDiff;
1066
+ return rtl ? (b.x - a.x) : (a.x - b.x);
1067
+ });
1068
+
1069
+ // SOLUÇÃO 3: Calcula threshold conforme modo
1070
+ const thresholdStrategy = ThresholdStrategies[opts.thresholdMode] || ThresholdStrategies.auto;
1071
+ const thresholdResult = thresholdStrategy(glyphs, opts.lineThreshold, paragraph);
1072
+ const threshold = thresholdResult.threshold;
1073
+
1074
+ // Atribui linhas
1075
+ let line = 0;
1076
+ let currentTop = glyphs[0]._rawY;
1077
+
1078
+ glyphs.forEach((g) => {
1079
+ if (Math.abs(g._rawY - currentTop) > threshold) {
1080
+ line++;
1081
+ currentTop = g._rawY;
1082
+ }
1083
+ g.line = line;
1084
+ });
1085
+
1086
+ // Agrupa por linha
1087
+ const byLine = new Map();
1088
+ glyphs.forEach((g) => {
1089
+ if (!byLine.has(g.line)) byLine.set(g.line, []);
1090
+ byLine.get(g.line).push(g);
1091
+ });
1092
+
1093
+ const lines = [];
1094
+ byLine.forEach((items, lineNumber) => {
1095
+ items.sort((a, b) => rtl ? (b.x - a.x) : (a.x - b.x));
1096
+ const first = items[0];
1097
+ const last = items[items.length - 1];
1098
+ const maxHeight = items.reduce((m, g) => Math.max(m, g.height), 0);
1099
+
1100
+ lines.push({
1101
+ line: lineNumber,
1102
+ text: items.map((g) => g.text).join(""),
1103
+ x: first.x,
1104
+ y: first.y,
1105
+ width: last.x + last.width - first.x,
1106
+ height: maxHeight,
1107
+ xPage: first.pageX,
1108
+ yPage: first.pageY,
1109
+ glyphs: items,
1110
+ });
1111
+ });
1112
+
1113
+ lines.sort((a, b) => a.line - b.line);
1114
+
1115
+ // Limpa dados internos dos glifos
1116
+ glyphs.forEach(g => delete g._rawY);
1117
+
1118
+ let overlay = null;
1119
+ if (opts.overlay) {
1120
+ overlay = attachOverlay(paragraph, glyphs, lines, opts);
1121
+ }
1122
+
1123
+ // Metadados sobre a análise
1124
+ const meta = {
1125
+ threshold: thresholdResult,
1126
+ rectStrategy: opts.rectStrategy,
1127
+ coordMode: opts.coordMode,
1128
+ precision: opts.precision,
1129
+ totalRects,
1130
+ rtl,
1131
+ zoomLevel: getZoomLevel()
1132
+ };
1133
+
1134
+ return { glyphs, lines, overlay, meta };
1135
+ }
1136
+
1137
+ // Exporta funções
1138
+ window.debugJustifiedParagraph = debugJustifiedParagraph;
1139
+ window.clearParagraphDebugOverlay = clearOverlay;
1140
+ window.getZoomLevel = getZoomLevel;
1141
+ window.monitorZoom = monitorZoom;
1142
+ })();
1143
+
1144
+ // =========================================
1145
+ // UI / APP WIRING
1146
+ // =========================================
1147
+
1148
+ (function () {
1149
+ const sourceText = document.getElementById("sourceText");
1150
+ const preview = document.getElementById("preview");
1151
+ const fontSizeInput = document.getElementById("fontSize");
1152
+ const fontSizeValue = document.getElementById("fontSizeValue");
1153
+ const textAlignSelect = document.getElementById("textAlign");
1154
+ const fontFamilySelect = document.getElementById("fontFamily");
1155
+ const textDirectionSelect = document.getElementById("textDirection");
1156
+ const showOverlayInput = document.getElementById("showOverlay");
1157
+ const analyzeBtn = document.getElementById("analyzeBtn");
1158
+ const linesOutput = document.getElementById("linesOutput");
1159
+ const glyphTableBody = document.getElementById("glyphTableBody");
1160
+ const summaryLines = document.getElementById("summaryLines");
1161
+ const summaryGlyphs = document.getElementById("summaryGlyphs");
1162
+ const summaryRects = document.getElementById("summaryRects");
1163
+ const exportBtn = document.getElementById("exportBtn");
1164
+
1165
+ // Novos controles avançados
1166
+ const precisionModeSelect = document.getElementById("precisionMode");
1167
+ const coordModeSelect = document.getElementById("coordMode");
1168
+ const rectStrategySelect = document.getElementById("rectStrategy");
1169
+ const thresholdModeSelect = document.getElementById("thresholdMode");
1170
+ const lineThresholdInput = document.getElementById("lineThreshold");
1171
+ const manualThresholdLabel = document.getElementById("manualThresholdLabel");
1172
+ const thresholdInfo = document.getElementById("thresholdInfo");
1173
+ const calculatedThreshold = document.getElementById("calculatedThreshold");
1174
+ const autoThresholdBadge = document.getElementById("autoThresholdBadge");
1175
+ const autoThresholdValue = document.getElementById("autoThresholdValue");
1176
+ const coordModeNote = document.getElementById("coordModeNote");
1177
+ const zoomWarning = document.getElementById("zoomWarning");
1178
+ const zoomLevelSpan = document.getElementById("zoomLevel");
1179
+
1180
+ let lastGlyphs = [];
1181
+ let lastLines = [];
1182
+ let lastMeta = {};
1183
+
1184
+ const defaultText =
1185
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. " +
1186
+ "Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. " +
1187
+ "Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi.";
1188
+
1189
+ // Monitora zoom
1190
+ const initialZoom = window.monitorZoom((newZoom) => {
1191
+ zoomLevelSpan.textContent = newZoom;
1192
+ zoomWarning.classList.toggle('visible', newZoom !== 100);
1193
+ });
1194
+
1195
+ zoomLevelSpan.textContent = initialZoom;
1196
+ zoomWarning.classList.toggle('visible', initialZoom !== 100);
1197
+
1198
+ function setPreviewFromTextarea() {
1199
+ preview.textContent = sourceText.value || "";
1200
+ }
1201
+
1202
+ function applyStyles() {
1203
+ const size = parseFloat(fontSizeInput.value) || 16;
1204
+ preview.style.fontSize = size + "px";
1205
+ fontSizeValue.textContent = size + "px";
1206
+ preview.style.textAlign = textAlignSelect.value;
1207
+ preview.style.fontFamily = fontFamilySelect.value;
1208
+ preview.style.direction = textDirectionSelect.value;
1209
+ }
1210
+
1211
+ function updateThresholdUI() {
1212
+ const mode = thresholdModeSelect.value;
1213
+ manualThresholdLabel.style.display = mode === 'manual' ? '' : 'none';
1214
+ thresholdInfo.style.display = mode === 'auto' ? '' : 'none';
1215
+ autoThresholdBadge.style.display = mode !== 'manual' ? '' : 'none';
1216
+ }
1217
+
1218
+ function updateCoordModeUI() {
1219
+ const mode = coordModeSelect.value;
1220
+ coordModeNote.textContent = mode === 'viewport' ? '(Viewport)' : '(Relativas ao container)';
1221
+ }
1222
+
1223
+ function formatLinesSummary(lines) {
1224
+ if (!lines.length) return "Nenhuma linha detectada.";
1225
+ return lines
1226
+ .map((line) => {
1227
+ const txt = line.text.replace(/\s/g, " ");
1228
+ const display = txt.length > 60 ? txt.slice(0, 57) + "…" : txt;
1229
+ return `L${line.line}: "${display}"`;
1230
+ })
1231
+ .join("\n");
1232
+ }
1233
+
1234
+ function analyze() {
1235
+ window.clearParagraphDebugOverlay?.();
1236
+
1237
+ setPreviewFromTextarea();
1238
+ applyStyles();
1239
+
1240
+ if (!preview.textContent || !preview.textContent.trim()) {
1241
+ linesOutput.textContent = "Nada para medir. Digite algum texto.";
1242
+ glyphTableBody.innerHTML = "";
1243
+ summaryLines.textContent = "0 linhas";
1244
+ summaryGlyphs.textContent = "0 glifos";
1245
+ summaryRects.textContent = "0 rects";
1246
+ lastGlyphs = [];
1247
+ lastLines = [];
1248
+ lastMeta = {};
1249
+ return;
1250
+ }
1251
+
1252
+ const options = {
1253
+ overlay: showOverlayInput.checked,
1254
+ lineThreshold: parseFloat(lineThresholdInput.value) || 2,
1255
+ thresholdMode: thresholdModeSelect.value,
1256
+ rectStrategy: rectStrategySelect.value,
1257
+ precision: precisionModeSelect.value,
1258
+ coordMode: coordModeSelect.value
1259
+ };
1260
+
1261
+ const { glyphs, lines, meta } = window.debugJustifiedParagraph(preview, options);
1262
+
1263
+ lastGlyphs = glyphs;
1264
+ lastLines = lines;
1265
+ lastMeta = meta;
1266
+
1267
+ // Atualiza UI com info de threshold
1268
+ if (meta.threshold) {
1269
+ const t = meta.threshold.threshold;
1270
+ calculatedThreshold.textContent = t.toFixed(2);
1271
+ autoThresholdValue.textContent = t.toFixed(1);
1272
+ }
1273
+
1274
+ summaryLines.textContent = `${lines.length} linha${lines.length === 1 ? "" : "s"}`;
1275
+ summaryGlyphs.textContent = `${glyphs.length} glifo${glyphs.length === 1 ? "" : "s"}`;
1276
+ summaryRects.textContent = `${meta.totalRects || 0} rects`;
1277
+
1278
+ linesOutput.textContent = formatLinesSummary(lines);
1279
+
1280
+ glyphTableBody.innerHTML = "";
1281
+ const frag = document.createDocumentFragment();
1282
+
1283
+ glyphs.forEach((g) => {
1284
+ const tr = document.createElement("tr");
1285
+
1286
+ const tdIdx = document.createElement("td");
1287
+ tdIdx.textContent = g.globalIndex;
1288
+
1289
+ const tdChar = document.createElement("td");
1290
+ tdChar.className = "char-cell";
1291
+ const span = document.createElement("span");
1292
+ let displayChar = g.text;
1293
+ if (displayChar === " ") displayChar = "␣";
1294
+ if (displayChar === "\n") displayChar = "↵";
1295
+ if (!displayChar) displayChar = "∅";
1296
+ span.textContent = displayChar;
1297
+ tdChar.appendChild(span);
1298
+
1299
+ const tdLine = document.createElement("td");
1300
+ tdLine.textContent = g.line;
1301
+
1302
+ const tdX = document.createElement("td");
1303
+ const tdY = document.createElement("td");
1304
+ const tdW = document.createElement("td");
1305
+ const tdH = document.createElement("td");
1306
+ const tdRects = document.createElement("td");
1307
+
1308
+ // Formata conforme precisão
1309
+ const precision = precisionModeSelect.value;
1310
+ const formatVal = (v) => {
1311
+ if (precision === 'integer') return Math.round(v);
1312
+ if (precision === 'full') return v;
1313
+ return typeof v === 'number' ? v.toFixed(2) : v;
1314
+ };
1315
+
1316
+ tdX.textContent = formatVal(g.x);
1317
+ tdY.textContent = formatVal(g.y);
1318
+ tdW.textContent = formatVal(g.width);
1319
+ tdH.textContent = formatVal(g.height);
1320
+ tdRects.textContent = g.rectCount || 1;
1321
+
1322
+ tr.appendChild(tdIdx);
1323
+ tr.appendChild(tdChar);
1324
+ tr.appendChild(tdLine);
1325
+ tr.appendChild(tdX);
1326
+ tr.appendChild(tdY);
1327
+ tr.appendChild(tdW);
1328
+ tr.appendChild(tdH);
1329
+ tr.appendChild(tdRects);
1330
+
1331
+ frag.appendChild(tr);
1332
+ });
1333
+
1334
+ glyphTableBody.appendChild(frag);
1335
+ }
1336
+
1337
+ function exportJson() {
1338
+ if (!lastGlyphs.length && !lastLines.length) {
1339
+ alert("Nenhum dado para exportar. Clique em \"Analisar layout\" primeiro.");
1340
+ return;
1341
+ }
1342
+
1343
+ const meta = {
1344
+ text: sourceText.value,
1345
+ fontSize: parseFloat(fontSizeInput.value) || 16,
1346
+ textAlign: textAlignSelect.value,
1347
+ fontFamily: fontFamilySelect.value,
1348
+ direction: textDirectionSelect.value,
1349
+ // Configurações avançadas
1350
+ precision: precisionModeSelect.value,
1351
+ coordMode: coordModeSelect.value,
1352
+ rectStrategy: rectStrategySelect.value,
1353
+ thresholdMode: thresholdModeSelect.value,
1354
+ lineThreshold: lastMeta.threshold ? lastMeta.threshold.threshold : parseFloat(lineThresholdInput.value),
1355
+ thresholdMethod: lastMeta.threshold ? lastMeta.threshold.method : 'manual',
1356
+ // Stats
1357
+ overlay: showOverlayInput.checked,
1358
+ glyphCount: lastGlyphs.length,
1359
+ lineCount: lastLines.length,
1360
+ totalRects: lastMeta.totalRects || 0,
1361
+ rtl: lastMeta.rtl || false,
1362
+ zoomLevel: lastMeta.zoomLevel || 100,
1363
+ generatedAt: new Date().toISOString(),
1364
+ };
1365
+
1366
+ const exportGlyphs = lastGlyphs.map((g) => ({
1367
+ index: g.globalIndex,
1368
+ text: g.text,
1369
+ line: g.line,
1370
+ x: g.x,
1371
+ y: g.y,
1372
+ width: g.width,
1373
+ height: g.height,
1374
+ pageX: g.pageX,
1375
+ pageY: g.pageY,
1376
+ nodeIndex: g.nodeIndex,
1377
+ startOffset: g.startOffset,
1378
+ endOffset: g.endOffset,
1379
+ rectCount: g.rectCount,
1380
+ allRects: g.allRects || null
1381
+ }));
1382
+
1383
+ const exportLines = lastLines.map((line) => ({
1384
+ line: line.line,
1385
+ text: line.text,
1386
+ x: line.x,
1387
+ y: line.y,
1388
+ width: line.width,
1389
+ height: line.height,
1390
+ glyphIndices: line.glyphs ? line.glyphs.map((g) => g.globalIndex) : [],
1391
+ }));
1392
+
1393
+ const payload = {
1394
+ meta,
1395
+ lines: exportLines,
1396
+ glyphs: exportGlyphs,
1397
+ };
1398
+
1399
+ const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
1400
+ const url = URL.createObjectURL(blob);
1401
+
1402
+ const a = document.createElement("a");
1403
+ a.href = url;
1404
+ const ts = new Date().toISOString().replace(/[:.]/g, "-");
1405
+ a.download = `glyph-debug-${ts}.json`;
1406
+ document.body.appendChild(a);
1407
+ a.click();
1408
+ document.body.removeChild(a);
1409
+ URL.revokeObjectURL(url);
1410
+ }
1411
+
1412
+ // Inicialização
1413
+ sourceText.value = defaultText;
1414
+ setPreviewFromTextarea();
1415
+ applyStyles();
1416
+ updateThresholdUI();
1417
+ updateCoordModeUI();
1418
+
1419
+ // Event listeners básicos
1420
+ fontSizeInput.addEventListener("input", () => {
1421
+ applyStyles();
1422
+ analyze();
1423
+ });
1424
+
1425
+ textAlignSelect.addEventListener("change", analyze);
1426
+ fontFamilySelect.addEventListener("change", analyze);
1427
+ textDirectionSelect.addEventListener("change", analyze);
1428
+ showOverlayInput.addEventListener("change", analyze);
1429
+
1430
+ sourceText.addEventListener("input", setPreviewFromTextarea);
1431
+
1432
+ analyzeBtn.addEventListener("click", analyze);
1433
+ exportBtn.addEventListener("click", exportJson);
1434
+
1435
+ // Event listeners avançados
1436
+ thresholdModeSelect.addEventListener("change", () => {
1437
+ updateThresholdUI();
1438
+ analyze();
1439
+ });
1440
+
1441
+ lineThresholdInput.addEventListener("change", analyze);
1442
+ precisionModeSelect.addEventListener("change", analyze);
1443
+
1444
+ coordModeSelect.addEventListener("change", () => {
1445
+ updateCoordModeUI();
1446
+ analyze();
1447
+ });
1448
+
1449
+ rectStrategySelect.addEventListener("change", analyze);
1450
+
1451
+ // Análise inicial
1452
+ window.addEventListener("load", () => {
1453
+ setTimeout(analyze, 50);
1454
+ });
1455
+ })();
1456
+ </script>
1457
+ </body>
1458
+ </html>