kreuzberg 4.0.8 → 4.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 (308) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +2 -2
  3. data/README.md +1 -1
  4. data/ext/kreuzberg_rb/native/Cargo.lock +94 -98
  5. data/ext/kreuzberg_rb/native/Cargo.toml +4 -2
  6. data/ext/kreuzberg_rb/native/src/batch.rs +139 -0
  7. data/ext/kreuzberg_rb/native/src/config/mod.rs +10 -0
  8. data/ext/kreuzberg_rb/native/src/config/types.rs +1058 -0
  9. data/ext/kreuzberg_rb/native/src/error_handling.rs +125 -0
  10. data/ext/kreuzberg_rb/native/src/extraction.rs +79 -0
  11. data/ext/kreuzberg_rb/native/src/gc_guarded_value.rs +35 -0
  12. data/ext/kreuzberg_rb/native/src/helpers.rs +176 -0
  13. data/ext/kreuzberg_rb/native/src/lib.rs +342 -3622
  14. data/ext/kreuzberg_rb/native/src/metadata.rs +34 -0
  15. data/ext/kreuzberg_rb/native/src/plugins/mod.rs +92 -0
  16. data/ext/kreuzberg_rb/native/src/plugins/ocr_backend.rs +159 -0
  17. data/ext/kreuzberg_rb/native/src/plugins/post_processor.rs +126 -0
  18. data/ext/kreuzberg_rb/native/src/plugins/validator.rs +99 -0
  19. data/ext/kreuzberg_rb/native/src/result.rs +326 -0
  20. data/ext/kreuzberg_rb/native/src/validation.rs +4 -0
  21. data/lib/kreuzberg/config.rb +66 -0
  22. data/lib/kreuzberg/result.rb +107 -2
  23. data/lib/kreuzberg/types.rb +104 -0
  24. data/lib/kreuzberg/version.rb +1 -1
  25. data/lib/kreuzberg.rb +0 -4
  26. data/sig/kreuzberg.rbs +105 -1
  27. data/vendor/Cargo.toml +3 -3
  28. data/vendor/kreuzberg/Cargo.toml +4 -3
  29. data/vendor/kreuzberg/README.md +1 -1
  30. data/vendor/kreuzberg/src/api/config.rs +69 -0
  31. data/vendor/kreuzberg/src/api/handlers.rs +99 -2
  32. data/vendor/kreuzberg/src/api/mod.rs +14 -7
  33. data/vendor/kreuzberg/src/api/router.rs +214 -0
  34. data/vendor/kreuzberg/src/api/startup.rs +243 -0
  35. data/vendor/kreuzberg/src/api/types.rs +78 -0
  36. data/vendor/kreuzberg/src/cache/cleanup.rs +277 -0
  37. data/vendor/kreuzberg/src/cache/core.rs +428 -0
  38. data/vendor/kreuzberg/src/cache/mod.rs +21 -843
  39. data/vendor/kreuzberg/src/cache/utilities.rs +156 -0
  40. data/vendor/kreuzberg/src/chunking/boundaries.rs +301 -0
  41. data/vendor/kreuzberg/src/chunking/builder.rs +294 -0
  42. data/vendor/kreuzberg/src/chunking/config.rs +52 -0
  43. data/vendor/kreuzberg/src/chunking/core.rs +1017 -0
  44. data/vendor/kreuzberg/src/chunking/mod.rs +14 -2211
  45. data/vendor/kreuzberg/src/chunking/processor.rs +10 -0
  46. data/vendor/kreuzberg/src/chunking/validation.rs +686 -0
  47. data/vendor/kreuzberg/src/core/config/extraction/core.rs +169 -0
  48. data/vendor/kreuzberg/src/core/config/extraction/env.rs +179 -0
  49. data/vendor/kreuzberg/src/core/config/extraction/loaders.rs +204 -0
  50. data/vendor/kreuzberg/src/core/config/extraction/mod.rs +42 -0
  51. data/vendor/kreuzberg/src/core/config/extraction/types.rs +93 -0
  52. data/vendor/kreuzberg/src/core/config/formats.rs +135 -0
  53. data/vendor/kreuzberg/src/core/config/mod.rs +20 -0
  54. data/vendor/kreuzberg/src/core/config/ocr.rs +73 -0
  55. data/vendor/kreuzberg/src/core/config/page.rs +57 -0
  56. data/vendor/kreuzberg/src/core/config/pdf.rs +111 -0
  57. data/vendor/kreuzberg/src/core/config/processing.rs +312 -0
  58. data/vendor/kreuzberg/src/core/config_validation/dependencies.rs +187 -0
  59. data/vendor/kreuzberg/src/core/config_validation/mod.rs +386 -0
  60. data/vendor/kreuzberg/src/core/config_validation/sections.rs +401 -0
  61. data/vendor/kreuzberg/src/core/extractor/batch.rs +246 -0
  62. data/vendor/kreuzberg/src/core/extractor/bytes.rs +116 -0
  63. data/vendor/kreuzberg/src/core/extractor/file.rs +240 -0
  64. data/vendor/kreuzberg/src/core/extractor/helpers.rs +71 -0
  65. data/vendor/kreuzberg/src/core/extractor/legacy.rs +62 -0
  66. data/vendor/kreuzberg/src/core/extractor/mod.rs +490 -0
  67. data/vendor/kreuzberg/src/core/extractor/sync.rs +208 -0
  68. data/vendor/kreuzberg/src/core/mod.rs +4 -1
  69. data/vendor/kreuzberg/src/core/pipeline/cache.rs +60 -0
  70. data/vendor/kreuzberg/src/core/pipeline/execution.rs +89 -0
  71. data/vendor/kreuzberg/src/core/pipeline/features.rs +108 -0
  72. data/vendor/kreuzberg/src/core/pipeline/format.rs +392 -0
  73. data/vendor/kreuzberg/src/core/pipeline/initialization.rs +67 -0
  74. data/vendor/kreuzberg/src/core/pipeline/mod.rs +135 -0
  75. data/vendor/kreuzberg/src/core/pipeline/tests.rs +975 -0
  76. data/vendor/kreuzberg/src/core/server_config/env.rs +90 -0
  77. data/vendor/kreuzberg/src/core/server_config/loader.rs +202 -0
  78. data/vendor/kreuzberg/src/core/server_config/mod.rs +380 -0
  79. data/vendor/kreuzberg/src/core/server_config/tests/basic_tests.rs +124 -0
  80. data/vendor/kreuzberg/src/core/server_config/tests/env_tests.rs +216 -0
  81. data/vendor/kreuzberg/src/core/server_config/tests/file_loading_tests.rs +341 -0
  82. data/vendor/kreuzberg/src/core/server_config/tests/mod.rs +5 -0
  83. data/vendor/kreuzberg/src/core/server_config/validation.rs +17 -0
  84. data/vendor/kreuzberg/src/embeddings.rs +136 -13
  85. data/vendor/kreuzberg/src/extraction/{archive.rs → archive/mod.rs} +45 -239
  86. data/vendor/kreuzberg/src/extraction/archive/sevenz.rs +98 -0
  87. data/vendor/kreuzberg/src/extraction/archive/tar.rs +118 -0
  88. data/vendor/kreuzberg/src/extraction/archive/zip.rs +101 -0
  89. data/vendor/kreuzberg/src/extraction/html/converter.rs +592 -0
  90. data/vendor/kreuzberg/src/extraction/html/image_handling.rs +95 -0
  91. data/vendor/kreuzberg/src/extraction/html/mod.rs +53 -0
  92. data/vendor/kreuzberg/src/extraction/html/processor.rs +659 -0
  93. data/vendor/kreuzberg/src/extraction/html/stack_management.rs +103 -0
  94. data/vendor/kreuzberg/src/extraction/html/types.rs +28 -0
  95. data/vendor/kreuzberg/src/extraction/mod.rs +6 -2
  96. data/vendor/kreuzberg/src/extraction/pptx/container.rs +159 -0
  97. data/vendor/kreuzberg/src/extraction/pptx/content_builder.rs +168 -0
  98. data/vendor/kreuzberg/src/extraction/pptx/elements.rs +132 -0
  99. data/vendor/kreuzberg/src/extraction/pptx/image_handling.rs +57 -0
  100. data/vendor/kreuzberg/src/extraction/pptx/metadata.rs +160 -0
  101. data/vendor/kreuzberg/src/extraction/pptx/mod.rs +558 -0
  102. data/vendor/kreuzberg/src/extraction/pptx/parser.rs +379 -0
  103. data/vendor/kreuzberg/src/extraction/transform/content.rs +205 -0
  104. data/vendor/kreuzberg/src/extraction/transform/elements.rs +211 -0
  105. data/vendor/kreuzberg/src/extraction/transform/mod.rs +480 -0
  106. data/vendor/kreuzberg/src/extraction/transform/types.rs +27 -0
  107. data/vendor/kreuzberg/src/extractors/archive.rs +2 -0
  108. data/vendor/kreuzberg/src/extractors/bibtex.rs +2 -0
  109. data/vendor/kreuzberg/src/extractors/djot_format/attributes.rs +134 -0
  110. data/vendor/kreuzberg/src/extractors/djot_format/conversion.rs +223 -0
  111. data/vendor/kreuzberg/src/extractors/djot_format/extractor.rs +172 -0
  112. data/vendor/kreuzberg/src/extractors/djot_format/mod.rs +24 -0
  113. data/vendor/kreuzberg/src/extractors/djot_format/parsing/block_handlers.rs +271 -0
  114. data/vendor/kreuzberg/src/extractors/djot_format/parsing/content_extraction.rs +257 -0
  115. data/vendor/kreuzberg/src/extractors/djot_format/parsing/event_handlers.rs +101 -0
  116. data/vendor/kreuzberg/src/extractors/djot_format/parsing/inline_handlers.rs +201 -0
  117. data/vendor/kreuzberg/src/extractors/djot_format/parsing/mod.rs +16 -0
  118. data/vendor/kreuzberg/src/extractors/djot_format/parsing/state.rs +78 -0
  119. data/vendor/kreuzberg/src/extractors/djot_format/parsing/table_extraction.rs +68 -0
  120. data/vendor/kreuzberg/src/extractors/djot_format/parsing/text_extraction.rs +61 -0
  121. data/vendor/kreuzberg/src/extractors/djot_format/rendering.rs +452 -0
  122. data/vendor/kreuzberg/src/extractors/docbook.rs +2 -0
  123. data/vendor/kreuzberg/src/extractors/docx.rs +12 -1
  124. data/vendor/kreuzberg/src/extractors/email.rs +2 -0
  125. data/vendor/kreuzberg/src/extractors/epub/content.rs +333 -0
  126. data/vendor/kreuzberg/src/extractors/epub/metadata.rs +137 -0
  127. data/vendor/kreuzberg/src/extractors/epub/mod.rs +186 -0
  128. data/vendor/kreuzberg/src/extractors/epub/parsing.rs +86 -0
  129. data/vendor/kreuzberg/src/extractors/excel.rs +4 -0
  130. data/vendor/kreuzberg/src/extractors/fictionbook.rs +2 -0
  131. data/vendor/kreuzberg/src/extractors/frontmatter_utils.rs +466 -0
  132. data/vendor/kreuzberg/src/extractors/html.rs +80 -8
  133. data/vendor/kreuzberg/src/extractors/image.rs +8 -1
  134. data/vendor/kreuzberg/src/extractors/jats/elements.rs +350 -0
  135. data/vendor/kreuzberg/src/extractors/jats/metadata.rs +21 -0
  136. data/vendor/kreuzberg/src/extractors/{jats.rs → jats/mod.rs} +10 -412
  137. data/vendor/kreuzberg/src/extractors/jats/parser.rs +52 -0
  138. data/vendor/kreuzberg/src/extractors/jupyter.rs +2 -0
  139. data/vendor/kreuzberg/src/extractors/latex/commands.rs +93 -0
  140. data/vendor/kreuzberg/src/extractors/latex/environments.rs +157 -0
  141. data/vendor/kreuzberg/src/extractors/latex/metadata.rs +27 -0
  142. data/vendor/kreuzberg/src/extractors/latex/mod.rs +146 -0
  143. data/vendor/kreuzberg/src/extractors/latex/parser.rs +231 -0
  144. data/vendor/kreuzberg/src/extractors/latex/utilities.rs +126 -0
  145. data/vendor/kreuzberg/src/extractors/markdown.rs +39 -162
  146. data/vendor/kreuzberg/src/extractors/mod.rs +9 -1
  147. data/vendor/kreuzberg/src/extractors/odt.rs +2 -0
  148. data/vendor/kreuzberg/src/extractors/opml/core.rs +165 -0
  149. data/vendor/kreuzberg/src/extractors/opml/mod.rs +31 -0
  150. data/vendor/kreuzberg/src/extractors/opml/parser.rs +479 -0
  151. data/vendor/kreuzberg/src/extractors/orgmode.rs +2 -0
  152. data/vendor/kreuzberg/src/extractors/pdf/extraction.rs +106 -0
  153. data/vendor/kreuzberg/src/extractors/{pdf.rs → pdf/mod.rs} +25 -324
  154. data/vendor/kreuzberg/src/extractors/pdf/ocr.rs +214 -0
  155. data/vendor/kreuzberg/src/extractors/pdf/pages.rs +51 -0
  156. data/vendor/kreuzberg/src/extractors/pptx.rs +9 -2
  157. data/vendor/kreuzberg/src/extractors/rst.rs +2 -0
  158. data/vendor/kreuzberg/src/extractors/rtf/encoding.rs +116 -0
  159. data/vendor/kreuzberg/src/extractors/rtf/formatting.rs +24 -0
  160. data/vendor/kreuzberg/src/extractors/rtf/images.rs +72 -0
  161. data/vendor/kreuzberg/src/extractors/rtf/metadata.rs +216 -0
  162. data/vendor/kreuzberg/src/extractors/rtf/mod.rs +142 -0
  163. data/vendor/kreuzberg/src/extractors/rtf/parser.rs +259 -0
  164. data/vendor/kreuzberg/src/extractors/rtf/tables.rs +83 -0
  165. data/vendor/kreuzberg/src/extractors/structured.rs +2 -0
  166. data/vendor/kreuzberg/src/extractors/text.rs +4 -0
  167. data/vendor/kreuzberg/src/extractors/typst.rs +2 -0
  168. data/vendor/kreuzberg/src/extractors/xml.rs +2 -0
  169. data/vendor/kreuzberg/src/keywords/processor.rs +14 -0
  170. data/vendor/kreuzberg/src/language_detection/processor.rs +10 -0
  171. data/vendor/kreuzberg/src/lib.rs +2 -2
  172. data/vendor/kreuzberg/src/mcp/errors.rs +312 -0
  173. data/vendor/kreuzberg/src/mcp/format.rs +211 -0
  174. data/vendor/kreuzberg/src/mcp/mod.rs +9 -3
  175. data/vendor/kreuzberg/src/mcp/params.rs +196 -0
  176. data/vendor/kreuzberg/src/mcp/server.rs +39 -1438
  177. data/vendor/kreuzberg/src/mcp/tools/cache.rs +179 -0
  178. data/vendor/kreuzberg/src/mcp/tools/extraction.rs +403 -0
  179. data/vendor/kreuzberg/src/mcp/tools/mime.rs +150 -0
  180. data/vendor/kreuzberg/src/mcp/tools/mod.rs +11 -0
  181. data/vendor/kreuzberg/src/ocr/backends/easyocr.rs +96 -0
  182. data/vendor/kreuzberg/src/ocr/backends/mod.rs +7 -0
  183. data/vendor/kreuzberg/src/ocr/backends/paddleocr.rs +27 -0
  184. data/vendor/kreuzberg/src/ocr/backends/tesseract.rs +134 -0
  185. data/vendor/kreuzberg/src/ocr/hocr.rs +60 -16
  186. data/vendor/kreuzberg/src/ocr/language_registry.rs +11 -235
  187. data/vendor/kreuzberg/src/ocr/mod.rs +1 -0
  188. data/vendor/kreuzberg/src/ocr/processor/config.rs +203 -0
  189. data/vendor/kreuzberg/src/ocr/processor/execution.rs +494 -0
  190. data/vendor/kreuzberg/src/ocr/processor/mod.rs +265 -0
  191. data/vendor/kreuzberg/src/ocr/processor/validation.rs +145 -0
  192. data/vendor/kreuzberg/src/ocr/tesseract_backend.rs +41 -24
  193. data/vendor/kreuzberg/src/pdf/bindings.rs +21 -8
  194. data/vendor/kreuzberg/src/pdf/hierarchy/bounding_box.rs +289 -0
  195. data/vendor/kreuzberg/src/pdf/hierarchy/clustering.rs +199 -0
  196. data/vendor/kreuzberg/src/pdf/{hierarchy.rs → hierarchy/extraction.rs} +6 -346
  197. data/vendor/kreuzberg/src/pdf/hierarchy/mod.rs +18 -0
  198. data/vendor/kreuzberg/src/plugins/extractor/mod.rs +319 -0
  199. data/vendor/kreuzberg/src/plugins/extractor/registry.rs +434 -0
  200. data/vendor/kreuzberg/src/plugins/extractor/trait.rs +391 -0
  201. data/vendor/kreuzberg/src/plugins/mod.rs +13 -0
  202. data/vendor/kreuzberg/src/plugins/ocr.rs +11 -0
  203. data/vendor/kreuzberg/src/plugins/processor/mod.rs +365 -0
  204. data/vendor/kreuzberg/src/plugins/processor/registry.rs +37 -0
  205. data/vendor/kreuzberg/src/plugins/processor/trait.rs +284 -0
  206. data/vendor/kreuzberg/src/plugins/registry/extractor.rs +416 -0
  207. data/vendor/kreuzberg/src/plugins/registry/mod.rs +116 -0
  208. data/vendor/kreuzberg/src/plugins/registry/ocr.rs +293 -0
  209. data/vendor/kreuzberg/src/plugins/registry/processor.rs +304 -0
  210. data/vendor/kreuzberg/src/plugins/registry/validator.rs +238 -0
  211. data/vendor/kreuzberg/src/plugins/validator/mod.rs +424 -0
  212. data/vendor/kreuzberg/src/plugins/validator/registry.rs +355 -0
  213. data/vendor/kreuzberg/src/plugins/validator/trait.rs +276 -0
  214. data/vendor/kreuzberg/src/stopwords/languages/asian.rs +40 -0
  215. data/vendor/kreuzberg/src/stopwords/languages/germanic.rs +36 -0
  216. data/vendor/kreuzberg/src/stopwords/languages/mod.rs +10 -0
  217. data/vendor/kreuzberg/src/stopwords/languages/other.rs +44 -0
  218. data/vendor/kreuzberg/src/stopwords/languages/romance.rs +36 -0
  219. data/vendor/kreuzberg/src/stopwords/languages/slavic.rs +36 -0
  220. data/vendor/kreuzberg/src/stopwords/mod.rs +7 -33
  221. data/vendor/kreuzberg/src/text/quality.rs +1 -1
  222. data/vendor/kreuzberg/src/text/quality_processor.rs +10 -0
  223. data/vendor/kreuzberg/src/text/token_reduction/core/analysis.rs +238 -0
  224. data/vendor/kreuzberg/src/text/token_reduction/core/mod.rs +8 -0
  225. data/vendor/kreuzberg/src/text/token_reduction/core/punctuation.rs +54 -0
  226. data/vendor/kreuzberg/src/text/token_reduction/core/reducer.rs +384 -0
  227. data/vendor/kreuzberg/src/text/token_reduction/core/sentence_selection.rs +68 -0
  228. data/vendor/kreuzberg/src/text/token_reduction/core/word_filtering.rs +156 -0
  229. data/vendor/kreuzberg/src/text/token_reduction/filters/general.rs +377 -0
  230. data/vendor/kreuzberg/src/text/token_reduction/filters/html.rs +51 -0
  231. data/vendor/kreuzberg/src/text/token_reduction/filters/markdown.rs +285 -0
  232. data/vendor/kreuzberg/src/text/token_reduction/filters.rs +131 -246
  233. data/vendor/kreuzberg/src/types/djot.rs +209 -0
  234. data/vendor/kreuzberg/src/types/extraction.rs +301 -0
  235. data/vendor/kreuzberg/src/types/formats.rs +443 -0
  236. data/vendor/kreuzberg/src/types/metadata.rs +560 -0
  237. data/vendor/kreuzberg/src/types/mod.rs +281 -0
  238. data/vendor/kreuzberg/src/types/page.rs +182 -0
  239. data/vendor/kreuzberg/src/types/serde_helpers.rs +132 -0
  240. data/vendor/kreuzberg/src/types/tables.rs +39 -0
  241. data/vendor/kreuzberg/src/utils/quality/heuristics.rs +58 -0
  242. data/vendor/kreuzberg/src/utils/{quality.rs → quality/mod.rs} +168 -489
  243. data/vendor/kreuzberg/src/utils/quality/patterns.rs +117 -0
  244. data/vendor/kreuzberg/src/utils/quality/scoring.rs +178 -0
  245. data/vendor/kreuzberg/src/utils/string_pool/buffer_pool.rs +325 -0
  246. data/vendor/kreuzberg/src/utils/string_pool/interned.rs +102 -0
  247. data/vendor/kreuzberg/src/utils/string_pool/language_pool.rs +119 -0
  248. data/vendor/kreuzberg/src/utils/string_pool/mime_pool.rs +235 -0
  249. data/vendor/kreuzberg/src/utils/string_pool/mod.rs +41 -0
  250. data/vendor/kreuzberg/tests/api_chunk.rs +313 -0
  251. data/vendor/kreuzberg/tests/api_embed.rs +6 -9
  252. data/vendor/kreuzberg/tests/batch_orchestration.rs +1 -0
  253. data/vendor/kreuzberg/tests/concurrency_stress.rs +7 -0
  254. data/vendor/kreuzberg/tests/core_integration.rs +1 -0
  255. data/vendor/kreuzberg/tests/docx_metadata_extraction_test.rs +130 -0
  256. data/vendor/kreuzberg/tests/epub_native_extractor_tests.rs +5 -14
  257. data/vendor/kreuzberg/tests/format_integration.rs +2 -0
  258. data/vendor/kreuzberg/tests/helpers/mod.rs +1 -0
  259. data/vendor/kreuzberg/tests/html_table_test.rs +11 -11
  260. data/vendor/kreuzberg/tests/ocr_configuration.rs +16 -0
  261. data/vendor/kreuzberg/tests/ocr_errors.rs +18 -0
  262. data/vendor/kreuzberg/tests/ocr_quality.rs +9 -0
  263. data/vendor/kreuzberg/tests/ocr_stress.rs +1 -0
  264. data/vendor/kreuzberg/tests/pipeline_integration.rs +50 -0
  265. data/vendor/kreuzberg/tests/plugin_ocr_backend_test.rs +13 -0
  266. data/vendor/kreuzberg/tests/plugin_system.rs +12 -0
  267. data/vendor/kreuzberg/tests/registry_integration_tests.rs +2 -0
  268. data/vendor/kreuzberg-ffi/Cargo.toml +2 -1
  269. data/vendor/kreuzberg-ffi/benches/result_view_benchmark.rs +2 -0
  270. data/vendor/kreuzberg-ffi/kreuzberg.h +347 -178
  271. data/vendor/kreuzberg-ffi/src/config/html.rs +318 -0
  272. data/vendor/kreuzberg-ffi/src/config/loader.rs +154 -0
  273. data/vendor/kreuzberg-ffi/src/config/merge.rs +104 -0
  274. data/vendor/kreuzberg-ffi/src/config/mod.rs +385 -0
  275. data/vendor/kreuzberg-ffi/src/config/parse.rs +91 -0
  276. data/vendor/kreuzberg-ffi/src/config/serialize.rs +118 -0
  277. data/vendor/kreuzberg-ffi/src/config_builder.rs +598 -0
  278. data/vendor/kreuzberg-ffi/src/error.rs +46 -14
  279. data/vendor/kreuzberg-ffi/src/helpers.rs +10 -0
  280. data/vendor/kreuzberg-ffi/src/html_options.rs +421 -0
  281. data/vendor/kreuzberg-ffi/src/lib.rs +16 -0
  282. data/vendor/kreuzberg-ffi/src/panic_shield.rs +11 -0
  283. data/vendor/kreuzberg-ffi/src/plugins/ocr_backend.rs +2 -0
  284. data/vendor/kreuzberg-ffi/src/result.rs +148 -122
  285. data/vendor/kreuzberg-ffi/src/result_view.rs +4 -0
  286. data/vendor/kreuzberg-tesseract/Cargo.toml +2 -2
  287. metadata +200 -28
  288. data/vendor/kreuzberg/src/api/server.rs +0 -518
  289. data/vendor/kreuzberg/src/core/config.rs +0 -1914
  290. data/vendor/kreuzberg/src/core/config_validation.rs +0 -949
  291. data/vendor/kreuzberg/src/core/extractor.rs +0 -1200
  292. data/vendor/kreuzberg/src/core/pipeline.rs +0 -1223
  293. data/vendor/kreuzberg/src/core/server_config.rs +0 -1220
  294. data/vendor/kreuzberg/src/extraction/html.rs +0 -1830
  295. data/vendor/kreuzberg/src/extraction/pptx.rs +0 -3102
  296. data/vendor/kreuzberg/src/extractors/epub.rs +0 -696
  297. data/vendor/kreuzberg/src/extractors/latex.rs +0 -653
  298. data/vendor/kreuzberg/src/extractors/opml.rs +0 -635
  299. data/vendor/kreuzberg/src/extractors/rtf.rs +0 -809
  300. data/vendor/kreuzberg/src/ocr/processor.rs +0 -858
  301. data/vendor/kreuzberg/src/plugins/extractor.rs +0 -1042
  302. data/vendor/kreuzberg/src/plugins/processor.rs +0 -650
  303. data/vendor/kreuzberg/src/plugins/registry.rs +0 -1339
  304. data/vendor/kreuzberg/src/plugins/validator.rs +0 -967
  305. data/vendor/kreuzberg/src/text/token_reduction/core.rs +0 -832
  306. data/vendor/kreuzberg/src/types.rs +0 -1713
  307. data/vendor/kreuzberg/src/utils/string_pool.rs +0 -762
  308. data/vendor/kreuzberg-ffi/src/config.rs +0 -1341
@@ -0,0 +1,379 @@
1
+ //! XML parsing for PPTX slide content.
2
+ //!
3
+ //! This module handles parsing slide XML, extracting text, tables, lists, images,
4
+ //! and relationships from PowerPoint presentations.
5
+
6
+ use roxmltree::{Document, Node};
7
+
8
+ use crate::error::{KreuzbergError, Result};
9
+ use crate::text::utf8_validation;
10
+
11
+ use super::elements::{
12
+ ElementPosition, Formatting, ImageReference, ListElement, ListItem, ParsedContent, Run, SlideElement, TableCell,
13
+ TableElement, TableRow, TextElement,
14
+ };
15
+
16
+ const P_NAMESPACE: &str = "http://schemas.openxmlformats.org/presentationml/2006/main";
17
+ const A_NAMESPACE: &str = "http://schemas.openxmlformats.org/drawingml/2006/main";
18
+ const RELS_NAMESPACE: &str = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
19
+
20
+ pub(super) fn parse_slide_xml(xml_data: &[u8]) -> Result<Vec<SlideElement>> {
21
+ let xml_str = utf8_validation::from_utf8(xml_data)
22
+ .map_err(|_| KreuzbergError::parsing("Invalid UTF-8 in slide XML".to_string()))?;
23
+
24
+ let doc =
25
+ Document::parse(xml_str).map_err(|e| KreuzbergError::parsing(format!("Failed to parse slide XML: {}", e)))?;
26
+
27
+ let root = doc.root_element();
28
+ let ns = root.tag_name().namespace();
29
+
30
+ let c_sld = root
31
+ .descendants()
32
+ .find(|n| n.tag_name().name() == "cSld" && n.tag_name().namespace() == ns)
33
+ .ok_or_else(|| KreuzbergError::parsing("No <p:cSld> tag found".to_string()))?;
34
+
35
+ let sp_tree = c_sld
36
+ .children()
37
+ .find(|n| n.tag_name().name() == "spTree" && n.tag_name().namespace() == ns)
38
+ .ok_or_else(|| KreuzbergError::parsing("No <p:spTree> tag found".to_string()))?;
39
+
40
+ let mut elements = Vec::new();
41
+ for child_node in sp_tree.children().filter(|n| n.is_element()) {
42
+ elements.extend(parse_group(&child_node)?);
43
+ }
44
+
45
+ Ok(elements)
46
+ }
47
+
48
+ fn parse_group(node: &Node) -> Result<Vec<SlideElement>> {
49
+ let mut elements = Vec::new();
50
+
51
+ let tag_name = node.tag_name().name();
52
+ let namespace = node.tag_name().namespace().unwrap_or("");
53
+
54
+ if namespace != P_NAMESPACE {
55
+ return Ok(elements);
56
+ }
57
+
58
+ let position = extract_position(node);
59
+
60
+ match tag_name {
61
+ "sp" => {
62
+ let position = extract_position(node);
63
+ match parse_sp(node)? {
64
+ ParsedContent::Text(text) => elements.push(SlideElement::Text(text, position)),
65
+ ParsedContent::List(list) => elements.push(SlideElement::List(list, position)),
66
+ }
67
+ }
68
+ "graphicFrame" => {
69
+ if let Some(graphic_element) = parse_graphic_frame(node)? {
70
+ elements.push(SlideElement::Table(graphic_element, position));
71
+ }
72
+ }
73
+ "pic" => {
74
+ let image_reference = parse_pic(node)?;
75
+ elements.push(SlideElement::Image(image_reference, position));
76
+ }
77
+ "grpSp" => {
78
+ for child in node.children().filter(|n| n.is_element()) {
79
+ elements.extend(parse_group(&child)?);
80
+ }
81
+ }
82
+ _ => elements.push(SlideElement::Unknown),
83
+ }
84
+
85
+ Ok(elements)
86
+ }
87
+
88
+ fn parse_sp(sp_node: &Node) -> Result<ParsedContent> {
89
+ let tx_body_node = sp_node
90
+ .children()
91
+ .find(|n| n.tag_name().name() == "txBody" && n.tag_name().namespace() == Some(P_NAMESPACE))
92
+ .ok_or_else(|| KreuzbergError::parsing("No txBody found".to_string()))?;
93
+
94
+ let is_list = tx_body_node.descendants().any(|n| {
95
+ n.is_element()
96
+ && n.tag_name().name() == "pPr"
97
+ && n.tag_name().namespace() == Some(A_NAMESPACE)
98
+ && (n.attribute("lvl").is_some()
99
+ || n.children().any(|child| {
100
+ child.is_element()
101
+ && (child.tag_name().name() == "buAutoNum" || child.tag_name().name() == "buChar")
102
+ }))
103
+ });
104
+
105
+ if is_list {
106
+ Ok(ParsedContent::List(parse_list(&tx_body_node)?))
107
+ } else {
108
+ Ok(ParsedContent::Text(parse_text(&tx_body_node)?))
109
+ }
110
+ }
111
+
112
+ pub(super) fn parse_text(tx_body_node: &Node) -> Result<TextElement> {
113
+ let mut runs = Vec::new();
114
+
115
+ for p_node in tx_body_node
116
+ .children()
117
+ .filter(|n| n.is_element() && n.tag_name().name() == "p" && n.tag_name().namespace() == Some(A_NAMESPACE))
118
+ {
119
+ let mut paragraph_runs = parse_paragraph(&p_node, true)?;
120
+ runs.append(&mut paragraph_runs);
121
+ }
122
+
123
+ Ok(TextElement { runs })
124
+ }
125
+
126
+ fn parse_graphic_frame(node: &Node) -> Result<Option<TableElement>> {
127
+ let graphic_data_node = node.descendants().find(|n| {
128
+ n.is_element()
129
+ && n.tag_name().name() == "graphicData"
130
+ && n.tag_name().namespace() == Some(A_NAMESPACE)
131
+ && n.attribute("uri") == Some("http://schemas.openxmlformats.org/drawingml/2006/table")
132
+ });
133
+
134
+ if let Some(graphic_data) = graphic_data_node
135
+ && let Some(tbl_node) = graphic_data
136
+ .children()
137
+ .find(|n| n.is_element() && n.tag_name().name() == "tbl" && n.tag_name().namespace() == Some(A_NAMESPACE))
138
+ {
139
+ let table = parse_table(&tbl_node)?;
140
+ return Ok(Some(table));
141
+ }
142
+
143
+ Ok(None)
144
+ }
145
+
146
+ fn parse_table(tbl_node: &Node) -> Result<TableElement> {
147
+ let mut rows = Vec::new();
148
+
149
+ for tr_node in tbl_node
150
+ .children()
151
+ .filter(|n| n.is_element() && n.tag_name().name() == "tr" && n.tag_name().namespace() == Some(A_NAMESPACE))
152
+ {
153
+ let row = parse_table_row(&tr_node)?;
154
+ rows.push(row);
155
+ }
156
+
157
+ Ok(TableElement { rows })
158
+ }
159
+
160
+ fn parse_table_row(tr_node: &Node) -> Result<TableRow> {
161
+ let mut cells = Vec::new();
162
+
163
+ for tc_node in tr_node
164
+ .children()
165
+ .filter(|n| n.is_element() && n.tag_name().name() == "tc" && n.tag_name().namespace() == Some(A_NAMESPACE))
166
+ {
167
+ let cell = parse_table_cell(&tc_node)?;
168
+ cells.push(cell);
169
+ }
170
+
171
+ Ok(TableRow { cells })
172
+ }
173
+
174
+ fn parse_table_cell(tc_node: &Node) -> Result<TableCell> {
175
+ let mut runs = Vec::new();
176
+
177
+ if let Some(tx_body_node) = tc_node
178
+ .children()
179
+ .find(|n| n.is_element() && n.tag_name().name() == "txBody" && n.tag_name().namespace() == Some(A_NAMESPACE))
180
+ {
181
+ for p_node in tx_body_node
182
+ .children()
183
+ .filter(|n| n.is_element() && n.tag_name().name() == "p" && n.tag_name().namespace() == Some(A_NAMESPACE))
184
+ {
185
+ let mut paragraph_runs = parse_paragraph(&p_node, false)?;
186
+ runs.append(&mut paragraph_runs);
187
+ }
188
+ }
189
+
190
+ Ok(TableCell { runs })
191
+ }
192
+
193
+ fn parse_pic(pic_node: &Node) -> Result<ImageReference> {
194
+ let blip_node = pic_node
195
+ .descendants()
196
+ .find(|n| n.is_element() && n.tag_name().name() == "blip" && n.tag_name().namespace() == Some(A_NAMESPACE))
197
+ .ok_or_else(|| KreuzbergError::parsing("Image blip not found".to_string()))?;
198
+
199
+ let embed_attr = blip_node
200
+ .attribute((RELS_NAMESPACE, "embed"))
201
+ .or_else(|| blip_node.attribute("r:embed"))
202
+ .ok_or_else(|| KreuzbergError::parsing("Image embed attribute not found".to_string()))?;
203
+
204
+ let image_ref = ImageReference {
205
+ id: embed_attr.to_string(),
206
+ target: String::new(),
207
+ };
208
+
209
+ Ok(image_ref)
210
+ }
211
+
212
+ fn parse_list(tx_body_node: &Node) -> Result<ListElement> {
213
+ let mut items = Vec::new();
214
+
215
+ for p_node in tx_body_node
216
+ .children()
217
+ .filter(|n| n.is_element() && n.tag_name().name() == "p" && n.tag_name().namespace() == Some(A_NAMESPACE))
218
+ {
219
+ let (level, is_ordered) = parse_list_properties(&p_node)?;
220
+
221
+ let runs = parse_paragraph(&p_node, true)?;
222
+
223
+ items.push(ListItem {
224
+ level,
225
+ is_ordered,
226
+ runs,
227
+ });
228
+ }
229
+
230
+ Ok(ListElement { items })
231
+ }
232
+
233
+ fn parse_list_properties(p_node: &Node) -> Result<(u32, bool)> {
234
+ let mut level = 1;
235
+ let mut is_ordered = false;
236
+
237
+ if let Some(p_pr_node) = p_node
238
+ .children()
239
+ .find(|n| n.is_element() && n.tag_name().name() == "pPr" && n.tag_name().namespace() == Some(A_NAMESPACE))
240
+ {
241
+ if let Some(lvl_attr) = p_pr_node.attribute("lvl") {
242
+ level = lvl_attr.parse::<u32>().unwrap_or(0) + 1;
243
+ }
244
+
245
+ is_ordered = p_pr_node.children().any(|n| {
246
+ n.is_element() && n.tag_name().namespace() == Some(A_NAMESPACE) && n.tag_name().name() == "buAutoNum"
247
+ });
248
+ }
249
+
250
+ Ok((level, is_ordered))
251
+ }
252
+
253
+ fn parse_paragraph(p_node: &Node, add_new_line: bool) -> Result<Vec<Run>> {
254
+ let run_nodes: Vec<_> = p_node
255
+ .children()
256
+ .filter(|n| n.is_element() && n.tag_name().name() == "r" && n.tag_name().namespace() == Some(A_NAMESPACE))
257
+ .collect();
258
+
259
+ let count = run_nodes.len();
260
+ let mut runs: Vec<Run> = Vec::new();
261
+
262
+ for (idx, r_node) in run_nodes.iter().enumerate() {
263
+ let mut run = parse_run(r_node)?;
264
+
265
+ if add_new_line && idx == count - 1 {
266
+ run.text.push('\n');
267
+ }
268
+
269
+ runs.push(run);
270
+ }
271
+ Ok(runs)
272
+ }
273
+
274
+ fn parse_run(r_node: &Node) -> Result<Run> {
275
+ let mut text = String::new();
276
+ let mut formatting = Formatting::default();
277
+
278
+ if let Some(r_pr_node) = r_node
279
+ .children()
280
+ .find(|n| n.is_element() && n.tag_name().name() == "rPr" && n.tag_name().namespace() == Some(A_NAMESPACE))
281
+ {
282
+ if let Some(b_attr) = r_pr_node.attribute("b") {
283
+ formatting.bold = b_attr == "1" || b_attr.eq_ignore_ascii_case("true");
284
+ }
285
+ if let Some(i_attr) = r_pr_node.attribute("i") {
286
+ formatting.italic = i_attr == "1" || i_attr.eq_ignore_ascii_case("true");
287
+ }
288
+ if let Some(u_attr) = r_pr_node.attribute("u") {
289
+ formatting.underlined = u_attr != "none";
290
+ }
291
+ if let Some(lang_attr) = r_pr_node.attribute("lang") {
292
+ formatting.lang = lang_attr.to_string();
293
+ }
294
+ }
295
+
296
+ if let Some(t_node) = r_node
297
+ .children()
298
+ .find(|n| n.is_element() && n.tag_name().name() == "t" && n.tag_name().namespace() == Some(A_NAMESPACE))
299
+ && let Some(t) = t_node.text()
300
+ {
301
+ text.push_str(t);
302
+ }
303
+ Ok(Run { text, formatting })
304
+ }
305
+
306
+ pub(super) fn extract_position(node: &Node) -> ElementPosition {
307
+ let default = ElementPosition::default();
308
+
309
+ node.descendants()
310
+ .find(|n| n.tag_name().namespace() == Some(A_NAMESPACE) && n.tag_name().name() == "xfrm")
311
+ .and_then(|xfrm| {
312
+ let x = xfrm
313
+ .children()
314
+ .find(|n| n.tag_name().name() == "off" && n.tag_name().namespace() == Some(A_NAMESPACE))
315
+ .and_then(|off| off.attribute("x")?.parse::<i64>().ok())?;
316
+
317
+ let y = xfrm
318
+ .children()
319
+ .find(|n| n.tag_name().name() == "off" && n.tag_name().namespace() == Some(A_NAMESPACE))
320
+ .and_then(|off| off.attribute("y")?.parse::<i64>().ok())?;
321
+
322
+ Some(ElementPosition { x, y })
323
+ })
324
+ .unwrap_or(default)
325
+ }
326
+
327
+ pub(super) fn parse_slide_rels(rels_data: &[u8]) -> Result<Vec<ImageReference>> {
328
+ let xml_str = utf8_validation::from_utf8(rels_data)
329
+ .map_err(|e| KreuzbergError::parsing(format!("Invalid UTF-8 in rels XML: {}", e)))?;
330
+
331
+ let doc =
332
+ Document::parse(xml_str).map_err(|e| KreuzbergError::parsing(format!("Failed to parse rels XML: {}", e)))?;
333
+
334
+ let mut images = Vec::new();
335
+
336
+ for node in doc.descendants() {
337
+ if node.has_tag_name("Relationship")
338
+ && let Some(rel_type) = node.attribute("Type")
339
+ && rel_type.contains("image")
340
+ && let (Some(id), Some(target)) = (node.attribute("Id"), node.attribute("Target"))
341
+ {
342
+ images.push(ImageReference {
343
+ id: id.to_string(),
344
+ target: target.to_string(),
345
+ });
346
+ }
347
+ }
348
+
349
+ Ok(images)
350
+ }
351
+
352
+ pub(super) fn parse_presentation_rels(rels_data: &[u8]) -> Result<Vec<String>> {
353
+ let xml_str = utf8_validation::from_utf8(rels_data)
354
+ .map_err(|e| KreuzbergError::parsing(format!("Invalid UTF-8 in presentation rels: {}", e)))?;
355
+
356
+ let doc = Document::parse(xml_str)
357
+ .map_err(|e| KreuzbergError::parsing(format!("Failed to parse presentation rels: {}", e)))?;
358
+
359
+ let mut slide_paths = Vec::new();
360
+
361
+ for node in doc.descendants() {
362
+ if node.has_tag_name("Relationship")
363
+ && let Some(rel_type) = node.attribute("Type")
364
+ && rel_type.contains("slide")
365
+ && !rel_type.contains("slideMaster")
366
+ && let Some(target) = node.attribute("Target")
367
+ {
368
+ let normalized_target = target.strip_prefix('/').unwrap_or(target);
369
+ let final_path = if normalized_target.starts_with("ppt/") {
370
+ normalized_target.to_string()
371
+ } else {
372
+ format!("ppt/{}", normalized_target)
373
+ };
374
+ slide_paths.push(final_path);
375
+ }
376
+ }
377
+
378
+ Ok(slide_paths)
379
+ }
@@ -0,0 +1,205 @@
1
+ //! Content processing utilities for transformation.
2
+ //!
3
+ //! This module handles processing of page content, tables, and images
4
+ //! during the transformation to semantic elements.
5
+
6
+ use crate::types::{BoundingBox, Element, ElementMetadata, ElementType};
7
+ use std::collections::HashMap;
8
+
9
+ use super::elements::{add_paragraphs, detect_list_items, generate_element_id};
10
+
11
+ /// Process page content to extract paragraphs and list items.
12
+ pub(super) fn process_content(elements: &mut Vec<Element>, content: &str, page_number: usize, title: &Option<String>) {
13
+ let list_items = detect_list_items(content);
14
+ let mut current_byte_offset = 0;
15
+
16
+ for list_item in list_items {
17
+ // Add narrative text/paragraphs before this list item
18
+ if current_byte_offset < list_item.byte_start {
19
+ let text_slice = content[current_byte_offset..list_item.byte_start].trim();
20
+ add_paragraphs(elements, text_slice, page_number, title);
21
+ }
22
+
23
+ // Add the list item itself
24
+ let item_text = content[list_item.byte_start..list_item.byte_end].trim();
25
+ if !item_text.is_empty() {
26
+ let element_id = generate_element_id(item_text, ElementType::ListItem, Some(page_number));
27
+ elements.push(Element {
28
+ element_id,
29
+ element_type: ElementType::ListItem,
30
+ text: item_text.to_string(),
31
+ metadata: ElementMetadata {
32
+ page_number: Some(page_number),
33
+ filename: title.clone(),
34
+ coordinates: None,
35
+ element_index: Some(elements.len()),
36
+ additional: {
37
+ let mut m = HashMap::new();
38
+ m.insert("indent_level".to_string(), list_item.indent_level.to_string());
39
+ m.insert("list_type".to_string(), format!("{:?}", list_item.list_type));
40
+ m
41
+ },
42
+ },
43
+ });
44
+ }
45
+
46
+ current_byte_offset = list_item.byte_end;
47
+ }
48
+
49
+ // Add any remaining narrative text/paragraphs
50
+ if current_byte_offset < content.len() {
51
+ let text_slice = content[current_byte_offset..].trim();
52
+ add_paragraphs(elements, text_slice, page_number, title);
53
+ }
54
+ }
55
+
56
+ /// Format a table as plain text for element representation.
57
+ pub(super) fn format_table_as_text(table: &crate::types::Table) -> String {
58
+ let mut output = String::new();
59
+
60
+ // Simple text representation: rows separated by newlines, cells by tabs
61
+ for row in &table.cells {
62
+ for (i, cell) in row.iter().enumerate() {
63
+ if i > 0 {
64
+ output.push('\t');
65
+ }
66
+ output.push_str(cell);
67
+ }
68
+ output.push('\n');
69
+ }
70
+
71
+ output.trim().to_string()
72
+ }
73
+
74
+ /// Process hierarchy blocks (PDF headings) into Title elements.
75
+ pub(super) fn process_hierarchy(
76
+ elements: &mut Vec<Element>,
77
+ hierarchy: &crate::types::PageHierarchy,
78
+ page_number: usize,
79
+ title: &Option<String>,
80
+ ) {
81
+ for block in &hierarchy.blocks {
82
+ let element_type = match block.level.as_str() {
83
+ "h1" | "h2" | "h3" | "h4" | "h5" | "h6" => ElementType::Title,
84
+ _ => continue, // Body text will be processed separately
85
+ };
86
+
87
+ let coords = block.bbox.as_ref().map(|(left, top, right, bottom)| BoundingBox {
88
+ x0: *left as f64,
89
+ y0: *top as f64,
90
+ x1: *right as f64,
91
+ y1: *bottom as f64,
92
+ });
93
+
94
+ let element_id = generate_element_id(&block.text, element_type, Some(page_number));
95
+ elements.push(Element {
96
+ element_id,
97
+ element_type,
98
+ text: block.text.clone(),
99
+ metadata: ElementMetadata {
100
+ page_number: Some(page_number),
101
+ filename: title.clone(),
102
+ coordinates: coords,
103
+ element_index: Some(elements.len()),
104
+ additional: {
105
+ let mut m = HashMap::new();
106
+ m.insert("level".to_string(), block.level.clone());
107
+ m.insert("font_size".to_string(), block.font_size.to_string());
108
+ m
109
+ },
110
+ },
111
+ });
112
+ }
113
+ }
114
+
115
+ /// Process tables on a page into Table elements.
116
+ pub(super) fn process_tables(
117
+ elements: &mut Vec<Element>,
118
+ tables: &[std::sync::Arc<crate::types::Table>],
119
+ page_number: usize,
120
+ title: &Option<String>,
121
+ ) {
122
+ for table_arc in tables {
123
+ let table = table_arc.as_ref();
124
+ let table_text = format_table_as_text(table);
125
+
126
+ let element_id = generate_element_id(&table_text, ElementType::Table, Some(page_number));
127
+ elements.push(Element {
128
+ element_id,
129
+ element_type: ElementType::Table,
130
+ text: table_text,
131
+ metadata: ElementMetadata {
132
+ page_number: Some(page_number),
133
+ filename: title.clone(),
134
+ coordinates: None, // Tables don't have bbox in current structure
135
+ element_index: Some(elements.len()),
136
+ additional: HashMap::new(),
137
+ },
138
+ });
139
+ }
140
+ }
141
+
142
+ /// Process images on a page into Image elements.
143
+ pub(super) fn process_images(
144
+ elements: &mut Vec<Element>,
145
+ images: &[std::sync::Arc<crate::types::ExtractedImage>],
146
+ page_number: usize,
147
+ title: &Option<String>,
148
+ ) {
149
+ for image_arc in images {
150
+ let image = image_arc.as_ref();
151
+ let image_text = format!(
152
+ "Image: {} ({}x{})",
153
+ image.format,
154
+ image.width.unwrap_or(0),
155
+ image.height.unwrap_or(0)
156
+ );
157
+
158
+ let element_id = generate_element_id(&image_text, ElementType::Image, Some(page_number));
159
+ elements.push(Element {
160
+ element_id,
161
+ element_type: ElementType::Image,
162
+ text: image_text,
163
+ metadata: ElementMetadata {
164
+ page_number: Some(page_number),
165
+ filename: title.clone(),
166
+ coordinates: None, // Images don't have bbox in current structure
167
+ element_index: Some(elements.len()),
168
+ additional: {
169
+ let mut m = HashMap::new();
170
+ m.insert("format".to_string(), image.format.clone());
171
+ if let Some(width) = image.width {
172
+ m.insert("width".to_string(), width.to_string());
173
+ }
174
+ if let Some(height) = image.height {
175
+ m.insert("height".to_string(), height.to_string());
176
+ }
177
+ m
178
+ },
179
+ },
180
+ });
181
+ }
182
+ }
183
+
184
+ /// Add a PageBreak element between pages.
185
+ pub(super) fn add_page_break(
186
+ elements: &mut Vec<Element>,
187
+ current_page: usize,
188
+ next_page: usize,
189
+ title: &Option<String>,
190
+ ) {
191
+ let page_break_text = format!("--- PAGE BREAK (page {} → {}) ---", current_page, next_page);
192
+ let element_id = generate_element_id(&page_break_text, ElementType::PageBreak, Some(current_page));
193
+ elements.push(Element {
194
+ element_id,
195
+ element_type: ElementType::PageBreak,
196
+ text: page_break_text,
197
+ metadata: ElementMetadata {
198
+ page_number: Some(current_page),
199
+ filename: title.clone(),
200
+ coordinates: None,
201
+ element_index: Some(elements.len()),
202
+ additional: HashMap::new(),
203
+ },
204
+ });
205
+ }