blitz-pdf 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (370) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +355 -2
  3. package/THIRD_PARTY_NOTICES.md +25 -0
  4. package/assets/icon.svg +4 -0
  5. package/dist/api.d.ts +7 -0
  6. package/dist/api.d.ts.map +1 -0
  7. package/dist/api.js +46 -0
  8. package/dist/api.js.map +1 -0
  9. package/dist/assets.d.ts +7 -0
  10. package/dist/assets.d.ts.map +1 -0
  11. package/dist/assets.js +52 -0
  12. package/dist/assets.js.map +1 -0
  13. package/dist/browser.d.ts +3 -0
  14. package/dist/browser.d.ts.map +1 -0
  15. package/dist/browser.js +3 -0
  16. package/dist/browser.js.map +1 -0
  17. package/dist/node.d.ts +3 -0
  18. package/dist/node.d.ts.map +1 -0
  19. package/dist/node.js +10 -0
  20. package/dist/node.js.map +1 -0
  21. package/dist/options.d.ts +24 -0
  22. package/dist/options.d.ts.map +1 -0
  23. package/dist/options.js +135 -0
  24. package/dist/options.js.map +1 -0
  25. package/dist/types.d.ts +51 -0
  26. package/dist/types.d.ts.map +1 -0
  27. package/dist/types.js +2 -0
  28. package/dist/types.js.map +1 -0
  29. package/examples/bun.ts +8 -0
  30. package/examples/german-invoice/README.md +70 -0
  31. package/examples/german-invoice/data.mjs +83 -0
  32. package/examples/german-invoice/invoice.css +59 -0
  33. package/examples/german-invoice/template.mjs +49 -0
  34. package/examples/german-invoice.mjs +20 -0
  35. package/examples/invoice.html +29 -0
  36. package/examples/node.mjs +8 -0
  37. package/examples/pagination.html +40 -0
  38. package/examples/pagination.mjs +8 -0
  39. package/licenses/DejaVu.txt +187 -0
  40. package/licenses/RUST_DEPENDENCIES.txt +11577 -0
  41. package/package.json +89 -4
  42. package/src/api.ts +44 -0
  43. package/src/assets.ts +43 -0
  44. package/src/browser.ts +6 -0
  45. package/src/node.ts +12 -0
  46. package/src/options.ts +101 -0
  47. package/src/types.ts +49 -0
  48. package/vendor/README.md +17 -0
  49. package/vendor/stylo/.cargo-ok +1 -0
  50. package/vendor/stylo/.cargo_vcs_info.json +6 -0
  51. package/vendor/stylo/Cargo.lock +1485 -0
  52. package/vendor/stylo/Cargo.toml +274 -0
  53. package/vendor/stylo/LICENSE +373 -0
  54. package/vendor/stylo/README.md +6 -0
  55. package/vendor/stylo/applicable_declarations.rs +328 -0
  56. package/vendor/stylo/author_styles.rs +79 -0
  57. package/vendor/stylo/bezier.rs +176 -0
  58. package/vendor/stylo/bloom.rs +414 -0
  59. package/vendor/stylo/build.rs +91 -0
  60. package/vendor/stylo/build_gecko.rs +393 -0
  61. package/vendor/stylo/color/color_function.rs +601 -0
  62. package/vendor/stylo/color/component.rs +207 -0
  63. package/vendor/stylo/color/convert.rs +935 -0
  64. package/vendor/stylo/color/gamut/raytrace.rs +265 -0
  65. package/vendor/stylo/color/gamut.rs +227 -0
  66. package/vendor/stylo/color/mix.rs +645 -0
  67. package/vendor/stylo/color/mod.rs +750 -0
  68. package/vendor/stylo/color/parsing.rs +770 -0
  69. package/vendor/stylo/color/to_css.rs +297 -0
  70. package/vendor/stylo/context.rs +716 -0
  71. package/vendor/stylo/counter_style/mod.rs +754 -0
  72. package/vendor/stylo/counter_style/predefined.rs +61 -0
  73. package/vendor/stylo/counter_style/update_predefined.py +35 -0
  74. package/vendor/stylo/custom_properties.rs +2023 -0
  75. package/vendor/stylo/custom_properties_map.rs +220 -0
  76. package/vendor/stylo/data.rs +693 -0
  77. package/vendor/stylo/device/gecko.rs +486 -0
  78. package/vendor/stylo/device/mod.rs +304 -0
  79. package/vendor/stylo/device/servo.rs +507 -0
  80. package/vendor/stylo/dom.rs +1164 -0
  81. package/vendor/stylo/dom_apis.rs +937 -0
  82. package/vendor/stylo/driver.rs +172 -0
  83. package/vendor/stylo/error_reporting.rs +484 -0
  84. package/vendor/stylo/font_face.rs +558 -0
  85. package/vendor/stylo/font_metrics.rs +124 -0
  86. package/vendor/stylo/gecko/anon_boxes.toml +113 -0
  87. package/vendor/stylo/gecko/arc_types.rs +211 -0
  88. package/vendor/stylo/gecko/conversions.rs +59 -0
  89. package/vendor/stylo/gecko/data.rs +222 -0
  90. package/vendor/stylo/gecko/media_features.rs +971 -0
  91. package/vendor/stylo/gecko/mod.rs +20 -0
  92. package/vendor/stylo/gecko/non_ts_pseudo_class_list.rs +119 -0
  93. package/vendor/stylo/gecko/pseudo_element.rs +640 -0
  94. package/vendor/stylo/gecko/pseudo_element_definition.mako.rs +298 -0
  95. package/vendor/stylo/gecko/pseudo_elements.py +137 -0
  96. package/vendor/stylo/gecko/pseudo_elements.toml +197 -0
  97. package/vendor/stylo/gecko/regen_pseudo_elements.py +33 -0
  98. package/vendor/stylo/gecko/restyle_damage.rs +127 -0
  99. package/vendor/stylo/gecko/selector_parser.rs +627 -0
  100. package/vendor/stylo/gecko/snapshot.rs +202 -0
  101. package/vendor/stylo/gecko/snapshot_helpers.rs +334 -0
  102. package/vendor/stylo/gecko/traversal.rs +52 -0
  103. package/vendor/stylo/gecko/wrapper.rs +2258 -0
  104. package/vendor/stylo/gecko_bindings/mod.rs +34 -0
  105. package/vendor/stylo/gecko_bindings/sugar/mod.rs +12 -0
  106. package/vendor/stylo/gecko_bindings/sugar/ns_com_ptr.rs +15 -0
  107. package/vendor/stylo/gecko_bindings/sugar/ns_compatibility.rs +19 -0
  108. package/vendor/stylo/gecko_bindings/sugar/ns_style_auto_array.rs +111 -0
  109. package/vendor/stylo/gecko_bindings/sugar/origin_flags.rs +31 -0
  110. package/vendor/stylo/gecko_bindings/sugar/ownership.rs +70 -0
  111. package/vendor/stylo/gecko_bindings/sugar/refptr.rs +299 -0
  112. package/vendor/stylo/gecko_string_cache/mod.rs +543 -0
  113. package/vendor/stylo/gecko_string_cache/namespace.rs +106 -0
  114. package/vendor/stylo/global_style_data.rs +224 -0
  115. package/vendor/stylo/invalidation/element/document_state.rs +156 -0
  116. package/vendor/stylo/invalidation/element/element_wrapper.rs +432 -0
  117. package/vendor/stylo/invalidation/element/invalidation_map.rs +1631 -0
  118. package/vendor/stylo/invalidation/element/invalidator.rs +1470 -0
  119. package/vendor/stylo/invalidation/element/mod.rs +13 -0
  120. package/vendor/stylo/invalidation/element/relative_selector.rs +1399 -0
  121. package/vendor/stylo/invalidation/element/restyle_hints.rs +226 -0
  122. package/vendor/stylo/invalidation/element/state_and_attributes.rs +756 -0
  123. package/vendor/stylo/invalidation/media_queries.rs +144 -0
  124. package/vendor/stylo/invalidation/mod.rs +10 -0
  125. package/vendor/stylo/invalidation/stylesheets.rs +745 -0
  126. package/vendor/stylo/invalidation/viewport_units.rs +71 -0
  127. package/vendor/stylo/lib.rs +336 -0
  128. package/vendor/stylo/logical_geometry.rs +1684 -0
  129. package/vendor/stylo/macros.rs +70 -0
  130. package/vendor/stylo/matching.rs +1226 -0
  131. package/vendor/stylo/media_queries/media_list.rs +168 -0
  132. package/vendor/stylo/media_queries/media_query.rs +190 -0
  133. package/vendor/stylo/media_queries/mod.rs +13 -0
  134. package/vendor/stylo/parallel.rs +205 -0
  135. package/vendor/stylo/parser.rs +276 -0
  136. package/vendor/stylo/piecewise_linear.rs +282 -0
  137. package/vendor/stylo/prefs.rs +33 -0
  138. package/vendor/stylo/properties/build.py +119 -0
  139. package/vendor/stylo/properties/cascade.rs +2704 -0
  140. package/vendor/stylo/properties/computed_value_flags.rs +206 -0
  141. package/vendor/stylo/properties/counted_unknown_properties.py +104 -0
  142. package/vendor/stylo/properties/counter_style_descriptors.toml +45 -0
  143. package/vendor/stylo/properties/data.py +1375 -0
  144. package/vendor/stylo/properties/declaration_block.rs +1788 -0
  145. package/vendor/stylo/properties/font_face_descriptors.toml +50 -0
  146. package/vendor/stylo/properties/gecko.mako.rs +1555 -0
  147. package/vendor/stylo/properties/helpers/animated_properties.mako.rs +800 -0
  148. package/vendor/stylo/properties/helpers.mako.rs +812 -0
  149. package/vendor/stylo/properties/longhands.toml +4305 -0
  150. package/vendor/stylo/properties/mod.rs +1857 -0
  151. package/vendor/stylo/properties/properties.html.mako +31 -0
  152. package/vendor/stylo/properties/properties.mako.rs +3103 -0
  153. package/vendor/stylo/properties/property_descriptors.toml +18 -0
  154. package/vendor/stylo/properties/shorthands.rs +4241 -0
  155. package/vendor/stylo/properties/shorthands.toml +575 -0
  156. package/vendor/stylo/properties/vendored_python/mako-1.3.10-py3-none-any.whl +0 -0
  157. package/vendor/stylo/properties/vendored_python/markupsafe/LICENSE.txt +28 -0
  158. package/vendor/stylo/properties/vendored_python/markupsafe/__init__.py +384 -0
  159. package/vendor/stylo/properties/vendored_python/toml-0.10.2-py2.py3-none-any.whl +0 -0
  160. package/vendor/stylo/properties/view_transition_descriptors.toml +11 -0
  161. package/vendor/stylo/properties_and_values/mod.rs +12 -0
  162. package/vendor/stylo/properties_and_values/registry.rs +65 -0
  163. package/vendor/stylo/properties_and_values/rule.rs +331 -0
  164. package/vendor/stylo/properties_and_values/syntax/ascii.rs +60 -0
  165. package/vendor/stylo/properties_and_values/syntax/data_type.rs +146 -0
  166. package/vendor/stylo/properties_and_values/syntax/mod.rs +437 -0
  167. package/vendor/stylo/properties_and_values/value.rs +758 -0
  168. package/vendor/stylo/queries/condition.rs +996 -0
  169. package/vendor/stylo/queries/feature.rs +199 -0
  170. package/vendor/stylo/queries/feature_expression.rs +1170 -0
  171. package/vendor/stylo/queries/mod.rs +19 -0
  172. package/vendor/stylo/queries/values.rs +46 -0
  173. package/vendor/stylo/rule_cache.rs +267 -0
  174. package/vendor/stylo/rule_collector.rs +554 -0
  175. package/vendor/stylo/rule_tree/core.rs +787 -0
  176. package/vendor/stylo/rule_tree/level.rs +396 -0
  177. package/vendor/stylo/rule_tree/map.rs +201 -0
  178. package/vendor/stylo/rule_tree/mod.rs +427 -0
  179. package/vendor/stylo/rule_tree/source.rs +80 -0
  180. package/vendor/stylo/rule_tree/unsafe_box.rs +78 -0
  181. package/vendor/stylo/scoped_tls.rs +80 -0
  182. package/vendor/stylo/selector_map.rs +1050 -0
  183. package/vendor/stylo/selector_parser.rs +305 -0
  184. package/vendor/stylo/servo/animation.rs +1922 -0
  185. package/vendor/stylo/servo/attr.rs +714 -0
  186. package/vendor/stylo/servo/encoding_support.rs +81 -0
  187. package/vendor/stylo/servo/media_features.rs +254 -0
  188. package/vendor/stylo/servo/mod.rs +16 -0
  189. package/vendor/stylo/servo/restyle_damage.rs +215 -0
  190. package/vendor/stylo/servo/selector_parser.rs +1036 -0
  191. package/vendor/stylo/servo/shadow_parts.rs +207 -0
  192. package/vendor/stylo/shared_lock.rs +271 -0
  193. package/vendor/stylo/sharing/checks.rs +281 -0
  194. package/vendor/stylo/sharing/mod.rs +1000 -0
  195. package/vendor/stylo/simple_buckets_map.rs +52 -0
  196. package/vendor/stylo/str.rs +168 -0
  197. package/vendor/stylo/style_adjuster.rs +1086 -0
  198. package/vendor/stylo/style_resolver.rs +658 -0
  199. package/vendor/stylo/stylesheet_set.rs +658 -0
  200. package/vendor/stylo/stylesheets/appearance_base_rule.rs +51 -0
  201. package/vendor/stylo/stylesheets/container_rule.rs +769 -0
  202. package/vendor/stylo/stylesheets/counter_style_rule.rs +7 -0
  203. package/vendor/stylo/stylesheets/document_rule.rs +272 -0
  204. package/vendor/stylo/stylesheets/font_face_rule.rs +7 -0
  205. package/vendor/stylo/stylesheets/font_feature_values_rule.rs +481 -0
  206. package/vendor/stylo/stylesheets/font_palette_values_rule.rs +304 -0
  207. package/vendor/stylo/stylesheets/import_rule.rs +259 -0
  208. package/vendor/stylo/stylesheets/keyframes_rule.rs +733 -0
  209. package/vendor/stylo/stylesheets/layer_rule.rs +221 -0
  210. package/vendor/stylo/stylesheets/loader.rs +29 -0
  211. package/vendor/stylo/stylesheets/margin_rule.rs +190 -0
  212. package/vendor/stylo/stylesheets/media_rule.rs +191 -0
  213. package/vendor/stylo/stylesheets/mod.rs +918 -0
  214. package/vendor/stylo/stylesheets/namespace_rule.rs +43 -0
  215. package/vendor/stylo/stylesheets/nested_declarations_rule.rs +51 -0
  216. package/vendor/stylo/stylesheets/origin.rs +249 -0
  217. package/vendor/stylo/stylesheets/page_rule.rs +354 -0
  218. package/vendor/stylo/stylesheets/position_try_rule.rs +68 -0
  219. package/vendor/stylo/stylesheets/property_rule.rs +5 -0
  220. package/vendor/stylo/stylesheets/rule_list.rs +167 -0
  221. package/vendor/stylo/stylesheets/rule_parser.rs +1189 -0
  222. package/vendor/stylo/stylesheets/rules_iterator.rs +397 -0
  223. package/vendor/stylo/stylesheets/scope_rule.rs +511 -0
  224. package/vendor/stylo/stylesheets/starting_style_rule.rs +54 -0
  225. package/vendor/stylo/stylesheets/style_rule.rs +80 -0
  226. package/vendor/stylo/stylesheets/stylesheet.rs +570 -0
  227. package/vendor/stylo/stylesheets/supports_rule.rs +486 -0
  228. package/vendor/stylo/stylesheets/view_transition_rule.rs +81 -0
  229. package/vendor/stylo/stylist.rs +5076 -0
  230. package/vendor/stylo/thread_state.rs +87 -0
  231. package/vendor/stylo/traversal.rs +765 -0
  232. package/vendor/stylo/traversal_flags.rs +68 -0
  233. package/vendor/stylo/typed_om/mod.rs +981 -0
  234. package/vendor/stylo/typed_om/numeric.rs +128 -0
  235. package/vendor/stylo/typed_om/numeric_declaration.rs +93 -0
  236. package/vendor/stylo/typed_om/numeric_type.rs +640 -0
  237. package/vendor/stylo/typed_om/sum_value.rs +426 -0
  238. package/vendor/stylo/url/gecko.rs +367 -0
  239. package/vendor/stylo/url/mod.rs +71 -0
  240. package/vendor/stylo/url/servo.rs +239 -0
  241. package/vendor/stylo/use_counters/mod.rs +137 -0
  242. package/vendor/stylo/values/animated/color.rs +103 -0
  243. package/vendor/stylo/values/animated/effects.rs +26 -0
  244. package/vendor/stylo/values/animated/font.rs +37 -0
  245. package/vendor/stylo/values/animated/grid.rs +161 -0
  246. package/vendor/stylo/values/animated/lists.rs +141 -0
  247. package/vendor/stylo/values/animated/mod.rs +550 -0
  248. package/vendor/stylo/values/animated/svg.rs +46 -0
  249. package/vendor/stylo/values/animated/text.rs +58 -0
  250. package/vendor/stylo/values/animated/transform.rs +1592 -0
  251. package/vendor/stylo/values/computed/align.rs +90 -0
  252. package/vendor/stylo/values/computed/angle.rs +130 -0
  253. package/vendor/stylo/values/computed/animation.rs +124 -0
  254. package/vendor/stylo/values/computed/background.rs +13 -0
  255. package/vendor/stylo/values/computed/basic_shape.rs +267 -0
  256. package/vendor/stylo/values/computed/border.rs +163 -0
  257. package/vendor/stylo/values/computed/box.rs +267 -0
  258. package/vendor/stylo/values/computed/calc.rs +334 -0
  259. package/vendor/stylo/values/computed/color.rs +161 -0
  260. package/vendor/stylo/values/computed/column.rs +11 -0
  261. package/vendor/stylo/values/computed/corner_shape.rs +155 -0
  262. package/vendor/stylo/values/computed/counters.rs +26 -0
  263. package/vendor/stylo/values/computed/easing.rs +109 -0
  264. package/vendor/stylo/values/computed/effects.rs +30 -0
  265. package/vendor/stylo/values/computed/flex.rs +19 -0
  266. package/vendor/stylo/values/computed/font.rs +1517 -0
  267. package/vendor/stylo/values/computed/image.rs +273 -0
  268. package/vendor/stylo/values/computed/length.rs +576 -0
  269. package/vendor/stylo/values/computed/length_percentage.rs +1055 -0
  270. package/vendor/stylo/values/computed/list.rs +16 -0
  271. package/vendor/stylo/values/computed/mod.rs +1141 -0
  272. package/vendor/stylo/values/computed/motion.rs +73 -0
  273. package/vendor/stylo/values/computed/outline.rs +7 -0
  274. package/vendor/stylo/values/computed/page.rs +77 -0
  275. package/vendor/stylo/values/computed/param.rs +9 -0
  276. package/vendor/stylo/values/computed/percentage.rs +143 -0
  277. package/vendor/stylo/values/computed/position.rs +273 -0
  278. package/vendor/stylo/values/computed/ratio.rs +88 -0
  279. package/vendor/stylo/values/computed/rect.rs +19 -0
  280. package/vendor/stylo/values/computed/resolution.rs +76 -0
  281. package/vendor/stylo/values/computed/svg.rs +66 -0
  282. package/vendor/stylo/values/computed/table.rs +7 -0
  283. package/vendor/stylo/values/computed/text.rs +199 -0
  284. package/vendor/stylo/values/computed/time.rs +83 -0
  285. package/vendor/stylo/values/computed/transform.rs +559 -0
  286. package/vendor/stylo/values/computed/tree_counting.rs +30 -0
  287. package/vendor/stylo/values/computed/ui.rs +23 -0
  288. package/vendor/stylo/values/computed/url.rs +12 -0
  289. package/vendor/stylo/values/distance.rs +139 -0
  290. package/vendor/stylo/values/generics/animation.rs +363 -0
  291. package/vendor/stylo/values/generics/background.rs +65 -0
  292. package/vendor/stylo/values/generics/basic_shape.rs +1389 -0
  293. package/vendor/stylo/values/generics/border.rs +303 -0
  294. package/vendor/stylo/values/generics/box.rs +552 -0
  295. package/vendor/stylo/values/generics/calc.rs +2986 -0
  296. package/vendor/stylo/values/generics/color.rs +318 -0
  297. package/vendor/stylo/values/generics/column.rs +44 -0
  298. package/vendor/stylo/values/generics/counters.rs +320 -0
  299. package/vendor/stylo/values/generics/easing.rs +132 -0
  300. package/vendor/stylo/values/generics/effects.rs +131 -0
  301. package/vendor/stylo/values/generics/flex.rs +34 -0
  302. package/vendor/stylo/values/generics/font.rs +376 -0
  303. package/vendor/stylo/values/generics/grid.rs +962 -0
  304. package/vendor/stylo/values/generics/image.rs +653 -0
  305. package/vendor/stylo/values/generics/length.rs +656 -0
  306. package/vendor/stylo/values/generics/mod.rs +303 -0
  307. package/vendor/stylo/values/generics/motion.rs +274 -0
  308. package/vendor/stylo/values/generics/page.rs +167 -0
  309. package/vendor/stylo/values/generics/position.rs +669 -0
  310. package/vendor/stylo/values/generics/ratio.rs +88 -0
  311. package/vendor/stylo/values/generics/rect.rs +147 -0
  312. package/vendor/stylo/values/generics/size.rs +102 -0
  313. package/vendor/stylo/values/generics/svg.rs +226 -0
  314. package/vendor/stylo/values/generics/text.rs +268 -0
  315. package/vendor/stylo/values/generics/transform.rs +1157 -0
  316. package/vendor/stylo/values/generics/ui.rs +147 -0
  317. package/vendor/stylo/values/generics/url.rs +51 -0
  318. package/vendor/stylo/values/mod.rs +820 -0
  319. package/vendor/stylo/values/resolved/animation.rs +33 -0
  320. package/vendor/stylo/values/resolved/color.rs +53 -0
  321. package/vendor/stylo/values/resolved/counters.rs +45 -0
  322. package/vendor/stylo/values/resolved/mod.rs +316 -0
  323. package/vendor/stylo/values/specified/align.rs +711 -0
  324. package/vendor/stylo/values/specified/angle.rs +407 -0
  325. package/vendor/stylo/values/specified/animation.rs +921 -0
  326. package/vendor/stylo/values/specified/background.rs +270 -0
  327. package/vendor/stylo/values/specified/basic_shape.rs +986 -0
  328. package/vendor/stylo/values/specified/border.rs +430 -0
  329. package/vendor/stylo/values/specified/box.rs +2231 -0
  330. package/vendor/stylo/values/specified/calc.rs +1657 -0
  331. package/vendor/stylo/values/specified/color.rs +1210 -0
  332. package/vendor/stylo/values/specified/column.rs +11 -0
  333. package/vendor/stylo/values/specified/corner_shape.rs +152 -0
  334. package/vendor/stylo/values/specified/counters.rs +242 -0
  335. package/vendor/stylo/values/specified/easing.rs +225 -0
  336. package/vendor/stylo/values/specified/effects.rs +421 -0
  337. package/vendor/stylo/values/specified/flex.rs +42 -0
  338. package/vendor/stylo/values/specified/font.rs +2103 -0
  339. package/vendor/stylo/values/specified/frequency.rs +39 -0
  340. package/vendor/stylo/values/specified/grid.rs +407 -0
  341. package/vendor/stylo/values/specified/image.rs +1385 -0
  342. package/vendor/stylo/values/specified/intersection_observer.rs +80 -0
  343. package/vendor/stylo/values/specified/length.rs +2024 -0
  344. package/vendor/stylo/values/specified/list.rs +190 -0
  345. package/vendor/stylo/values/specified/mod.rs +531 -0
  346. package/vendor/stylo/values/specified/motion.rs +316 -0
  347. package/vendor/stylo/values/specified/number.rs +603 -0
  348. package/vendor/stylo/values/specified/outline.rs +69 -0
  349. package/vendor/stylo/values/specified/page.rs +94 -0
  350. package/vendor/stylo/values/specified/param.rs +121 -0
  351. package/vendor/stylo/values/specified/percentage.rs +364 -0
  352. package/vendor/stylo/values/specified/position.rs +2247 -0
  353. package/vendor/stylo/values/specified/ratio.rs +29 -0
  354. package/vendor/stylo/values/specified/rect.rs +28 -0
  355. package/vendor/stylo/values/specified/resolution.rs +237 -0
  356. package/vendor/stylo/values/specified/source_size_list.rs +135 -0
  357. package/vendor/stylo/values/specified/svg.rs +427 -0
  358. package/vendor/stylo/values/specified/svg_path.rs +1115 -0
  359. package/vendor/stylo/values/specified/table.rs +38 -0
  360. package/vendor/stylo/values/specified/text.rs +1561 -0
  361. package/vendor/stylo/values/specified/time.rs +313 -0
  362. package/vendor/stylo/values/specified/transform.rs +599 -0
  363. package/vendor/stylo/values/specified/tree_counting.rs +32 -0
  364. package/vendor/stylo/values/specified/ui.rs +344 -0
  365. package/vendor/stylo/values/specified/url.rs +12 -0
  366. package/vendor/stylo/values/tagged_numeric.rs +253 -0
  367. package/wasm/blitz_pdf.d.ts +67 -0
  368. package/wasm/blitz_pdf.js +699 -0
  369. package/wasm/blitz_pdf_bg.wasm +0 -0
  370. package/wasm/blitz_pdf_bg.wasm.d.ts +20 -0
@@ -0,0 +1,135 @@
1
+ import { assetBytes, copyBytes, MAX_ASSET_BYTES } from "./assets.js";
2
+ const formats = {
3
+ A3: [297 * 96 / 25.4, 420 * 96 / 25.4],
4
+ A4: [210 * 96 / 25.4, 297 * 96 / 25.4],
5
+ A5: [148 * 96 / 25.4, 210 * 96 / 25.4],
6
+ Letter: [8.5 * 96, 11 * 96], Legal: [8.5 * 96, 14 * 96], Tabloid: [11 * 96, 17 * 96],
7
+ };
8
+ const optionKeys = new Set(["format", "width", "height", "landscape", "margin", "media", "baseUrl", "assets", "fonts", "title", "pageNumbers", "maxPages"]);
9
+ const unitScale = { px: 1, pt: 96 / 72, mm: 96 / 25.4, cm: 96 / 2.54, in: 96 };
10
+ function length(value, name) {
11
+ let result;
12
+ if (typeof value === "number")
13
+ result = value;
14
+ else if (typeof value === "string") {
15
+ const match = /^\s*(\d+(?:\.\d+)?|\.\d+)(px|pt|mm|cm|in)\s*$/.exec(value);
16
+ if (!match)
17
+ throw new TypeError(`${name} must be a number of CSS pixels or a length in px, pt, mm, cm, or in`);
18
+ result = Number(match[1]) * unitScale[match[2]];
19
+ }
20
+ else
21
+ throw new TypeError(`${name} must be a CSS length`);
22
+ if (!Number.isFinite(result) || result < 0)
23
+ throw new RangeError(`${name} must be finite and non-negative`);
24
+ return result;
25
+ }
26
+ function record(value, name) {
27
+ if (value === null || typeof value !== "object" || Array.isArray(value) ||
28
+ ![Object.prototype, null].includes(Object.getPrototypeOf(value))) {
29
+ throw new TypeError(`${name} must be a plain object`);
30
+ }
31
+ }
32
+ export function normalizeOptions(options = {}) {
33
+ record(options, "options");
34
+ for (const key of Object.keys(options)) {
35
+ if (!optionKeys.has(key))
36
+ throw new TypeError(`Unknown PDF option: ${key}`);
37
+ }
38
+ if (options.landscape !== undefined && typeof options.landscape !== "boolean")
39
+ throw new TypeError("landscape must be a boolean");
40
+ const format = options.format ?? "A4";
41
+ if (!Object.hasOwn(formats, format))
42
+ throw new TypeError(`Unknown page format: ${String(format)}`);
43
+ if ((options.width === undefined) !== (options.height === undefined))
44
+ throw new TypeError("width and height must be provided together");
45
+ if (options.format !== undefined && options.width !== undefined)
46
+ throw new TypeError("Use either format or custom width and height");
47
+ let [width, height] = options.width === undefined
48
+ ? formats[format] : [length(options.width, "width"), length(options.height, "height")];
49
+ if (options.landscape)
50
+ [width, height] = [height, width];
51
+ if (width <= 0 || height <= 0 || width > 19200 || height > 19200)
52
+ throw new RangeError("Page dimensions must be positive and at most 19200 CSS pixels");
53
+ const inputMargin = options.margin ?? 0;
54
+ const margin = { top: 0, right: 0, bottom: 0, left: 0 };
55
+ if (typeof inputMargin === "object") {
56
+ record(inputMargin, "margin");
57
+ for (const key of Object.keys(inputMargin))
58
+ if (!Object.hasOwn(margin, key))
59
+ throw new TypeError(`Unknown margin side: ${key}`);
60
+ for (const side of Object.keys(margin))
61
+ margin[side] = length(inputMargin[side] ?? 0, `margin.${side}`);
62
+ }
63
+ else {
64
+ const value = length(inputMargin, "margin");
65
+ for (const side of Object.keys(margin))
66
+ margin[side] = value;
67
+ }
68
+ let pageNumbers = null;
69
+ if (options.pageNumbers !== undefined && options.pageNumbers !== false) {
70
+ const input = options.pageNumbers === true ? {} : options.pageNumbers;
71
+ record(input, "pageNumbers");
72
+ for (const key of Object.keys(input))
73
+ if (!["template", "align", "fontSize"].includes(key))
74
+ throw new TypeError(`Unknown pageNumbers option: ${key}`);
75
+ const template = input.template ?? "{page} / {pages}";
76
+ if (typeof template !== "string" || template.length > 512 || /[\u0000-\u001f\u007f\u0085\u2028\u2029]/u.test(template))
77
+ throw new TypeError("pageNumbers.template must be a single line of at most 512 characters");
78
+ if (!template.includes("{page}") || /[{}]/.test(template.replaceAll("{page}", "").replaceAll("{pages}", "")))
79
+ throw new TypeError("pageNumbers.template must include {page}; only {page} and {pages} placeholders are supported");
80
+ const align = input.align ?? "right";
81
+ if (!["left", "center", "right"].includes(align))
82
+ throw new TypeError("pageNumbers.align must be left, center, or right");
83
+ const fontSize = length(input.fontSize ?? 12, "pageNumbers.fontSize");
84
+ if (fontSize < 1 || fontSize > 48)
85
+ throw new RangeError("pageNumbers.fontSize must be between 1 and 48 CSS pixels");
86
+ pageNumbers = { template, align, fontSize };
87
+ }
88
+ const footerHeight = pageNumbers ? pageNumbers.fontSize * 2 + 8 : 0;
89
+ if (width - margin.left - margin.right < 1 || height - margin.top - margin.bottom - footerHeight < 1)
90
+ throw new RangeError("Margins and pageNumbers must leave at least one CSS pixel of content");
91
+ const media = options.media ?? "print";
92
+ if (media !== "print" && media !== "screen")
93
+ throw new TypeError("media must be print or screen");
94
+ const maxPages = options.maxPages ?? 100;
95
+ if (!Number.isInteger(maxPages) || maxPages < 1 || maxPages > 1000)
96
+ throw new RangeError("maxPages must be an integer between 1 and 1000");
97
+ if (options.title !== undefined && typeof options.title !== "string")
98
+ throw new TypeError("title must be a string");
99
+ const baseUrl = options.baseUrl ?? "https://blitz-pdf.invalid/";
100
+ if (typeof baseUrl !== "string")
101
+ throw new TypeError("baseUrl must be an absolute URL string");
102
+ let base;
103
+ try {
104
+ base = new URL(baseUrl);
105
+ }
106
+ catch {
107
+ throw new TypeError("baseUrl must be an absolute URL string");
108
+ }
109
+ if (!["http:", "https:", "file:"].includes(base.protocol))
110
+ throw new TypeError("baseUrl must use http:, https:, or file:");
111
+ const assets = Object.create(null);
112
+ const inputAssets = options.assets;
113
+ const assetResolver = typeof inputAssets === "function" ? inputAssets : undefined;
114
+ if (inputAssets !== undefined && !assetResolver) {
115
+ record(inputAssets, "assets");
116
+ let size = 0;
117
+ for (const [key, value] of Object.entries(inputAssets)) {
118
+ const data = assetBytes(value, `assets[${key}]`);
119
+ size += data.byteLength;
120
+ if (size > MAX_ASSET_BYTES)
121
+ throw new RangeError("Assets exceed the 64 MiB limit");
122
+ const url = new URL(key, base).href;
123
+ if (Object.hasOwn(assets, url))
124
+ throw new TypeError(`Duplicate resolved asset URL: ${url}`);
125
+ assets[url] = data;
126
+ }
127
+ }
128
+ if (options.fonts !== undefined && !Array.isArray(options.fonts))
129
+ throw new TypeError("fonts must be an array of font bytes");
130
+ const fonts = (options.fonts ?? []).map((font, i) => copyBytes(font, `fonts[${i}]`));
131
+ if (fonts.reduce((total, font) => total + font.byteLength, 0) > 32 * 1024 * 1024)
132
+ throw new RangeError("Fonts exceed the 32 MiB limit");
133
+ return { width, height, margin, media, maxPages, baseUrl: base.href, assets, assetResolver, fonts, pageNumbers, title: options.title ?? null };
134
+ }
135
+ //# sourceMappingURL=options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /** Numbers are CSS pixels (96 px = 1 inch). */
2
+ export type Length = number | `${number}${"px" | "pt" | "mm" | "cm" | "in"}`;
3
+ export type PageFormat = "A3" | "A4" | "A5" | "Letter" | "Legal" | "Tabloid";
4
+ export type BinaryData = Uint8Array | ArrayBuffer;
5
+ /** UTF-8 text (for CSS/SVG) or binary resource contents. */
6
+ export type AssetData = BinaryData | string;
7
+ export type AssetMap = Record<string, AssetData>;
8
+ /** Receives an absolute URL. Returning null/undefined rejects the render as a missing asset. */
9
+ export type AssetResolver = (url: string) => AssetData | null | undefined | Promise<AssetData | null | undefined>;
10
+ export type WasmSource = BinaryData | WebAssembly.Module | URL | Request | Response | string;
11
+ export interface InitOptions {
12
+ /** Override the bundled WASM location, or supply bytes/a compiled module. */
13
+ wasm?: WasmSource;
14
+ }
15
+ export interface PageNumberOptions {
16
+ /** Single line with {page} and optionally {pages}. Default: "{page} / {pages}". */
17
+ template?: string;
18
+ /** Default: right. */
19
+ align?: "left" | "center" | "right";
20
+ /** Default: 12 CSS pixels (9pt). */
21
+ fontSize?: Length;
22
+ }
23
+ export interface PdfOptions {
24
+ /** Default: A4. Use width and height together for a custom page. */
25
+ format?: PageFormat;
26
+ width?: Length;
27
+ height?: Length;
28
+ landscape?: boolean;
29
+ /** Default: 0. Missing sides of an object default to 0. */
30
+ margin?: Length | {
31
+ top?: Length;
32
+ right?: Length;
33
+ bottom?: Length;
34
+ left?: Length;
35
+ };
36
+ /** CSS media queries to use. Default: print. */
37
+ media?: "print" | "screen";
38
+ /** Base for resolving asset URLs. Default: https://blitz-pdf.invalid/ */
39
+ baseUrl?: string;
40
+ /** Resource map or async loader, called once per absolute URL per render. Data URLs are handled internally. */
41
+ assets?: AssetMap | AssetResolver;
42
+ /** Additional static TTF, OTF, WOFF, or WOFF2 fonts, using their internal family names. */
43
+ fonts?: readonly BinaryData[];
44
+ /** PDF document title. */
45
+ title?: string;
46
+ /** Selectable page-number footer. Reserves space above the bottom margin. Default: false. */
47
+ pageNumbers?: boolean | PageNumberOptions;
48
+ /** Reject larger documents. Default: 100; maximum: 1000. */
49
+ maxPages?: number;
50
+ }
51
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;AAC7E,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAC7E,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,WAAW,CAAC;AAClD,4DAA4D;AAC5D,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;AAC5C,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AACjD,gGAAgG;AAChG,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,SAAS,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAClH,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,WAAW,CAAC,MAAM,GAAG,GAAG,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE7F,MAAM,WAAW,WAAW;IAC1B,6EAA6E;IAC7E,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sBAAsB;IACtB,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpC,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IAEzB,oEAAoE;IACpE,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,GAAG;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnF,gDAAgD;IAChD,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC3B,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+GAA+G;IAC/G,MAAM,CAAC,EAAE,QAAQ,GAAG,aAAa,CAAC;IAClC,2FAA2F;IAC3F,KAAK,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAC9B,0BAA0B;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6FAA6F;IAC7F,WAAW,CAAC,EAAE,OAAO,GAAG,iBAAiB,CAAC;IAC1C,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,8 @@
1
+ import { mkdir } from "node:fs/promises";
2
+ import { renderPdf } from "blitz-pdf";
3
+
4
+ const html = await Bun.file(new URL("./invoice.html", import.meta.url)).text();
5
+ const pdf = await renderPdf(html, { format: "A4", margin: "18mm", title: "Invoice INV-0042" });
6
+ await mkdir(new URL("../output/pdf/", import.meta.url), { recursive: true });
7
+ await Bun.write(new URL("../output/pdf/invoice.pdf", import.meta.url), pdf);
8
+ console.log(`Created output/pdf/invoice.pdf (${pdf.length} bytes).`);
@@ -0,0 +1,70 @@
1
+ # German invoice example
2
+
3
+ Run `npm run example:german-invoice` from the project root, or
4
+ `bun examples/german-invoice.mjs`. Output: `output/pdf/german-invoice.pdf`.
5
+
6
+ The sample contains three A4 pages and 24 software project services. It uses
7
+ the public `renderPdf` API, an async stylesheet loader, CSS page breaks between
8
+ project sections, protected table rows, and automatic page-number footers.
9
+ No PDF editing step is needed after rendering. Numbers, invoice text, and
10
+ amounts remain selectable; the output targets PDF/A-3u without XML attachments.
11
+
12
+ Edit `data.mjs` for invoice data, `template.mjs` for HTML, and `invoice.css`
13
+ for appearance. `invoiceOptions()` configures margins and page numbers.
14
+ Page totals are computed by the renderer; they are never hard-coded in the
15
+ HTML. Longer content can add pages automatically. Each project section starts
16
+ on a new page; table headings are authored once per section. Automatic repeated
17
+ table headers across additional overflow pages are not currently supported.
18
+
19
+ Amounts are integer cents. Quantities are hundredths of an hour, and rates are
20
+ basis points (1900 = 19%). Each line is rounded half-up, then the discount is
21
+ rounded on the subtotal and VAT on the discounted net amount. The generated
22
+ `german-invoice-totals.json` contains every line and intermediate value.
23
+
24
+ | Amount | EUR |
25
+ | --- | ---: |
26
+ | Services before discount | 22.205,00 |
27
+ | Agreed project discount, 3% | -666,15 |
28
+ | Taxable net, 19% rate | 21.538,85 |
29
+ | VAT, 19% | 4.092,38 |
30
+ | Amount payable | **25.631,23** |
31
+
32
+ ## Scope of the sample
33
+
34
+ All companies, people, addresses, tax and register identifiers, and bank details
35
+ are fictitious demonstration data. The PDF is marked “Musterrechnung” and
36
+ “Nicht zur Zahlung bestimmt”; it does not issue a real invoice. Replace the
37
+ sample identifiers with the actual supplier's registered details in a real
38
+ application. The demonstration bank details do not identify a payable account.
39
+
40
+ The scenario is an ordinary domestic German B2B service invoice dated
41
+ 13 October 2026, covering services performed from 1 to 30 September 2026, with
42
+ 19% standard VAT and a previously agreed discount. It demonstrates the relevant
43
+ mandatory information: full supplier/customer names and addresses, supplier
44
+ VAT ID, issue date, a sample unique invoice number, type and extent of services,
45
+ performance period, discounted taxable net, tax rate and tax amount.
46
+ [§ 14(4) UStG](https://www.gesetze-im-internet.de/ustg_1980/__14.html),
47
+ [§ 12(1) UStG](https://www.gesetze-im-internet.de/ustg_1980/__12.html).
48
+
49
+ The supplier block also includes the GmbH's seat, register court and number,
50
+ and managing director. The fictitious company has no supervisory board.
51
+ [§ 35a GmbHG](https://www.gesetze-im-internet.de/gmbhg/__35a.html).
52
+
53
+ PDF/A describes the file's archival format. This example contains no structured
54
+ invoice XML and makes no ZUGFeRD, XRechnung or EN 16931 claim. For the stated
55
+ 2026 scenario, PDF delivery assumes the recipient's consent under the transition
56
+ rule. That assumption is not an exemption for every future invoice. Different
57
+ transactions, including reverse charge, exemptions and advance payments, need
58
+ their applicable invoice treatment.
59
+ [§ 27(38) UStG](https://www.gesetze-im-internet.de/ustg_1980/__27.html).
60
+
61
+ ## Verification
62
+
63
+ `bun test` checks the arithmetic, required sample fields, all 24 rows on their
64
+ intended pages, correct footers, selectable text, absence of raster pages and
65
+ XML attachments, and spacing between body text and footers.
66
+
67
+ `npm run test:pdfa` captures the invoice along with the other integration PDFs
68
+ and validates them using veraPDF's PDF/A-3u profile. The validation reports are
69
+ under `output/pdfa-validation/`. The content tests check this example's fields
70
+ and calculations; the archival validator does not certify tax-law compliance.
@@ -0,0 +1,83 @@
1
+ // Entirely fictitious demonstration data. No invoice is issued by this example.
2
+ export const invoice = {
3
+ number: "RE-2026-1042",
4
+ issueDate: "2026-10-13",
5
+ dueDate: "2026-10-27",
6
+ serviceFrom: "2026-09-01",
7
+ serviceTo: "2026-09-30",
8
+ customerNumber: "KD-2087",
9
+ orderNumber: "EW-2026-0831",
10
+ project: "Kundenportal und Bestellverwaltung",
11
+ supplier: {
12
+ name: "Musterwerk Digital GmbH", street: "Musterstraße 14", city: "10115 Berlin", country: "Deutschland",
13
+ vatId: "DE123456788", managingDirector: "Clara Muster", register: "Amtsgericht Charlottenburg, HRB 999999 B",
14
+ email: "rechnung@musterwerk.example", website: "musterwerk.example",
15
+ bank: "Beispielbank (Musterdaten)", iban: "DE36 0000 0000 0000 0000 00", bic: "TESTDE00XXX",
16
+ },
17
+ customer: { name: "Elbwiese Handels GmbH", department: "Finanzbuchhaltung", street: "Beispielweg 8", city: "20457 Hamburg", country: "Deutschland" },
18
+ // All services in this domestic B2B example use the standard 19% VAT rate.
19
+ vatBasisPoints: 1900,
20
+ discountBasisPoints: 300,
21
+ sections: [
22
+ { title: "Konzeption und Gestaltung", subtitle: "Anforderungen, Abläufe und verbindliche Gestaltungsvorlagen", items: [
23
+ ["01", "Projektauftakt und Zielbild", "Workshop mit Vertrieb, Service und IT; dokumentierte Ziele und Abnahmekriterien.", 400, 13500],
24
+ ["02", "Anforderungsanalyse", "Aufnahme von Rollen, Bestellabläufen und Freigaben; abgestimmter Anforderungskatalog.", 650, 13500],
25
+ ["03", "Informationsarchitektur", "Struktur für Kundenkonten, Bestellungen, Dokumente und interne Bearbeitung.", 500, 12500],
26
+ ["04", "Prozessmodellierung", "Abläufe für Angebot, Bestellung und Statusmeldungen inklusive Sonderfällen.", 550, 12500],
27
+ ["05", "Interaktiver Prototyp", "Klickbarer Entwurf der zentralen Ansichten für Desktop und Mobilgeräte.", 800, 12500],
28
+ ["06", "Gestaltung der Oberfläche", "Ausarbeitung von Navigation, Formularen und Tabellen im vereinbarten Erscheinungsbild.", 900, 12500],
29
+ ] },
30
+ { title: "Umsetzung und Integration", subtitle: "Designsystem, Funktionen und Anbindung an bestehende Abläufe", items: [
31
+ ["07", "Komponentenbibliothek", "Wiederverwendbare Schaltflächen, Eingaben, Hinweise und Statusanzeigen.", 700, 12500],
32
+ ["08", "Designabstimmung", "Zwei gemeinsame Durchsprachen einschließlich dokumentierter Korrekturen.", 350, 12500],
33
+ ["09", "Projektgrundlage und Entwicklungsumgebung", "Repository, Build-Prozess sowie getrennte Entwicklungs- und Testumgebungen.", 500, 13500],
34
+ ["10", "Anmeldung und Rollenverwaltung", "Geschützte Anmeldung, Passwortzurücksetzung und abgestimmte Rollenrechte.", 1050, 13500],
35
+ ["11", "Kundenstammdaten", "Pflege von Kontaktdaten und Lieferadressen einschließlich Eingabeprüfung.", 800, 12500],
36
+ ["12", "Produkt- und Preisdaten", "Übernahme des vereinbarten Artikelsortiments und kundenspezifischer Preise.", 950, 13500],
37
+ ["13", "Bestellerfassung", "Warenkorb, Mengenprüfung und Erfassung interner Bestellreferenzen.", 1200, 13500],
38
+ ["14", "Freigabeprozess", "Mehrstufige Freigaben, Rückfragen und nachvollziehbare Statusübergänge.", 1000, 13500],
39
+ ["15", "Schnittstelle zur Warenwirtschaft", "Übertragung von Bestellungen und Rückübernahme vereinbarter Statusdaten.", 1400, 15000],
40
+ ["16", "Dokumentenübersicht", "Zuordnung und geschützter Abruf von Angeboten, Auftragsbestätigungen und Rechnungen.", 750, 12500],
41
+ ["17", "Benachrichtigungen", "Vorlagen und Versandlogik für Bestelleingang, Rückfragen und Freigaben.", 550, 12500],
42
+ ["18", "Responsive Umsetzung", "Anpassung der freigegebenen Ansichten an Smartphone, Tablet und Desktop.", 800, 12500],
43
+ ] },
44
+ { title: "Qualitätssicherung und Übergabe", subtitle: "Prüfung, Abnahme und Einführung des vereinbarten Lieferumfangs", items: [
45
+ ["19", "Automatisierte Funktionstests", "Tests der zentralen Bestell-, Freigabe- und Berechtigungsabläufe.", 850, 12500],
46
+ ["20", "Barrierearme Bedienung", "Prüfung von Tastaturbedienung, Fokusführung und Formularbeschriftungen.", 450, 11000],
47
+ ["21", "Leistungs- und Sicherheitstests", "Abgestimmte Lastfälle sowie Prüfung von Zugriffsschutz und Fehlermeldungen.", 600, 13500],
48
+ ["22", "Abnahme und Nacharbeiten", "Begleitung der fachlichen Abnahme und Bearbeitung des vereinbarten Prüfprotokolls.", 550, 12500],
49
+ ["23", "Dokumentation und Schulung", "Betriebshandbuch und gemeinsame Einführung der zuständigen Mitarbeitenden.", 400, 11000],
50
+ ["24", "Produktivsetzung und Übergabe", "Bereitstellung, Funktionskontrolle und dokumentierte Übergabe am 30.09.2026.", 350, 13500],
51
+ ] },
52
+ ],
53
+ };
54
+
55
+ // Tuple columns: position, name, description, hundredths of an hour, net unit price in cents.
56
+ // Integer arithmetic and half-up rounding keep amounts deterministic in Node, Bun and browsers.
57
+ function roundedRatio(amount, numerator, denominator) {
58
+ const result = (BigInt(amount) * BigInt(numerator) + BigInt(denominator) / 2n) / BigInt(denominator);
59
+ if (result > BigInt(Number.MAX_SAFE_INTEGER)) throw new RangeError("Example invoice amount is too large");
60
+ return Number(result);
61
+ }
62
+
63
+ export function calculateInvoice(data = invoice) {
64
+ let subtotalCents = 0;
65
+ const sections = data.sections.map(section => {
66
+ const carriedCents = subtotalCents;
67
+ const items = section.items.map(([position, name, description, quantityHundredths, unitPriceCents]) => {
68
+ if (!Number.isSafeInteger(quantityHundredths) || quantityHundredths <= 0 || !Number.isSafeInteger(unitPriceCents) || unitPriceCents < 0) throw new TypeError("Invoice quantities and prices must be non-negative fixed-point integers");
69
+ const netCents = roundedRatio(quantityHundredths, unitPriceCents, 100);
70
+ subtotalCents += netCents;
71
+ if (!Number.isSafeInteger(subtotalCents)) throw new RangeError("Example invoice amount is too large");
72
+ return { position, name, description, quantityHundredths, unitPriceCents, netCents };
73
+ });
74
+ return { ...section, items, carriedCents, cumulativeCents: subtotalCents };
75
+ });
76
+ for (const rate of [data.discountBasisPoints, data.vatBasisPoints]) {
77
+ if (!Number.isInteger(rate) || rate < 0 || rate > 10000) throw new RangeError("Invoice rates must be between 0 and 10000 basis points");
78
+ }
79
+ const discountCents = roundedRatio(subtotalCents, data.discountBasisPoints, 10000);
80
+ const netCents = subtotalCents - discountCents;
81
+ const vatCents = roundedRatio(netCents, data.vatBasisPoints, 10000);
82
+ return { sections, subtotalCents, discountCents, netCents, vatCents, totalCents: netCents + vatCents };
83
+ }
@@ -0,0 +1,59 @@
1
+ * { box-sizing: border-box; }
2
+ body { margin: 0; color: #172d3b; font: 11px/1.45 sans-serif; }
3
+ .masthead, .brand, .address-row, .title-row, .invoice-meta, .section-heading, .carry, .settlement, .bank, .continuation-header { display: flex; }
4
+ .masthead { justify-content: space-between; align-items: center; border-bottom: 2px solid #167d8d; padding-bottom: 15px; }
5
+ .brand { align-items: center; gap: 12px; }
6
+ .brand-mark { display: block; color: white; background: #167d8d; border-radius: 8px; font-size: 27px; font-weight: bold; line-height: 42px; text-align: center; width: 42px; height: 42px; }
7
+ .brand strong { font-size: 21px; letter-spacing: 1px; }
8
+ .brand small { display: block; color: #637480; font-size: 10px; }
9
+ .contact { color: #637480; text-align: right; }
10
+ .sample-note { margin-top: 10px; color: #637480; font-size: 9px; letter-spacing: .3px; }
11
+ .address-row { justify-content: space-between; margin: 22px 0 20px; }
12
+ .sender-line { margin: 0 0 12px; font-size: 8px; color: #637480; }
13
+ address { font-style: normal; font-size: 12px; line-height: 1.55; }
14
+ .supplier-address { font-size: 10px; line-height: 1.65; }
15
+ .title-row { align-items: baseline; justify-content: space-between; }
16
+ h1 { margin: 0; font-size: 31px; line-height: 1.2; }
17
+ .title-row > span { font-weight: bold; font-size: 16px; }
18
+ .invoice-meta { margin-top: 15px; padding: 12px 14px; background: #edf5f5; justify-content: space-between; }
19
+ .invoice-meta span, .bank span { display: block; font-size: 9px; color: #637480; margin-bottom: 3px; }
20
+ .project { margin: 16px 0 18px; line-height: 1.65; }
21
+ .project > strong { font-size: 12px; }
22
+ .continuation { break-before: page; }
23
+ .continuation-header { justify-content: space-between; align-items: center; border-bottom: 2px solid #167d8d; padding-bottom: 16px; margin-bottom: 24px; }
24
+ .continuation-header > strong { font-size: 18px; }
25
+ .continuation-header > strong > span { font-size: 11px; font-weight: normal; }
26
+ .continuation-header > div { text-align: right; font-size: 10px; }
27
+ .section-heading { align-items: center; gap: 12px; margin: 0 0 12px; break-after: avoid; }
28
+ .section-index { color: #167d8d; font-size: 26px; line-height: 1; font-weight: bold; }
29
+ h2 { font-size: 14px; line-height: 1.3; margin: 0 0 3px; }
30
+ .section-heading p { margin: 0; font-size: 10px; color: #637480; }
31
+ table { width: 100%; border-collapse: collapse; }
32
+ .col-position { width: 6%; } .col-description { width: 49%; } .col-quantity { width: 10%; } .col-unit { width: 16%; } .col-total { width: 19%; }
33
+ th { padding: 8px 6px; border-bottom: 1px solid #aac6cc; background: #edf5f5; color: #43616e; font-size: 9px; font-weight: bold; text-align: left; }
34
+ .items td { vertical-align: top; padding: 9px 6px; border-bottom: 1px solid #dce6e8; }
35
+ .items tbody tr { break-inside: avoid; }
36
+ .items td strong { font-size: 10px; font-weight: bold; }
37
+ .description { margin-top: 3px; font-size: 9px; color: #637480; line-height: 1.45; }
38
+ .position { font-size: 10px; color: #637480; }
39
+ .numeric { text-align: right; white-space: nowrap; font-size: 10px; }
40
+ .amount { font-weight: bold; }
41
+ .carry { justify-content: space-between; padding: 9px 6px; font-size: 10px; border-top: 1px solid #dce6e8; }
42
+ .after-table { border-top: 0; background: #f4f7f8; }
43
+ .continuation-note { font-size: 9px; color: #637480; margin: 13px 0 22px; }
44
+ .company-details { border-top: 1px solid #dce6e8; padding-top: 10px; color: #637480; font-size: 8px; line-height: 1.65; break-inside: avoid; }
45
+ .settlement { gap: 28px; margin-top: 20px; align-items: flex-start; break-inside: avoid; }
46
+ .tax-note { flex: 1; color: #637480; font-size: 9px; }
47
+ .tax-note > strong { color: #172d3b; font-size: 11px; }
48
+ .tax-note p { margin: 8px 0 0; }
49
+ .totals { width: 62%; font-size: 11px; }
50
+ .totals td { padding: 5px 0; }
51
+ .totals td:last-child { text-align: right; white-space: nowrap; font-weight: bold; }
52
+ .totals .net td { border-top: 1px solid #dce6e8; padding-top: 9px; }
53
+ .totals .grand-total td { padding: 12px 0; font-size: 16px; color: #126a78; border-top: 2px solid #167d8d; }
54
+ .payment { background: #edf5f5; padding: 14px 16px; margin-top: 18px; break-inside: avoid; }
55
+ h3 { font-size: 11px; margin: 0; }
56
+ .payment p { margin: 7px 0 12px; font-size: 10px; line-height: 1.6; }
57
+ .bank { justify-content: space-between; gap: 10px; font-size: 9px; }
58
+ .closing { margin: 18px 0; font-size: 10px; }
59
+ p { widows: 2; orphans: 2; }
@@ -0,0 +1,49 @@
1
+ import { invoice, calculateInvoice } from "./data.mjs";
2
+
3
+ export const money = cents => new Intl.NumberFormat("de-DE", { style: "currency", currency: "EUR" }).format(cents / 100);
4
+ const quantity = hundredths => new Intl.NumberFormat("de-DE", { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(hundredths / 100);
5
+ const percent = basisPoints => new Intl.NumberFormat("de-DE", { maximumFractionDigits: 2 }).format(basisPoints / 100) + " %";
6
+ const date = value => value.split("-").reverse().join(".");
7
+ const escape = value => String(value).replace(/[&<>"']/g, ch => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[ch]);
8
+ const e = escape;
9
+
10
+ export function invoiceOptions(data = invoice) {
11
+ return {
12
+ format: "A4", margin: { top: "16mm", right: "17mm", bottom: "12mm", left: "17mm" },
13
+ title: `Musterrechnung ${data.number} - ${data.customer.name}`,
14
+ pageNumbers: { template: `${data.number} · Musterrechnung · Seite {page} von {pages}`, fontSize: "8pt", align: "right" },
15
+ };
16
+ }
17
+
18
+ export function createInvoiceHtml(data = invoice) {
19
+ const totals = calculateInvoice(data);
20
+ const s = data.supplier;
21
+ const c = data.customer;
22
+ const identity = `<div class="company-details"><strong>${e(s.name)}</strong> · Sitz: Berlin · Geschäftsführung: ${e(s.managingDirector)}<br>${e(s.register)} · USt-IdNr.: ${e(s.vatId)}</div>`;
23
+ const sections = totals.sections.map((section, index) => {
24
+ const first = index === 0;
25
+ const last = index === totals.sections.length - 1;
26
+ return `<section class="invoice-section ${first ? "first" : "continuation"}">
27
+ ${first ? `
28
+ <header class="masthead"><div class="brand"><span class="brand-mark">M</span><div><strong>MUSTERWERK</strong><small>Digitalprodukte. Klar umgesetzt.</small></div></div><div class="contact">${e(s.email)}<br>${e(s.website)}</div></header>
29
+ <div class="sample-note">MUSTERRECHNUNG · Fiktive Geschäftsdaten · Nicht zur Zahlung bestimmt</div>
30
+ <div class="address-row"><div><p class="sender-line">${e(s.name)} · ${e(s.street)} · ${e(s.city)}</p><address><strong>${e(c.name)}</strong><br>${e(c.department)}<br>${e(c.street)}<br>${e(c.city)}<br>${e(c.country)}</address></div>
31
+ <div class="supplier-address"><strong>Rechnungssteller</strong><br>${e(s.name)}<br>${e(s.street)}<br>${e(s.city)}<br>${e(s.country)}<br>USt-IdNr. ${e(s.vatId)}</div></div>
32
+ <div class="title-row"><h1>Rechnung</h1><span>${e(data.number)}</span></div>
33
+ <div class="invoice-meta"><div><span>Rechnungsdatum</span><strong>${date(data.issueDate)}</strong></div><div><span>Kundennummer</span><strong>${e(data.customerNumber)}</strong></div><div><span>Ihre Bestellung</span><strong>${e(data.orderNumber)}</strong></div><div><span>Zahlbar bis</span><strong>${date(data.dueDate)}</strong></div></div>
34
+ <p class="project"><strong>${e(data.project)}</strong><br>Leistungszeitraum: ${date(data.serviceFrom)} bis ${date(data.serviceTo)}.<br>Wir berechnen die folgenden vereinbarten und erbrachten Leistungen.</p>
35
+ ` : `<header class="continuation-header"><strong>MUSTERWERK <span>Digital GmbH</span></strong><div>Rechnung ${e(data.number)}<br>${e(c.name)}</div></header>`}
36
+ <div class="section-heading"><span class="section-index">${String(index + 1).padStart(2, "0")}</span><div><h2>${e(section.title)}</h2><p>${e(section.subtitle)}</p></div></div>
37
+ ${!first ? `<div class="carry"><span>Bisherige Leistungen netto</span><strong>${money(section.carriedCents)}</strong></div>` : ""}
38
+ <table class="items"><colgroup><col class="col-position"><col class="col-description"><col class="col-quantity"><col class="col-unit"><col class="col-total"></colgroup>
39
+ <thead><tr><th>Pos.</th><th>Leistung</th><th class="numeric">Std.</th><th class="numeric">Preis / Std.</th><th class="numeric">Netto</th></tr></thead>
40
+ <tbody>${section.items.map(item => `<tr><td class="position">${e(item.position)}</td><td><strong>${e(item.name)}</strong><div class="description">${e(item.description)}</div></td><td class="numeric">${quantity(item.quantityHundredths)}</td><td class="numeric">${money(item.unitPriceCents)}</td><td class="numeric amount">${money(item.netCents)}</td></tr>`).join("")}</tbody></table>
41
+ ${last ? `<div class="settlement"><div class="tax-note"><strong>Abrechnung</strong><p>Alle Positionen netto in EUR.<br>Alle Leistungen unterliegen ${percent(data.vatBasisPoints)} Umsatzsteuer.</p><p>Vereinbarter Projektrabatt: ${percent(data.discountBasisPoints)} auf sämtliche Positionen.</p></div>
42
+ <table class="totals"><tr><td>Summe Leistungen netto</td><td>${money(totals.subtotalCents)}</td></tr><tr><td>Projektrabatt ${percent(data.discountBasisPoints)}</td><td>-${money(totals.discountCents)}</td></tr><tr class="net"><td>Entgelt netto (${percent(data.vatBasisPoints)} USt.)</td><td>${money(totals.netCents)}</td></tr><tr><td>Umsatzsteuer ${percent(data.vatBasisPoints)}</td><td>${money(totals.vatCents)}</td></tr><tr class="grand-total"><td>Rechnungsbetrag</td><td>${money(totals.totalCents)}</td></tr></table></div>
43
+ <div class="payment"><h3>Zahlungsbedingungen</h3><p>Zahlbar bis zum <strong>${date(data.dueDate)}</strong> ohne Abzug per Überweisung.<br>Verwendungszweck: <strong>${e(data.number)} / ${e(data.customerNumber)}</strong></p><div class="bank"><div><span>Kontoinhaber</span><strong>${e(s.name)}</strong></div><div><span>IBAN (Musterdaten)</span><strong>${e(s.iban)}</strong></div><div><span>BIC</span><strong>${e(s.bic)}</strong></div></div></div>
44
+ <p class="closing">Vielen Dank für die vertrauensvolle Zusammenarbeit.</p>${identity}
45
+ ` : `<div class="carry after-table"><span>Kumulierte Leistungen netto</span><strong>${money(section.cumulativeCents)}</strong></div><p class="continuation-note">Fortsetzung im folgenden Abschnitt. Rabatt, Umsatzsteuer und Rechnungsbetrag am Ende der Rechnung.</p>${identity}`}
46
+ </section>`;
47
+ }).join("");
48
+ return `<!doctype html><html lang="de"><head><meta charset="utf-8"><title>${e(data.number)}</title><link rel="stylesheet" href="invoice.css"></head><body>${sections}</body></html>`;
49
+ }
@@ -0,0 +1,20 @@
1
+ import { readFile, mkdir, writeFile } from "node:fs/promises";
2
+ import { renderPdf } from "blitz-pdf";
3
+ import { createInvoiceHtml, invoiceOptions } from "./german-invoice/template.mjs";
4
+ import { invoice, calculateInvoice } from "./german-invoice/data.mjs";
5
+
6
+ const baseUrl = new URL("german-invoice/", import.meta.url);
7
+ const html = createInvoiceHtml(invoice);
8
+ const bytes = await renderPdf(html, {
9
+ ...invoiceOptions(invoice),
10
+ baseUrl: baseUrl.href,
11
+ // Demonstrates the async asset API with a real external stylesheet.
12
+ assets: async url => readFile(new URL(url)),
13
+ });
14
+ const output = new URL("../output/pdf/", import.meta.url);
15
+ await mkdir(output, { recursive: true });
16
+ await writeFile(new URL("german-invoice.pdf", output), bytes);
17
+ await writeFile(new URL("german-invoice.html", output), html);
18
+ await writeFile(new URL("invoice.css", output), await readFile(new URL("invoice.css", baseUrl)));
19
+ await writeFile(new URL("german-invoice-totals.json", output), JSON.stringify(calculateInvoice(invoice), null, 2) + "\n");
20
+ console.log(`Created output/pdf/german-invoice.pdf (${bytes.length} bytes, PDF/A-3u with automatic page numbers).`);
@@ -0,0 +1,29 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <meta charset="utf-8">
4
+ <style>
5
+ body { margin: 0; font-family: sans-serif; color: #172033; font-size: 14px; line-height: 1.5; }
6
+ header { display: flex; justify-content: space-between; align-items: center; border-bottom: 3px solid #2457db; padding-bottom: 20px; }
7
+ h1 { color: #2457db; margin: 0; font-size: 36px; }
8
+ .meta { color: #56647a; text-align: right; }
9
+ .customer { margin: 28px 0; }
10
+ table { width: 100%; border-collapse: collapse; }
11
+ th { background: #edf2fc; text-align: left; }
12
+ th, td { padding: 12px; border-bottom: 1px solid #dde3ed; }
13
+ .number { text-align: right; }
14
+ .total { margin-top: 20px; text-align: right; font-size: 20px; }
15
+ footer { margin-top: 50px; padding-top: 18px; border-top: 1px solid #dde3ed; color: #56647a; }
16
+ </style>
17
+ <header><h1>INVOICE</h1><div class="meta">BLITZ STUDIO<br>INV-0042 · 13 October 2026</div></header>
18
+ <section class="customer"><b>Prepared for</b><br>Example Company<br>Berlin, Germany</section>
19
+ <table>
20
+ <thead><tr><th>Description</th><th class="number">Hours</th><th class="number">Rate</th><th class="number">Amount</th></tr></thead>
21
+ <tbody>
22
+ <tr><td>Design and typography</td><td class="number">8</td><td class="number">€125.00</td><td class="number">€1,000.00</td></tr>
23
+ <tr><td>HTML and CSS implementation</td><td class="number">12</td><td class="number">€125.00</td><td class="number">€1,500.00</td></tr>
24
+ <tr><td>PDF integration and testing</td><td class="number">6</td><td class="number">€125.00</td><td class="number">€750.00</td></tr>
25
+ </tbody>
26
+ </table>
27
+ <p class="total"><b>Total: €3,250.00</b></p>
28
+ <footer>Generated from HTML with Blitz and WebAssembly.<br>Text stays selectable. Fonts are embedded. Shapes are vectors.</footer>
29
+ </html>
@@ -0,0 +1,8 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { renderPdf } from "blitz-pdf";
3
+
4
+ const html = await readFile(new URL("./invoice.html", import.meta.url), "utf8");
5
+ const pdf = await renderPdf(html, { format: "A4", margin: "18mm", title: "Invoice INV-0042" });
6
+ await mkdir(new URL("../output/pdf/", import.meta.url), { recursive: true });
7
+ await writeFile(new URL("../output/pdf/invoice.pdf", import.meta.url), pdf);
8
+ console.log(`Created output/pdf/invoice.pdf (${pdf.length} bytes).`);
@@ -0,0 +1,40 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <meta charset="utf-8">
4
+ <style>
5
+ body { margin:0; font:14px/1.5 sans-serif; color:#172033; }
6
+ h1 { font-size:30px; line-height:1.2; color:#2457db; margin:0 0 10px; }
7
+ h2 { font-size:18px; margin:0 0 8px; break-after:avoid; }
8
+ p { margin:6px 0; widows:2; orphans:2; }
9
+ header { border-bottom:3px solid #2457db; padding-bottom:16px; margin-bottom:22px; }
10
+ .card { break-inside:avoid; border:1px solid #cad7ee; border-left:4px solid #2457db; background:#f4f7fe; padding:18px; margin:0 0 16px; }
11
+ .chapter { break-before:page; }
12
+ table { width:100%; border-collapse:collapse; margin-top:20px; }
13
+ td,th { text-align:left; padding:10px; border-bottom:1px solid #dae1ed; }
14
+ th { background:#edf2fc; }
15
+ footer { margin-top:24px; color:#56647a; font-size:12px; }
16
+ </style>
17
+ <header><h1>Page break verification</h1><p>Vector PDF/A-3u generated by Blitz and WebAssembly.</p></header>
18
+ <div class="card"><h2>01 / Complete blocks</h2><p>Each card stays together when it fits on a page. Borders and text move together.</p></div>
19
+ <div class="card"><h2>02 / Selectable text</h2><p>These are embedded font glyphs with Unicode mappings: café, office, affinity, €.</p></div>
20
+ <div class="card"><h2>03 / CSS rules</h2><p>The renderer reads computed break rules from the same CSS cascade used for layout.</p></div>
21
+ <div class="card"><h2>04 / Automatic flow</h2><p>This block moves intact if the current page has insufficient remaining space.</p></div>
22
+ <div class="card"><h2>05 / Line integrity</h2><p>Text lines stay inside the printable area. Content appears once across the document.</p></div>
23
+ <div class="card"><h2>06 / Archival output</h2><p>The document contains XMP identification, an sRGB output intent, and embedded fonts.</p></div>
24
+ <section class="chapter">
25
+ <h1>Explicit chapter break</h1>
26
+ <p>This section starts on a fresh page through <b>break-before: page</b>.</p>
27
+ <table>
28
+ <thead><tr><th>Control</th><th>Expected behavior</th></tr></thead>
29
+ <tbody>
30
+ <tr><td>break-before: page</td><td>Begin a new page</td></tr>
31
+ <tr><td>break-after: avoid</td><td>Keep the heading with text</td></tr>
32
+ <tr><td>break-inside: avoid</td><td>Move a complete block</td></tr>
33
+ <tr><td>widows / orphans</td><td>Protect paragraph fragments</td></tr>
34
+ <tr><td>Table rows</td><td>Keep fitting rows intact</td></tr>
35
+ <tr><td>Legacy page-break-*</td><td>Use the same cascade</td></tr>
36
+ </tbody>
37
+ </table>
38
+ <footer>No clipped lines, duplicated text, or accidental blank pages.</footer>
39
+ </section>
40
+ </html>
@@ -0,0 +1,8 @@
1
+ import { readFile, mkdir, writeFile } from "node:fs/promises";
2
+ import { renderPdf } from "blitz-pdf";
3
+
4
+ const html = await readFile(new URL("./pagination.html", import.meta.url), "utf8");
5
+ const pdf = await renderPdf(html, { format:"A5", margin:"12mm", title:"PDF/A-3u page break verification" });
6
+ await mkdir(new URL("../output/pdf/", import.meta.url), { recursive:true });
7
+ await writeFile(new URL("../output/pdf/pagination.pdf", import.meta.url), pdf);
8
+ console.log(`Created output/pdf/pagination.pdf (${pdf.length} bytes).`);