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/msword/utils.py
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Timestamp: 2025-12-11 16:45:00
|
|
4
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/msword/utils.py
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
Utility functions for processing MS Word documents.
|
|
8
|
+
|
|
9
|
+
These functions can be used as post_import_hooks or called directly
|
|
10
|
+
to process document structures.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Any, Dict, List
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def link_captions_to_images(doc: Dict[str, Any]) -> Dict[str, Any]:
|
|
19
|
+
"""
|
|
20
|
+
Link figure captions to images by matching order.
|
|
21
|
+
|
|
22
|
+
This function pairs figure captions with images based on their
|
|
23
|
+
sequential order in the document. Each figure caption is assigned
|
|
24
|
+
an `image_hash` that corresponds to the image at the same position.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
doc : dict
|
|
29
|
+
SciTeX writer document with 'blocks' and 'images' keys.
|
|
30
|
+
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
dict
|
|
34
|
+
The same document with image_hash added to figure captions.
|
|
35
|
+
|
|
36
|
+
Examples
|
|
37
|
+
--------
|
|
38
|
+
>>> from scitex.msword import load_docx
|
|
39
|
+
>>> from scitex.msword.utils import link_captions_to_images
|
|
40
|
+
>>> doc = load_docx("manuscript.docx")
|
|
41
|
+
>>> doc = link_captions_to_images(doc)
|
|
42
|
+
>>> # Now captions have image_hash for LaTeX export
|
|
43
|
+
"""
|
|
44
|
+
blocks = doc.get("blocks", [])
|
|
45
|
+
images = doc.get("images", [])
|
|
46
|
+
|
|
47
|
+
# Find all figure captions
|
|
48
|
+
figure_captions = [
|
|
49
|
+
b for b in blocks
|
|
50
|
+
if b.get("type") == "caption" and b.get("caption_type") == "figure"
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
# Link by order (figure 1 -> image 0, figure 2 -> image 1, etc.)
|
|
54
|
+
for caption in figure_captions:
|
|
55
|
+
fig_num = caption.get("number")
|
|
56
|
+
if fig_num is not None and isinstance(fig_num, int):
|
|
57
|
+
# Figure numbers are typically 1-indexed
|
|
58
|
+
img_idx = fig_num - 1
|
|
59
|
+
if 0 <= img_idx < len(images):
|
|
60
|
+
caption["image_hash"] = images[img_idx].get("hash")
|
|
61
|
+
|
|
62
|
+
return doc
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def link_captions_to_images_by_proximity(doc: Dict[str, Any]) -> Dict[str, Any]:
|
|
66
|
+
"""
|
|
67
|
+
Link figure captions to images by document proximity.
|
|
68
|
+
|
|
69
|
+
This function uses the image blocks (type="image") that are inserted
|
|
70
|
+
at their actual positions in the document body. It finds the nearest
|
|
71
|
+
unlinked image block to each figure caption.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
doc : dict
|
|
76
|
+
SciTeX writer document.
|
|
77
|
+
|
|
78
|
+
Returns
|
|
79
|
+
-------
|
|
80
|
+
dict
|
|
81
|
+
Document with image_hash added to captions.
|
|
82
|
+
"""
|
|
83
|
+
blocks = doc.get("blocks", [])
|
|
84
|
+
|
|
85
|
+
# Collect image blocks and figure captions with their indices
|
|
86
|
+
image_blocks = []
|
|
87
|
+
figure_captions = []
|
|
88
|
+
|
|
89
|
+
for i, block in enumerate(blocks):
|
|
90
|
+
if block.get("type") == "image":
|
|
91
|
+
image_blocks.append((i, block))
|
|
92
|
+
elif block.get("type") == "caption" and block.get("caption_type") == "figure":
|
|
93
|
+
figure_captions.append((i, block))
|
|
94
|
+
|
|
95
|
+
if not image_blocks:
|
|
96
|
+
# Fallback to old behavior using doc["images"] list
|
|
97
|
+
images = doc.get("images", [])
|
|
98
|
+
if not images:
|
|
99
|
+
return doc
|
|
100
|
+
image_hashes = [img.get("hash") for img in images]
|
|
101
|
+
for idx, (_, caption) in enumerate(figure_captions):
|
|
102
|
+
if idx < len(image_hashes):
|
|
103
|
+
caption["image_hash"] = image_hashes[idx]
|
|
104
|
+
return doc
|
|
105
|
+
|
|
106
|
+
used_images = set()
|
|
107
|
+
|
|
108
|
+
# For each caption, find the nearest preceding image block
|
|
109
|
+
for cap_idx, caption in figure_captions:
|
|
110
|
+
best_img_idx = None
|
|
111
|
+
best_img_hash = None
|
|
112
|
+
best_distance = float("inf")
|
|
113
|
+
|
|
114
|
+
for img_idx, img_block in image_blocks:
|
|
115
|
+
img_hash = img_block.get("image_hash")
|
|
116
|
+
if img_hash in used_images:
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
# Prefer images that come before the caption (typical layout)
|
|
120
|
+
distance = cap_idx - img_idx
|
|
121
|
+
if distance >= 0 and distance < best_distance:
|
|
122
|
+
best_distance = distance
|
|
123
|
+
best_img_idx = img_idx
|
|
124
|
+
best_img_hash = img_hash
|
|
125
|
+
|
|
126
|
+
# If no preceding image, try following images
|
|
127
|
+
if best_img_hash is None:
|
|
128
|
+
for img_idx, img_block in image_blocks:
|
|
129
|
+
img_hash = img_block.get("image_hash")
|
|
130
|
+
if img_hash in used_images:
|
|
131
|
+
continue
|
|
132
|
+
|
|
133
|
+
distance = abs(cap_idx - img_idx)
|
|
134
|
+
if distance < best_distance:
|
|
135
|
+
best_distance = distance
|
|
136
|
+
best_img_idx = img_idx
|
|
137
|
+
best_img_hash = img_hash
|
|
138
|
+
|
|
139
|
+
if best_img_hash:
|
|
140
|
+
caption["image_hash"] = best_img_hash
|
|
141
|
+
used_images.add(best_img_hash)
|
|
142
|
+
|
|
143
|
+
return doc
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def normalize_section_headings(doc: Dict[str, Any]) -> Dict[str, Any]:
|
|
147
|
+
"""
|
|
148
|
+
Normalize section headings for consistency.
|
|
149
|
+
|
|
150
|
+
Converts common section titles to standard academic format:
|
|
151
|
+
- "intro" -> "Introduction"
|
|
152
|
+
- "method" -> "Methods"
|
|
153
|
+
- etc.
|
|
154
|
+
|
|
155
|
+
Parameters
|
|
156
|
+
----------
|
|
157
|
+
doc : dict
|
|
158
|
+
SciTeX writer document.
|
|
159
|
+
|
|
160
|
+
Returns
|
|
161
|
+
-------
|
|
162
|
+
dict
|
|
163
|
+
Document with normalized headings.
|
|
164
|
+
"""
|
|
165
|
+
blocks = doc.get("blocks", [])
|
|
166
|
+
|
|
167
|
+
# Common normalizations
|
|
168
|
+
normalizations = {
|
|
169
|
+
"intro": "Introduction",
|
|
170
|
+
"introduction": "Introduction",
|
|
171
|
+
"method": "Methods",
|
|
172
|
+
"methods": "Methods",
|
|
173
|
+
"materials and methods": "Materials and Methods",
|
|
174
|
+
"result": "Results",
|
|
175
|
+
"results": "Results",
|
|
176
|
+
"discussion": "Discussion",
|
|
177
|
+
"conclusion": "Conclusions",
|
|
178
|
+
"conclusions": "Conclusions",
|
|
179
|
+
"acknowledgement": "Acknowledgements",
|
|
180
|
+
"acknowledgements": "Acknowledgements",
|
|
181
|
+
"reference": "References",
|
|
182
|
+
"references": "References",
|
|
183
|
+
"bibliography": "References",
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
for block in blocks:
|
|
187
|
+
if block.get("type") == "heading" and block.get("level") == 1:
|
|
188
|
+
text = block.get("text", "").strip().lower()
|
|
189
|
+
if text in normalizations:
|
|
190
|
+
block["text"] = normalizations[text]
|
|
191
|
+
|
|
192
|
+
return doc
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def validate_document(doc: Dict[str, Any]) -> Dict[str, Any]:
|
|
196
|
+
"""
|
|
197
|
+
Validate document structure and add warnings.
|
|
198
|
+
|
|
199
|
+
Checks for common issues:
|
|
200
|
+
- Missing required sections
|
|
201
|
+
- Unmatched caption numbers
|
|
202
|
+
- Empty references section
|
|
203
|
+
- Duplicate figure numbers
|
|
204
|
+
|
|
205
|
+
Parameters
|
|
206
|
+
----------
|
|
207
|
+
doc : dict
|
|
208
|
+
SciTeX writer document.
|
|
209
|
+
|
|
210
|
+
Returns
|
|
211
|
+
-------
|
|
212
|
+
dict
|
|
213
|
+
Document with warnings added.
|
|
214
|
+
"""
|
|
215
|
+
blocks = doc.get("blocks", [])
|
|
216
|
+
warnings = doc.get("warnings", [])
|
|
217
|
+
|
|
218
|
+
# Check for required sections
|
|
219
|
+
headings = [b.get("text", "").lower() for b in blocks if b.get("type") == "heading"]
|
|
220
|
+
|
|
221
|
+
required_sections = ["introduction", "methods", "results", "discussion", "references"]
|
|
222
|
+
for section in required_sections:
|
|
223
|
+
if not any(section in h for h in headings):
|
|
224
|
+
warnings.append(f"Missing section: {section.title()}")
|
|
225
|
+
|
|
226
|
+
# Check for duplicate figure numbers
|
|
227
|
+
figure_numbers = [
|
|
228
|
+
b.get("number") for b in blocks
|
|
229
|
+
if b.get("type") == "caption" and b.get("caption_type") == "figure"
|
|
230
|
+
]
|
|
231
|
+
seen = set()
|
|
232
|
+
for num in figure_numbers:
|
|
233
|
+
if num in seen:
|
|
234
|
+
warnings.append(f"Duplicate figure number: {num}")
|
|
235
|
+
seen.add(num)
|
|
236
|
+
|
|
237
|
+
# Check for missing references
|
|
238
|
+
references = doc.get("references", [])
|
|
239
|
+
if not references:
|
|
240
|
+
ref_blocks = [b for b in blocks if b.get("type") == "reference-paragraph"]
|
|
241
|
+
if not ref_blocks:
|
|
242
|
+
warnings.append("No references found in document")
|
|
243
|
+
|
|
244
|
+
doc["warnings"] = warnings
|
|
245
|
+
return doc
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def create_post_import_hook(*functions):
|
|
249
|
+
"""
|
|
250
|
+
Create a composite post_import_hook from multiple functions.
|
|
251
|
+
|
|
252
|
+
Parameters
|
|
253
|
+
----------
|
|
254
|
+
*functions : callable
|
|
255
|
+
Functions to apply in sequence.
|
|
256
|
+
|
|
257
|
+
Returns
|
|
258
|
+
-------
|
|
259
|
+
callable
|
|
260
|
+
A single hook that applies all functions.
|
|
261
|
+
|
|
262
|
+
Examples
|
|
263
|
+
--------
|
|
264
|
+
>>> from scitex.msword.utils import (
|
|
265
|
+
... link_captions_to_images,
|
|
266
|
+
... normalize_section_headings,
|
|
267
|
+
... create_post_import_hook,
|
|
268
|
+
... )
|
|
269
|
+
>>> hook = create_post_import_hook(
|
|
270
|
+
... link_captions_to_images,
|
|
271
|
+
... normalize_section_headings,
|
|
272
|
+
... )
|
|
273
|
+
>>> # Use with custom profile
|
|
274
|
+
>>> profile.post_import_hooks = [hook]
|
|
275
|
+
"""
|
|
276
|
+
def composite_hook(doc: Dict[str, Any]) -> Dict[str, Any]:
|
|
277
|
+
for func in functions:
|
|
278
|
+
doc = func(doc)
|
|
279
|
+
return doc
|
|
280
|
+
return composite_hook
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
__all__ = [
|
|
284
|
+
"link_captions_to_images",
|
|
285
|
+
"link_captions_to_images_by_proximity",
|
|
286
|
+
"normalize_section_headings",
|
|
287
|
+
"validate_document",
|
|
288
|
+
"create_post_import_hook",
|
|
289
|
+
]
|
scitex/msword/writer.py
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Timestamp: 2025-12-11 15:15:00
|
|
4
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/msword/writer.py
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
SciTeX writer document -> DOCX converter.
|
|
8
|
+
|
|
9
|
+
This module exports SciTeX documents to MS Word .docx files,
|
|
10
|
+
applying journal-specific styles and formatting.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any, Dict, List, Optional
|
|
17
|
+
|
|
18
|
+
from .profiles import BaseWordProfile
|
|
19
|
+
|
|
20
|
+
# Lazy import for python-docx
|
|
21
|
+
try:
|
|
22
|
+
import docx
|
|
23
|
+
from docx.document import Document as DocxDocument
|
|
24
|
+
from docx.shared import Inches, Pt, Cm
|
|
25
|
+
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
|
26
|
+
from docx.enum.style import WD_STYLE_TYPE
|
|
27
|
+
|
|
28
|
+
DOCX_AVAILABLE = True
|
|
29
|
+
_DOCX_IMPORT_ERROR = None
|
|
30
|
+
except ImportError as exc:
|
|
31
|
+
DOCX_AVAILABLE = False
|
|
32
|
+
_DOCX_IMPORT_ERROR = exc
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class WordWriter:
|
|
36
|
+
"""
|
|
37
|
+
Export a SciTeX writer document to a DOCX file.
|
|
38
|
+
|
|
39
|
+
This writer handles:
|
|
40
|
+
- Section headings with proper styles
|
|
41
|
+
- Paragraphs with formatting
|
|
42
|
+
- Figure and table captions
|
|
43
|
+
- References section
|
|
44
|
+
- Image embedding
|
|
45
|
+
- Journal-specific template application
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
profile: BaseWordProfile,
|
|
51
|
+
template_path: Optional[Path] = None,
|
|
52
|
+
):
|
|
53
|
+
"""
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
profile : BaseWordProfile
|
|
57
|
+
Mapping from writer structures to Word styles.
|
|
58
|
+
template_path : Path | None
|
|
59
|
+
Optional path to a Word template (.dotx/.docx) to use as base.
|
|
60
|
+
"""
|
|
61
|
+
if not DOCX_AVAILABLE:
|
|
62
|
+
raise ImportError(
|
|
63
|
+
"python-docx is required for scitex.msword.WordWriter. "
|
|
64
|
+
"Install it via `pip install python-docx`."
|
|
65
|
+
) from _DOCX_IMPORT_ERROR
|
|
66
|
+
self.profile = profile
|
|
67
|
+
self.template_path = template_path
|
|
68
|
+
|
|
69
|
+
def write(
|
|
70
|
+
self,
|
|
71
|
+
writer_doc: Dict[str, Any] | Any,
|
|
72
|
+
path: Path,
|
|
73
|
+
) -> None:
|
|
74
|
+
"""
|
|
75
|
+
Write a SciTeX writer document to a DOCX file.
|
|
76
|
+
|
|
77
|
+
Parameters
|
|
78
|
+
----------
|
|
79
|
+
writer_doc : dict | Any
|
|
80
|
+
Writer document or intermediate structure.
|
|
81
|
+
path : Path
|
|
82
|
+
Output path for the DOCX file.
|
|
83
|
+
"""
|
|
84
|
+
# Create document (from template if specified)
|
|
85
|
+
if self.template_path and Path(self.template_path).exists():
|
|
86
|
+
doc = docx.Document(str(self.template_path))
|
|
87
|
+
# Clear existing content but keep styles
|
|
88
|
+
self._clear_document_content(doc)
|
|
89
|
+
else:
|
|
90
|
+
doc = docx.Document()
|
|
91
|
+
|
|
92
|
+
# Run pre-export hooks
|
|
93
|
+
for hook in self.profile.pre_export_hooks:
|
|
94
|
+
writer_doc = hook(writer_doc)
|
|
95
|
+
|
|
96
|
+
# Extract blocks from writer_doc
|
|
97
|
+
if isinstance(writer_doc, dict) and "blocks" in writer_doc:
|
|
98
|
+
blocks = writer_doc["blocks"]
|
|
99
|
+
images = writer_doc.get("images", [])
|
|
100
|
+
else:
|
|
101
|
+
blocks = list(writer_doc)
|
|
102
|
+
images = []
|
|
103
|
+
|
|
104
|
+
# Build image lookup by hash
|
|
105
|
+
image_lookup = {img.get("hash"): img for img in images if "hash" in img}
|
|
106
|
+
|
|
107
|
+
# Process each block
|
|
108
|
+
for block in blocks:
|
|
109
|
+
self._add_block(doc, block, image_lookup)
|
|
110
|
+
|
|
111
|
+
# Apply double-anonymous processing if needed
|
|
112
|
+
if self.profile.double_anonymous:
|
|
113
|
+
self._apply_double_anonymous(doc, writer_doc)
|
|
114
|
+
|
|
115
|
+
# Save document
|
|
116
|
+
doc.save(str(path))
|
|
117
|
+
|
|
118
|
+
def _clear_document_content(self, doc: DocxDocument) -> None:
|
|
119
|
+
"""Clear document content while preserving styles."""
|
|
120
|
+
for element in doc.element.body[:]:
|
|
121
|
+
doc.element.body.remove(element)
|
|
122
|
+
|
|
123
|
+
def _add_block(
|
|
124
|
+
self,
|
|
125
|
+
doc: DocxDocument,
|
|
126
|
+
block: Dict[str, Any],
|
|
127
|
+
image_lookup: Dict[str, Any],
|
|
128
|
+
) -> None:
|
|
129
|
+
"""Add a single block to the document."""
|
|
130
|
+
btype = block.get("type", "paragraph")
|
|
131
|
+
text = block.get("text", "")
|
|
132
|
+
|
|
133
|
+
if not text and btype not in ("table", "image"):
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
if btype == "heading":
|
|
137
|
+
level = block.get("level", 1)
|
|
138
|
+
self._add_heading(doc, text, level)
|
|
139
|
+
|
|
140
|
+
elif btype == "caption":
|
|
141
|
+
self._add_caption(doc, block)
|
|
142
|
+
|
|
143
|
+
elif btype == "reference-paragraph":
|
|
144
|
+
self._add_reference(doc, block)
|
|
145
|
+
|
|
146
|
+
elif btype == "table":
|
|
147
|
+
self._add_table(doc, block)
|
|
148
|
+
|
|
149
|
+
elif btype == "image":
|
|
150
|
+
self._add_image(doc, block, image_lookup)
|
|
151
|
+
|
|
152
|
+
elif btype == "list-item":
|
|
153
|
+
self._add_list_item(doc, block)
|
|
154
|
+
|
|
155
|
+
else:
|
|
156
|
+
# Default: paragraph
|
|
157
|
+
self._add_paragraph(doc, text, block.get("runs"))
|
|
158
|
+
|
|
159
|
+
def _add_heading(
|
|
160
|
+
self,
|
|
161
|
+
doc: DocxDocument,
|
|
162
|
+
text: str,
|
|
163
|
+
level: int,
|
|
164
|
+
) -> None:
|
|
165
|
+
"""Add a heading paragraph at the given logical level."""
|
|
166
|
+
style_name = self.profile.heading_styles.get(level)
|
|
167
|
+
|
|
168
|
+
if style_name and self._style_exists(doc, style_name):
|
|
169
|
+
p = doc.add_paragraph(text)
|
|
170
|
+
p.style = style_name
|
|
171
|
+
else:
|
|
172
|
+
# Fallback to built-in heading
|
|
173
|
+
doc.add_heading(text, level=min(level, 9))
|
|
174
|
+
|
|
175
|
+
def _add_paragraph(
|
|
176
|
+
self,
|
|
177
|
+
doc: DocxDocument,
|
|
178
|
+
text: str,
|
|
179
|
+
runs: Optional[List[Dict[str, Any]]] = None,
|
|
180
|
+
) -> None:
|
|
181
|
+
"""Add a paragraph with optional formatted runs."""
|
|
182
|
+
p = doc.add_paragraph()
|
|
183
|
+
|
|
184
|
+
if runs:
|
|
185
|
+
# Add formatted runs
|
|
186
|
+
for run_data in runs:
|
|
187
|
+
run = p.add_run(run_data.get("text", ""))
|
|
188
|
+
if run_data.get("bold"):
|
|
189
|
+
run.bold = True
|
|
190
|
+
if run_data.get("italic"):
|
|
191
|
+
run.italic = True
|
|
192
|
+
if run_data.get("underline"):
|
|
193
|
+
run.underline = True
|
|
194
|
+
if run_data.get("font_size"):
|
|
195
|
+
run.font.size = Pt(run_data["font_size"])
|
|
196
|
+
if run_data.get("font_name"):
|
|
197
|
+
run.font.name = run_data["font_name"]
|
|
198
|
+
else:
|
|
199
|
+
p.add_run(text)
|
|
200
|
+
|
|
201
|
+
# Apply normal style
|
|
202
|
+
if self._style_exists(doc, self.profile.normal_style):
|
|
203
|
+
try:
|
|
204
|
+
p.style = self.profile.normal_style
|
|
205
|
+
except Exception:
|
|
206
|
+
pass
|
|
207
|
+
|
|
208
|
+
def _add_caption(
|
|
209
|
+
self,
|
|
210
|
+
doc: DocxDocument,
|
|
211
|
+
block: Dict[str, Any],
|
|
212
|
+
) -> None:
|
|
213
|
+
"""Add a figure or table caption."""
|
|
214
|
+
caption_type = block.get("caption_type", "")
|
|
215
|
+
number = block.get("number", "")
|
|
216
|
+
caption_text = block.get("caption_text", block.get("text", ""))
|
|
217
|
+
|
|
218
|
+
# Build caption text
|
|
219
|
+
if caption_type == "figure" and number:
|
|
220
|
+
full_text = f"Figure {number}. {caption_text}"
|
|
221
|
+
elif caption_type == "table" and number:
|
|
222
|
+
full_text = f"Table {number}. {caption_text}"
|
|
223
|
+
else:
|
|
224
|
+
full_text = block.get("text", caption_text)
|
|
225
|
+
|
|
226
|
+
p = doc.add_paragraph(full_text)
|
|
227
|
+
|
|
228
|
+
if self._style_exists(doc, self.profile.caption_style):
|
|
229
|
+
try:
|
|
230
|
+
p.style = self.profile.caption_style
|
|
231
|
+
except Exception:
|
|
232
|
+
pass
|
|
233
|
+
|
|
234
|
+
def _add_reference(
|
|
235
|
+
self,
|
|
236
|
+
doc: DocxDocument,
|
|
237
|
+
block: Dict[str, Any],
|
|
238
|
+
) -> None:
|
|
239
|
+
"""Add a reference entry."""
|
|
240
|
+
ref_number = block.get("ref_number")
|
|
241
|
+
ref_text = block.get("ref_text", block.get("text", ""))
|
|
242
|
+
|
|
243
|
+
if ref_number is not None:
|
|
244
|
+
full_text = f"[{ref_number}] {ref_text}"
|
|
245
|
+
else:
|
|
246
|
+
full_text = ref_text
|
|
247
|
+
|
|
248
|
+
p = doc.add_paragraph(full_text)
|
|
249
|
+
|
|
250
|
+
if self._style_exists(doc, self.profile.normal_style):
|
|
251
|
+
try:
|
|
252
|
+
p.style = self.profile.normal_style
|
|
253
|
+
except Exception:
|
|
254
|
+
pass
|
|
255
|
+
|
|
256
|
+
def _add_table(
|
|
257
|
+
self,
|
|
258
|
+
doc: DocxDocument,
|
|
259
|
+
block: Dict[str, Any],
|
|
260
|
+
) -> None:
|
|
261
|
+
"""Add a table."""
|
|
262
|
+
rows = block.get("rows", [])
|
|
263
|
+
if not rows:
|
|
264
|
+
return
|
|
265
|
+
|
|
266
|
+
num_rows = len(rows)
|
|
267
|
+
num_cols = len(rows[0]) if rows else 0
|
|
268
|
+
|
|
269
|
+
table = doc.add_table(rows=num_rows, cols=num_cols)
|
|
270
|
+
table.style = "Table Grid"
|
|
271
|
+
|
|
272
|
+
for i, row_data in enumerate(rows):
|
|
273
|
+
row = table.rows[i]
|
|
274
|
+
for j, cell_text in enumerate(row_data):
|
|
275
|
+
if j < len(row.cells):
|
|
276
|
+
row.cells[j].text = str(cell_text)
|
|
277
|
+
|
|
278
|
+
def _add_image(
|
|
279
|
+
self,
|
|
280
|
+
doc: DocxDocument,
|
|
281
|
+
block: Dict[str, Any],
|
|
282
|
+
image_lookup: Dict[str, Any],
|
|
283
|
+
) -> None:
|
|
284
|
+
"""Add an image."""
|
|
285
|
+
image_hash = block.get("image_hash")
|
|
286
|
+
image_data = block.get("data")
|
|
287
|
+
|
|
288
|
+
if image_hash and image_hash in image_lookup:
|
|
289
|
+
image_info = image_lookup[image_hash]
|
|
290
|
+
image_data = image_info.get("data")
|
|
291
|
+
|
|
292
|
+
if image_data:
|
|
293
|
+
from io import BytesIO
|
|
294
|
+
|
|
295
|
+
image_stream = BytesIO(image_data)
|
|
296
|
+
width = block.get("width_inches", 5.0)
|
|
297
|
+
doc.add_picture(image_stream, width=Inches(width))
|
|
298
|
+
|
|
299
|
+
def _add_list_item(
|
|
300
|
+
self,
|
|
301
|
+
doc: DocxDocument,
|
|
302
|
+
block: Dict[str, Any],
|
|
303
|
+
) -> None:
|
|
304
|
+
"""Add a list item (bullet or numbered)."""
|
|
305
|
+
text = block.get("text", "")
|
|
306
|
+
list_type = block.get("list_type", "bullet")
|
|
307
|
+
|
|
308
|
+
p = doc.add_paragraph(text)
|
|
309
|
+
|
|
310
|
+
style_key = "bullet" if list_type == "bullet" else "numbered"
|
|
311
|
+
style_name = self.profile.list_styles.get(style_key)
|
|
312
|
+
|
|
313
|
+
if style_name and self._style_exists(doc, style_name):
|
|
314
|
+
try:
|
|
315
|
+
p.style = style_name
|
|
316
|
+
except Exception:
|
|
317
|
+
pass
|
|
318
|
+
|
|
319
|
+
def _style_exists(self, doc: DocxDocument, style_name: str) -> bool:
|
|
320
|
+
"""Check if a style exists in the document."""
|
|
321
|
+
try:
|
|
322
|
+
_ = doc.styles[style_name]
|
|
323
|
+
return True
|
|
324
|
+
except KeyError:
|
|
325
|
+
return False
|
|
326
|
+
|
|
327
|
+
def _apply_double_anonymous(
|
|
328
|
+
self,
|
|
329
|
+
doc: DocxDocument,
|
|
330
|
+
writer_doc: Dict[str, Any],
|
|
331
|
+
) -> None:
|
|
332
|
+
"""
|
|
333
|
+
Apply double-anonymous formatting.
|
|
334
|
+
|
|
335
|
+
This removes or masks author-identifying information.
|
|
336
|
+
"""
|
|
337
|
+
# Get author info to mask
|
|
338
|
+
metadata = writer_doc.get("metadata", {})
|
|
339
|
+
author = metadata.get("author", "")
|
|
340
|
+
|
|
341
|
+
if not author:
|
|
342
|
+
return
|
|
343
|
+
|
|
344
|
+
# Search and replace author names with placeholder
|
|
345
|
+
# This is a simple implementation; more sophisticated
|
|
346
|
+
# masking may be needed for real use
|
|
347
|
+
for para in doc.paragraphs:
|
|
348
|
+
if author.lower() in para.text.lower():
|
|
349
|
+
for run in para.runs:
|
|
350
|
+
if author.lower() in run.text.lower():
|
|
351
|
+
# Mask author name
|
|
352
|
+
import re
|
|
353
|
+
|
|
354
|
+
run.text = re.sub(
|
|
355
|
+
re.escape(author),
|
|
356
|
+
"[Author]",
|
|
357
|
+
run.text,
|
|
358
|
+
flags=re.IGNORECASE,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
__all__ = ["WordWriter"]
|
scitex/plt/__init__.py
CHANGED
|
@@ -31,6 +31,10 @@ import matplotlib.font_manager as fm
|
|
|
31
31
|
import matplotlib as mpl
|
|
32
32
|
import matplotlib.pyplot as plt
|
|
33
33
|
|
|
34
|
+
from scitex import logging as _logging
|
|
35
|
+
|
|
36
|
+
_logger = _logging.getLogger(__name__)
|
|
37
|
+
|
|
34
38
|
# =============================================================================
|
|
35
39
|
# Auto-configure matplotlib with SciTeX style on import
|
|
36
40
|
# =============================================================================
|
|
@@ -316,7 +320,6 @@ def load(path, apply_manual=True):
|
|
|
316
320
|
"""
|
|
317
321
|
from pathlib import Path
|
|
318
322
|
import hashlib
|
|
319
|
-
import warnings
|
|
320
323
|
import scitex as stx
|
|
321
324
|
|
|
322
325
|
path = Path(path)
|
|
@@ -345,7 +348,7 @@ def load(path, apply_manual=True):
|
|
|
345
348
|
if "base_hash" in manual_data:
|
|
346
349
|
current_hash = _compute_file_hash(json_path)
|
|
347
350
|
if manual_data["base_hash"] != current_hash:
|
|
348
|
-
|
|
351
|
+
_logger.warning(
|
|
349
352
|
f"Manual overrides may be stale: base data changed since manual edits.\n"
|
|
350
353
|
f" Expected hash: {manual_data['base_hash'][:16]}...\n"
|
|
351
354
|
f" Current hash: {current_hash[:16]}...\n"
|
|
@@ -14,6 +14,10 @@ from functools import wraps
|
|
|
14
14
|
import numpy as np
|
|
15
15
|
import pandas as pd
|
|
16
16
|
|
|
17
|
+
from scitex import logging
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
17
21
|
|
|
18
22
|
class AxesWrapper:
|
|
19
23
|
def __init__(self, fig_scitex, axes_scitex):
|
|
@@ -124,14 +128,10 @@ class AxesWrapper:
|
|
|
124
128
|
Returns:
|
|
125
129
|
np.ndarray: Array of wrapped axes with the same shape
|
|
126
130
|
"""
|
|
127
|
-
import warnings
|
|
128
|
-
|
|
129
131
|
# Show a warning to help users avoid common mistakes
|
|
130
|
-
|
|
132
|
+
logger.warning(
|
|
131
133
|
"Converting AxesWrapper to numpy array. If you're trying to flatten "
|
|
132
|
-
"the axes, use 'list(axes.flatten())' instead of 'np.array(axes).flatten()'."
|
|
133
|
-
UserWarning,
|
|
134
|
-
stacklevel=2,
|
|
134
|
+
"the axes, use 'list(axes.flatten())' instead of 'np.array(axes).flatten()'."
|
|
135
135
|
)
|
|
136
136
|
|
|
137
137
|
# Convert the underlying axes to a compatible numpy array representation
|