yomitoku 0.9.1__tar.gz → 0.9.3__tar.gz

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 (184) hide show
  1. {yomitoku-0.9.1 → yomitoku-0.9.3}/PKG-INFO +5 -3
  2. {yomitoku-0.9.1 → yomitoku-0.9.3}/README.md +2 -2
  3. {yomitoku-0.9.1 → yomitoku-0.9.3}/README_EN.md +1 -1
  4. {yomitoku-0.9.1 → yomitoku-0.9.3}/docs/cli.en.md +3 -1
  5. {yomitoku-0.9.1 → yomitoku-0.9.3}/docs/cli.ja.md +4 -3
  6. {yomitoku-0.9.1 → yomitoku-0.9.3}/docs/index.en.md +1 -1
  7. {yomitoku-0.9.1 → yomitoku-0.9.3}/docs/index.ja.md +1 -1
  8. {yomitoku-0.9.1 → yomitoku-0.9.3}/docs/mcp.en.md +16 -0
  9. {yomitoku-0.9.1 → yomitoku-0.9.3}/docs/mcp.ja.md +14 -0
  10. {yomitoku-0.9.1 → yomitoku-0.9.3}/pyproject.toml +3 -1
  11. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/cli/main.py +49 -8
  12. yomitoku-0.9.1/src/yomitoku/cli/mcp.py → yomitoku-0.9.3/src/yomitoku/cli/mcp_server.py +37 -6
  13. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/constants.py +1 -1
  14. yomitoku-0.9.3/src/yomitoku/utils/searchable_pdf.py +119 -0
  15. {yomitoku-0.9.1 → yomitoku-0.9.3}/tests/test_cli.py +25 -0
  16. yomitoku-0.9.3/uv.lock +1898 -0
  17. yomitoku-0.9.1/uv.lock +0 -1815
  18. {yomitoku-0.9.1 → yomitoku-0.9.3}/.github/FUNDING.yml +0 -0
  19. {yomitoku-0.9.1 → yomitoku-0.9.3}/.github/release-drafter.yml +0 -0
  20. {yomitoku-0.9.1 → yomitoku-0.9.3}/.github/workflows/build-and-publish-docs.yaml +0 -0
  21. {yomitoku-0.9.1 → yomitoku-0.9.3}/.github/workflows/build-and-publish.yml +0 -0
  22. {yomitoku-0.9.1 → yomitoku-0.9.3}/.github/workflows/create-release.yml +0 -0
  23. {yomitoku-0.9.1 → yomitoku-0.9.3}/.github/workflows/lint-and-test.yml +0 -0
  24. {yomitoku-0.9.1 → yomitoku-0.9.3}/.gitignore +0 -0
  25. {yomitoku-0.9.1 → yomitoku-0.9.3}/.pre-commit-config.yaml +0 -0
  26. {yomitoku-0.9.1 → yomitoku-0.9.3}/.python-version +0 -0
  27. {yomitoku-0.9.1 → yomitoku-0.9.3}/configs/yomitoku-layout-parser-rtdtrv2-open-beta.yaml +0 -0
  28. {yomitoku-0.9.1 → yomitoku-0.9.3}/configs/yomitoku-table-structure-recognizer-rtdtrv2-open-beta.yaml +0 -0
  29. {yomitoku-0.9.1 → yomitoku-0.9.3}/configs/yomitoku-text-detector-dbnet-open-beta.yaml +0 -0
  30. {yomitoku-0.9.1 → yomitoku-0.9.3}/configs/yomitoku-text-recognizer-parseq-open-beta.yaml +0 -0
  31. {yomitoku-0.9.1 → yomitoku-0.9.3}/configs/yomitoku-text-recognizer-parseq-small-open-beta.yaml +0 -0
  32. {yomitoku-0.9.1 → yomitoku-0.9.3}/demo/sample.pdf +0 -0
  33. {yomitoku-0.9.1 → yomitoku-0.9.3}/demo/setting_document_anaysis.py +0 -0
  34. {yomitoku-0.9.1 → yomitoku-0.9.3}/demo/simple_document_analysis.py +0 -0
  35. {yomitoku-0.9.1 → yomitoku-0.9.3}/demo/simple_layout.py +0 -0
  36. {yomitoku-0.9.1 → yomitoku-0.9.3}/demo/simple_ocr.py +0 -0
  37. {yomitoku-0.9.1 → yomitoku-0.9.3}/demo/text_detector.yaml +0 -0
  38. {yomitoku-0.9.1 → yomitoku-0.9.3}/dockerfile +0 -0
  39. {yomitoku-0.9.1 → yomitoku-0.9.3}/docs/assets/logo.svg +0 -0
  40. {yomitoku-0.9.1 → yomitoku-0.9.3}/docs/configuration.en.md +0 -0
  41. {yomitoku-0.9.1 → yomitoku-0.9.3}/docs/configuration.ja.md +0 -0
  42. {yomitoku-0.9.1 → yomitoku-0.9.3}/docs/installation.en.md +0 -0
  43. {yomitoku-0.9.1 → yomitoku-0.9.3}/docs/installation.ja.md +0 -0
  44. {yomitoku-0.9.1 → yomitoku-0.9.3}/docs/module.en.md +0 -0
  45. {yomitoku-0.9.1 → yomitoku-0.9.3}/docs/module.ja.md +0 -0
  46. {yomitoku-0.9.1 → yomitoku-0.9.3}/gallery.md +0 -0
  47. {yomitoku-0.9.1 → yomitoku-0.9.3}/mkdocs.yml +0 -0
  48. {yomitoku-0.9.1 → yomitoku-0.9.3}/pytest.ini +0 -0
  49. {yomitoku-0.9.1 → yomitoku-0.9.3}/scripts/register_hugging_face_hub.py +0 -0
  50. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/__init__.py +0 -0
  51. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/base.py +0 -0
  52. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/cli/__init__.py +0 -0
  53. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/configs/__init__.py +0 -0
  54. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/configs/cfg_layout_parser_rtdtrv2.py +0 -0
  55. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/configs/cfg_layout_parser_rtdtrv2_v2.py +0 -0
  56. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/configs/cfg_table_structure_recognizer_rtdtrv2.py +0 -0
  57. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/configs/cfg_text_detector_dbnet.py +0 -0
  58. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/configs/cfg_text_detector_dbnet_v2.py +0 -0
  59. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/configs/cfg_text_recognizer_parseq.py +0 -0
  60. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/configs/cfg_text_recognizer_parseq_small.py +0 -0
  61. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/configs/cfg_text_recognizer_parseq_v2.py +0 -0
  62. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/data/__init__.py +0 -0
  63. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/data/dataset.py +0 -0
  64. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/data/functions.py +0 -0
  65. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/document_analyzer.py +0 -0
  66. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/export/__init__.py +0 -0
  67. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/export/export_csv.py +0 -0
  68. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/export/export_html.py +0 -0
  69. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/export/export_json.py +0 -0
  70. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/export/export_markdown.py +0 -0
  71. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/layout_analyzer.py +0 -0
  72. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/layout_parser.py +0 -0
  73. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/models/__init__.py +0 -0
  74. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/models/dbnet_plus.py +0 -0
  75. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/models/layers/__init__.py +0 -0
  76. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/models/layers/activate.py +0 -0
  77. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/models/layers/dbnet_feature_attention.py +0 -0
  78. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/models/layers/parseq_transformer.py +0 -0
  79. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/models/layers/rtdetr_backbone.py +0 -0
  80. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/models/layers/rtdetr_hybrid_encoder.py +0 -0
  81. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/models/layers/rtdetrv2_decoder.py +0 -0
  82. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/models/parseq.py +0 -0
  83. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/models/rtdetr.py +0 -0
  84. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/ocr.py +0 -0
  85. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/onnx/.gitkeep +0 -0
  86. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/postprocessor/__init__.py +0 -0
  87. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/postprocessor/dbnet_postporcessor.py +0 -0
  88. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/postprocessor/parseq_tokenizer.py +0 -0
  89. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/postprocessor/rtdetr_postprocessor.py +0 -0
  90. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/reading_order.py +0 -0
  91. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/resource/MPLUS1p-Medium.ttf +0 -0
  92. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/resource/charset.txt +0 -0
  93. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/table_structure_recognizer.py +0 -0
  94. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/text_detector.py +0 -0
  95. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/text_recognizer.py +0 -0
  96. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/utils/__init__.py +0 -0
  97. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/utils/graph.py +0 -0
  98. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/utils/logger.py +0 -0
  99. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/utils/misc.py +0 -0
  100. {yomitoku-0.9.1 → yomitoku-0.9.3}/src/yomitoku/utils/visualizer.py +0 -0
  101. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/in/demo.jpg +0 -0
  102. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/in/gallery1.jpg +0 -0
  103. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/in/gallery2.jpg +0 -0
  104. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/in/gallery3.jpg +0 -0
  105. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/in/gallery4.jpg +0 -0
  106. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/in/gallery5.jpg +0 -0
  107. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/in/gallery6.jpg +0 -0
  108. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/in/gallery7.jpeg +0 -0
  109. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/logo/horizontal.png +0 -0
  110. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/demo_html.png +0 -0
  111. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/figures/in_demo_p1_figure_0.png +0 -0
  112. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/figures/in_gallery1_p1_figure_0.png +0 -0
  113. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/figures/in_gallery1_p1_figure_1.png +0 -0
  114. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/figures/in_gallery1_p1_figure_10.png +0 -0
  115. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/figures/in_gallery1_p1_figure_2.png +0 -0
  116. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/figures/in_gallery1_p1_figure_3.png +0 -0
  117. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/figures/in_gallery1_p1_figure_4.png +0 -0
  118. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/figures/in_gallery1_p1_figure_5.png +0 -0
  119. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/figures/in_gallery1_p1_figure_6.png +0 -0
  120. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/figures/in_gallery1_p1_figure_7.png +0 -0
  121. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/figures/in_gallery1_p1_figure_8.png +0 -0
  122. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/figures/in_gallery1_p1_figure_9.png +0 -0
  123. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/figures/in_gallery3_p1_figure_0.png +0 -0
  124. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/figures/in_gallery3_p1_figure_1.png +0 -0
  125. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/figures/in_gallery5_p1_figure_0.png +0 -0
  126. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/figures/in_gallery5_p1_figure_1.png +0 -0
  127. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/figures/in_gallery6_p1_figure_0.png +0 -0
  128. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/figures/in_gallery6_p1_figure_1.png +0 -0
  129. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/figures/in_gallery7_p1_figure_0.png +0 -0
  130. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_demo_p1.html +0 -0
  131. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_demo_p1.md +0 -0
  132. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_demo_p1_layout.jpg +0 -0
  133. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_demo_p1_ocr.jpg +0 -0
  134. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery1_p1.html +0 -0
  135. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery1_p1.md +0 -0
  136. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery1_p1_layout.jpg +0 -0
  137. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery1_p1_ocr.jpg +0 -0
  138. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery2_p1.html +0 -0
  139. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery2_p1.md +0 -0
  140. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery2_p1_layout.jpg +0 -0
  141. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery2_p1_ocr.jpg +0 -0
  142. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery3_p1.html +0 -0
  143. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery3_p1.md +0 -0
  144. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery3_p1_layout.jpg +0 -0
  145. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery3_p1_ocr.jpg +0 -0
  146. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery4_p1.html +0 -0
  147. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery4_p1.md +0 -0
  148. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery4_p1_layout.jpg +0 -0
  149. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery4_p1_ocr.jpg +0 -0
  150. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery5_p1.html +0 -0
  151. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery5_p1.md +0 -0
  152. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery5_p1_layout.jpg +0 -0
  153. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery5_p1_ocr.jpg +0 -0
  154. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery6_p1.html +0 -0
  155. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery6_p1.md +0 -0
  156. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery6_p1_layout.jpg +0 -0
  157. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery6_p1_ocr.jpg +0 -0
  158. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery7_p1.html +0 -0
  159. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery7_p1.md +0 -0
  160. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery7_p1_layout.jpg +0 -0
  161. {yomitoku-0.9.1 → yomitoku-0.9.3}/static/out/in_gallery7_p1_ocr.jpg +0 -0
  162. {yomitoku-0.9.1 → yomitoku-0.9.3}/tests/data/invalid.jpg +0 -0
  163. {yomitoku-0.9.1 → yomitoku-0.9.3}/tests/data/invalid.pdf +0 -0
  164. {yomitoku-0.9.1 → yomitoku-0.9.3}/tests/data/rgba.png +0 -0
  165. {yomitoku-0.9.1 → yomitoku-0.9.3}/tests/data/sampldoc.tif +0 -0
  166. {yomitoku-0.9.1 → yomitoku-0.9.3}/tests/data/small.jpg +0 -0
  167. {yomitoku-0.9.1 → yomitoku-0.9.3}/tests/data/subdir/test.jpg +0 -0
  168. {yomitoku-0.9.1 → yomitoku-0.9.3}/tests/data/test.bmp +0 -0
  169. {yomitoku-0.9.1 → yomitoku-0.9.3}/tests/data/test.jpg +0 -0
  170. {yomitoku-0.9.1 → yomitoku-0.9.3}/tests/data/test.pdf +0 -0
  171. {yomitoku-0.9.1 → yomitoku-0.9.3}/tests/data/test.png +0 -0
  172. {yomitoku-0.9.1 → yomitoku-0.9.3}/tests/data/test.tiff +0 -0
  173. {yomitoku-0.9.1 → yomitoku-0.9.3}/tests/data/test.txt +0 -0
  174. {yomitoku-0.9.1 → yomitoku-0.9.3}/tests/data/test_gray.jpg +0 -0
  175. {yomitoku-0.9.1 → yomitoku-0.9.3}/tests/test_base.py +0 -0
  176. {yomitoku-0.9.1 → yomitoku-0.9.3}/tests/test_data.py +0 -0
  177. {yomitoku-0.9.1 → yomitoku-0.9.3}/tests/test_document_analyzer.py +0 -0
  178. {yomitoku-0.9.1 → yomitoku-0.9.3}/tests/test_export.py +0 -0
  179. {yomitoku-0.9.1 → yomitoku-0.9.3}/tests/test_layout_analyzer.py +0 -0
  180. {yomitoku-0.9.1 → yomitoku-0.9.3}/tests/test_ocr.py +0 -0
  181. {yomitoku-0.9.1 → yomitoku-0.9.3}/tests/yaml/layout_parser.yaml +0 -0
  182. {yomitoku-0.9.1 → yomitoku-0.9.3}/tests/yaml/table_structure_recognizer.yaml +0 -0
  183. {yomitoku-0.9.1 → yomitoku-0.9.3}/tests/yaml/text_detector.yaml +0 -0
  184. {yomitoku-0.9.1 → yomitoku-0.9.3}/tests/yaml/text_recognizer.yaml +0 -0
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yomitoku
3
- Version: 0.9.1
3
+ Version: 0.9.3
4
4
  Summary: Yomitoku is an AI-powered document image analysis package designed specifically for the Japanese language.
5
5
  Author-email: Kotaro Kinoshita <kotaro.kinoshita@mlism.com>
6
6
  License: CC BY-NC-SA 4.0
7
7
  Keywords: Deep Learning,Japanese,OCR
8
8
  Requires-Python: <3.13,>=3.10
9
9
  Requires-Dist: huggingface-hub>=0.26.1
10
+ Requires-Dist: jaconv>=0.4.0
10
11
  Requires-Dist: lxml>=5.3.0
11
12
  Requires-Dist: omegaconf>=2.3.0
12
13
  Requires-Dist: onnx>=1.17.0
@@ -15,6 +16,7 @@ Requires-Dist: opencv-python>=4.10.0.84
15
16
  Requires-Dist: pyclipper>=1.3.0.post6
16
17
  Requires-Dist: pydantic>=2.9.2
17
18
  Requires-Dist: pypdfium2>=4.30.0
19
+ Requires-Dist: reportlab>=4.4.1
18
20
  Requires-Dist: shapely>=2.0.6
19
21
  Requires-Dist: timm>=1.0.11
20
22
  Requires-Dist: torch>=2.5.0
@@ -41,7 +43,7 @@ YomiToku は日本語に特化した AI 文章画像解析エンジン(Document
41
43
  - 🤖 日本語データセットで学習した 4 種類(文字位置の検知、文字列認識、レイアウト解析、表の構造認識)の AI モデルを搭載しています。4 種類のモデルはすべて独自に学習されたモデルで日本語文書に対して、高精度に推論可能です。
42
44
  - 🇯🇵 各モデルは日本語の文書画像に特化して学習されており、7000 文字を超える日本語文字の認識をサーポート、手書き文字、縦書きなど日本語特有のレイアウト構造の文書画像の解析も可能です。(日本語以外にも英語の文書に対しても対応しています)。
43
45
  - 📈 レイアウト解析、表の構造解析, 読み順推定機能により、文書画像のレイアウトの意味的構造を壊さずに情報を抽出することが可能です。
44
- - 📄 多様な出力形式をサポートしています。html やマークダウン、json、csv のいずれかのフォーマットに変換可能です。また、文書内に含まれる図表、画像の抽出の出力も可能です。
46
+ - 📄 多様な出力形式をサポートしています。html やマークダウン、json、csv のいずれかのフォーマットに変換可能です。また、文書内に含まれる図表、画像の抽出の出力も可能です。文書画像をサーチャブルPDFに変換する処理もサポートしています。
45
47
  - ⚡ GPU 環境で高速に動作し、効率的に文書の文字起こし解析が可能です。また、VRAM も 8GB 以内で動作し、ハイエンドな GPU を用意する必要はありません。
46
48
 
47
49
  ## 🖼️ デモ
@@ -85,7 +87,7 @@ yomitoku ${path_data} -f md -o results -v --figure --lite
85
87
  ```
86
88
 
87
89
  - `${path_data}` 解析対象の画像が含まれたディレクトリか画像ファイルのパスを直接して指定してください。ディレクトリを対象とした場合はディレクトリのサブディレクトリ内の画像も含めて処理を実行します。
88
- - `-f`, `--format` 出力形式のファイルフォーマットを指定します。(json, csv, html, md をサポート)
90
+ - `-f`, `--format` 出力形式のファイルフォーマットを指定します。(json, csv, html, md, pdf(searchable-pdf) をサポート)
89
91
  - `-o`, `--outdir` 出力先のディレクトリ名を指定します。存在しない場合は新規で作成されます。
90
92
  - `-v`, `--vis` を指定すると解析結果を可視化した画像を出力します。
91
93
  - `-l`, `--lite` を指定すると軽量モデルで推論を実行します。通常より高速に推論できますが、若干、精度が低下する可能性があります。
@@ -16,7 +16,7 @@ YomiToku は日本語に特化した AI 文章画像解析エンジン(Document
16
16
  - 🤖 日本語データセットで学習した 4 種類(文字位置の検知、文字列認識、レイアウト解析、表の構造認識)の AI モデルを搭載しています。4 種類のモデルはすべて独自に学習されたモデルで日本語文書に対して、高精度に推論可能です。
17
17
  - 🇯🇵 各モデルは日本語の文書画像に特化して学習されており、7000 文字を超える日本語文字の認識をサーポート、手書き文字、縦書きなど日本語特有のレイアウト構造の文書画像の解析も可能です。(日本語以外にも英語の文書に対しても対応しています)。
18
18
  - 📈 レイアウト解析、表の構造解析, 読み順推定機能により、文書画像のレイアウトの意味的構造を壊さずに情報を抽出することが可能です。
19
- - 📄 多様な出力形式をサポートしています。html やマークダウン、json、csv のいずれかのフォーマットに変換可能です。また、文書内に含まれる図表、画像の抽出の出力も可能です。
19
+ - 📄 多様な出力形式をサポートしています。html やマークダウン、json、csv のいずれかのフォーマットに変換可能です。また、文書内に含まれる図表、画像の抽出の出力も可能です。文書画像をサーチャブルPDFに変換する処理もサポートしています。
20
20
  - ⚡ GPU 環境で高速に動作し、効率的に文書の文字起こし解析が可能です。また、VRAM も 8GB 以内で動作し、ハイエンドな GPU を用意する必要はありません。
21
21
 
22
22
  ## 🖼️ デモ
@@ -60,7 +60,7 @@ yomitoku ${path_data} -f md -o results -v --figure --lite
60
60
  ```
61
61
 
62
62
  - `${path_data}` 解析対象の画像が含まれたディレクトリか画像ファイルのパスを直接して指定してください。ディレクトリを対象とした場合はディレクトリのサブディレクトリ内の画像も含めて処理を実行します。
63
- - `-f`, `--format` 出力形式のファイルフォーマットを指定します。(json, csv, html, md をサポート)
63
+ - `-f`, `--format` 出力形式のファイルフォーマットを指定します。(json, csv, html, md, pdf(searchable-pdf) をサポート)
64
64
  - `-o`, `--outdir` 出力先のディレクトリ名を指定します。存在しない場合は新規で作成されます。
65
65
  - `-v`, `--vis` を指定すると解析結果を可視化した画像を出力します。
66
66
  - `-l`, `--lite` を指定すると軽量モデルで推論を実行します。通常より高速に推論できますが、若干、精度が低下する可能性があります。
@@ -59,7 +59,7 @@ yomitoku ${path_data} -f md -o results -v --figure --lite
59
59
  ```
60
60
 
61
61
  - `${path_data}`: Specify the path to a directory containing images to be analyzed or directly provide the path to an image file. If a directory is specified, images in its subdirectories will also be processed.
62
- - `-f`, `--format`: Specify the output file format. Supported formats are json, csv, html, and md.
62
+ - `-f`, `--format`: Specify the output file format. Supported formats are json, csv, html, md , and pdf(searchable-pdf).
63
63
  - `-o`, `--outdir`: Specify the name of the output directory. If it does not exist, it will be created.
64
64
  - `-v`, `--vis`: If specified, outputs visualized images of the analysis results.
65
65
  - `-l`, `--lite`: inference is performed using a lightweight model. This enables fast inference even on a CPU.
@@ -7,7 +7,7 @@ yomitoku ${path_data} -v -o results
7
7
  ```
8
8
 
9
9
  - `${path_data}`: Specify the path to a directory containing images to be analyzed or directly provide the path to an image file. If a directory is specified, images in its subdirectories will also be processed.
10
- - `-f`, `--format`: Specify the output file format. Supported formats are json, csv, html, and md.
10
+ - `-f`, `--format`: Specify the output file format. Supported formats are json, csv, html, md , and pdf(searchable-pdf).
11
11
  - `-o`, `--outdir`: Specify the name of the output directory. If it does not exist, it will be created.
12
12
  - `-v`, `--vis`: If specified, outputs visualized images of the analysis results.
13
13
 
@@ -41,6 +41,8 @@ You can specify the output format of the analysis results using the --format or
41
41
  yomitoku ${path_data} -f md
42
42
  ```
43
43
 
44
+ - `pdf`: Detect the text in the image and embed it into the PDF as invisible text, converting the file into a searchable PDF.
45
+
44
46
  ## Specifying the Output Device
45
47
 
46
48
  You can specify the device for running the model using the -d or --device option. Supported options are cuda, cpu, and mps. If a GPU is not available, inference will be performed on the CPU. (Default: cuda)
@@ -31,12 +31,14 @@ yomitoku ${path_data} --lite -v
31
31
 
32
32
  ## 出力フォーマットの指定
33
33
 
34
- `--format`, `-f` オプションを使用することで解析結果の出力形式のフォーマットを指定できます。出力フォーマットの種類は json, csv, html, md(markdown)をサポートしています。
34
+ - `-f`, `--format` 出力形式のファイルフォーマットを指定します。(json, csv, html, md, pdf(searchable-pdf) をサポート)
35
35
 
36
36
  ```
37
37
  yomitoku ${path_data} -f md
38
38
  ```
39
39
 
40
+ - pdf: 画像内の文字情報を認識し、文字情報を透明テキストとして、PDFに埋め込むことで、サーチャブルPDFに変換します。
41
+
40
42
  ## 出力デバイスの指定
41
43
 
42
44
  - `-d`, `--device` オプションを使用することで、モデルを実行するためのデバイスを指定します。(cuda | cpu | mps)。gpu が利用できない場合は cpu で推論が実行されます。(デフォルト: cuda)
@@ -117,5 +119,4 @@ yomitoku ${path_data} --reading_order left2right
117
119
 
118
120
  - `left2right`: 左から右方向に優先的に読み取り順を推定します。レシートや保険証などキーに対して、値を示すテキストが段組みになっているようなレイアウトに有効です。
119
121
 
120
- - `right2left:` 右から左方向に優先的に読み取り順を推定します。縦書きのドキュメントに対して有効です。
121
-
122
+ - `right2left:` 右から左方向に優先的に読み取り順を推定します。縦書きのドキュメントに対して有効です。
@@ -5,7 +5,7 @@ YomiToku is a Document AI engine specialized in Japanese document image analysis
5
5
  - 🤖 Equipped with four AI models trained on Japanese datasets: text detection, text recognition, layout analysis, and table structure recognition. All models are independently trained and optimized for Japanese documents, delivering high-precision inference.
6
6
  - 🇯🇵 Each model is specifically trained for Japanese document images, supporting the recognition of over 7,000 Japanese characters, including vertical text and other layout structures unique to Japanese documents. (It also supports English documents.)
7
7
  - 📈 By leveraging layout analysis, table structure parsing, and reading order estimation, it extracts information while preserving the semantic structure of the document layout.
8
- - 📄 Supports a variety of output formats, including HTML, Markdown, JSON, and CSV. It also allows for the extraction of diagrams and images contained within the documents.
8
+ - 📄 Supports a variety of output formats, including HTML, Markdown, JSON, and CSV. It also allows for the extraction of diagrams and images contained within the documents.It also supports converting document images into fully text-searchable PDFs.
9
9
  - ⚡ Operates efficiently in GPU environments, enabling fast document transcription and analysis. It requires less than 8GB of VRAM, eliminating the need for high-end GPUs.。
10
10
 
11
11
  ## 🙋 FAQ
@@ -5,7 +5,7 @@ YomiToku は日本語に特化した AI 文章画像解析エンジン(Document
5
5
  - 🤖 日本語データセットで学習した 4 種類(文字位置の検知、文字列認識、レイアウト解析、表の構造認識)の AI モデルを搭載しています。4 種類のモデルはすべて独自に学習されたモデルで日本語文書に対して、高精度に推論可能です。
6
6
  - 🇯🇵 各モデルは日本語の文書画像に特化して学習されており、7000 文字を超える日本語文字の認識をサーポート、手書き文字、縦書きなど日本語特有のレイアウト構造の文書画像の解析も可能です。(日本語以外にも英語の文書に対しても対応しています)。
7
7
  - 📈 レイアウト解析、表の構造解析, 読み順推定機能により、文書画像のレイアウトの意味的構造を壊さずに情報を抽出することが可能です。
8
- - 📄 多様な出力形式をサポートしています。html やマークダウン、json、csv のいずれかのフォーマットに変換可能です。また、文書内に含まれる図表、画像の抽出の出力も可能です。
8
+ - 📄 多様な出力形式をサポートしています。html やマークダウン、json、csv のいずれかのフォーマットに変換可能です。また、文書内に含まれる図表、画像の抽出の出力も可能です。文書画像を全文検索可能なサーチャブルPDFに変換する処理もサポートしています。
9
9
  - ⚡ GPU 環境で高速に動作し、効率的に文書の文字起こし解析が可能です。また、VRAM も 8GB 以内で動作し、ハイエンドな GPU を用意する必要はありません。
10
10
 
11
11
  ## 🙋 FAQ
@@ -46,3 +46,19 @@ For example, if you use `yomitoku/demo/sample.pdf` as a sample, instruct as foll
46
46
  ```txt
47
47
  Analyze sample.pdf using OCR and translate it into English.
48
48
  ```
49
+
50
+ ## Starting the SSE Server
51
+
52
+ Set the path to the folder containing the images to be processed by OCR in the resource directory.
53
+
54
+ ```
55
+ export RESOURCE_DIR="path of dataset"
56
+ ```
57
+
58
+ Start the SSE server using the following command:
59
+
60
+ ```
61
+ uv run yomitoku_mcp -t sse
62
+ ```
63
+
64
+ The SSE server endpoint will be available at `http://127.0.0.1:8000/sse`.
@@ -13,6 +13,7 @@
13
13
  uv sync --extra mcp
14
14
  ```
15
15
 
16
+
16
17
  ## Claude Desktopの設定
17
18
 
18
19
  次にClaude Desktopの設定ファイルの`mcpServers`に以下ように設定を追加します。(設定ファイルの開き方は[こちら](https://modelcontextprotocol.io/quickstart/user)を参照してください)
@@ -48,3 +49,16 @@ uv sync --extra mcp
48
49
  ```txt
49
50
  sample.pdfをOCRで解析して要約してください。
50
51
  ```
52
+
53
+ ## SSEサーバーの起動
54
+ 環境変数の`RESOURCE_DIR`にOCRの対象画像が含まれたフォルダのパスを設定してください。
55
+ ```
56
+ export RESOURCE_DIR="path of dataset"
57
+ ```
58
+
59
+ 以下のコマンドでSSEサーバーを起動します。
60
+ ```
61
+ uv run yomitoku_mcp -t sse
62
+ ```
63
+
64
+ ` http://127.0.0.1:8000/sse`がSSEサーバーのエンドポイントになります。
@@ -30,6 +30,8 @@ dependencies = [
30
30
  "torch>=2.5.0",
31
31
  "torchvision>=0.20.0",
32
32
  "onnxruntime>=1.20.1",
33
+ "reportlab>=4.4.1",
34
+ "jaconv>=0.4.0",
33
35
  ]
34
36
 
35
37
  [tool.uv-dynamic-versioning]
@@ -70,7 +72,7 @@ explicit = true
70
72
 
71
73
  [project.scripts]
72
74
  yomitoku = "yomitoku.cli.main:main"
73
- yomitoku_mcp = "yomitoku.cli.mcp:run_mcp_server"
75
+ yomitoku_mcp = "yomitoku.cli.mcp_server:main"
74
76
 
75
77
  [project.optional-dependencies]
76
78
  mcp = [
@@ -9,6 +9,7 @@ from ..constants import SUPPORT_OUTPUT_FORMAT
9
9
  from ..data.functions import load_image, load_pdf
10
10
  from ..document_analyzer import DocumentAnalyzer
11
11
  from ..utils.logger import set_logger
12
+ from ..utils.searchable_pdf import create_searchable_pdf
12
13
 
13
14
  from ..export import save_csv, save_html, save_json, save_markdown
14
15
  from ..export import convert_json, convert_csv, convert_html, convert_markdown
@@ -48,10 +49,15 @@ def merge_all_pages(results):
48
49
  else:
49
50
  out += "\n" + data
50
51
 
52
+ elif format == "pdf":
53
+ if out is None:
54
+ out = [data]
55
+ else:
56
+ out.append(data)
51
57
  return out
52
58
 
53
59
 
54
- def save_merged_file(out_path, args, out):
60
+ def save_merged_file(out_path, args, out, imgs):
55
61
  if args.format == "json":
56
62
  save_json(out, out_path, args.encoding)
57
63
  elif args.format == "csv":
@@ -60,6 +66,13 @@ def save_merged_file(out_path, args, out):
60
66
  save_html(out, out_path, args.encoding)
61
67
  elif args.format == "md":
62
68
  save_markdown(out, out_path, args.encoding)
69
+ elif args.format == "pdf":
70
+ create_searchable_pdf(
71
+ imgs,
72
+ out,
73
+ output_path=out_path,
74
+ font_path=args.font_path,
75
+ )
63
76
 
64
77
 
65
78
  def validate_encoding(encoding):
@@ -80,7 +93,7 @@ def process_single_file(args, analyzer, path, format):
80
93
  else:
81
94
  imgs = load_image(path)
82
95
 
83
- results = []
96
+ format_results = []
84
97
  for page, img in enumerate(imgs):
85
98
  result, ocr, layout = analyzer(img)
86
99
  dirname = path.parent.name
@@ -130,7 +143,7 @@ def process_single_file(args, analyzer, path, format):
130
143
  figure_dir=args.figure_dir,
131
144
  )
132
145
 
133
- results.append(
146
+ format_results.append(
134
147
  {
135
148
  "format": format,
136
149
  "data": json.model_dump(),
@@ -157,7 +170,7 @@ def process_single_file(args, analyzer, path, format):
157
170
  figure_dir=args.figure_dir,
158
171
  )
159
172
 
160
- results.append(
173
+ format_results.append(
161
174
  {
162
175
  "format": format,
163
176
  "data": csv,
@@ -188,7 +201,7 @@ def process_single_file(args, analyzer, path, format):
188
201
  encoding=args.encoding,
189
202
  )
190
203
 
191
- results.append(
204
+ format_results.append(
192
205
  {
193
206
  "format": format,
194
207
  "data": html,
@@ -219,20 +232,36 @@ def process_single_file(args, analyzer, path, format):
219
232
  encoding=args.encoding,
220
233
  )
221
234
 
222
- results.append(
235
+ format_results.append(
223
236
  {
224
237
  "format": format,
225
238
  "data": md,
226
239
  }
227
240
  )
241
+ elif format == "pdf":
242
+ if not args.combine:
243
+ create_searchable_pdf(
244
+ [img],
245
+ [result],
246
+ output_path=out_path,
247
+ font_path=args.font_path,
248
+ )
249
+
250
+ format_results.append(
251
+ {
252
+ "format": format,
253
+ "data": result,
254
+ }
255
+ )
228
256
 
229
- out = merge_all_pages(results)
257
+ out = merge_all_pages(format_results)
230
258
  if args.combine:
231
259
  out_path = os.path.join(args.outdir, f"{dirname}_{filename}.{format}")
232
260
  save_merged_file(
233
261
  out_path,
234
262
  args,
235
263
  out,
264
+ imgs,
236
265
  )
237
266
 
238
267
 
@@ -349,7 +378,12 @@ def main():
349
378
  type=str,
350
379
  choices=["auto", "left2right", "top2bottom", "right2left"],
351
380
  )
352
-
381
+ parser.add_argument(
382
+ "--font_path",
383
+ default=None,
384
+ type=str,
385
+ help="Path to the font file(.ttf) for PDF output",
386
+ )
353
387
  args = parser.parse_args()
354
388
 
355
389
  path = Path(args.arg1)
@@ -362,6 +396,13 @@ def main():
362
396
  f"Invalid output format: {args.format}. Supported formats are {SUPPORT_OUTPUT_FORMAT}"
363
397
  )
364
398
 
399
+ if (
400
+ args.font_path is not None
401
+ and not os.path.exists(args.font_path)
402
+ and format == "pdf"
403
+ ):
404
+ raise FileNotFoundError(f"Font file not found: {args.font_path}")
405
+
365
406
  validate_encoding(args.encoding)
366
407
 
367
408
  if format == "markdown":
@@ -1,14 +1,20 @@
1
- import json
2
- import io
3
1
  import csv
2
+ import io
3
+ import json
4
4
  import os
5
+ from argparse import ArgumentParser
5
6
  from pathlib import Path
6
7
 
7
8
  from mcp.server.fastmcp import Context, FastMCP
8
9
 
9
10
  from yomitoku import DocumentAnalyzer
10
11
  from yomitoku.data.functions import load_image, load_pdf
11
- from yomitoku.export import convert_json, convert_markdown, convert_csv, convert_html
12
+ from yomitoku.export import (
13
+ convert_csv,
14
+ convert_html,
15
+ convert_json,
16
+ convert_markdown,
17
+ )
12
18
 
13
19
  try:
14
20
  RESOURCE_DIR = os.environ["RESOURCE_DIR"]
@@ -154,12 +160,37 @@ async def get_file_list() -> list[str]:
154
160
  return os.listdir(RESOURCE_DIR)
155
161
 
156
162
 
157
- def run_mcp_server():
163
+ def run_mcp_server(transport="stdio", mount_path=None):
158
164
  """
159
165
  Run the MCP server.
160
166
  """
161
- mcp.run(transport="stdio")
167
+
168
+ if transport == "stdio":
169
+ mcp.run()
170
+ elif transport == "sse":
171
+ mcp.run(transport=transport, mount_path=mount_path)
172
+
173
+
174
+ def main():
175
+ parser = ArgumentParser(description="Run the MCP server.")
176
+ parser.add_argument(
177
+ "--transport",
178
+ "-t",
179
+ type=str,
180
+ default="stdio",
181
+ choices=["stdio", "sse"],
182
+ help="Transport method for the MCP server.",
183
+ )
184
+ parser.add_argument(
185
+ "--mount_path",
186
+ "-m",
187
+ type=str,
188
+ default=None,
189
+ help="Mount path for the MCP server (only used with SSE transport).",
190
+ )
191
+ args = parser.parse_args()
192
+ run_mcp_server(transport=args.transport, mount_path=args.mount_path)
162
193
 
163
194
 
164
195
  if __name__ == "__main__":
165
- run_mcp_server()
196
+ main()
@@ -1,7 +1,7 @@
1
1
  import os
2
2
 
3
3
  ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
4
- SUPPORT_OUTPUT_FORMAT = ["json", "csv", "html", "markdown", "md"]
4
+ SUPPORT_OUTPUT_FORMAT = ["json", "csv", "html", "markdown", "md", "pdf"]
5
5
  SUPPORT_INPUT_FORMAT = ["jpg", "jpeg", "png", "bmp", "tiff", "tif", "pdf"]
6
6
  MIN_IMAGE_SIZE = 32
7
7
  WARNING_IMAGE_SIZE = 720
@@ -0,0 +1,119 @@
1
+ import os
2
+
3
+ from PIL import Image
4
+ from io import BytesIO
5
+
6
+ from reportlab.pdfgen import canvas
7
+ from reportlab.pdfbase.ttfonts import TTFont
8
+ from reportlab.pdfbase import pdfmetrics
9
+ from reportlab.pdfbase.pdfmetrics import stringWidth
10
+
11
+ import numpy as np
12
+ import jaconv
13
+
14
+ from ..constants import ROOT_DIR
15
+
16
+ FONT_PATH = ROOT_DIR + "/resource/MPLUS1p-Medium.ttf"
17
+
18
+
19
+ def _poly2rect(points):
20
+ """
21
+ Convert a polygon defined by its corner points to a rectangle.
22
+ The points should be in the format [[x1, y1], [x2, y2], [x3, y3], [x4, y4]].
23
+ """
24
+ points = np.array(points, dtype=int)
25
+ x_min = points[:, 0].min()
26
+ x_max = points[:, 0].max()
27
+ y_min = points[:, 1].min()
28
+ y_max = points[:, 1].max()
29
+
30
+ return [x_min, y_min, x_max, y_max]
31
+
32
+
33
+ def _calc_font_size(content, bbox_height, bbox_width):
34
+ rates = np.arange(0.5, 1.0, 0.01)
35
+
36
+ min_diff = np.inf
37
+ best_font_size = None
38
+ for rate in rates:
39
+ font_size = bbox_height * rate
40
+ text_w = stringWidth(content, "MPLUS1p-Medium", font_size)
41
+ diff = abs(text_w - bbox_width)
42
+ if diff < min_diff:
43
+ min_diff = diff
44
+ best_font_size = font_size
45
+
46
+ return best_font_size
47
+
48
+
49
+ def to_full_width(text):
50
+ fw_map = {
51
+ "\u00a5": "\uffe5", # ¥ → ¥
52
+ "\u00b7": "\u30fb", # · → ・
53
+ " ": "\u3000", # 半角スペース→全角スペース
54
+ }
55
+
56
+ TO_FULLWIDTH = str.maketrans(fw_map)
57
+
58
+ jaconv_text = jaconv.h2z(text, kana=True, ascii=True, digit=True)
59
+ jaconv_text = jaconv_text.translate(TO_FULLWIDTH)
60
+
61
+ return jaconv_text
62
+
63
+
64
+ def create_searchable_pdf(images, ocr_results, output_path, font_path=None):
65
+ if font_path is None:
66
+ font_path = FONT_PATH
67
+
68
+ pdfmetrics.registerFont(TTFont("MPLUS1p-Medium", font_path))
69
+
70
+ packet = BytesIO()
71
+ c = canvas.Canvas(packet)
72
+
73
+ for i, (image, ocr_result) in enumerate(zip(images, ocr_results)):
74
+ image = Image.fromarray(image[:, :, ::-1]) # Convert BGR to RGB
75
+ pdfmetrics.registerFont(TTFont("MPLUS1p-Medium", FONT_PATH))
76
+
77
+ image_path = f"tmp_{i}.png"
78
+ image.save(image_path)
79
+ w, h = image.size
80
+
81
+ c.setPageSize((w, h))
82
+ c.drawImage(image_path, 0, 0, width=w, height=h)
83
+ os.remove(image_path) # Clean up temporary image file
84
+
85
+ for word in ocr_result.words:
86
+ text = word.content
87
+ bbox = _poly2rect(word.points)
88
+ direction = word.direction
89
+
90
+ x1, y1, x2, y2 = bbox
91
+ bbox_height = y2 - y1
92
+ bbox_width = x2 - x1
93
+
94
+ if direction == "vertical":
95
+ text = to_full_width(text)
96
+
97
+ if direction == "horizontal":
98
+ font_size = _calc_font_size(text, bbox_height, bbox_width)
99
+ else:
100
+ font_size = _calc_font_size(text, bbox_width, bbox_height)
101
+
102
+ c.setFont("MPLUS1p-Medium", font_size)
103
+ c.setFillColorRGB(1, 1, 1, alpha=0) # 透明
104
+ if direction == "vertical":
105
+ base_y = h - y2 + (bbox_height - font_size)
106
+ for j, ch in enumerate(text):
107
+ c.saveState()
108
+ c.translate(x1 + font_size * 0.5, base_y - (j - 1) * font_size)
109
+ c.rotate(-90)
110
+ c.drawString(0, 0, ch)
111
+ c.restoreState()
112
+ else:
113
+ base_y = h - y2 + (bbox_height - font_size) * 0.5
114
+ c.drawString(x1, base_y, text)
115
+ c.showPage()
116
+ c.save()
117
+
118
+ with open(output_path, "wb") as f:
119
+ f.write(packet.getvalue())
@@ -152,6 +152,31 @@ def test_run_tiff_csv(monkeypatch, tmp_path):
152
152
  assert os.path.exists(out_path)
153
153
 
154
154
 
155
+ def test_run_tiff_pdf(monkeypatch, tmp_path):
156
+ path_img = "tests/data/test.tiff"
157
+ monkeypatch.setattr(
158
+ "sys.argv",
159
+ [
160
+ "main.py",
161
+ path_img,
162
+ "-o",
163
+ str(tmp_path),
164
+ "-f",
165
+ "pdf",
166
+ "--tsr_cfg",
167
+ "tests/yaml/table_structure_recognizer.yaml",
168
+ "--lite",
169
+ "--figure",
170
+ ],
171
+ )
172
+ main.main()
173
+ path = Path(path_img)
174
+ dirname = path.parent.name
175
+ filename = path.stem
176
+ out_path = os.path.join(str(tmp_path), f"{dirname}_{filename}_p1.pdf")
177
+ assert os.path.exists(out_path)
178
+
179
+
155
180
  def test_run_pdf_md(monkeypatch, tmp_path):
156
181
  path_img = "tests/data/test.pdf"
157
182
  monkeypatch.setattr(