scitex 2.7.0__py3-none-any.whl → 2.8.1__py3-none-any.whl
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.
- scitex/__init__.py +6 -2
- scitex/__version__.py +1 -1
- scitex/audio/README.md +52 -0
- scitex/audio/__init__.py +384 -0
- scitex/audio/__main__.py +129 -0
- scitex/audio/_tts.py +334 -0
- scitex/audio/engines/__init__.py +44 -0
- scitex/audio/engines/base.py +275 -0
- scitex/audio/engines/elevenlabs_engine.py +143 -0
- scitex/audio/engines/gtts_engine.py +162 -0
- scitex/audio/engines/pyttsx3_engine.py +131 -0
- scitex/audio/mcp_server.py +757 -0
- scitex/bridge/_helpers.py +1 -1
- scitex/bridge/_plt_vis.py +1 -1
- scitex/bridge/_stats_vis.py +1 -1
- scitex/dev/plt/__init__.py +272 -0
- scitex/dev/plt/plot_mpl_axhline.py +28 -0
- scitex/dev/plt/plot_mpl_axhspan.py +28 -0
- scitex/dev/plt/plot_mpl_axvline.py +28 -0
- scitex/dev/plt/plot_mpl_axvspan.py +28 -0
- scitex/dev/plt/plot_mpl_bar.py +29 -0
- scitex/dev/plt/plot_mpl_barh.py +29 -0
- scitex/dev/plt/plot_mpl_boxplot.py +28 -0
- scitex/dev/plt/plot_mpl_contour.py +31 -0
- scitex/dev/plt/plot_mpl_contourf.py +31 -0
- scitex/dev/plt/plot_mpl_errorbar.py +30 -0
- scitex/dev/plt/plot_mpl_eventplot.py +28 -0
- scitex/dev/plt/plot_mpl_fill.py +30 -0
- scitex/dev/plt/plot_mpl_fill_between.py +31 -0
- scitex/dev/plt/plot_mpl_hexbin.py +28 -0
- scitex/dev/plt/plot_mpl_hist.py +28 -0
- scitex/dev/plt/plot_mpl_hist2d.py +28 -0
- scitex/dev/plt/plot_mpl_imshow.py +29 -0
- scitex/dev/plt/plot_mpl_pcolormesh.py +31 -0
- scitex/dev/plt/plot_mpl_pie.py +29 -0
- scitex/dev/plt/plot_mpl_plot.py +29 -0
- scitex/dev/plt/plot_mpl_quiver.py +31 -0
- scitex/dev/plt/plot_mpl_scatter.py +28 -0
- scitex/dev/plt/plot_mpl_stackplot.py +31 -0
- scitex/dev/plt/plot_mpl_stem.py +29 -0
- scitex/dev/plt/plot_mpl_step.py +29 -0
- scitex/dev/plt/plot_mpl_violinplot.py +28 -0
- scitex/dev/plt/plot_sns_barplot.py +29 -0
- scitex/dev/plt/plot_sns_boxplot.py +29 -0
- scitex/dev/plt/plot_sns_heatmap.py +28 -0
- scitex/dev/plt/plot_sns_histplot.py +29 -0
- scitex/dev/plt/plot_sns_kdeplot.py +29 -0
- scitex/dev/plt/plot_sns_lineplot.py +31 -0
- scitex/dev/plt/plot_sns_scatterplot.py +29 -0
- scitex/dev/plt/plot_sns_stripplot.py +29 -0
- scitex/dev/plt/plot_sns_swarmplot.py +29 -0
- scitex/dev/plt/plot_sns_violinplot.py +29 -0
- scitex/dev/plt/plot_stx_bar.py +29 -0
- scitex/dev/plt/plot_stx_barh.py +29 -0
- scitex/dev/plt/plot_stx_box.py +28 -0
- scitex/dev/plt/plot_stx_boxplot.py +28 -0
- scitex/dev/plt/plot_stx_conf_mat.py +28 -0
- scitex/dev/plt/plot_stx_contour.py +31 -0
- scitex/dev/plt/plot_stx_ecdf.py +28 -0
- scitex/dev/plt/plot_stx_errorbar.py +30 -0
- scitex/dev/plt/plot_stx_fill_between.py +31 -0
- scitex/dev/plt/plot_stx_fillv.py +28 -0
- scitex/dev/plt/plot_stx_heatmap.py +28 -0
- scitex/dev/plt/plot_stx_image.py +28 -0
- scitex/dev/plt/plot_stx_imshow.py +28 -0
- scitex/dev/plt/plot_stx_joyplot.py +28 -0
- scitex/dev/plt/plot_stx_kde.py +28 -0
- scitex/dev/plt/plot_stx_line.py +28 -0
- scitex/dev/plt/plot_stx_mean_ci.py +28 -0
- scitex/dev/plt/plot_stx_mean_std.py +28 -0
- scitex/dev/plt/plot_stx_median_iqr.py +28 -0
- scitex/dev/plt/plot_stx_raster.py +28 -0
- scitex/dev/plt/plot_stx_rectangle.py +28 -0
- scitex/dev/plt/plot_stx_scatter.py +29 -0
- scitex/dev/plt/plot_stx_shaded_line.py +29 -0
- scitex/dev/plt/plot_stx_violin.py +28 -0
- scitex/dev/plt/plot_stx_violinplot.py +28 -0
- scitex/diagram/README.md +197 -0
- scitex/diagram/__init__.py +48 -0
- scitex/diagram/_compile.py +312 -0
- scitex/diagram/_diagram.py +355 -0
- scitex/diagram/_presets.py +173 -0
- scitex/diagram/_schema.py +182 -0
- scitex/diagram/_split.py +278 -0
- scitex/fig/__init__.py +352 -0
- scitex/{vis → fig}/backend/_parser.py +1 -1
- scitex/{vis → fig}/canvas.py +1 -1
- scitex/{vis → fig}/editor/__init__.py +5 -2
- scitex/{vis → fig}/editor/_dearpygui_editor.py +1 -1
- scitex/{vis → fig}/editor/_defaults.py +70 -5
- scitex/{vis → fig}/editor/_mpl_editor.py +1 -1
- scitex/{vis → fig}/editor/_qt_editor.py +182 -2
- scitex/{vis → fig}/editor/_tkinter_editor.py +1 -1
- scitex/fig/editor/edit/__init__.py +50 -0
- scitex/fig/editor/edit/backend_detector.py +109 -0
- scitex/fig/editor/edit/bundle_resolver.py +240 -0
- scitex/fig/editor/edit/editor_launcher.py +239 -0
- scitex/fig/editor/edit/manual_handler.py +53 -0
- scitex/fig/editor/edit/panel_loader.py +232 -0
- scitex/fig/editor/edit/path_resolver.py +67 -0
- scitex/fig/editor/flask_editor/_bbox.py +1299 -0
- scitex/fig/editor/flask_editor/_core.py +1429 -0
- scitex/{vis → fig}/editor/flask_editor/_plotter.py +38 -4
- scitex/fig/editor/flask_editor/_renderer.py +813 -0
- scitex/fig/editor/flask_editor/static/css/base/reset.css +41 -0
- scitex/fig/editor/flask_editor/static/css/base/typography.css +16 -0
- scitex/fig/editor/flask_editor/static/css/base/variables.css +85 -0
- scitex/fig/editor/flask_editor/static/css/components/buttons.css +217 -0
- scitex/fig/editor/flask_editor/static/css/components/context-menu.css +93 -0
- scitex/fig/editor/flask_editor/static/css/components/dropdown.css +57 -0
- scitex/fig/editor/flask_editor/static/css/components/forms.css +112 -0
- scitex/fig/editor/flask_editor/static/css/components/modal.css +59 -0
- scitex/fig/editor/flask_editor/static/css/components/sections.css +212 -0
- scitex/fig/editor/flask_editor/static/css/features/canvas.css +176 -0
- scitex/fig/editor/flask_editor/static/css/features/element-inspector.css +190 -0
- scitex/fig/editor/flask_editor/static/css/features/loading.css +59 -0
- scitex/fig/editor/flask_editor/static/css/features/overlay.css +45 -0
- scitex/fig/editor/flask_editor/static/css/features/panel-grid.css +95 -0
- scitex/fig/editor/flask_editor/static/css/features/selection.css +101 -0
- scitex/fig/editor/flask_editor/static/css/features/statistics.css +138 -0
- scitex/fig/editor/flask_editor/static/css/index.css +31 -0
- scitex/fig/editor/flask_editor/static/css/layout/container.css +7 -0
- scitex/fig/editor/flask_editor/static/css/layout/controls.css +56 -0
- scitex/fig/editor/flask_editor/static/css/layout/preview.css +78 -0
- scitex/fig/editor/flask_editor/static/js/alignment/axis.js +314 -0
- scitex/fig/editor/flask_editor/static/js/alignment/basic.js +107 -0
- scitex/fig/editor/flask_editor/static/js/alignment/distribute.js +54 -0
- scitex/fig/editor/flask_editor/static/js/canvas/canvas.js +172 -0
- scitex/fig/editor/flask_editor/static/js/canvas/dragging.js +258 -0
- scitex/fig/editor/flask_editor/static/js/canvas/resize.js +48 -0
- scitex/fig/editor/flask_editor/static/js/canvas/selection.js +71 -0
- scitex/fig/editor/flask_editor/static/js/core/api.js +288 -0
- scitex/fig/editor/flask_editor/static/js/core/state.js +143 -0
- scitex/fig/editor/flask_editor/static/js/core/utils.js +245 -0
- scitex/fig/editor/flask_editor/static/js/dev/element-inspector.js +992 -0
- scitex/fig/editor/flask_editor/static/js/editor/bbox.js +339 -0
- scitex/fig/editor/flask_editor/static/js/editor/element-drag.js +286 -0
- scitex/fig/editor/flask_editor/static/js/editor/overlay.js +371 -0
- scitex/fig/editor/flask_editor/static/js/editor/preview.js +293 -0
- scitex/fig/editor/flask_editor/static/js/main.js +426 -0
- scitex/fig/editor/flask_editor/static/js/shortcuts/context-menu.js +152 -0
- scitex/fig/editor/flask_editor/static/js/shortcuts/keyboard.js +265 -0
- scitex/fig/editor/flask_editor/static/js/ui/controls.js +184 -0
- scitex/fig/editor/flask_editor/static/js/ui/download.js +57 -0
- scitex/fig/editor/flask_editor/static/js/ui/help.js +100 -0
- scitex/fig/editor/flask_editor/static/js/ui/theme.js +34 -0
- scitex/fig/editor/flask_editor/templates/__init__.py +123 -0
- scitex/fig/editor/flask_editor/templates/_html.py +852 -0
- scitex/fig/editor/flask_editor/templates/_scripts.py +4933 -0
- scitex/fig/editor/flask_editor/templates/_styles.py +1658 -0
- scitex/{vis → fig}/io/__init__.py +13 -1
- scitex/fig/io/_bundle.py +1058 -0
- scitex/{vis → fig}/io/_canvas.py +1 -1
- scitex/{vis → fig}/io/_data.py +1 -1
- scitex/{vis → fig}/io/_export.py +1 -1
- scitex/{vis → fig}/io/_load.py +1 -1
- scitex/{vis → fig}/io/_panel.py +1 -1
- scitex/{vis → fig}/io/_save.py +1 -1
- scitex/{vis → fig}/model/__init__.py +1 -1
- scitex/{vis → fig}/model/_annotations.py +1 -1
- scitex/{vis → fig}/model/_axes.py +1 -1
- scitex/{vis → fig}/model/_figure.py +1 -1
- scitex/{vis → fig}/model/_guides.py +1 -1
- scitex/{vis → fig}/model/_plot.py +1 -1
- scitex/{vis → fig}/model/_styles.py +1 -1
- scitex/{vis → fig}/utils/__init__.py +1 -1
- scitex/io/__init__.py +22 -26
- scitex/io/_bundle.py +493 -0
- scitex/io/_flush.py +5 -2
- scitex/io/_load.py +98 -0
- scitex/io/_load_modules/_H5Explorer.py +5 -2
- scitex/io/_load_modules/_canvas.py +2 -2
- scitex/io/_load_modules/_image.py +3 -4
- scitex/io/_load_modules/_txt.py +4 -2
- scitex/io/_metadata.py +34 -324
- scitex/io/_metadata_modules/__init__.py +46 -0
- scitex/io/_metadata_modules/_embed.py +70 -0
- scitex/io/_metadata_modules/_read.py +64 -0
- scitex/io/_metadata_modules/_utils.py +79 -0
- scitex/io/_metadata_modules/embed_metadata_jpeg.py +74 -0
- scitex/io/_metadata_modules/embed_metadata_pdf.py +53 -0
- scitex/io/_metadata_modules/embed_metadata_png.py +26 -0
- scitex/io/_metadata_modules/embed_metadata_svg.py +62 -0
- scitex/io/_metadata_modules/read_metadata_jpeg.py +57 -0
- scitex/io/_metadata_modules/read_metadata_pdf.py +51 -0
- scitex/io/_metadata_modules/read_metadata_png.py +39 -0
- scitex/io/_metadata_modules/read_metadata_svg.py +44 -0
- scitex/io/_qr_utils.py +5 -3
- scitex/io/_save.py +548 -30
- scitex/io/_save_modules/_canvas.py +3 -3
- scitex/io/_save_modules/_image.py +5 -9
- scitex/io/_save_modules/_tex.py +7 -4
- scitex/io/_zip_bundle.py +439 -0
- scitex/io/utils/h5_to_zarr.py +11 -9
- scitex/msword/__init__.py +255 -0
- scitex/msword/profiles.py +357 -0
- scitex/msword/reader.py +753 -0
- scitex/msword/utils.py +289 -0
- scitex/msword/writer.py +362 -0
- scitex/plt/__init__.py +5 -2
- scitex/plt/_subplots/_AxesWrapper.py +6 -6
- scitex/plt/_subplots/_AxisWrapper.py +15 -9
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/__init__.py +36 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_labels.py +264 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_metadata.py +213 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_visual.py +128 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/__init__.py +59 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_base.py +34 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_scientific.py +593 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_statistical.py +654 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_stx_aliases.py +527 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_RawMatplotlibMixin.py +321 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/__init__.py +33 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_base.py +152 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +600 -0
- scitex/plt/_subplots/_AxisWrapperMixins/__init__.py +79 -5
- scitex/plt/_subplots/_FigWrapper.py +6 -6
- scitex/plt/_subplots/_SubplotsWrapper.py +28 -18
- scitex/plt/_subplots/_export_as_csv.py +35 -5
- scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +8 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_annotate.py +10 -21
- scitex/plt/_subplots/_export_as_csv_formatters/_format_eventplot.py +18 -7
- scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow2d.py +28 -12
- scitex/plt/_subplots/_export_as_csv_formatters/_format_matshow.py +10 -4
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_imshow.py +13 -1
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_kde.py +12 -2
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_scatter.py +10 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_quiver.py +10 -4
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_jointplot.py +18 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_lineplot.py +44 -36
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_pairplot.py +14 -2
- scitex/plt/_subplots/_export_as_csv_formatters/_format_streamplot.py +11 -5
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_bar.py +84 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_barh.py +85 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_conf_mat.py +14 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_contour.py +54 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_ecdf.py +14 -2
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_errorbar.py +120 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_heatmap.py +16 -6
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_image.py +29 -19
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_imshow.py +63 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_joyplot.py +22 -5
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_ci.py +18 -14
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_std.py +18 -14
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_median_iqr.py +18 -14
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_raster.py +10 -2
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter.py +51 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter_hist.py +18 -9
- scitex/plt/ax/_plot/_stx_ecdf.py +4 -2
- scitex/plt/gallery/_generate.py +421 -14
- scitex/plt/io/__init__.py +53 -0
- scitex/plt/io/_bundle.py +490 -0
- scitex/plt/io/_layered_bundle.py +1343 -0
- scitex/plt/styles/SCITEX_STYLE.yaml +26 -0
- scitex/plt/styles/__init__.py +14 -0
- scitex/plt/styles/presets.py +78 -0
- scitex/plt/utils/__init__.py +13 -1
- scitex/plt/utils/_collect_figure_metadata.py +10 -14
- scitex/plt/utils/_configure_mpl.py +6 -18
- scitex/plt/utils/_crop.py +32 -14
- scitex/plt/utils/_csv_column_naming.py +54 -0
- scitex/plt/utils/_figure_mm.py +116 -1
- scitex/plt/utils/_hitmap.py +1643 -0
- scitex/plt/utils/metadata/__init__.py +25 -0
- scitex/plt/utils/metadata/_core.py +9 -10
- scitex/plt/utils/metadata/_dimensions.py +6 -3
- scitex/plt/utils/metadata/_editable_export.py +405 -0
- scitex/plt/utils/metadata/_geometry_extraction.py +570 -0
- scitex/schema/__init__.py +109 -16
- scitex/schema/_canvas.py +1 -1
- scitex/schema/_plot.py +1015 -0
- scitex/schema/_stats.py +2 -2
- scitex/stats/__init__.py +117 -0
- scitex/stats/io/__init__.py +29 -0
- scitex/stats/io/_bundle.py +156 -0
- scitex/tex/__init__.py +4 -0
- scitex/tex/_export.py +890 -0
- {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/METADATA +11 -1
- {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/RECORD +294 -170
- scitex/io/memo.md +0 -2827
- scitex/plt/REQUESTS.md +0 -191
- scitex/plt/_subplots/TODO.md +0 -53
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin.py +0 -559
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin.py +0 -1609
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin.py +0 -447
- scitex/plt/templates/research-master/scitex/vis/gallery/area/fill_between.json +0 -110
- scitex/plt/templates/research-master/scitex/vis/gallery/area/fill_betweenx.json +0 -88
- scitex/plt/templates/research-master/scitex/vis/gallery/area/stx_fill_between.json +0 -103
- scitex/plt/templates/research-master/scitex/vis/gallery/area/stx_fillv.json +0 -106
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/bar.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/barh.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/boxplot.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_bar.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_barh.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_box.json +0 -83
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_boxplot.json +0 -93
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_violin.json +0 -91
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_violinplot.json +0 -91
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/violinplot.json +0 -91
- scitex/plt/templates/research-master/scitex/vis/gallery/contour/contour.json +0 -97
- scitex/plt/templates/research-master/scitex/vis/gallery/contour/contourf.json +0 -98
- scitex/plt/templates/research-master/scitex/vis/gallery/contour/stx_contour.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/hist.json +0 -101
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/hist2d.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_ecdf.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_joyplot.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_kde.json +0 -93
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/imshow.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/matshow.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_conf_mat.json +0 -83
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_heatmap.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_image.json +0 -121
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_imshow.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/line/plot.json +0 -110
- scitex/plt/templates/research-master/scitex/vis/gallery/line/step.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/line/stx_line.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/line/stx_shaded_line.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/scatter/hexbin.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/scatter/scatter.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/scatter/stem.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/scatter/stx_scatter.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/special/pie.json +0 -94
- scitex/plt/templates/research-master/scitex/vis/gallery/special/stx_raster.json +0 -109
- scitex/plt/templates/research-master/scitex/vis/gallery/special/stx_rectangle.json +0 -108
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/errorbar.json +0 -93
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_errorbar.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_mean_ci.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_mean_std.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_median_iqr.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/vector/quiver.json +0 -99
- scitex/plt/templates/research-master/scitex/vis/gallery/vector/streamplot.json +0 -100
- scitex/vis/__init__.py +0 -177
- scitex/vis/editor/_edit.py +0 -390
- scitex/vis/editor/flask_editor/_bbox.py +0 -529
- scitex/vis/editor/flask_editor/_core.py +0 -168
- scitex/vis/editor/flask_editor/_renderer.py +0 -393
- scitex/vis/editor/flask_editor/templates/__init__.py +0 -33
- scitex/vis/editor/flask_editor/templates/_html.py +0 -513
- scitex/vis/editor/flask_editor/templates/_scripts.py +0 -1261
- scitex/vis/editor/flask_editor/templates/_styles.py +0 -739
- /scitex/{vis → fig}/README.md +0 -0
- /scitex/{vis → fig}/backend/__init__.py +0 -0
- /scitex/{vis → fig}/backend/_export.py +0 -0
- /scitex/{vis → fig}/backend/_render.py +0 -0
- /scitex/{vis → fig}/docs/CANVAS_ARCHITECTURE.md +0 -0
- /scitex/{vis → fig}/editor/_flask_editor.py +0 -0
- /scitex/{vis → fig}/editor/flask_editor/__init__.py +0 -0
- /scitex/{vis → fig}/editor/flask_editor/_utils.py +0 -0
- /scitex/{vis → fig}/io/_directory.py +0 -0
- /scitex/{vis → fig}/model/_plot_types.py +0 -0
- /scitex/{vis → fig}/utils/_defaults.py +0 -0
- /scitex/{vis → fig}/utils/_validate.py +0 -0
- {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/WHEEL +0 -0
- {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/entry_points.txt +0 -0
- {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/licenses/LICENSE +0 -0
scitex/tex/_export.py
ADDED
|
@@ -0,0 +1,890 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Timestamp: 2025-12-11 16:00:00
|
|
4
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/tex/_export.py
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
Export SciTeX writer documents to LaTeX format.
|
|
8
|
+
|
|
9
|
+
This module converts the intermediate document format (from scitex.msword
|
|
10
|
+
or scitex.writer) into LaTeX source files.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import re
|
|
17
|
+
import shutil
|
|
18
|
+
import subprocess
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
22
|
+
|
|
23
|
+
# Journal-specific document class configurations
|
|
24
|
+
JOURNAL_PRESETS = {
|
|
25
|
+
"article": {
|
|
26
|
+
"document_class": "article",
|
|
27
|
+
"class_options": [],
|
|
28
|
+
"required_packages": [],
|
|
29
|
+
},
|
|
30
|
+
"ieee": {
|
|
31
|
+
"document_class": "IEEEtran",
|
|
32
|
+
"class_options": ["conference"],
|
|
33
|
+
"required_packages": ["cite", "amsmath", "algorithmic"],
|
|
34
|
+
},
|
|
35
|
+
"elsevier": {
|
|
36
|
+
"document_class": "elsarticle",
|
|
37
|
+
"class_options": ["preprint", "12pt"],
|
|
38
|
+
"required_packages": ["lineno", "hyperref"],
|
|
39
|
+
},
|
|
40
|
+
"springer": {
|
|
41
|
+
"document_class": "svjour3",
|
|
42
|
+
"class_options": ["smallextended"],
|
|
43
|
+
"required_packages": [],
|
|
44
|
+
},
|
|
45
|
+
"aps": {
|
|
46
|
+
"document_class": "revtex4-2",
|
|
47
|
+
"class_options": ["aps", "prl", "preprint"],
|
|
48
|
+
"required_packages": [],
|
|
49
|
+
},
|
|
50
|
+
"mdpi": {
|
|
51
|
+
"document_class": "article",
|
|
52
|
+
"class_options": [],
|
|
53
|
+
"required_packages": ["mdpi"],
|
|
54
|
+
},
|
|
55
|
+
"acm": {
|
|
56
|
+
"document_class": "acmart",
|
|
57
|
+
"class_options": ["sigconf"],
|
|
58
|
+
"required_packages": [],
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def export_tex(
|
|
64
|
+
writer_doc: Dict[str, Any],
|
|
65
|
+
output_path: str | Path,
|
|
66
|
+
document_class: str = "article",
|
|
67
|
+
packages: Optional[List[str]] = None,
|
|
68
|
+
preamble: Optional[str] = None,
|
|
69
|
+
image_dir: Optional[str | Path] = None,
|
|
70
|
+
export_images: bool = True,
|
|
71
|
+
journal_preset: Optional[str] = None,
|
|
72
|
+
class_options: Optional[List[str]] = None,
|
|
73
|
+
use_bibtex: bool = False,
|
|
74
|
+
) -> Path:
|
|
75
|
+
"""
|
|
76
|
+
Export a SciTeX writer document to LaTeX format.
|
|
77
|
+
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
writer_doc : dict
|
|
81
|
+
SciTeX writer document structure containing:
|
|
82
|
+
- blocks: List of document blocks (headings, paragraphs, captions, etc.)
|
|
83
|
+
- metadata: Document metadata (title, author, etc.)
|
|
84
|
+
- images: Image references with binary data
|
|
85
|
+
- references: Bibliography entries
|
|
86
|
+
output_path : str | Path
|
|
87
|
+
Output path for the .tex file.
|
|
88
|
+
document_class : str
|
|
89
|
+
LaTeX document class (article, report, book, etc.).
|
|
90
|
+
Overridden if journal_preset is specified.
|
|
91
|
+
packages : list[str] | None
|
|
92
|
+
Additional LaTeX packages to include.
|
|
93
|
+
preamble : str | None
|
|
94
|
+
Additional preamble content.
|
|
95
|
+
image_dir : str | Path | None
|
|
96
|
+
Directory to save extracted images. If None, uses
|
|
97
|
+
"{output_stem}_figures/" next to the output .tex file.
|
|
98
|
+
Set export_images=False to skip image export.
|
|
99
|
+
export_images : bool
|
|
100
|
+
Whether to export images to files. Default True.
|
|
101
|
+
journal_preset : str | None
|
|
102
|
+
Use a journal-specific preset: "ieee", "elsevier", "springer",
|
|
103
|
+
"aps", "mdpi", "acm". Sets document_class and required packages.
|
|
104
|
+
class_options : list[str] | None
|
|
105
|
+
Document class options (e.g., ["12pt", "twocolumn"]).
|
|
106
|
+
use_bibtex : bool
|
|
107
|
+
If True, generate \\bibliography{} instead of thebibliography.
|
|
108
|
+
Creates a .bib file alongside the .tex file.
|
|
109
|
+
|
|
110
|
+
Returns
|
|
111
|
+
-------
|
|
112
|
+
Path
|
|
113
|
+
The path to the written .tex file.
|
|
114
|
+
|
|
115
|
+
Examples
|
|
116
|
+
--------
|
|
117
|
+
>>> from scitex.msword import load_docx
|
|
118
|
+
>>> from scitex.tex import export_tex
|
|
119
|
+
>>> doc = load_docx("manuscript.docx")
|
|
120
|
+
>>> export_tex(doc, "manuscript.tex")
|
|
121
|
+
PosixPath('manuscript.tex')
|
|
122
|
+
|
|
123
|
+
>>> # Export for IEEE conference
|
|
124
|
+
>>> export_tex(doc, "manuscript.tex", journal_preset="ieee")
|
|
125
|
+
|
|
126
|
+
>>> # Export with custom image directory
|
|
127
|
+
>>> export_tex(doc, "manuscript.tex", image_dir="./figures")
|
|
128
|
+
"""
|
|
129
|
+
output_path = Path(output_path)
|
|
130
|
+
|
|
131
|
+
# Apply journal preset if specified
|
|
132
|
+
effective_class = document_class
|
|
133
|
+
effective_options = class_options or []
|
|
134
|
+
extra_packages = []
|
|
135
|
+
|
|
136
|
+
if journal_preset and journal_preset in JOURNAL_PRESETS:
|
|
137
|
+
preset = JOURNAL_PRESETS[journal_preset]
|
|
138
|
+
effective_class = preset["document_class"]
|
|
139
|
+
effective_options = preset["class_options"] + (class_options or [])
|
|
140
|
+
extra_packages = preset["required_packages"]
|
|
141
|
+
|
|
142
|
+
# Extract components from writer_doc
|
|
143
|
+
blocks = writer_doc.get("blocks", [])
|
|
144
|
+
metadata = writer_doc.get("metadata", {})
|
|
145
|
+
references = writer_doc.get("references", [])
|
|
146
|
+
images = writer_doc.get("images", [])
|
|
147
|
+
|
|
148
|
+
# Handle image export
|
|
149
|
+
image_map: Dict[str, str] = {} # hash -> relative path
|
|
150
|
+
if export_images and images:
|
|
151
|
+
if image_dir is None:
|
|
152
|
+
image_dir = output_path.parent / f"{output_path.stem}_figures"
|
|
153
|
+
else:
|
|
154
|
+
image_dir = Path(image_dir)
|
|
155
|
+
|
|
156
|
+
image_dir.mkdir(parents=True, exist_ok=True)
|
|
157
|
+
image_map = _write_images_to_dir(images, image_dir, output_path.parent)
|
|
158
|
+
|
|
159
|
+
# Combine packages
|
|
160
|
+
all_packages = extra_packages + (packages or [])
|
|
161
|
+
|
|
162
|
+
# Build LaTeX content
|
|
163
|
+
latex_content = _build_latex_document(
|
|
164
|
+
blocks=blocks,
|
|
165
|
+
metadata=metadata,
|
|
166
|
+
references=references,
|
|
167
|
+
document_class=effective_class,
|
|
168
|
+
class_options=effective_options,
|
|
169
|
+
packages=all_packages if all_packages else None,
|
|
170
|
+
preamble=preamble,
|
|
171
|
+
image_map=image_map,
|
|
172
|
+
use_bibtex=use_bibtex,
|
|
173
|
+
output_stem=output_path.stem,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Write to file
|
|
177
|
+
output_path.write_text(latex_content, encoding="utf-8")
|
|
178
|
+
|
|
179
|
+
# Generate .bib file if using bibtex
|
|
180
|
+
if use_bibtex and references:
|
|
181
|
+
bib_path = output_path.with_suffix(".bib")
|
|
182
|
+
bib_content = _generate_bibtex(references)
|
|
183
|
+
bib_path.write_text(bib_content, encoding="utf-8")
|
|
184
|
+
|
|
185
|
+
return output_path
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _generate_bibtex(references: List[Dict[str, Any]]) -> str:
|
|
189
|
+
"""Generate BibTeX content from references."""
|
|
190
|
+
entries = []
|
|
191
|
+
for ref in references:
|
|
192
|
+
num = ref.get("number", len(entries) + 1)
|
|
193
|
+
text = ref.get("text", ref.get("raw", ""))
|
|
194
|
+
|
|
195
|
+
# Basic entry - in practice, would parse author/title/year
|
|
196
|
+
entry = f"""@misc{{ref{num},
|
|
197
|
+
note = {{{text}}}
|
|
198
|
+
}}"""
|
|
199
|
+
entries.append(entry)
|
|
200
|
+
|
|
201
|
+
return "\n\n".join(entries)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _write_images_to_dir(
|
|
205
|
+
images: List[Dict[str, Any]],
|
|
206
|
+
image_dir: Path,
|
|
207
|
+
tex_parent: Path,
|
|
208
|
+
) -> Dict[str, str]:
|
|
209
|
+
"""
|
|
210
|
+
Write images to directory and return hash->relative_path mapping.
|
|
211
|
+
|
|
212
|
+
Parameters
|
|
213
|
+
----------
|
|
214
|
+
images : list
|
|
215
|
+
List of image dicts with 'hash', 'extension', 'data' keys.
|
|
216
|
+
image_dir : Path
|
|
217
|
+
Directory to write images to.
|
|
218
|
+
tex_parent : Path
|
|
219
|
+
Parent directory of the .tex file (for relative paths).
|
|
220
|
+
|
|
221
|
+
Returns
|
|
222
|
+
-------
|
|
223
|
+
dict
|
|
224
|
+
Mapping from image hash to relative path for LaTeX.
|
|
225
|
+
"""
|
|
226
|
+
image_map = {}
|
|
227
|
+
fig_counter = 0
|
|
228
|
+
|
|
229
|
+
for img in images:
|
|
230
|
+
img_hash = img.get("hash")
|
|
231
|
+
ext = img.get("extension", ".png")
|
|
232
|
+
data = img.get("data")
|
|
233
|
+
|
|
234
|
+
if data is None or img_hash is None:
|
|
235
|
+
continue
|
|
236
|
+
|
|
237
|
+
# Skip duplicates (same hash = same image content)
|
|
238
|
+
if img_hash in image_map:
|
|
239
|
+
continue
|
|
240
|
+
|
|
241
|
+
fig_counter += 1
|
|
242
|
+
filename = f"fig_{fig_counter}{ext}"
|
|
243
|
+
filepath = image_dir / filename
|
|
244
|
+
|
|
245
|
+
# Write image data
|
|
246
|
+
filepath.write_bytes(data)
|
|
247
|
+
|
|
248
|
+
# Store relative path from tex file location
|
|
249
|
+
try:
|
|
250
|
+
rel_path = filepath.relative_to(tex_parent)
|
|
251
|
+
except ValueError:
|
|
252
|
+
rel_path = filepath
|
|
253
|
+
|
|
254
|
+
image_map[img_hash] = str(rel_path)
|
|
255
|
+
|
|
256
|
+
return image_map
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _build_latex_document(
|
|
260
|
+
blocks: List[Dict[str, Any]],
|
|
261
|
+
metadata: Dict[str, Any],
|
|
262
|
+
references: List[Dict[str, Any]],
|
|
263
|
+
document_class: str,
|
|
264
|
+
class_options: Optional[List[str]] = None,
|
|
265
|
+
packages: Optional[List[str]] = None,
|
|
266
|
+
preamble: Optional[str] = None,
|
|
267
|
+
image_map: Optional[Dict[str, str]] = None,
|
|
268
|
+
use_bibtex: bool = False,
|
|
269
|
+
output_stem: str = "document",
|
|
270
|
+
) -> str:
|
|
271
|
+
"""Build complete LaTeX document content."""
|
|
272
|
+
if image_map is None:
|
|
273
|
+
image_map = {}
|
|
274
|
+
lines = []
|
|
275
|
+
|
|
276
|
+
# Document class with options
|
|
277
|
+
if class_options:
|
|
278
|
+
opts = ",".join(class_options)
|
|
279
|
+
lines.append(f"\\documentclass[{opts}]{{{document_class}}}")
|
|
280
|
+
else:
|
|
281
|
+
lines.append(f"\\documentclass{{{document_class}}}")
|
|
282
|
+
lines.append("")
|
|
283
|
+
|
|
284
|
+
# Default packages
|
|
285
|
+
default_packages = [
|
|
286
|
+
"inputenc",
|
|
287
|
+
"fontenc",
|
|
288
|
+
"amsmath",
|
|
289
|
+
"amssymb",
|
|
290
|
+
"graphicx",
|
|
291
|
+
"hyperref",
|
|
292
|
+
]
|
|
293
|
+
|
|
294
|
+
# Package options
|
|
295
|
+
package_options = {
|
|
296
|
+
"inputenc": "utf8",
|
|
297
|
+
"fontenc": "T1",
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
for pkg in default_packages:
|
|
301
|
+
opt = package_options.get(pkg)
|
|
302
|
+
if opt:
|
|
303
|
+
lines.append(f"\\usepackage[{opt}]{{{pkg}}}")
|
|
304
|
+
else:
|
|
305
|
+
lines.append(f"\\usepackage{{{pkg}}}")
|
|
306
|
+
|
|
307
|
+
# Additional packages
|
|
308
|
+
if packages:
|
|
309
|
+
for pkg in packages:
|
|
310
|
+
if pkg not in default_packages:
|
|
311
|
+
lines.append(f"\\usepackage{{{pkg}}}")
|
|
312
|
+
|
|
313
|
+
lines.append("")
|
|
314
|
+
|
|
315
|
+
# Metadata
|
|
316
|
+
if metadata.get("title"):
|
|
317
|
+
title = _escape_latex(metadata["title"])
|
|
318
|
+
lines.append(f"\\title{{{title}}}")
|
|
319
|
+
if metadata.get("author"):
|
|
320
|
+
author = _escape_latex(metadata["author"])
|
|
321
|
+
lines.append(f"\\author{{{author}}}")
|
|
322
|
+
|
|
323
|
+
lines.append("")
|
|
324
|
+
|
|
325
|
+
# Additional preamble
|
|
326
|
+
if preamble:
|
|
327
|
+
lines.append(preamble)
|
|
328
|
+
lines.append("")
|
|
329
|
+
|
|
330
|
+
# Begin document
|
|
331
|
+
lines.append("\\begin{document}")
|
|
332
|
+
lines.append("")
|
|
333
|
+
|
|
334
|
+
# Title
|
|
335
|
+
if metadata.get("title"):
|
|
336
|
+
lines.append("\\maketitle")
|
|
337
|
+
lines.append("")
|
|
338
|
+
|
|
339
|
+
# Track list state for proper itemize/enumerate environments
|
|
340
|
+
in_list = False
|
|
341
|
+
list_type = None
|
|
342
|
+
|
|
343
|
+
# Process blocks
|
|
344
|
+
for i, block in enumerate(blocks):
|
|
345
|
+
btype = block.get("type")
|
|
346
|
+
|
|
347
|
+
# Handle list transitions
|
|
348
|
+
if btype == "list-item":
|
|
349
|
+
item_list_type = block.get("list_type", "unordered")
|
|
350
|
+
if not in_list:
|
|
351
|
+
env = "enumerate" if item_list_type == "ordered" else "itemize"
|
|
352
|
+
lines.append(f"\\begin{{{env}}}")
|
|
353
|
+
in_list = True
|
|
354
|
+
list_type = item_list_type
|
|
355
|
+
elif in_list:
|
|
356
|
+
# Close list environment
|
|
357
|
+
env = "enumerate" if list_type == "ordered" else "itemize"
|
|
358
|
+
lines.append(f"\\end{{{env}}}")
|
|
359
|
+
lines.append("")
|
|
360
|
+
in_list = False
|
|
361
|
+
list_type = None
|
|
362
|
+
|
|
363
|
+
block_latex = _convert_block_to_latex(block, image_map)
|
|
364
|
+
if block_latex:
|
|
365
|
+
lines.append(block_latex)
|
|
366
|
+
|
|
367
|
+
# Close any open list
|
|
368
|
+
if in_list:
|
|
369
|
+
env = "enumerate" if list_type == "ordered" else "itemize"
|
|
370
|
+
lines.append(f"\\end{{{env}}}")
|
|
371
|
+
lines.append("")
|
|
372
|
+
|
|
373
|
+
# References section
|
|
374
|
+
if references:
|
|
375
|
+
lines.append("")
|
|
376
|
+
if use_bibtex:
|
|
377
|
+
lines.append(f"\\bibliographystyle{{plain}}")
|
|
378
|
+
lines.append(f"\\bibliography{{{output_stem}}}")
|
|
379
|
+
else:
|
|
380
|
+
lines.append("\\begin{thebibliography}{99}")
|
|
381
|
+
for ref in references:
|
|
382
|
+
ref_latex = _convert_reference_to_latex(ref)
|
|
383
|
+
if ref_latex:
|
|
384
|
+
lines.append(ref_latex)
|
|
385
|
+
lines.append("\\end{thebibliography}")
|
|
386
|
+
|
|
387
|
+
# End document
|
|
388
|
+
lines.append("")
|
|
389
|
+
lines.append("\\end{document}")
|
|
390
|
+
|
|
391
|
+
return "\n".join(lines)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def _convert_block_to_latex(
|
|
395
|
+
block: Dict[str, Any],
|
|
396
|
+
image_map: Optional[Dict[str, str]] = None,
|
|
397
|
+
) -> Optional[str]:
|
|
398
|
+
"""Convert a single block to LaTeX."""
|
|
399
|
+
if image_map is None:
|
|
400
|
+
image_map = {}
|
|
401
|
+
|
|
402
|
+
btype = block.get("type", "paragraph")
|
|
403
|
+
text = block.get("text", "")
|
|
404
|
+
|
|
405
|
+
if not text and btype not in ("table", "image", "caption", "equation"):
|
|
406
|
+
return None
|
|
407
|
+
|
|
408
|
+
if btype == "heading":
|
|
409
|
+
return _convert_heading(block)
|
|
410
|
+
elif btype == "paragraph":
|
|
411
|
+
return _convert_paragraph(block)
|
|
412
|
+
elif btype == "caption":
|
|
413
|
+
return _convert_caption(block, image_map)
|
|
414
|
+
elif btype == "table":
|
|
415
|
+
return _convert_table(block)
|
|
416
|
+
elif btype == "image":
|
|
417
|
+
return _convert_image(block, image_map)
|
|
418
|
+
elif btype == "list-item":
|
|
419
|
+
return _convert_list_item(block)
|
|
420
|
+
elif btype == "equation":
|
|
421
|
+
return _convert_equation(block)
|
|
422
|
+
elif btype == "reference-paragraph":
|
|
423
|
+
# Skip - handled separately in references section
|
|
424
|
+
return None
|
|
425
|
+
else:
|
|
426
|
+
# Default: treat as paragraph
|
|
427
|
+
return _escape_latex(text) + "\n"
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def _convert_equation(block: Dict[str, Any]) -> str:
|
|
431
|
+
"""Convert an equation block to LaTeX."""
|
|
432
|
+
latex = block.get("latex", "")
|
|
433
|
+
text = block.get("text", "")
|
|
434
|
+
|
|
435
|
+
if latex:
|
|
436
|
+
# Use the converted LaTeX from OMML
|
|
437
|
+
return f"\\begin{{equation}}\n{latex}\n\\end{{equation}}\n"
|
|
438
|
+
elif text:
|
|
439
|
+
# Fallback: wrap text in equation environment
|
|
440
|
+
return f"\\begin{{equation}}\n{_escape_latex(text)}\n\\end{{equation}}\n"
|
|
441
|
+
return ""
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def _convert_heading(block: Dict[str, Any]) -> str:
|
|
445
|
+
"""Convert a heading block to LaTeX."""
|
|
446
|
+
level = block.get("level", 1)
|
|
447
|
+
text = _escape_latex(block.get("text", ""))
|
|
448
|
+
|
|
449
|
+
# Map heading levels to LaTeX commands
|
|
450
|
+
level_commands = {
|
|
451
|
+
1: "section",
|
|
452
|
+
2: "subsection",
|
|
453
|
+
3: "subsubsection",
|
|
454
|
+
4: "paragraph",
|
|
455
|
+
5: "subparagraph",
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
command = level_commands.get(level, "paragraph")
|
|
459
|
+
return f"\\{command}{{{text}}}\n"
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def _convert_paragraph(block: Dict[str, Any]) -> str:
|
|
463
|
+
"""Convert a paragraph block to LaTeX."""
|
|
464
|
+
runs = block.get("runs", [])
|
|
465
|
+
|
|
466
|
+
if runs:
|
|
467
|
+
# Build paragraph from formatted runs
|
|
468
|
+
parts = []
|
|
469
|
+
for run in runs:
|
|
470
|
+
run_text = _escape_latex(run.get("text", ""))
|
|
471
|
+
if run.get("bold"):
|
|
472
|
+
run_text = f"\\textbf{{{run_text}}}"
|
|
473
|
+
if run.get("italic"):
|
|
474
|
+
run_text = f"\\textit{{{run_text}}}"
|
|
475
|
+
if run.get("underline"):
|
|
476
|
+
run_text = f"\\underline{{{run_text}}}"
|
|
477
|
+
parts.append(run_text)
|
|
478
|
+
return "".join(parts) + "\n"
|
|
479
|
+
else:
|
|
480
|
+
return _escape_latex(block.get("text", "")) + "\n"
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def _convert_caption(
|
|
484
|
+
block: Dict[str, Any],
|
|
485
|
+
image_map: Optional[Dict[str, str]] = None,
|
|
486
|
+
) -> str:
|
|
487
|
+
"""Convert a caption block to LaTeX figure/table environment."""
|
|
488
|
+
if image_map is None:
|
|
489
|
+
image_map = {}
|
|
490
|
+
|
|
491
|
+
caption_type = block.get("caption_type", "")
|
|
492
|
+
number = block.get("number", "")
|
|
493
|
+
caption_text = _escape_latex(block.get("caption_text", block.get("text", "")))
|
|
494
|
+
image_hash = block.get("image_hash")
|
|
495
|
+
|
|
496
|
+
if caption_type == "figure":
|
|
497
|
+
# Check if we have an associated image
|
|
498
|
+
image_path = None
|
|
499
|
+
if image_hash and image_hash in image_map:
|
|
500
|
+
image_path = image_map[image_hash]
|
|
501
|
+
|
|
502
|
+
lines = [
|
|
503
|
+
"\\begin{figure}[htbp]",
|
|
504
|
+
"\\centering",
|
|
505
|
+
]
|
|
506
|
+
|
|
507
|
+
if image_path:
|
|
508
|
+
# Remove extension for includegraphics
|
|
509
|
+
image_path_no_ext = image_path.rsplit(".", 1)[0] if "." in image_path else image_path
|
|
510
|
+
lines.append(f"\\includegraphics[width=0.8\\textwidth]{{{image_path_no_ext}}}")
|
|
511
|
+
else:
|
|
512
|
+
lines.append(f"% Image placeholder for Figure {number}")
|
|
513
|
+
|
|
514
|
+
lines.extend([
|
|
515
|
+
f"\\caption{{{caption_text}}}",
|
|
516
|
+
f"\\label{{fig:{number}}}",
|
|
517
|
+
"\\end{figure}",
|
|
518
|
+
"",
|
|
519
|
+
])
|
|
520
|
+
return "\n".join(lines)
|
|
521
|
+
|
|
522
|
+
elif caption_type == "table":
|
|
523
|
+
# Table captions - typically above the table
|
|
524
|
+
return f"% Table {number}: {caption_text}\n"
|
|
525
|
+
|
|
526
|
+
else:
|
|
527
|
+
return f"% Caption: {caption_text}\n"
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def _convert_image(
|
|
531
|
+
block: Dict[str, Any],
|
|
532
|
+
image_map: Optional[Dict[str, str]] = None,
|
|
533
|
+
) -> str:
|
|
534
|
+
"""Convert an image block to LaTeX includegraphics."""
|
|
535
|
+
if image_map is None:
|
|
536
|
+
image_map = {}
|
|
537
|
+
|
|
538
|
+
image_hash = block.get("image_hash") or block.get("hash")
|
|
539
|
+
width = block.get("width", "0.8\\textwidth")
|
|
540
|
+
|
|
541
|
+
if image_hash and image_hash in image_map:
|
|
542
|
+
image_path = image_map[image_hash]
|
|
543
|
+
# Remove extension for includegraphics
|
|
544
|
+
image_path_no_ext = image_path.rsplit(".", 1)[0] if "." in image_path else image_path
|
|
545
|
+
|
|
546
|
+
lines = [
|
|
547
|
+
"\\begin{figure}[htbp]",
|
|
548
|
+
"\\centering",
|
|
549
|
+
f"\\includegraphics[width={width}]{{{image_path_no_ext}}}",
|
|
550
|
+
"\\end{figure}",
|
|
551
|
+
"",
|
|
552
|
+
]
|
|
553
|
+
return "\n".join(lines)
|
|
554
|
+
|
|
555
|
+
return "% Image placeholder\n"
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def _convert_table(block: Dict[str, Any]) -> str:
|
|
559
|
+
"""Convert a table block to LaTeX."""
|
|
560
|
+
rows = block.get("rows", [])
|
|
561
|
+
if not rows:
|
|
562
|
+
return ""
|
|
563
|
+
|
|
564
|
+
num_cols = len(rows[0]) if rows else 0
|
|
565
|
+
col_spec = "|" + "c|" * num_cols
|
|
566
|
+
|
|
567
|
+
lines = [
|
|
568
|
+
"\\begin{table}[htbp]",
|
|
569
|
+
"\\centering",
|
|
570
|
+
f"\\begin{{tabular}}{{{col_spec}}}",
|
|
571
|
+
"\\hline",
|
|
572
|
+
]
|
|
573
|
+
|
|
574
|
+
for i, row in enumerate(rows):
|
|
575
|
+
escaped_cells = [_escape_latex(str(cell)) for cell in row]
|
|
576
|
+
lines.append(" & ".join(escaped_cells) + " \\\\")
|
|
577
|
+
lines.append("\\hline")
|
|
578
|
+
|
|
579
|
+
lines.extend([
|
|
580
|
+
"\\end{tabular}",
|
|
581
|
+
"\\end{table}",
|
|
582
|
+
"",
|
|
583
|
+
])
|
|
584
|
+
|
|
585
|
+
return "\n".join(lines)
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def _convert_list_item(block: Dict[str, Any]) -> str:
|
|
589
|
+
"""Convert a list item to LaTeX."""
|
|
590
|
+
text = _escape_latex(block.get("text", ""))
|
|
591
|
+
return f"\\item {text}\n"
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def _convert_reference_to_latex(ref: Dict[str, Any]) -> str:
|
|
595
|
+
"""Convert a reference entry to LaTeX bibitem."""
|
|
596
|
+
number = ref.get("number")
|
|
597
|
+
text = _escape_latex(ref.get("text", ref.get("raw", "")))
|
|
598
|
+
|
|
599
|
+
if number:
|
|
600
|
+
return f"\\bibitem{{ref{number}}} {text}"
|
|
601
|
+
else:
|
|
602
|
+
return f"\\bibitem{{}} {text}"
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
def _escape_latex(text: str) -> str:
|
|
606
|
+
"""Escape special LaTeX characters."""
|
|
607
|
+
if not text:
|
|
608
|
+
return ""
|
|
609
|
+
|
|
610
|
+
# Characters that need escaping in LaTeX
|
|
611
|
+
replacements = [
|
|
612
|
+
("\\", "\\textbackslash{}"),
|
|
613
|
+
("&", "\\&"),
|
|
614
|
+
("%", "\\%"),
|
|
615
|
+
("$", "\\$"),
|
|
616
|
+
("#", "\\#"),
|
|
617
|
+
("_", "\\_"),
|
|
618
|
+
("{", "\\{"),
|
|
619
|
+
("}", "\\}"),
|
|
620
|
+
("~", "\\textasciitilde{}"),
|
|
621
|
+
("^", "\\textasciicircum{}"),
|
|
622
|
+
]
|
|
623
|
+
|
|
624
|
+
# Apply replacements (order matters - backslash first)
|
|
625
|
+
result = text
|
|
626
|
+
for old, new in replacements:
|
|
627
|
+
# Skip if already escaped
|
|
628
|
+
if old == "\\":
|
|
629
|
+
# Don't escape existing LaTeX commands
|
|
630
|
+
result = re.sub(r'(?<!\\)\\(?![a-zA-Z{])', new, result)
|
|
631
|
+
else:
|
|
632
|
+
result = result.replace(old, new)
|
|
633
|
+
|
|
634
|
+
return result
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
@dataclass
|
|
638
|
+
class CompileResult:
|
|
639
|
+
"""Result of LaTeX compilation.
|
|
640
|
+
|
|
641
|
+
Attributes
|
|
642
|
+
----------
|
|
643
|
+
success : bool
|
|
644
|
+
Whether compilation succeeded.
|
|
645
|
+
pdf_path : Path | None
|
|
646
|
+
Path to generated PDF, or None if failed.
|
|
647
|
+
exit_code : int
|
|
648
|
+
Process exit code.
|
|
649
|
+
stdout : str
|
|
650
|
+
Standard output from compiler.
|
|
651
|
+
stderr : str
|
|
652
|
+
Standard error from compiler.
|
|
653
|
+
log_content : str
|
|
654
|
+
Content of .log file if available.
|
|
655
|
+
errors : list[str]
|
|
656
|
+
Extracted error messages.
|
|
657
|
+
warnings : list[str]
|
|
658
|
+
Extracted warning messages.
|
|
659
|
+
"""
|
|
660
|
+
success: bool
|
|
661
|
+
pdf_path: Optional[Path]
|
|
662
|
+
exit_code: int
|
|
663
|
+
stdout: str
|
|
664
|
+
stderr: str
|
|
665
|
+
log_content: str = ""
|
|
666
|
+
errors: List[str] = None
|
|
667
|
+
warnings: List[str] = None
|
|
668
|
+
|
|
669
|
+
def __post_init__(self):
|
|
670
|
+
if self.errors is None:
|
|
671
|
+
self.errors = []
|
|
672
|
+
if self.warnings is None:
|
|
673
|
+
self.warnings = []
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
def compile_tex(
|
|
677
|
+
tex_path: str | Path,
|
|
678
|
+
output_dir: Optional[str | Path] = None,
|
|
679
|
+
compiler: str = "pdflatex",
|
|
680
|
+
runs: int = 2,
|
|
681
|
+
clean: bool = True,
|
|
682
|
+
timeout: int = 120,
|
|
683
|
+
) -> CompileResult:
|
|
684
|
+
"""
|
|
685
|
+
Compile a LaTeX file to PDF.
|
|
686
|
+
|
|
687
|
+
Parameters
|
|
688
|
+
----------
|
|
689
|
+
tex_path : str | Path
|
|
690
|
+
Path to the .tex file.
|
|
691
|
+
output_dir : str | Path | None
|
|
692
|
+
Output directory for PDF. If None, uses same directory as tex file.
|
|
693
|
+
compiler : str
|
|
694
|
+
LaTeX compiler to use: "pdflatex", "xelatex", "lualatex", or "latexmk".
|
|
695
|
+
Default is "pdflatex".
|
|
696
|
+
runs : int
|
|
697
|
+
Number of compilation passes (for references/ToC). Default is 2.
|
|
698
|
+
Ignored if compiler is "latexmk".
|
|
699
|
+
clean : bool
|
|
700
|
+
Remove auxiliary files (.aux, .log, .out, etc.) after compilation.
|
|
701
|
+
Default is True.
|
|
702
|
+
timeout : int
|
|
703
|
+
Timeout in seconds for each compilation pass. Default is 120.
|
|
704
|
+
|
|
705
|
+
Returns
|
|
706
|
+
-------
|
|
707
|
+
CompileResult
|
|
708
|
+
Compilation result with success status, PDF path, and logs.
|
|
709
|
+
|
|
710
|
+
Examples
|
|
711
|
+
--------
|
|
712
|
+
>>> from scitex.tex import compile_tex
|
|
713
|
+
>>> result = compile_tex("manuscript.tex")
|
|
714
|
+
>>> if result.success:
|
|
715
|
+
... print(f"PDF created: {result.pdf_path}")
|
|
716
|
+
... else:
|
|
717
|
+
... print(f"Errors: {result.errors}")
|
|
718
|
+
|
|
719
|
+
>>> # Use latexmk for automatic multi-pass compilation
|
|
720
|
+
>>> result = compile_tex("manuscript.tex", compiler="latexmk")
|
|
721
|
+
|
|
722
|
+
Notes
|
|
723
|
+
-----
|
|
724
|
+
Requires LaTeX to be installed on the system (texlive, miktex, etc.).
|
|
725
|
+
"""
|
|
726
|
+
tex_path = Path(tex_path).absolute()
|
|
727
|
+
|
|
728
|
+
if not tex_path.exists():
|
|
729
|
+
return CompileResult(
|
|
730
|
+
success=False,
|
|
731
|
+
pdf_path=None,
|
|
732
|
+
exit_code=1,
|
|
733
|
+
stdout="",
|
|
734
|
+
stderr=f"File not found: {tex_path}",
|
|
735
|
+
errors=[f"File not found: {tex_path}"],
|
|
736
|
+
)
|
|
737
|
+
|
|
738
|
+
# Determine output directory
|
|
739
|
+
if output_dir is None:
|
|
740
|
+
output_dir = tex_path.parent
|
|
741
|
+
else:
|
|
742
|
+
output_dir = Path(output_dir).absolute()
|
|
743
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
744
|
+
|
|
745
|
+
# Check if compiler is available
|
|
746
|
+
compiler_cmd = shutil.which(compiler)
|
|
747
|
+
if compiler_cmd is None:
|
|
748
|
+
return CompileResult(
|
|
749
|
+
success=False,
|
|
750
|
+
pdf_path=None,
|
|
751
|
+
exit_code=127,
|
|
752
|
+
stdout="",
|
|
753
|
+
stderr=f"Compiler not found: {compiler}",
|
|
754
|
+
errors=[f"Compiler not found: {compiler}. Install texlive or miktex."],
|
|
755
|
+
)
|
|
756
|
+
|
|
757
|
+
# Build command
|
|
758
|
+
if compiler == "latexmk":
|
|
759
|
+
cmd = [
|
|
760
|
+
compiler,
|
|
761
|
+
"-pdf",
|
|
762
|
+
"-interaction=nonstopmode",
|
|
763
|
+
f"-output-directory={output_dir}",
|
|
764
|
+
str(tex_path),
|
|
765
|
+
]
|
|
766
|
+
runs = 1 # latexmk handles multi-pass
|
|
767
|
+
else:
|
|
768
|
+
cmd = [
|
|
769
|
+
compiler,
|
|
770
|
+
"-interaction=nonstopmode",
|
|
771
|
+
"-halt-on-error",
|
|
772
|
+
f"-output-directory={output_dir}",
|
|
773
|
+
str(tex_path),
|
|
774
|
+
]
|
|
775
|
+
|
|
776
|
+
# Run compilation
|
|
777
|
+
stdout_all = []
|
|
778
|
+
stderr_all = []
|
|
779
|
+
exit_code = 0
|
|
780
|
+
|
|
781
|
+
for run_num in range(runs):
|
|
782
|
+
try:
|
|
783
|
+
result = subprocess.run(
|
|
784
|
+
cmd,
|
|
785
|
+
cwd=tex_path.parent,
|
|
786
|
+
capture_output=True,
|
|
787
|
+
text=True,
|
|
788
|
+
timeout=timeout,
|
|
789
|
+
)
|
|
790
|
+
stdout_all.append(f"=== Pass {run_num + 1} ===\n{result.stdout}")
|
|
791
|
+
stderr_all.append(result.stderr)
|
|
792
|
+
exit_code = result.returncode
|
|
793
|
+
|
|
794
|
+
# If compilation failed, don't continue
|
|
795
|
+
if exit_code != 0:
|
|
796
|
+
break
|
|
797
|
+
|
|
798
|
+
except subprocess.TimeoutExpired:
|
|
799
|
+
return CompileResult(
|
|
800
|
+
success=False,
|
|
801
|
+
pdf_path=None,
|
|
802
|
+
exit_code=124,
|
|
803
|
+
stdout="\n".join(stdout_all),
|
|
804
|
+
stderr=f"Compilation timed out after {timeout} seconds",
|
|
805
|
+
errors=[f"Compilation timed out after {timeout} seconds"],
|
|
806
|
+
)
|
|
807
|
+
except Exception as e:
|
|
808
|
+
return CompileResult(
|
|
809
|
+
success=False,
|
|
810
|
+
pdf_path=None,
|
|
811
|
+
exit_code=1,
|
|
812
|
+
stdout="\n".join(stdout_all),
|
|
813
|
+
stderr=str(e),
|
|
814
|
+
errors=[str(e)],
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
# Check for output PDF
|
|
818
|
+
pdf_name = tex_path.stem + ".pdf"
|
|
819
|
+
pdf_path = output_dir / pdf_name
|
|
820
|
+
|
|
821
|
+
# Read log file for detailed errors/warnings
|
|
822
|
+
log_path = output_dir / (tex_path.stem + ".log")
|
|
823
|
+
log_content = ""
|
|
824
|
+
errors = []
|
|
825
|
+
warnings = []
|
|
826
|
+
|
|
827
|
+
if log_path.exists():
|
|
828
|
+
try:
|
|
829
|
+
log_content = log_path.read_text(encoding="utf-8", errors="replace")
|
|
830
|
+
errors, warnings = _parse_latex_log(log_content)
|
|
831
|
+
except Exception:
|
|
832
|
+
pass
|
|
833
|
+
|
|
834
|
+
# Clean auxiliary files
|
|
835
|
+
if clean:
|
|
836
|
+
aux_extensions = [".aux", ".log", ".out", ".toc", ".lof", ".lot",
|
|
837
|
+
".bbl", ".blg", ".fls", ".fdb_latexmk", ".synctex.gz"]
|
|
838
|
+
for ext in aux_extensions:
|
|
839
|
+
aux_file = output_dir / (tex_path.stem + ext)
|
|
840
|
+
if aux_file.exists():
|
|
841
|
+
try:
|
|
842
|
+
aux_file.unlink()
|
|
843
|
+
except Exception:
|
|
844
|
+
pass
|
|
845
|
+
|
|
846
|
+
success = exit_code == 0 and pdf_path.exists()
|
|
847
|
+
|
|
848
|
+
return CompileResult(
|
|
849
|
+
success=success,
|
|
850
|
+
pdf_path=pdf_path if pdf_path.exists() else None,
|
|
851
|
+
exit_code=exit_code,
|
|
852
|
+
stdout="\n".join(stdout_all),
|
|
853
|
+
stderr="\n".join(stderr_all),
|
|
854
|
+
log_content=log_content,
|
|
855
|
+
errors=errors,
|
|
856
|
+
warnings=warnings,
|
|
857
|
+
)
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
def _parse_latex_log(log_content: str) -> Tuple[List[str], List[str]]:
|
|
861
|
+
"""Parse LaTeX log file for errors and warnings."""
|
|
862
|
+
errors = []
|
|
863
|
+
warnings = []
|
|
864
|
+
|
|
865
|
+
lines = log_content.split("\n")
|
|
866
|
+
|
|
867
|
+
for i, line in enumerate(lines):
|
|
868
|
+
# Error patterns
|
|
869
|
+
if line.startswith("!"):
|
|
870
|
+
# Collect multi-line error message
|
|
871
|
+
error_lines = [line]
|
|
872
|
+
for j in range(i + 1, min(i + 5, len(lines))):
|
|
873
|
+
if lines[j].startswith("l.") or lines[j].strip() == "":
|
|
874
|
+
break
|
|
875
|
+
error_lines.append(lines[j])
|
|
876
|
+
errors.append(" ".join(error_lines))
|
|
877
|
+
|
|
878
|
+
elif "Error:" in line or "Fatal error" in line:
|
|
879
|
+
errors.append(line.strip())
|
|
880
|
+
|
|
881
|
+
# Warning patterns
|
|
882
|
+
elif "Warning:" in line:
|
|
883
|
+
warnings.append(line.strip())
|
|
884
|
+
elif "Underfull" in line or "Overfull" in line:
|
|
885
|
+
warnings.append(line.strip())
|
|
886
|
+
|
|
887
|
+
return errors, warnings
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
__all__ = ["export_tex", "compile_tex", "CompileResult"]
|