latextify 0.3.0__tar.gz → 0.3.2__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 (372) hide show
  1. {latextify-0.3.0 → latextify-0.3.2}/.github/workflows/ci.yml +48 -6
  2. {latextify-0.3.0 → latextify-0.3.2}/.github/workflows/release.yml +57 -2
  3. {latextify-0.3.0 → latextify-0.3.2}/PKG-INFO +69 -7
  4. {latextify-0.3.0 → latextify-0.3.2}/README.md +68 -6
  5. {latextify-0.3.0 → latextify-0.3.2}/latextify/citations/body_markers.py +11 -2
  6. {latextify-0.3.0 → latextify-0.3.2}/latextify/citations/crossref.py +3 -0
  7. {latextify-0.3.0 → latextify-0.3.2}/latextify/citations/plaintext.py +87 -35
  8. {latextify-0.3.0 → latextify-0.3.2}/latextify/cli_convert.py +23 -0
  9. {latextify-0.3.0 → latextify-0.3.2}/latextify/compile/tectonic.py +4 -0
  10. {latextify-0.3.0 → latextify-0.3.2}/latextify/emit/citation_resolution.py +66 -1
  11. {latextify-0.3.0 → latextify-0.3.2}/latextify/emit/figures_copy.py +33 -4
  12. {latextify-0.3.0 → latextify-0.3.2}/latextify/emit/figures_stage.py +11 -1
  13. latextify-0.3.2/latextify/emit/inline_supplement.py +91 -0
  14. {latextify-0.3.0 → latextify-0.3.2}/latextify/emit/project.py +49 -23
  15. {latextify-0.3.0 → latextify-0.3.2}/latextify/emit/supplement.py +5 -0
  16. {latextify-0.3.0 → latextify-0.3.2}/latextify/figures/convert.py +7 -2
  17. latextify-0.3.2/latextify/figures/placement.py +44 -0
  18. {latextify-0.3.0 → latextify-0.3.2}/latextify/figures/vector.py +89 -7
  19. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/convert_routes.py +27 -7
  20. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/server.py +17 -0
  21. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/static/app.js +41 -16
  22. latextify-0.3.2/latextify/gui/static/figure-placement.js +115 -0
  23. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/static/index.html +26 -1
  24. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/static/style.css +16 -0
  25. {latextify-0.3.0 → latextify-0.3.2}/latextify/kit/README-OFFLINE.md +23 -3
  26. {latextify-0.3.0 → latextify-0.3.2}/latextify/kit/install_template.py +59 -10
  27. latextify-0.3.2/latextify/portable.py +160 -0
  28. latextify-0.3.2/packaging/README-WINDOWS-PORTABLE.txt +34 -0
  29. {latextify-0.3.0 → latextify-0.3.2}/pyproject.toml +6 -1
  30. {latextify-0.3.0 → latextify-0.3.2}/startup.bat +22 -3
  31. {latextify-0.3.0 → latextify-0.3.2}/startup.sh +13 -7
  32. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_crossref.py +13 -0
  33. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_plaintext.py +23 -0
  34. {latextify-0.3.0 → latextify-0.3.2}/tests/test_emit.py +21 -0
  35. latextify-0.3.2/tests/test_figure_placement.py +74 -0
  36. {latextify-0.3.0 → latextify-0.3.2}/tests/test_figures_convert.py +116 -2
  37. {latextify-0.3.0 → latextify-0.3.2}/tests/test_gui.py +149 -1
  38. latextify-0.3.2/tests/test_inline_citations.py +52 -0
  39. latextify-0.3.2/tests/test_inline_supplement.py +272 -0
  40. {latextify-0.3.0 → latextify-0.3.2}/tests/test_kit_build.py +20 -1
  41. latextify-0.3.2/tests/test_portable.py +52 -0
  42. latextify-0.3.2/tools/build_windows_portable.py +134 -0
  43. {latextify-0.3.0 → latextify-0.3.2}/uv.lock +93 -1
  44. {latextify-0.3.0 → latextify-0.3.2}/.dockerignore +0 -0
  45. {latextify-0.3.0 → latextify-0.3.2}/.gitattributes +0 -0
  46. {latextify-0.3.0 → latextify-0.3.2}/.github/dependabot.yml +0 -0
  47. {latextify-0.3.0 → latextify-0.3.2}/.github/workflows/codeql.yml +0 -0
  48. {latextify-0.3.0 → latextify-0.3.2}/.github/workflows/dependency-audit.yml +0 -0
  49. {latextify-0.3.0 → latextify-0.3.2}/.github/workflows/publish.yml +0 -0
  50. {latextify-0.3.0 → latextify-0.3.2}/.gitignore +0 -0
  51. {latextify-0.3.0 → latextify-0.3.2}/.python-version +0 -0
  52. {latextify-0.3.0 → latextify-0.3.2}/CONTRIBUTING.md +0 -0
  53. {latextify-0.3.0 → latextify-0.3.2}/LICENSE +0 -0
  54. {latextify-0.3.0 → latextify-0.3.2}/NOTICE +0 -0
  55. {latextify-0.3.0 → latextify-0.3.2}/SECURITY.md +0 -0
  56. {latextify-0.3.0 → latextify-0.3.2}/deploy/hf-space/DEPLOY.md +0 -0
  57. {latextify-0.3.0 → latextify-0.3.2}/deploy/hf-space/Dockerfile +0 -0
  58. {latextify-0.3.0 → latextify-0.3.2}/deploy/hf-space/README.md +0 -0
  59. {latextify-0.3.0 → latextify-0.3.2}/deploy/hf-space/warm_cache.py +0 -0
  60. {latextify-0.3.0 → latextify-0.3.2}/deploy/render/DEPLOY.md +0 -0
  61. {latextify-0.3.0 → latextify-0.3.2}/deploy/render/Dockerfile +0 -0
  62. {latextify-0.3.0 → latextify-0.3.2}/examples/01-all-embedded/.gitignore +0 -0
  63. {latextify-0.3.0 → latextify-0.3.2}/examples/01-all-embedded/README.md +0 -0
  64. {latextify-0.3.0 → latextify-0.3.2}/examples/01-all-embedded/make_manuscript.py +0 -0
  65. {latextify-0.3.0 → latextify-0.3.2}/examples/01-all-embedded/run.py +0 -0
  66. {latextify-0.3.0 → latextify-0.3.2}/examples/02-word-plus-figures/.gitignore +0 -0
  67. {latextify-0.3.0 → latextify-0.3.2}/examples/02-word-plus-figures/README.md +0 -0
  68. {latextify-0.3.0 → latextify-0.3.2}/examples/02-word-plus-figures/make_manuscript.py +0 -0
  69. {latextify-0.3.0 → latextify-0.3.2}/examples/02-word-plus-figures/run.py +0 -0
  70. {latextify-0.3.0 → latextify-0.3.2}/examples/03-multipart-refmanager/.gitignore +0 -0
  71. {latextify-0.3.0 → latextify-0.3.2}/examples/03-multipart-refmanager/README.md +0 -0
  72. {latextify-0.3.0 → latextify-0.3.2}/examples/03-multipart-refmanager/make_manuscript.py +0 -0
  73. {latextify-0.3.0 → latextify-0.3.2}/examples/03-multipart-refmanager/run.py +0 -0
  74. {latextify-0.3.0 → latextify-0.3.2}/examples/README.md +0 -0
  75. {latextify-0.3.0 → latextify-0.3.2}/latextify/__init__.py +0 -0
  76. {latextify-0.3.0 → latextify-0.3.2}/latextify/__main__.py +0 -0
  77. {latextify-0.3.0 → latextify-0.3.2}/latextify/audit/__init__.py +0 -0
  78. {latextify-0.3.0 → latextify-0.3.2}/latextify/audit/equations.py +0 -0
  79. {latextify-0.3.0 → latextify-0.3.2}/latextify/citations/__init__.py +0 -0
  80. {latextify-0.3.0 → latextify-0.3.2}/latextify/citations/authoryear_index.py +0 -0
  81. {latextify-0.3.0 → latextify-0.3.2}/latextify/citations/bib.py +0 -0
  82. {latextify-0.3.0 → latextify-0.3.2}/latextify/citations/bibmatch.py +0 -0
  83. {latextify-0.3.0 → latextify-0.3.2}/latextify/citations/bibtex_in.py +0 -0
  84. {latextify-0.3.0 → latextify-0.3.2}/latextify/citations/corrections.py +0 -0
  85. {latextify-0.3.0 → latextify-0.3.2}/latextify/citations/csl_json_in.py +0 -0
  86. {latextify-0.3.0 → latextify-0.3.2}/latextify/citations/endnote.py +0 -0
  87. {latextify-0.3.0 → latextify-0.3.2}/latextify/citations/endnote_xml_in.py +0 -0
  88. {latextify-0.3.0 → latextify-0.3.2}/latextify/citations/fields.py +0 -0
  89. {latextify-0.3.0 → latextify-0.3.2}/latextify/citations/mendeley.py +0 -0
  90. {latextify-0.3.0 → latextify-0.3.2}/latextify/citations/merge.py +0 -0
  91. {latextify-0.3.0 → latextify-0.3.2}/latextify/citations/nbib_in.py +0 -0
  92. {latextify-0.3.0 → latextify-0.3.2}/latextify/citations/reconcile.py +0 -0
  93. {latextify-0.3.0 → latextify-0.3.2}/latextify/citations/reflist_nondocx.py +0 -0
  94. {latextify-0.3.0 → latextify-0.3.2}/latextify/citations/refs_import.py +0 -0
  95. {latextify-0.3.0 → latextify-0.3.2}/latextify/citations/validate.py +0 -0
  96. {latextify-0.3.0 → latextify-0.3.2}/latextify/citations/wordnative.py +0 -0
  97. {latextify-0.3.0 → latextify-0.3.2}/latextify/citations/zotero.py +0 -0
  98. {latextify-0.3.0 → latextify-0.3.2}/latextify/cli.py +0 -0
  99. {latextify-0.3.0 → latextify-0.3.2}/latextify/cli_batch.py +0 -0
  100. {latextify-0.3.0 → latextify-0.3.2}/latextify/cli_equations.py +0 -0
  101. {latextify-0.3.0 → latextify-0.3.2}/latextify/cli_export.py +0 -0
  102. {latextify-0.3.0 → latextify-0.3.2}/latextify/cli_figures.py +0 -0
  103. {latextify-0.3.0 → latextify-0.3.2}/latextify/cli_kit.py +0 -0
  104. {latextify-0.3.0 → latextify-0.3.2}/latextify/cli_privacy.py +0 -0
  105. {latextify-0.3.0 → latextify-0.3.2}/latextify/cli_review.py +0 -0
  106. {latextify-0.3.0 → latextify-0.3.2}/latextify/compile/__init__.py +0 -0
  107. {latextify-0.3.0 → latextify-0.3.2}/latextify/compile/logs.py +0 -0
  108. {latextify-0.3.0 → latextify-0.3.2}/latextify/compile/pdf.py +0 -0
  109. {latextify-0.3.0 → latextify-0.3.2}/latextify/emit/__init__.py +0 -0
  110. {latextify-0.3.0 → latextify-0.3.2}/latextify/emit/alt_formats.py +0 -0
  111. {latextify-0.3.0 → latextify-0.3.2}/latextify/emit/alt_formats_render.py +0 -0
  112. {latextify-0.3.0 → latextify-0.3.2}/latextify/emit/anchors.py +0 -0
  113. {latextify-0.3.0 → latextify-0.3.2}/latextify/emit/bibliography.py +0 -0
  114. {latextify-0.3.0 → latextify-0.3.2}/latextify/emit/metadata.py +0 -0
  115. {latextify-0.3.0 → latextify-0.3.2}/latextify/emit/output_dir.py +0 -0
  116. {latextify-0.3.0 → latextify-0.3.2}/latextify/emit/submission.py +0 -0
  117. {latextify-0.3.0 → latextify-0.3.2}/latextify/figures/__init__.py +0 -0
  118. {latextify-0.3.0 → latextify-0.3.2}/latextify/figures/caption_gaps.py +0 -0
  119. {latextify-0.3.0 → latextify-0.3.2}/latextify/figures/crop.py +0 -0
  120. {latextify-0.3.0 → latextify-0.3.2}/latextify/figures/extract.py +0 -0
  121. {latextify-0.3.0 → latextify-0.3.2}/latextify/figures/gap_fill.py +0 -0
  122. {latextify-0.3.0 → latextify-0.3.2}/latextify/figures/inventory.py +0 -0
  123. {latextify-0.3.0 → latextify-0.3.2}/latextify/figures/outcome.py +0 -0
  124. {latextify-0.3.0 → latextify-0.3.2}/latextify/figures/override.py +0 -0
  125. {latextify-0.3.0 → latextify-0.3.2}/latextify/figures/raster.py +0 -0
  126. {latextify-0.3.0 → latextify-0.3.2}/latextify/figures/scrub.py +0 -0
  127. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/__init__.py +0 -0
  128. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/convert_inputs.py +0 -0
  129. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/demo.py +0 -0
  130. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/downloads.py +0 -0
  131. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/exporting.py +0 -0
  132. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/figures_view.py +0 -0
  133. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/folder_picker.py +0 -0
  134. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/guard.py +0 -0
  135. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/lifecycle.py +0 -0
  136. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/schemas.py +0 -0
  137. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/static/alt-export.js +0 -0
  138. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/static/clean.js +0 -0
  139. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/static/export.js +0 -0
  140. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/static/figures.js +0 -0
  141. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/static/lifecycle.js +0 -0
  142. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/static/results.js +0 -0
  143. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/static/review.js +0 -0
  144. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/upload_utils.py +0 -0
  145. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/uploads_routes.py +0 -0
  146. {latextify-0.3.0 → latextify-0.3.2}/latextify/gui/validation_view.py +0 -0
  147. {latextify-0.3.0 → latextify-0.3.2}/latextify/ingest/__init__.py +0 -0
  148. {latextify-0.3.0 → latextify-0.3.2}/latextify/ingest/_xml.py +0 -0
  149. {latextify-0.3.0 → latextify-0.3.2}/latextify/ingest/archive_guard.py +0 -0
  150. {latextify-0.3.0 → latextify-0.3.2}/latextify/ingest/citation_sentinels.py +0 -0
  151. {latextify-0.3.0 → latextify-0.3.2}/latextify/ingest/docx_clean.py +0 -0
  152. {latextify-0.3.0 → latextify-0.3.2}/latextify/ingest/filters.py +0 -0
  153. {latextify-0.3.0 → latextify-0.3.2}/latextify/ingest/formats.py +0 -0
  154. {latextify-0.3.0 → latextify-0.3.2}/latextify/ingest/frontmatter.py +0 -0
  155. {latextify-0.3.0 → latextify-0.3.2}/latextify/ingest/headings.py +0 -0
  156. {latextify-0.3.0 → latextify-0.3.2}/latextify/ingest/metadata_authors.py +0 -0
  157. {latextify-0.3.0 → latextify-0.3.2}/latextify/ingest/metadata_body.py +0 -0
  158. {latextify-0.3.0 → latextify-0.3.2}/latextify/ingest/metadata_guess.py +0 -0
  159. {latextify-0.3.0 → latextify-0.3.2}/latextify/ingest/metadata_guess_nondocx.py +0 -0
  160. {latextify-0.3.0 → latextify-0.3.2}/latextify/ingest/metadata_markers.py +0 -0
  161. {latextify-0.3.0 → latextify-0.3.2}/latextify/ingest/metadata_paragraphs.py +0 -0
  162. {latextify-0.3.0 → latextify-0.3.2}/latextify/ingest/metadata_schema.py +0 -0
  163. {latextify-0.3.0 → latextify-0.3.2}/latextify/ingest/pandoc.py +0 -0
  164. {latextify-0.3.0 → latextify-0.3.2}/latextify/ingest/portable_anchors.py +0 -0
  165. {latextify-0.3.0 → latextify-0.3.2}/latextify/ingest/preflight.py +0 -0
  166. {latextify-0.3.0 → latextify-0.3.2}/latextify/ingest/preflight_privacy.py +0 -0
  167. {latextify-0.3.0 → latextify-0.3.2}/latextify/ingest/tables.py +0 -0
  168. {latextify-0.3.0 → latextify-0.3.2}/latextify/ingest/tables_degraded.py +0 -0
  169. {latextify-0.3.0 → latextify-0.3.2}/latextify/kit/__init__.py +0 -0
  170. {latextify-0.3.0 → latextify-0.3.2}/latextify/kit/build.py +0 -0
  171. {latextify-0.3.0 → latextify-0.3.2}/latextify/kit/target.py +0 -0
  172. {latextify-0.3.0 → latextify-0.3.2}/latextify/kit/tex_cache.py +0 -0
  173. {latextify-0.3.0 → latextify-0.3.2}/latextify/model/__init__.py +0 -0
  174. {latextify-0.3.0 → latextify-0.3.2}/latextify/model/_compat.py +0 -0
  175. {latextify-0.3.0 → latextify-0.3.2}/latextify/model/body.py +0 -0
  176. {latextify-0.3.0 → latextify-0.3.2}/latextify/model/compile.py +0 -0
  177. {latextify-0.3.0 → latextify-0.3.2}/latextify/model/emit.py +0 -0
  178. {latextify-0.3.0 → latextify-0.3.2}/latextify/model/equations.py +0 -0
  179. {latextify-0.3.0 → latextify-0.3.2}/latextify/model/figure.py +0 -0
  180. {latextify-0.3.0 → latextify-0.3.2}/latextify/model/meta.py +0 -0
  181. {latextify-0.3.0 → latextify-0.3.2}/latextify/model/preflight.py +0 -0
  182. {latextify-0.3.0 → latextify-0.3.2}/latextify/model/reconcile.py +0 -0
  183. {latextify-0.3.0 → latextify-0.3.2}/latextify/model/refs.py +0 -0
  184. {latextify-0.3.0 → latextify-0.3.2}/latextify/model/validate.py +0 -0
  185. {latextify-0.3.0 → latextify-0.3.2}/latextify/privacy/__init__.py +0 -0
  186. {latextify-0.3.0 → latextify-0.3.2}/latextify/privacy/docx_adapter.py +0 -0
  187. {latextify-0.3.0 → latextify-0.3.2}/latextify/privacy/images.py +0 -0
  188. {latextify-0.3.0 → latextify-0.3.2}/latextify/privacy/ole.py +0 -0
  189. {latextify-0.3.0 → latextify-0.3.2}/latextify/privacy/opc.py +0 -0
  190. {latextify-0.3.0 → latextify-0.3.2}/latextify/privacy/pdf.py +0 -0
  191. {latextify-0.3.0 → latextify-0.3.2}/latextify/privacy/pdf_content.py +0 -0
  192. {latextify-0.3.0 → latextify-0.3.2}/latextify/privacy/pptx.py +0 -0
  193. {latextify-0.3.0 → latextify-0.3.2}/latextify/privacy/registry.py +0 -0
  194. {latextify-0.3.0 → latextify-0.3.2}/latextify/privacy/report.py +0 -0
  195. {latextify-0.3.0 → latextify-0.3.2}/latextify/privacy/xlsx.py +0 -0
  196. {latextify-0.3.0 → latextify-0.3.2}/latextify/report/__init__.py +0 -0
  197. {latextify-0.3.0 → latextify-0.3.2}/latextify/report/render.py +0 -0
  198. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/__init__.py +0 -0
  199. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/authors.py +0 -0
  200. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/.gitkeep +0 -0
  201. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/achemso/manifest.yaml +0 -0
  202. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/achemso/metadata.tex.j2 +0 -0
  203. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/achemso/preamble.tex.j2 +0 -0
  204. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/aip-advances/manifest.yaml +0 -0
  205. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/aip-apl/manifest.yaml +0 -0
  206. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/aip-jap/manifest.yaml +0 -0
  207. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/aps-prapplied/manifest.yaml +0 -0
  208. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/aps-prl/manifest.yaml +0 -0
  209. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/aps-prx/manifest.yaml +0 -0
  210. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/aps-rmp/manifest.yaml +0 -0
  211. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/elsarticle/manifest.yaml +0 -0
  212. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/elsarticle/metadata.tex.j2 +0 -0
  213. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/elsarticle/preamble.tex.j2 +0 -0
  214. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/elsarticle/vendor/elsarticle.cls +0 -0
  215. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/ieeetran/manifest.yaml +0 -0
  216. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/ieeetran/metadata.tex.j2 +0 -0
  217. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/ieeetran/preamble.tex.j2 +0 -0
  218. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/iopart/VENDOR_LICENSE.txt +0 -0
  219. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/iopart/manifest.yaml +0 -0
  220. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/iopart/metadata.tex.j2 +0 -0
  221. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/iopart/preamble.tex.j2 +0 -0
  222. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/iopart/vendor/.gitattributes +0 -0
  223. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/iopart/vendor/iopart.cls +0 -0
  224. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/iopart/vendor/iopart10.clo +0 -0
  225. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/iopart/vendor/iopart12.clo +0 -0
  226. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/revtex4-2/manifest.yaml +0 -0
  227. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/revtex4-2/metadata.tex.j2 +0 -0
  228. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/revtex4-2/preamble.tex.j2 +0 -0
  229. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/sn-jnl/VENDOR_LICENSE.txt +0 -0
  230. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/sn-jnl/manifest.yaml +0 -0
  231. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/sn-jnl/metadata.tex.j2 +0 -0
  232. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/sn-jnl/preamble.tex.j2 +0 -0
  233. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/sn-jnl/vendor/.gitattributes +0 -0
  234. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/sn-jnl/vendor/sn-jnl.cls +0 -0
  235. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/sn-jnl/vendor/sn-mathphys-ay.bst +0 -0
  236. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/sn-jnl/vendor/sn-mathphys-num.bst +0 -0
  237. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/wiley/manifest.yaml +0 -0
  238. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/wiley/metadata.tex.j2 +0 -0
  239. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/journals/wiley/preamble.tex.j2 +0 -0
  240. {latextify-0.3.0 → latextify-0.3.2}/latextify/templates/loader.py +0 -0
  241. {latextify-0.3.0 → latextify-0.3.2}/plans/FORMATS_AND_PRIVACY_PLAN.md +0 -0
  242. {latextify-0.3.0 → latextify-0.3.2}/plans/GUI_MULTIFILE_PLAN.md +0 -0
  243. {latextify-0.3.0 → latextify-0.3.2}/plans/GUI_OPTIONS_FORMATS_PLAN.md +0 -0
  244. {latextify-0.3.0 → latextify-0.3.2}/plans/METADATA_PRIVACY_PLAN.md +0 -0
  245. {latextify-0.3.0 → latextify-0.3.2}/plans/OFFLINE_DISTRIBUTION_PLAN.md +0 -0
  246. {latextify-0.3.0 → latextify-0.3.2}/plans/VECTOR_FIGURES_PLAN.md +0 -0
  247. {latextify-0.3.0 → latextify-0.3.2}/plans/archive/GUI_JOURNALS_EXPORT_PLAN.md +0 -0
  248. {latextify-0.3.0 → latextify-0.3.2}/plans/archive/LATEXTIFY_PLAN.md +0 -0
  249. {latextify-0.3.0 → latextify-0.3.2}/plans/archive/MANUSCRIPT_FIDELITY_PLAN.md +0 -0
  250. {latextify-0.3.0 → latextify-0.3.2}/plans/archive/OFFLINE_PORTABILITY_PLAN.md +0 -0
  251. {latextify-0.3.0 → latextify-0.3.2}/plans/archive/REPO_HEALTH_PLAN.md +0 -0
  252. {latextify-0.3.0 → latextify-0.3.2}/plans/archive/SECURITY_RELIABILITY_AUDIT_PLAN.md +0 -0
  253. {latextify-0.3.0 → latextify-0.3.2}/render.yaml +0 -0
  254. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/README.md +0 -0
  255. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/bracket_cited.docx +0 -0
  256. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/clean.docx +0 -0
  257. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/clean_black_figure.pdf +0 -0
  258. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/cropped_figure.docx +0 -0
  259. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/endnote_cited.docx +0 -0
  260. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/equations.docx +0 -0
  261. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/figures.docx +0 -0
  262. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/golden/achemso_metadata.tex +0 -0
  263. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/golden/achemso_preamble.tex +0 -0
  264. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/golden/elsarticle_metadata.tex +0 -0
  265. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/golden/elsarticle_preamble_authoryear.tex +0 -0
  266. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/golden/elsarticle_preamble_numeric.tex +0 -0
  267. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/golden/ieeetran_metadata.tex +0 -0
  268. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/golden/ieeetran_preamble.tex +0 -0
  269. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/golden/iopart_metadata.tex +0 -0
  270. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/golden/iopart_preamble.tex +0 -0
  271. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/golden/revtex4-2_metadata.tex +0 -0
  272. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/golden/revtex4-2_preamble.tex +0 -0
  273. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/golden/sn-jnl_metadata.tex +0 -0
  274. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/golden/sn-jnl_preamble.tex +0 -0
  275. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/golden/sn-jnl_preamble_authoryear.tex +0 -0
  276. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/golden/wiley_metadata.tex +0 -0
  277. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/golden/wiley_preamble_authoryear.tex +0 -0
  278. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/golden/wiley_preamble_numeric.tex +0 -0
  279. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/hand_cited.docx +0 -0
  280. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/leaky_book.xlsx +0 -0
  281. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/leaky_deck.pptx +0 -0
  282. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/leaky_doc.pdf +0 -0
  283. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/leaky_files.truth.json +0 -0
  284. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/leaky_photo.jpg +0 -0
  285. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/make_bracket_cited.py +0 -0
  286. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/make_clean.py +0 -0
  287. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/make_cropped_figure.py +0 -0
  288. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/make_endnote_cited.py +0 -0
  289. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/make_equations.py +0 -0
  290. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/make_figures.py +0 -0
  291. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/make_hand_cited.py +0 -0
  292. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/make_leaky_files.py +0 -0
  293. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/make_metadata_titlepage.py +0 -0
  294. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/make_supplement.py +0 -0
  295. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/make_tables.py +0 -0
  296. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/make_unsupported.py +0 -0
  297. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/make_wordnative_cited.py +0 -0
  298. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/make_zotero_cited.py +0 -0
  299. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/metadata_titlepage.docx +0 -0
  300. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/paper.yaml +0 -0
  301. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/supplement.docx +0 -0
  302. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/tables.docx +0 -0
  303. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/unsupported.docx +0 -0
  304. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/wordnative_cited.docx +0 -0
  305. {latextify-0.3.0 → latextify-0.3.2}/tests/fixtures/zotero_cited.docx +0 -0
  306. {latextify-0.3.0 → latextify-0.3.2}/tests/test_archive_guard.py +0 -0
  307. {latextify-0.3.0 → latextify-0.3.2}/tests/test_audit_equations.py +0 -0
  308. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citation_sentinels.py +0 -0
  309. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_bib.py +0 -0
  310. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_bibmatch.py +0 -0
  311. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_bibtex_in.py +0 -0
  312. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_bracket_refs.py +0 -0
  313. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_compile_stub.py +0 -0
  314. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_corrections.py +0 -0
  315. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_csl_json_in.py +0 -0
  316. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_degradation.py +0 -0
  317. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_endnote.py +0 -0
  318. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_endnote_compile.py +0 -0
  319. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_endnote_xml_in.py +0 -0
  320. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_extract.py +0 -0
  321. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_extract_endnote.py +0 -0
  322. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_extract_wordnative.py +0 -0
  323. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_fields.py +0 -0
  324. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_merge.py +0 -0
  325. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_nbib_in.py +0 -0
  326. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_parsers.py +0 -0
  327. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_reconstruct.py +0 -0
  328. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_refs_import.py +0 -0
  329. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_stress.py +0 -0
  330. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_validate.py +0 -0
  331. {latextify-0.3.0 → latextify-0.3.2}/tests/test_citations_wordnative.py +0 -0
  332. {latextify-0.3.0 → latextify-0.3.2}/tests/test_cli.py +0 -0
  333. {latextify-0.3.0 → latextify-0.3.2}/tests/test_cli_figures.py +0 -0
  334. {latextify-0.3.0 → latextify-0.3.2}/tests/test_cli_review.py +0 -0
  335. {latextify-0.3.0 → latextify-0.3.2}/tests/test_compat_strenum.py +0 -0
  336. {latextify-0.3.0 → latextify-0.3.2}/tests/test_compile_logs.py +0 -0
  337. {latextify-0.3.0 → latextify-0.3.2}/tests/test_compile_tectonic.py +0 -0
  338. {latextify-0.3.0 → latextify-0.3.2}/tests/test_docx_clean.py +0 -0
  339. {latextify-0.3.0 → latextify-0.3.2}/tests/test_emit_submission.py +0 -0
  340. {latextify-0.3.0 → latextify-0.3.2}/tests/test_examples.py +0 -0
  341. {latextify-0.3.0 → latextify-0.3.2}/tests/test_export_formats.py +0 -0
  342. {latextify-0.3.0 → latextify-0.3.2}/tests/test_fidelity_captions_reflist.py +0 -0
  343. {latextify-0.3.0 → latextify-0.3.2}/tests/test_figures.py +0 -0
  344. {latextify-0.3.0 → latextify-0.3.2}/tests/test_figures_caption_gaps.py +0 -0
  345. {latextify-0.3.0 → latextify-0.3.2}/tests/test_figures_crop.py +0 -0
  346. {latextify-0.3.0 → latextify-0.3.2}/tests/test_figures_inventory.py +0 -0
  347. {latextify-0.3.0 → latextify-0.3.2}/tests/test_figures_scrub.py +0 -0
  348. {latextify-0.3.0 → latextify-0.3.2}/tests/test_figures_stage2.py +0 -0
  349. {latextify-0.3.0 → latextify-0.3.2}/tests/test_figures_tiff.py +0 -0
  350. {latextify-0.3.0 → latextify-0.3.2}/tests/test_figures_wide.py +0 -0
  351. {latextify-0.3.0 → latextify-0.3.2}/tests/test_filters.py +0 -0
  352. {latextify-0.3.0 → latextify-0.3.2}/tests/test_filters_headings.py +0 -0
  353. {latextify-0.3.0 → latextify-0.3.2}/tests/test_frontmatter.py +0 -0
  354. {latextify-0.3.0 → latextify-0.3.2}/tests/test_gui_demo.py +0 -0
  355. {latextify-0.3.0 → latextify-0.3.2}/tests/test_journal_pdf_layout.py +0 -0
  356. {latextify-0.3.0 → latextify-0.3.2}/tests/test_manuscript_formats.py +0 -0
  357. {latextify-0.3.0 → latextify-0.3.2}/tests/test_metadata_guess.py +0 -0
  358. {latextify-0.3.0 → latextify-0.3.2}/tests/test_package.py +0 -0
  359. {latextify-0.3.0 → latextify-0.3.2}/tests/test_pandoc.py +0 -0
  360. {latextify-0.3.0 → latextify-0.3.2}/tests/test_pdf_combine.py +0 -0
  361. {latextify-0.3.0 → latextify-0.3.2}/tests/test_preflight.py +0 -0
  362. {latextify-0.3.0 → latextify-0.3.2}/tests/test_preflight_privacy.py +0 -0
  363. {latextify-0.3.0 → latextify-0.3.2}/tests/test_privacy.py +0 -0
  364. {latextify-0.3.0 → latextify-0.3.2}/tests/test_repo_integrity.py +0 -0
  365. {latextify-0.3.0 → latextify-0.3.2}/tests/test_report.py +0 -0
  366. {latextify-0.3.0 → latextify-0.3.2}/tests/test_supplement.py +0 -0
  367. {latextify-0.3.0 → latextify-0.3.2}/tests/test_tables.py +0 -0
  368. {latextify-0.3.0 → latextify-0.3.2}/tests/test_tectonic_integrity.py +0 -0
  369. {latextify-0.3.0 → latextify-0.3.2}/tests/test_templates.py +0 -0
  370. {latextify-0.3.0 → latextify-0.3.2}/tests/test_templates_item22.py +0 -0
  371. {latextify-0.3.0 → latextify-0.3.2}/tests/test_templates_sn_jnl.py +0 -0
  372. {latextify-0.3.0 → latextify-0.3.2}/tests/test_xml_hardening.py +0 -0
@@ -138,9 +138,8 @@ jobs:
138
138
  # OLDEST supported Python (3.10, our documented floor -- see
139
139
  # requires-python in pyproject.toml) on the platform most kit users
140
140
  # actually run: Windows. A future dependency bump could silently break
141
- # the 3.10 floor without this catching it. Kept fast: emit-only
142
- # (--no-warm-tex, no --pdf) -- the PDF/Tectonic offline path is already
143
- # covered by the `offline-kit` job above.
141
+ # the 3.10 floor without this catching it. This job also freezes and boots
142
+ # the no-Python portable application while the network is poisoned.
144
143
  runs-on: windows-latest
145
144
  steps:
146
145
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
@@ -148,14 +147,14 @@ jobs:
148
147
  with:
149
148
  python-version: "3.13"
150
149
  - name: Install
151
- run: uv sync --locked
150
+ run: uv sync --locked --group portable
152
151
  - name: Set up the TARGET Python 3.10 (what install.py will run under)
153
152
  id: py310
154
153
  uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
155
154
  with:
156
155
  python-version: "3.10"
157
- - name: Build an emit-only kit covering Python 3.10 (network ON)
158
- run: uv run latextify make-kit --target current --python-versions 3.10 --no-warm-tex --output build
156
+ - name: Build a GUI kit covering Python 3.10 and warm one journal (network ON)
157
+ run: uv run latextify make-kit --target current --python-versions 3.10 --with-gui --journals revtex4-2 --output build
159
158
  - name: Install the kit under Python 3.10, network POISONED
160
159
  env:
161
160
  # Same reasoning as the Linux offline-kit job: uppercase only, one
@@ -166,6 +165,12 @@ jobs:
166
165
  run: |
167
166
  & "${{ steps.py310.outputs.python-path }}" "build/latextify-offline-windows-x64/install.py"
168
167
  if ($LASTEXITCODE -ne 0) { throw "install.py failed under Python 3.10 (exit $LASTEXITCODE)" }
168
+ $kit = "build/latextify-offline-windows-x64"
169
+ & "$kit/.venv/Scripts/python.exe" -c "import fastapi, uvicorn, multipart; import latextify.gui.server"
170
+ if ($LASTEXITCODE -ne 0) { throw "offline GUI imports failed (exit $LASTEXITCODE)" }
171
+ if (-not (Test-Path "$kit/Start-LaTeXtify-GUI.bat")) {
172
+ throw "offline GUI launcher was not generated"
173
+ }
169
174
  - name: Convert a fixture manuscript offline (emit-only, no --pdf)
170
175
  env:
171
176
  HTTPS_PROXY: http://127.0.0.1:9
@@ -178,3 +183,40 @@ jobs:
178
183
  throw "Python 3.10 kit did not produce demo-out/revtex4-2/main.tex"
179
184
  }
180
185
  Write-Host "offline (Python 3.10, Windows): produced demo-out/revtex4-2/main.tex"
186
+ - name: Build the no-install portable Windows application
187
+ run: uv run --group portable python tools/build_windows_portable.py --kit build/latextify-offline-windows-x64 --output build/portable
188
+ - name: Exercise the frozen application with the network poisoned
189
+ env:
190
+ HTTPS_PROXY: http://127.0.0.1:9
191
+ HTTP_PROXY: http://127.0.0.1:9
192
+ ALL_PROXY: http://127.0.0.1:9
193
+ run: |
194
+ $app = Resolve-Path "build/portable/LaTeXtify-Windows-Portable/LaTeXtify.exe"
195
+ $selfTest = Start-Process -FilePath $app -ArgumentList "--self-test" -PassThru -Wait -WindowStyle Hidden
196
+ if ($selfTest.ExitCode -ne 0) {
197
+ Get-Content "build/portable/LaTeXtify-Windows-Portable/latextify-startup.log" -ErrorAction SilentlyContinue
198
+ throw "portable self-test failed (exit $($selfTest.ExitCode))"
199
+ }
200
+ $env:LATEXTIFY_NO_BROWSER = "1"
201
+ $env:LATEXTIFY_PORT = "8765"
202
+ $process = Start-Process -FilePath $app -PassThru -WindowStyle Hidden
203
+ try {
204
+ $ready = $false
205
+ foreach ($attempt in 1..30) {
206
+ Start-Sleep -Seconds 1
207
+ try {
208
+ $response = Invoke-WebRequest "http://127.0.0.1:8765/" -UseBasicParsing -NoProxy
209
+ if ($response.StatusCode -eq 200 -and $response.Content -match "LaTeXtify") {
210
+ $ready = $true
211
+ break
212
+ }
213
+ } catch {}
214
+ if ($process.HasExited) { break }
215
+ }
216
+ if (-not $ready) {
217
+ Get-Content "build/portable/LaTeXtify-Windows-Portable/latextify-startup.log" -ErrorAction SilentlyContinue
218
+ throw "portable GUI did not answer on loopback"
219
+ }
220
+ } finally {
221
+ if (-not $process.HasExited) { Stop-Process -Id $process.Id -Force }
222
+ }
@@ -27,7 +27,50 @@ concurrency:
27
27
  cancel-in-progress: false
28
28
 
29
29
  jobs:
30
+ build-windows-portable:
31
+ runs-on: windows-latest
32
+ timeout-minutes: 60
33
+ steps:
34
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
35
+ with:
36
+ ref: ${{ github.event.inputs.tag || github.ref_name }}
37
+ - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
38
+ with:
39
+ python-version: "3.13"
40
+ - name: Cache Tectonic binary and package bundle
41
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
42
+ with:
43
+ path: |
44
+ ~/.cache/latextify
45
+ ~/.cache/Tectonic
46
+ key: tectonic-${{ runner.os }}-v1
47
+ - name: Install build dependencies
48
+ run: uv sync --locked --no-dev --extra gui --group portable
49
+ - name: Build native Windows runtime assets
50
+ env:
51
+ GITHUB_TOKEN: ${{ github.token }}
52
+ run: uv run latextify make-kit --target current --python-versions 3.13 --with-gui -o build
53
+ - name: Freeze and verify the portable application
54
+ env:
55
+ HTTPS_PROXY: http://127.0.0.1:9
56
+ HTTP_PROXY: http://127.0.0.1:9
57
+ ALL_PROXY: http://127.0.0.1:9
58
+ run: |
59
+ uv run --group portable python tools/build_windows_portable.py --kit build/latextify-offline-windows-x64 --output build/portable
60
+ $app = Resolve-Path "build/portable/LaTeXtify-Windows-Portable/LaTeXtify.exe"
61
+ $selfTest = Start-Process -FilePath $app -ArgumentList "--self-test" -PassThru -Wait -WindowStyle Hidden
62
+ if ($selfTest.ExitCode -ne 0) {
63
+ Get-Content "build/portable/LaTeXtify-Windows-Portable/latextify-startup.log" -ErrorAction SilentlyContinue
64
+ throw "portable self-test failed (exit $($selfTest.ExitCode))"
65
+ }
66
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
67
+ with:
68
+ name: windows-portable
69
+ path: build/portable/LaTeXtify-Windows-Portable.zip
70
+ if-no-files-found: error
71
+
30
72
  build-kits:
73
+ needs: build-windows-portable
31
74
  runs-on: ubuntu-latest
32
75
  timeout-minutes: 60
33
76
  steps:
@@ -54,7 +97,7 @@ jobs:
54
97
  - name: Build native Linux kit (warms the shared, portable TeX cache)
55
98
  env:
56
99
  GITHUB_TOKEN: ${{ github.token }}
57
- run: uv run latextify make-kit --target linux-x64 -o build
100
+ run: uv run latextify make-kit --target linux-x64 --with-gui -o build
58
101
 
59
102
  # NOTE -- native-build preference: this workflow cross-builds the
60
103
  # win-x64 (and macos-arm64) kits from a single Linux runner, on a
@@ -80,7 +123,7 @@ jobs:
80
123
  # and reuse it for the cross targets rather than re-downloading the
81
124
  # whole TeX bundle two more times.
82
125
  for tgt in win-x64 macos-arm64; do
83
- uv run latextify make-kit --target "$tgt" --no-warm-tex -o build
126
+ uv run latextify make-kit --target "$tgt" --with-gui --no-warm-tex -o build
84
127
  done
85
128
  uv run python - <<'PY'
86
129
  import json
@@ -116,6 +159,17 @@ jobs:
116
159
  ls -lh *.zip SHA256SUMS.txt
117
160
  cat SHA256SUMS.txt
118
161
 
162
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
163
+ with:
164
+ name: windows-portable
165
+ path: build
166
+
167
+ - name: Add the portable Windows application to checksums
168
+ run: |
169
+ cd build
170
+ sha256sum LaTeXtify-Windows-Portable.zip >> SHA256SUMS.txt
171
+ cat SHA256SUMS.txt
172
+
119
173
  - name: Create release (if missing) and upload assets
120
174
  env:
121
175
  GH_TOKEN: ${{ github.token }}
@@ -126,6 +180,7 @@ jobs:
126
180
  latextify-offline-windows-x64.zip
127
181
  latextify-offline-linux-x64.zip
128
182
  latextify-offline-macos-arm64.zip
183
+ LaTeXtify-Windows-Portable.zip
129
184
  latextify-source.zip
130
185
  SHA256SUMS.txt
131
186
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: latextify
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Convert Word manuscripts to journal-ready LaTeX projects and PDFs
5
5
  Project-URL: Homepage, https://github.com/pquarterman17/LaTeXtify
6
6
  Project-URL: Repository, https://github.com/pquarterman17/LaTeXtify
@@ -252,8 +252,9 @@ holds real vector for anything drawn:
252
252
  Metafile)*. That lands as EMF, which is vector. A plain Ctrl+V usually gives
253
253
  you a bitmap.
254
254
  - **LaTeXtify converts EMF and WMF to PDF** when LibreOffice or Inkscape is on
255
- your PATH. Neither is a dependency — without one, the figure is skipped and
256
- the warning names the fix.
255
+ your PATH or in its usual Windows install directory. If neither is available,
256
+ it tries a 600-DPI PNG fallback and warns that the result is no longer vector;
257
+ a metafile it cannot safely decode is skipped with an actionable warning.
257
258
  - **Export from the plotting tool.** Saving directly to PDF or SVG and dropping
258
259
  it in as `figures/fig<N>.pdf` skips Word entirely and is the most reliable
259
260
  route.
@@ -330,7 +331,34 @@ To start the web GUI without touching a command line, double-click
330
331
  **`startup.bat`** (Windows) or run **`./startup.sh`** (macOS/Linux). It installs
331
332
  dependencies on first run, opens the GUI in your browser, and — if anything
332
333
  goes wrong — writes an easy-to-find `latextify-startup.log` next to the script
333
- and prints it for you to copy.
334
+ and prints it for you to copy. This source-tree launcher needs internet access
335
+ on its first run.
336
+
337
+ ### Offline / firewalled Windows computer
338
+
339
+ Do not transfer GitHub's automatically generated source-code ZIP or
340
+ `latextify-source.zip`: their `startup.bat` must fetch `uv` and Python packages.
341
+ For a locked-down work computer, download the release asset named
342
+ **`LaTeXtify-Windows-Portable.zip`**, transfer it using an approved method,
343
+ extract the whole folder to a short writable path such as `C:\LaTeXtify`, and
344
+ double-click **`LaTeXtify.exe`**. It carries its own Python runtime, GUI,
345
+ Pandoc, Tectonic, and warmed TeX cache. It performs no installation and needs
346
+ no administrator rights, Python, `uv`, `pip`, GitHub, Astral, or PyPI access.
347
+
348
+ The older **`latextify-offline-windows-x64.zip`** remains available for users
349
+ who prefer a conventional, inspectable Python environment. It requires a
350
+ supported 64-bit Python already present. Extract it and run:
351
+
352
+ ```bat
353
+ py install.py
354
+ ```
355
+
356
+ Then double-click **`Start-LaTeXtify-GUI.bat`**. That offline package contains
357
+ the GUI, Pandoc, Tectonic, dependencies, and a warmed TeX cache; it does not
358
+ contact GitHub, Astral, PyPI, or Crossref. It requires a supported 64-bit
359
+ Python already present on the work computer. Embedded EMF/WMF figures work,
360
+ but preserving them as vectors requires an offline installation of LibreOffice
361
+ or Inkscape; otherwise Windows attempts a 600-DPI raster fallback.
334
362
 
335
363
  ## Usage
336
364
 
@@ -347,6 +375,18 @@ latextify convert paper.docx --journal elsarticle --citation-style authoryear
347
375
  # Supplementary material: S-numbered second document sharing the bibliography
348
376
  latextify convert paper.docx --journal revtex4-2 --supplement si.docx --pdf
349
377
 
378
+ # Main text + supplement in ONE Word file and ONE main.pdf. Put a heading named
379
+ # "Supplementary Material", "Supplementary Information", "Supplemental
380
+ # Material", or "Supporting Information" at the boundary.
381
+ latextify convert merged.docx --journal revtex4-2 --inline-supplement --pdf
382
+
383
+ # Inline supplements cannot be combined with --figures-at-end: delayed floats
384
+ # would cross the numbering boundary. LaTeXtify rejects that combination.
385
+ # If the merged file contains separate main and supplementary reference lists,
386
+ # LaTeXtify combines them into one bibliography at the end of the PDF. Numeric
387
+ # citation numbering is continuous across the whole document and a warning is
388
+ # written to the conversion report so this policy is never implicit.
389
+
350
390
  # ...and staple the main text + supplement into one combined.pdf
351
391
  latextify convert paper.docx --journal revtex4-2 --supplement si.docx --pdf --combine-supplement
352
392
 
@@ -366,6 +406,11 @@ latextify figures paper.docx
366
406
  # Convert, reporting every figure that is still a screenshot rather than vector
367
407
  latextify convert paper.docx --journal revtex4-2 --pdf --vector-figures
368
408
 
409
+ # Force selected embedded or external figures to one/two columns. Numbers are
410
+ # document order as shown by `latextify figures`; all others stay automatic.
411
+ latextify convert paper.docx --journal revtex4-2 --pdf \
412
+ --figure-column 1=one --figure-column 2=two
413
+
369
414
  # A folder of manuscripts at once (continue-on-error + summary)
370
415
  latextify batch drafts/ --journal revtex4-2 --pdf
371
416
 
@@ -385,9 +430,11 @@ latextify journals
385
430
  `127.0.0.1` only — your uploads never leave your machine) and opens a tab.
386
431
  Drop your whole submission in at once — **main `.docx`, supplement `.docx`,
387
432
  figure files, and a `.bib` reference library together** — then set each
388
- file's role, pick a journal from the full publisher list, choose options
389
- (compile PDF, combine supplement, one-column SI, equation audit, project
390
- `.zip`), and click **Preview**. The compiled PDFs render inline so you can
433
+ file's role, pick a journal from the full publisher list, choose options,
434
+ and click **Preview**. The GUI reads the selected manuscript's figures and
435
+ gives every figure its own **Automatic / 1 column / 2 columns** selector;
436
+ separately uploaded replacements appear under the same document-order number.
437
+ The compiled PDFs render inline so you can
391
438
  confirm the conversion worked. A preview is held only in **temporary local
392
439
  storage** (a private working directory the app owns) — it is pruned
393
440
  automatically about an hour after its last use and deleted when the app shuts
@@ -396,6 +443,21 @@ panel lets you pick a destination folder (a native "Browse…" dialog) and copy
396
443
  any subset of the outputs — the LaTeX project, individual PDFs, or the `.zip` —
397
444
  to the folder you choose to keep.
398
445
 
446
+ For a merged main+supplement Word file, upload it only as **Main text**, do not
447
+ add a separate Supplement file, and choose one **Combined main + supplement
448
+ Word file** layout:
449
+
450
+ - **Single-column throughout (closest to Word)**
451
+ - **Main two columns; supplement two columns**
452
+ - **Main two columns; supplement one column**
453
+
454
+ Every combined mode produces one `main.pdf`, starts the recognized
455
+ Supplementary Material/Information heading on a new page, and restarts figures,
456
+ tables, equations, and sections as S1. **Check references online**, **Strip
457
+ figure metadata**, and **Optimize figure placement** are independent toggles.
458
+ The offline launcher disables the online reference check; the other two remain
459
+ available. Manual per-figure column choices override automatic placement.
460
+
399
461
  Output layout per conversion:
400
462
 
401
463
  ```
@@ -206,8 +206,9 @@ holds real vector for anything drawn:
206
206
  Metafile)*. That lands as EMF, which is vector. A plain Ctrl+V usually gives
207
207
  you a bitmap.
208
208
  - **LaTeXtify converts EMF and WMF to PDF** when LibreOffice or Inkscape is on
209
- your PATH. Neither is a dependency — without one, the figure is skipped and
210
- the warning names the fix.
209
+ your PATH or in its usual Windows install directory. If neither is available,
210
+ it tries a 600-DPI PNG fallback and warns that the result is no longer vector;
211
+ a metafile it cannot safely decode is skipped with an actionable warning.
211
212
  - **Export from the plotting tool.** Saving directly to PDF or SVG and dropping
212
213
  it in as `figures/fig<N>.pdf` skips Word entirely and is the most reliable
213
214
  route.
@@ -284,7 +285,34 @@ To start the web GUI without touching a command line, double-click
284
285
  **`startup.bat`** (Windows) or run **`./startup.sh`** (macOS/Linux). It installs
285
286
  dependencies on first run, opens the GUI in your browser, and — if anything
286
287
  goes wrong — writes an easy-to-find `latextify-startup.log` next to the script
287
- and prints it for you to copy.
288
+ and prints it for you to copy. This source-tree launcher needs internet access
289
+ on its first run.
290
+
291
+ ### Offline / firewalled Windows computer
292
+
293
+ Do not transfer GitHub's automatically generated source-code ZIP or
294
+ `latextify-source.zip`: their `startup.bat` must fetch `uv` and Python packages.
295
+ For a locked-down work computer, download the release asset named
296
+ **`LaTeXtify-Windows-Portable.zip`**, transfer it using an approved method,
297
+ extract the whole folder to a short writable path such as `C:\LaTeXtify`, and
298
+ double-click **`LaTeXtify.exe`**. It carries its own Python runtime, GUI,
299
+ Pandoc, Tectonic, and warmed TeX cache. It performs no installation and needs
300
+ no administrator rights, Python, `uv`, `pip`, GitHub, Astral, or PyPI access.
301
+
302
+ The older **`latextify-offline-windows-x64.zip`** remains available for users
303
+ who prefer a conventional, inspectable Python environment. It requires a
304
+ supported 64-bit Python already present. Extract it and run:
305
+
306
+ ```bat
307
+ py install.py
308
+ ```
309
+
310
+ Then double-click **`Start-LaTeXtify-GUI.bat`**. That offline package contains
311
+ the GUI, Pandoc, Tectonic, dependencies, and a warmed TeX cache; it does not
312
+ contact GitHub, Astral, PyPI, or Crossref. It requires a supported 64-bit
313
+ Python already present on the work computer. Embedded EMF/WMF figures work,
314
+ but preserving them as vectors requires an offline installation of LibreOffice
315
+ or Inkscape; otherwise Windows attempts a 600-DPI raster fallback.
288
316
 
289
317
  ## Usage
290
318
 
@@ -301,6 +329,18 @@ latextify convert paper.docx --journal elsarticle --citation-style authoryear
301
329
  # Supplementary material: S-numbered second document sharing the bibliography
302
330
  latextify convert paper.docx --journal revtex4-2 --supplement si.docx --pdf
303
331
 
332
+ # Main text + supplement in ONE Word file and ONE main.pdf. Put a heading named
333
+ # "Supplementary Material", "Supplementary Information", "Supplemental
334
+ # Material", or "Supporting Information" at the boundary.
335
+ latextify convert merged.docx --journal revtex4-2 --inline-supplement --pdf
336
+
337
+ # Inline supplements cannot be combined with --figures-at-end: delayed floats
338
+ # would cross the numbering boundary. LaTeXtify rejects that combination.
339
+ # If the merged file contains separate main and supplementary reference lists,
340
+ # LaTeXtify combines them into one bibliography at the end of the PDF. Numeric
341
+ # citation numbering is continuous across the whole document and a warning is
342
+ # written to the conversion report so this policy is never implicit.
343
+
304
344
  # ...and staple the main text + supplement into one combined.pdf
305
345
  latextify convert paper.docx --journal revtex4-2 --supplement si.docx --pdf --combine-supplement
306
346
 
@@ -320,6 +360,11 @@ latextify figures paper.docx
320
360
  # Convert, reporting every figure that is still a screenshot rather than vector
321
361
  latextify convert paper.docx --journal revtex4-2 --pdf --vector-figures
322
362
 
363
+ # Force selected embedded or external figures to one/two columns. Numbers are
364
+ # document order as shown by `latextify figures`; all others stay automatic.
365
+ latextify convert paper.docx --journal revtex4-2 --pdf \
366
+ --figure-column 1=one --figure-column 2=two
367
+
323
368
  # A folder of manuscripts at once (continue-on-error + summary)
324
369
  latextify batch drafts/ --journal revtex4-2 --pdf
325
370
 
@@ -339,9 +384,11 @@ latextify journals
339
384
  `127.0.0.1` only — your uploads never leave your machine) and opens a tab.
340
385
  Drop your whole submission in at once — **main `.docx`, supplement `.docx`,
341
386
  figure files, and a `.bib` reference library together** — then set each
342
- file's role, pick a journal from the full publisher list, choose options
343
- (compile PDF, combine supplement, one-column SI, equation audit, project
344
- `.zip`), and click **Preview**. The compiled PDFs render inline so you can
387
+ file's role, pick a journal from the full publisher list, choose options,
388
+ and click **Preview**. The GUI reads the selected manuscript's figures and
389
+ gives every figure its own **Automatic / 1 column / 2 columns** selector;
390
+ separately uploaded replacements appear under the same document-order number.
391
+ The compiled PDFs render inline so you can
345
392
  confirm the conversion worked. A preview is held only in **temporary local
346
393
  storage** (a private working directory the app owns) — it is pruned
347
394
  automatically about an hour after its last use and deleted when the app shuts
@@ -350,6 +397,21 @@ panel lets you pick a destination folder (a native "Browse…" dialog) and copy
350
397
  any subset of the outputs — the LaTeX project, individual PDFs, or the `.zip` —
351
398
  to the folder you choose to keep.
352
399
 
400
+ For a merged main+supplement Word file, upload it only as **Main text**, do not
401
+ add a separate Supplement file, and choose one **Combined main + supplement
402
+ Word file** layout:
403
+
404
+ - **Single-column throughout (closest to Word)**
405
+ - **Main two columns; supplement two columns**
406
+ - **Main two columns; supplement one column**
407
+
408
+ Every combined mode produces one `main.pdf`, starts the recognized
409
+ Supplementary Material/Information heading on a new page, and restarts figures,
410
+ tables, equations, and sections as S1. **Check references online**, **Strip
411
+ figure metadata**, and **Optimize figure placement** are independent toggles.
412
+ The offline launcher disables the online reference check; the other two remain
413
+ available. Manual per-figure column choices override automatic placement.
414
+
353
415
  Output layout per conversion:
354
416
 
355
417
  ```
@@ -235,12 +235,21 @@ def strip_reference_section_to_eof(tex: str) -> str:
235
235
  (:mod:`latextify.emit.project`) strips the reference manager's own
236
236
  formatted bibliography, which duplicates the generated ``\\bibliography``.
237
237
  """
238
+
239
+ def cut(start: int) -> str:
240
+ # A merged main+SI manuscript can put its main reference list before
241
+ # the supplementary heading. Preserve that protected tail rather than
242
+ # applying the historical "references are always EOF" assumption.
243
+ marker = tex.find("%%LATEXTIFY_INLINE_SUPPLEMENT%%", start)
244
+ tail = strip_reference_section_to_eof(tex[marker:]) if marker >= 0 else ""
245
+ return tex[:start].rstrip() + "\n" + tail
246
+
238
247
  for match in _REF_SECTION_RE.finditer(tex):
239
248
  if is_reference_heading_text(match.group(1).strip()):
240
- return tex[: match.start()].rstrip() + "\n"
249
+ return cut(match.start())
241
250
  offset = _find_bare_reference_heading(tex)
242
251
  if offset is not None:
243
- return tex[:offset].rstrip() + "\n"
252
+ return cut(offset)
244
253
  return tex
245
254
 
246
255
 
@@ -56,6 +56,7 @@ from .bib import csl_type_to_bibtex
56
56
  #: Placeholder contact address; override with a real one in production use.
57
57
  DEFAULT_MAILTO = "latextify@example.com"
58
58
  _MAILTO_ENV = "LATEXTIFY_CROSSREF_MAILTO"
59
+ _OFFLINE_ENV = "LATEXTIFY_OFFLINE"
59
60
 
60
61
  DEFAULT_BASE_URL = "https://api.crossref.org"
61
62
  _USER_AGENT_PRODUCT = "LaTeXtify/0.1 (https://github.com/latextify/latextify)"
@@ -313,6 +314,8 @@ class CrossrefClient:
313
314
  consecutive calls have failed, further calls raise immediately so a
314
315
  full outage costs a few timeouts, not one per remaining reference.
315
316
  """
317
+ if os.environ.get(_OFFLINE_ENV, "").lower() in {"1", "true", "yes"}:
318
+ raise CrossrefUnavailable("Crossref disabled by LATEXTIFY_OFFLINE")
316
319
  if self._consecutive_failures >= _BREAKER_THRESHOLD:
317
320
  raise CrossrefUnavailable(
318
321
  f"skipped after {self._consecutive_failures} consecutive Crossref failures"
@@ -113,6 +113,12 @@ def is_reference_heading_text(text: str) -> bool:
113
113
  return _is_heading_paragraph(text)
114
114
 
115
115
 
116
+ _SUPPLEMENT_HEADING_RE = re.compile(
117
+ r"^(?:supplement(?:ary|al)?\s+(?:material|information)|supporting\s+information)\s*:?$",
118
+ re.IGNORECASE,
119
+ )
120
+
121
+
116
122
  def _has_list_numbering(paragraph) -> bool:
117
123
  """True when a paragraph carries real Word list numbering (``w:numPr``).
118
124
 
@@ -139,6 +145,7 @@ class ReferenceList:
139
145
 
140
146
  heading: str | None
141
147
  references: list[ReferenceItem] = field(default_factory=list)
148
+ supplement: bool = False
142
149
 
143
150
  @property
144
151
  def found(self) -> bool:
@@ -155,46 +162,57 @@ def segment_reference_list(docx_path: Path | str) -> ReferenceList:
155
162
  ``word/document.xml`` to read) dispatches to
156
163
  :mod:`latextify.citations.reflist_nondocx` instead.
157
164
  """
165
+ lists = segment_reference_lists(docx_path)
166
+ return lists[0] if lists else ReferenceList(heading=None)
167
+
168
+
169
+ def segment_reference_lists(docx_path: Path | str) -> tuple[ReferenceList, ...]:
170
+ """Return each typed reference list, tagged as main or supplementary."""
158
171
  if not is_docx(docx_path):
159
172
  from .reflist_nondocx import segment_reference_list_from_manuscript
160
173
 
161
- return segment_reference_list_from_manuscript(Path(docx_path))
174
+ found = segment_reference_list_from_manuscript(Path(docx_path))
175
+ return (found,) if found.heading is not None else ()
162
176
  root = etree.fromstring(read_document_xml(docx_path))
163
177
  paragraphs = list(root.iter(_q("p")))
164
-
165
- heading_index: int | None = None
166
- heading_text: str | None = None
167
- for index, paragraph in enumerate(paragraphs):
168
- text = _paragraph_text(paragraph)
169
- if _is_heading_paragraph(text):
170
- heading_index = index
171
- heading_text = text.strip().rstrip(":").strip()
172
- break
173
-
174
- if heading_index is None:
175
- return ReferenceList(heading=None)
176
-
177
- references: list[ReferenceItem] = []
178
- auto_number = 0
179
- for paragraph in paragraphs[heading_index + 1 :]:
180
- text = _paragraph_text(paragraph).strip()
181
- if not text:
182
- continue
183
- match = _LIST_NUMBER_RE.match(text)
184
- if match:
185
- number = int(match.group("br") or match.group("pr") or match.group("dot"))
186
- body = text[match.end() :].strip()
187
- references.append(ReferenceItem(text=body, number=number))
188
- elif _has_list_numbering(paragraph):
189
- # Word's own auto-numbering: no typed digits to parse, so assign
190
- # sequential numbers in document order (a fresh Word list always
191
- # starts at 1 and increments by 1, matching what the reader sees).
192
- auto_number += 1
193
- references.append(ReferenceItem(text=text, number=auto_number))
194
- else:
195
- references.append(ReferenceItem(text=text, number=None))
196
-
197
- return ReferenceList(heading=heading_text, references=references)
178
+ texts = [_paragraph_text(paragraph).strip() for paragraph in paragraphs]
179
+ supplement_at = next(
180
+ (index for index, text in enumerate(texts) if _SUPPLEMENT_HEADING_RE.match(text)), None
181
+ )
182
+ headings = [index for index, text in enumerate(texts) if _is_heading_paragraph(text)]
183
+ lists: list[ReferenceList] = []
184
+ for position, heading_index in enumerate(headings):
185
+ stops = [len(paragraphs)]
186
+ if position + 1 < len(headings):
187
+ stops.append(headings[position + 1])
188
+ if supplement_at is not None and heading_index < supplement_at:
189
+ stops.append(supplement_at)
190
+ references: list[ReferenceItem] = []
191
+ auto_number = 0
192
+ for paragraph, text in zip(
193
+ paragraphs[heading_index + 1 : min(stops)],
194
+ texts[heading_index + 1 : min(stops)],
195
+ strict=True,
196
+ ):
197
+ if not text:
198
+ continue
199
+ match = _LIST_NUMBER_RE.match(text)
200
+ if match:
201
+ number = int(match.group("br") or match.group("pr") or match.group("dot"))
202
+ references.append(ReferenceItem(text=text[match.end() :].strip(), number=number))
203
+ elif _has_list_numbering(paragraph):
204
+ auto_number += 1
205
+ references.append(ReferenceItem(text=text, number=auto_number))
206
+ else:
207
+ references.append(ReferenceItem(text=text, number=None))
208
+ lists.append(
209
+ ReferenceList(
210
+ heading=texts[heading_index].rstrip(":").strip(),
211
+ references=references,
212
+ supplement=supplement_at is not None and heading_index > supplement_at,
213
+ )
214
+ )
215
+ return tuple(lists)
198
216
 
199
217
 
200
218
  # --- reconstruction ----------------------------------------------------------
@@ -217,6 +235,7 @@ class PlaintextResult:
217
235
  author_year_keys: dict[tuple[str, str], list[str]] = field(default_factory=dict)
218
236
  heading: str | None = None
219
237
  has_reference_list: bool = False
238
+ supplement: bool = False
220
239
 
221
240
  @property
222
241
  def report(self) -> ReconciliationReport:
@@ -256,6 +275,10 @@ def reconstruct_citations(
256
275
  if owns_client:
257
276
  client.close()
258
277
 
278
+ return _result_from_outcome(reflist, outcome)
279
+
280
+
281
+ def _result_from_outcome(reflist: ReferenceList, outcome) -> PlaintextResult:
259
282
  keys_by_number = {
260
283
  record.ref_number: record.key for record in outcome.records if record.ref_number is not None
261
284
  }
@@ -266,4 +289,33 @@ def reconstruct_citations(
266
289
  author_year_keys=build_author_year_index(outcome.entries),
267
290
  heading=reflist.heading,
268
291
  has_reference_list=True,
292
+ supplement=reflist.supplement,
269
293
  )
294
+
295
+
296
+ def reconstruct_citation_lists(
297
+ docx_path: Path | str,
298
+ *,
299
+ mailto: str | None = None,
300
+ threshold: float = reconcile.DEFAULT_THRESHOLD,
301
+ client: crossref.CrossrefClient | None = None,
302
+ bib_entries: list[RefEntry] | None = None,
303
+ ) -> tuple[PlaintextResult, ...]:
304
+ """Reconstruct every typed reference list in a merged main+SI manuscript."""
305
+ reflists = tuple(item for item in segment_reference_lists(docx_path) if item.found)
306
+ if not reflists:
307
+ return ()
308
+ owns_client = client is None
309
+ if client is None:
310
+ client = crossref.CrossrefClient(mailto=mailto)
311
+ results: list[PlaintextResult] = []
312
+ try:
313
+ for reflist in reflists:
314
+ outcome = reconcile.reconcile_references(
315
+ reflist.references, client, threshold=threshold, bib_entries=bib_entries
316
+ )
317
+ results.append(_result_from_outcome(reflist, outcome))
318
+ finally:
319
+ if owns_client:
320
+ client.close()
321
+ return tuple(results)