gratinglab 0.3.4__tar.gz → 0.3.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gratinglab-0.3.4/src/gratinglab.egg-info → gratinglab-0.3.5}/PKG-INFO +1 -1
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/__init__.py +1 -1
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/mathtext.py +83 -2
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/qt/setup_tab.py +2 -3
- gratinglab-0.3.5/src/gratinglab/gui/qt/theory_viewer.py +210 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/richtext.py +50 -4
- gratinglab-0.3.5/src/gratinglab/io/__init__.py +17 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/io/ggp.py +64 -44
- gratinglab-0.3.5/src/gratinglab/metrology/_analyzer_summary.py +385 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/analyzer.py +17 -368
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/wiki/reading-outputs.md +13 -6
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/profiles/_shapes.py +1 -1
- gratinglab-0.3.5/src/gratinglab/solvers/_scalar_groove.py +556 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/solvers/integral/__init__.py +1 -1
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/solvers/scalar.py +17 -517
- {gratinglab-0.3.4 → gratinglab-0.3.5/src/gratinglab.egg-info}/PKG-INFO +1 -1
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab.egg-info/SOURCES.txt +2 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_ggp.py +37 -29
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_gui_mathtext.py +56 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_gui_richtext.py +25 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_scalar.py +3 -2
- gratinglab-0.3.4/src/gratinglab/gui/qt/theory_viewer.py +0 -99
- gratinglab-0.3.4/src/gratinglab/io/__init__.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/LICENSE +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/README.md +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/pyproject.toml +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/setup.cfg +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/checks.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/compare.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/convergence.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/geometry.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/__init__.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/app.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/cmethod_options.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/comparison.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/diagram.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/diagram3d.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/docs.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/efficiency_plot.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/icon.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/integral_options.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/launcher.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/orders.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/provenance.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/qt/__init__.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/qt/cmethod_tab.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/qt/compare_tab.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/qt/geometry_panel.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/qt/geometry_tab.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/qt/integral_tab.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/qt/launcher.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/qt/main_window.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/qt/profile_plot_panel.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/qt/rcwa_tab.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/qt/scalar_tab.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/qt/scene3d_panel.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/qt/sizing.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/qt/theming.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/qt/worker.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/rcwa_options.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/scalar_options.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/state.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/gui/theme.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/illumination.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/io/boundary.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/io/efficiency_table.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/io/txl.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/materials/__init__.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/materials/data/SOURCES.md +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/materials/fresnel.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/materials/optical.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/materials/refractiveindex.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/materials/typical.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/measured.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/__init__.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/boundary/__init__.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/boundary/average.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/boundary/cross_section.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/boundary/pipeline.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/boundary/swaths.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/cli.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/config.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/core/__init__.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/core/analysis.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/core/image_flatten.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/core/orientation.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/core/processing.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/core/region_mask.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/core/scars.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/core/tip.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/data/__init__.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/data/aggregation.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/__init__.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/icon.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/qt/__init__.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/qt/app.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/qt/boundary_view.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/qt/canvas.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/qt/efficiency_dialog.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/qt/import_view.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/qt/layout.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/qt/main_window.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/qt/swath_dialog.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/qt/wiki_view.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/qt/worker.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/state.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/io/__init__.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/io/file_io.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/io/ibw.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/io/metrics.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/io/sem.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/io/spm.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/settings.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/stats/__init__.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/stats/analysis.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/stats/icc.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/visualization/__init__.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/visualization/diagnostics.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/visualization/profiles.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/visualization/statistics.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/wiki/__init__.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/wiki/facet-fitting.md +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/wiki/file-formats.md +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/wiki/flattening.md +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/wiki/groove-orientation.md +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/wiki/icc-correction.md +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/wiki/overview.md +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/wiki/row-groups.md +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/wiki/scars-and-masking.md +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/wiki/swath-statistics.md +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/wiki/tip-correction.md +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/metrology/workflows.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/problem.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/profiles/__init__.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/profiles/_base.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/profiles/_polyline.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/profiles/_trapezoid.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/profiles/_types.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/resolution.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/result.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/solvers/__init__.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/solvers/_conditioning.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/solvers/_execution.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/solvers/base.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/solvers/cmethod/__init__.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/solvers/cmethod/_core.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/solvers/cmethod/_series.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/solvers/integral/_boundary.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/solvers/integral/_core.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/solvers/integral/_finite.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/solvers/integral/_greens.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/solvers/integral/_kernels.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/solvers/integral/_nystrom.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/solvers/rcwa/__init__.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/solvers/rcwa/_core.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab/solvers/rcwa/_stack.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab.egg-info/dependency_links.txt +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab.egg-info/entry_points.txt +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab.egg-info/requires.txt +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/src/gratinglab.egg-info/top_level.txt +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_changelog.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_checks.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_cmethod.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_compare.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_conditioning.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_convergence.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_cross_method.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_efficiency_table.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_four_way.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_fresnel.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_geometry.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_gui_cmethod_options.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_gui_cmethod_tab.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_gui_compare_tab.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_gui_diagram.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_gui_diagram3d.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_gui_docs.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_gui_integral_options.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_gui_launcher.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_gui_orders.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_gui_provenance.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_gui_qt.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_gui_rcwa_options.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_gui_scalar_options.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_gui_state.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_gui_theme.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_illumination.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_integral_core.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_integral_corpus.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_integral_finite.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_integral_graded.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_integral_greens.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_integral_kernels.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_integral_measured_polygon.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_integral_operators.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_integral_solver.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_io_boundary.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_littrow.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_materials.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_measured.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_perturbation.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_profiles.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_rayleigh_anomaly.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_rcwa.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_rcwa_benchmarks.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_refractiveindex.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_resolution.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_result.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_solver_conformance.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_solver_execution.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_solver_progress.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_trapezoidal_solvers.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_txl.py +0 -0
- {gratinglab-0.3.4 → gratinglab-0.3.5}/tests/test_typical.py +0 -0
|
@@ -22,7 +22,7 @@ from .measured import MeasuredEfficiency, MeasurementProvenance
|
|
|
22
22
|
from .resolution import ResolvingPower, resolving_power
|
|
23
23
|
from .result import EfficiencyScan, OrderEfficiency, Provenance
|
|
24
24
|
|
|
25
|
-
__version__ = "0.3.
|
|
25
|
+
__version__ = "0.3.5"
|
|
26
26
|
|
|
27
27
|
__all__ = [
|
|
28
28
|
"Illumination",
|
|
@@ -173,7 +173,12 @@ def strip_boxed(latex: str) -> str:
|
|
|
173
173
|
|
|
174
174
|
|
|
175
175
|
def render_math_png(
|
|
176
|
-
latex: str,
|
|
176
|
+
latex: str,
|
|
177
|
+
*,
|
|
178
|
+
dpi: int = 140,
|
|
179
|
+
color: str = "black",
|
|
180
|
+
axis_pt: float | None = None,
|
|
181
|
+
min_height_pt: float = 0.0,
|
|
177
182
|
) -> bytes | None:
|
|
178
183
|
r"""Rasterize a LaTeX string (no `$` delimiters) to PNG bytes.
|
|
179
184
|
|
|
@@ -192,6 +197,20 @@ def render_math_png(
|
|
|
192
197
|
confirmed that mathtext's parser does not tolerate an embedded ``\n``. Any
|
|
193
198
|
run of whitespace is therefore collapsed to a single space before parsing,
|
|
194
199
|
which matches what plain LaTeX would do with the same source.
|
|
200
|
+
|
|
201
|
+
``axis_pt`` is for an equation set inside a line of text. Qt places an
|
|
202
|
+
image aligned ``vertical-align: middle`` with its centre a quarter of the
|
|
203
|
+
font's x-height above the baseline, so given that height in points of the
|
|
204
|
+
10 pt render, space is added above or below until the image's centre lands
|
|
205
|
+
there exactly when the equation's own baseline sits on the text's. Without
|
|
206
|
+
it the image's bottom edge sits on the baseline, and every symbol with a
|
|
207
|
+
descender rides above the line.
|
|
208
|
+
|
|
209
|
+
That rule holds only for an image at least about three x-heights tall;
|
|
210
|
+
Qt sets a shorter one higher, by an amount that depends on its height
|
|
211
|
+
(measured with PySide6 6.11 on 2026-11-12, by scanning solid images from
|
|
212
|
+
8 to 32 px against a 14 px font). ``min_height_pt`` pads the image evenly
|
|
213
|
+
above and below up to that height, which keeps the centre where it was.
|
|
195
214
|
"""
|
|
196
215
|
import io
|
|
197
216
|
import re
|
|
@@ -205,7 +224,69 @@ def render_math_png(
|
|
|
205
224
|
|
|
206
225
|
buffer = io.BytesIO()
|
|
207
226
|
try:
|
|
208
|
-
|
|
227
|
+
if axis_pt is None:
|
|
228
|
+
math_to_image(f"${normalized}$", buffer, dpi=dpi, format="png", color=color)
|
|
229
|
+
return buffer.getvalue()
|
|
230
|
+
return _inline_png(normalized, dpi, color, axis_pt, min_height_pt)
|
|
209
231
|
except ValueError:
|
|
210
232
|
return None
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _inline_png(
|
|
236
|
+
latex: str, dpi: int, color: str, axis_pt: float, min_height_pt: float
|
|
237
|
+
) -> bytes:
|
|
238
|
+
"""An inline equation as a transparent PNG, padded about its baseline.
|
|
239
|
+
|
|
240
|
+
The baseline is measured rather than taken from mathtext's box depth,
|
|
241
|
+
which is the layout box and not where the glyphs land: rasterised at
|
|
242
|
+
101 dpi the two differ by a pixel in either direction depending on the
|
|
243
|
+
expression. So the expression is rendered a second time with an upright
|
|
244
|
+
``x`` after it, which sits on the baseline, and the bottom of that ``x``
|
|
245
|
+
is read off. The two renders share one vertical frame whenever the ``x``
|
|
246
|
+
is no taller and no deeper than the expression, which is any expression
|
|
247
|
+
holding a letter or digit; otherwise the box depth is used.
|
|
248
|
+
"""
|
|
249
|
+
import io
|
|
250
|
+
|
|
251
|
+
import numpy as np
|
|
252
|
+
from matplotlib.colors import to_rgb
|
|
253
|
+
from matplotlib.image import imsave
|
|
254
|
+
from matplotlib.mathtext import MathTextParser
|
|
255
|
+
|
|
256
|
+
parser = MathTextParser("agg")
|
|
257
|
+
ink = np.asarray(parser.parse(f"${latex}$", dpi=dpi).image)
|
|
258
|
+
probe = parser.parse(f"${latex}" + r"\mathrm{x}$", dpi=dpi)
|
|
259
|
+
probe_ink = np.asarray(probe.image)
|
|
260
|
+
rows = ink.shape[0]
|
|
261
|
+
if probe_ink.shape[0] == rows:
|
|
262
|
+
# The rightmost couple of points of the x: its right leg ends on
|
|
263
|
+
# the baseline, and nothing of the expression reaches that far.
|
|
264
|
+
columns = np.flatnonzero(probe_ink.max(axis=0))
|
|
265
|
+
leg = probe_ink[:, columns[-1] - max(1, round(2 * dpi / 72)) + 1 : columns[-1] + 1]
|
|
266
|
+
baseline = int(np.flatnonzero(leg.max(axis=1) > 100)[-1]) + 1
|
|
267
|
+
else:
|
|
268
|
+
rendered = parser.parse(f"${latex}$", dpi=dpi)
|
|
269
|
+
baseline = int(rendered.height - rendered.depth) - 1
|
|
270
|
+
|
|
271
|
+
# Centre on the axis: (above - below) / 2 must equal the axis height.
|
|
272
|
+
scale = dpi / 72.0
|
|
273
|
+
above, below = float(baseline), float(rows - baseline)
|
|
274
|
+
excess = (above - below) - 2 * axis_pt * scale
|
|
275
|
+
if excess > 0:
|
|
276
|
+
below += excess
|
|
277
|
+
else:
|
|
278
|
+
above -= excess
|
|
279
|
+
short = min_height_pt * scale - (above + below)
|
|
280
|
+
if short > 0:
|
|
281
|
+
above += short / 2
|
|
282
|
+
below += short / 2
|
|
283
|
+
top = round(above) - baseline
|
|
284
|
+
bottom = round(below) - (rows - baseline)
|
|
285
|
+
|
|
286
|
+
alpha = np.pad(ink, ((top, bottom), (0, 0))).astype(float) / 255.0
|
|
287
|
+
rgba = np.empty(alpha.shape + (4,))
|
|
288
|
+
rgba[..., :3] = to_rgb(color)
|
|
289
|
+
rgba[..., 3] = alpha
|
|
290
|
+
buffer = io.BytesIO()
|
|
291
|
+
imsave(buffer, rgba, format="png")
|
|
211
292
|
return buffer.getvalue()
|
|
@@ -36,13 +36,12 @@ from PySide6.QtWidgets import (
|
|
|
36
36
|
QLabel,
|
|
37
37
|
QMessageBox,
|
|
38
38
|
QPushButton,
|
|
39
|
-
QTextBrowser,
|
|
40
39
|
QVBoxLayout,
|
|
41
40
|
QWidget,
|
|
42
41
|
)
|
|
43
42
|
|
|
44
43
|
from ..docs import TheoryPage
|
|
45
|
-
from .theory_viewer import render_into
|
|
44
|
+
from .theory_viewer import ReadingBrowser, render_into
|
|
46
45
|
|
|
47
46
|
#: What each reader's ``source`` string starts with, and what to cite for it.
|
|
48
47
|
_CITATIONS = {
|
|
@@ -242,7 +241,7 @@ class SetupTab(QWidget):
|
|
|
242
241
|
|
|
243
242
|
def __init__(self, parent: QWidget | None = None) -> None:
|
|
244
243
|
super().__init__(parent)
|
|
245
|
-
self._browser =
|
|
244
|
+
self._browser = ReadingBrowser()
|
|
246
245
|
self._browser.setOpenExternalLinks(False)
|
|
247
246
|
self._browser.setOpenLinks(False)
|
|
248
247
|
# Links leave for the system browser; the page itself never navigates.
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""The read-only viewer for one theory or reference page.
|
|
2
|
+
|
|
3
|
+
The viewer is thin by design. :mod:`gratinglab.gui.richtext` turns the page
|
|
4
|
+
into HTML and a list of rasterised equations, and this class only passes both
|
|
5
|
+
to a `QTextBrowser`. Everything that could be wrong in a non-trivial way
|
|
6
|
+
(table structure, escaping, math placement, the approximate-method banner) is
|
|
7
|
+
decided and tested in that module, without a window.
|
|
8
|
+
|
|
9
|
+
Images are registered with `QTextDocument.addResource` instead of being
|
|
10
|
+
embedded as `data:` URIs, because `loadResource` does not handle `data:` by
|
|
11
|
+
default. This also removes the least elegant detail of the Tk viewer: a list
|
|
12
|
+
monkey-patched onto the window solely to keep `PhotoImage` objects alive
|
|
13
|
+
against garbage collection while Tk was still displaying them.
|
|
14
|
+
|
|
15
|
+
:class:`ReadingBrowser` is the page itself: a slightly larger font than the
|
|
16
|
+
widgets around it, and a text column kept to a readable width and centred,
|
|
17
|
+
so a wide window adds margin rather than longer lines. The math is sized
|
|
18
|
+
from that font, so an equation in a sentence matches the words beside it.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from PySide6.QtCore import Qt, QUrl
|
|
24
|
+
from PySide6.QtGui import (
|
|
25
|
+
QFontMetricsF,
|
|
26
|
+
QImage,
|
|
27
|
+
QTextBlockFormat,
|
|
28
|
+
QTextCursor,
|
|
29
|
+
QTextDocument,
|
|
30
|
+
QTextImageFormat,
|
|
31
|
+
)
|
|
32
|
+
from PySide6.QtWidgets import QTextBrowser, QVBoxLayout, QWidget
|
|
33
|
+
|
|
34
|
+
from .. import richtext
|
|
35
|
+
|
|
36
|
+
__all__ = ["ReadingBrowser", "TheoryViewer", "render_into"]
|
|
37
|
+
|
|
38
|
+
#: Body text relative to the application font. The pages are long prose, and
|
|
39
|
+
#: the 9 pt that suits a button is small for reading paragraphs of it.
|
|
40
|
+
READING_SCALE = 1.15
|
|
41
|
+
|
|
42
|
+
#: Widest the text column grows, in ems of the reading font: about 100
|
|
43
|
+
#: characters of prose a line. A display equation wider than that widens the
|
|
44
|
+
#: column to fit rather than scrolling sideways.
|
|
45
|
+
READING_WIDTH_EMS = 50
|
|
46
|
+
|
|
47
|
+
#: Space either side of the column, in pixels, when the window is narrower
|
|
48
|
+
#: than the column.
|
|
49
|
+
READING_MARGIN = 24
|
|
50
|
+
|
|
51
|
+
#: Space around tables, which Qt otherwise sets flush against the paragraph
|
|
52
|
+
#: above them.
|
|
53
|
+
_STYLE = "table { margin-top: 6px; margin-bottom: 10px; } th { text-align: left; }"
|
|
54
|
+
|
|
55
|
+
#: Extra space between lines, as a fraction of the font's pixel size. Added
|
|
56
|
+
#: as a fixed distance rather than CSS ``line-height: 135%``, which scales
|
|
57
|
+
#: each line's own height and so opens a wide gap around every line holding
|
|
58
|
+
#: an equation.
|
|
59
|
+
LEADING = 0.3
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ReadingBrowser(QTextBrowser):
|
|
63
|
+
"""A `QTextBrowser` laid out for reading a page rather than a log."""
|
|
64
|
+
|
|
65
|
+
def __init__(self, parent: QWidget | None = None) -> None:
|
|
66
|
+
super().__init__(parent)
|
|
67
|
+
font = self.font()
|
|
68
|
+
font.setPointSizeF(font.pointSizeF() * READING_SCALE)
|
|
69
|
+
self.setFont(font)
|
|
70
|
+
self.document().setDefaultFont(font)
|
|
71
|
+
self.document().setDefaultStyleSheet(_STYLE)
|
|
72
|
+
self._widest_image = 0.0
|
|
73
|
+
|
|
74
|
+
def resizeEvent(self, event) -> None: # noqa: N802 - Qt's spelling
|
|
75
|
+
super().resizeEvent(event)
|
|
76
|
+
self._centre_column()
|
|
77
|
+
|
|
78
|
+
def pixel_size(self) -> float:
|
|
79
|
+
"""The reading font's size in pixels.
|
|
80
|
+
|
|
81
|
+
From the point size and the screen's DPI rather than `QFontInfo`,
|
|
82
|
+
which reports the matched font and so reads near zero where no system
|
|
83
|
+
fonts are installed, as on the offscreen CI runs.
|
|
84
|
+
"""
|
|
85
|
+
return self.font().pointSizeF() * self.logicalDpiY() / 72.0
|
|
86
|
+
|
|
87
|
+
def _centre_column(self) -> None:
|
|
88
|
+
column = max(self.pixel_size() * READING_WIDTH_EMS, self._widest_image)
|
|
89
|
+
spare = self.viewport().width() - column
|
|
90
|
+
side = max(READING_MARGIN, spare / 2)
|
|
91
|
+
root = self.document().rootFrame()
|
|
92
|
+
frame = root.frameFormat()
|
|
93
|
+
if frame.leftMargin() == side and frame.rightMargin() == side:
|
|
94
|
+
return
|
|
95
|
+
frame.setLeftMargin(side)
|
|
96
|
+
frame.setRightMargin(side)
|
|
97
|
+
frame.setTopMargin(READING_MARGIN / 2)
|
|
98
|
+
frame.setBottomMargin(READING_MARGIN)
|
|
99
|
+
root.setFrameFormat(frame)
|
|
100
|
+
|
|
101
|
+
def setHtml(self, text: str) -> None: # noqa: N802 - Qt's spelling
|
|
102
|
+
# setHtml replaces the root frame's format along with the content.
|
|
103
|
+
super().setHtml(text)
|
|
104
|
+
leading = QTextBlockFormat()
|
|
105
|
+
leading.setLineHeight(
|
|
106
|
+
round(self.pixel_size() * LEADING),
|
|
107
|
+
QTextBlockFormat.LineHeightTypes.LineDistanceHeight.value,
|
|
108
|
+
)
|
|
109
|
+
cursor = QTextCursor(self.document())
|
|
110
|
+
cursor.select(QTextCursor.SelectionType.Document)
|
|
111
|
+
cursor.mergeBlockFormat(leading)
|
|
112
|
+
self._widest_image = _widest_image(self.document())
|
|
113
|
+
self._centre_column()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _widest_image(document: QTextDocument) -> float:
|
|
117
|
+
"""The widest image in a document, in pixels, tables included."""
|
|
118
|
+
widest = 0.0
|
|
119
|
+
block = document.begin()
|
|
120
|
+
while block.isValid():
|
|
121
|
+
fragments = block.begin()
|
|
122
|
+
while not fragments.atEnd():
|
|
123
|
+
fmt = fragments.fragment().charFormat()
|
|
124
|
+
if fmt.isImageFormat():
|
|
125
|
+
widest = max(widest, QTextImageFormat(fmt).width())
|
|
126
|
+
fragments += 1
|
|
127
|
+
block = block.next()
|
|
128
|
+
return widest
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def render_into(
|
|
132
|
+
browser: QTextBrowser, page, *, device_pixel_ratio: float, text_color: str
|
|
133
|
+
) -> None:
|
|
134
|
+
"""Render a page into an existing `QTextBrowser`, images and all.
|
|
135
|
+
|
|
136
|
+
It is shared by :class:`TheoryViewer` and :class:`~.setup_tab.SetupTab`,
|
|
137
|
+
both of which show "one markdown-ish page, typeset, in a browser," and the
|
|
138
|
+
second use is what justified factoring this function out instead of
|
|
139
|
+
copying it.
|
|
140
|
+
"""
|
|
141
|
+
font = browser.document().defaultFont()
|
|
142
|
+
pixels = font.pointSizeF() * browser.logicalDpiY() / 72.0
|
|
143
|
+
rendered = richtext.to_html(
|
|
144
|
+
page,
|
|
145
|
+
device_pixel_ratio=device_pixel_ratio,
|
|
146
|
+
color=text_color,
|
|
147
|
+
inline_dpi=richtext.math_dpi(pixels),
|
|
148
|
+
display_dpi=richtext.math_dpi(pixels, display=True),
|
|
149
|
+
**richtext.inline_metrics(pixels, QFontMetricsF(font).xHeight()),
|
|
150
|
+
)
|
|
151
|
+
document = browser.document()
|
|
152
|
+
for image in rendered.images:
|
|
153
|
+
document.addResource(
|
|
154
|
+
QTextDocument.ResourceType.ImageResource,
|
|
155
|
+
QUrl(image.key),
|
|
156
|
+
QImage.fromData(image.png, "PNG"),
|
|
157
|
+
)
|
|
158
|
+
browser.setHtml(rendered.html)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class TheoryViewer(QWidget):
|
|
162
|
+
"""One page, typeset."""
|
|
163
|
+
|
|
164
|
+
def __init__(self, page, parent: QWidget | None = None) -> None:
|
|
165
|
+
# A window in its own right rather than a modal dialog: reading the
|
|
166
|
+
# theory while adjusting the grating is the point.
|
|
167
|
+
super().__init__(parent)
|
|
168
|
+
self.setWindowFlag(Qt.WindowType.Window)
|
|
169
|
+
self.setWindowTitle(page.title)
|
|
170
|
+
self.resize(860, 720)
|
|
171
|
+
|
|
172
|
+
self._browser = ReadingBrowser()
|
|
173
|
+
self._browser.setOpenExternalLinks(False)
|
|
174
|
+
# Relative links between doc pages point at files on disk that this
|
|
175
|
+
# viewer has no route to open. Following them would empty the window.
|
|
176
|
+
self._browser.setOpenLinks(False)
|
|
177
|
+
|
|
178
|
+
layout = QVBoxLayout(self)
|
|
179
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
180
|
+
layout.addWidget(self._browser)
|
|
181
|
+
|
|
182
|
+
self.set_page(page)
|
|
183
|
+
from . import theming
|
|
184
|
+
|
|
185
|
+
# Equations are images rasterised in the text colour, so a theme
|
|
186
|
+
# change re-renders the page rather than only repainting it.
|
|
187
|
+
theming.subscribe(self._on_theme)
|
|
188
|
+
|
|
189
|
+
def _on_theme(self, _theme) -> None:
|
|
190
|
+
# A bound method so Qt disconnects it with the window (see SetupTab).
|
|
191
|
+
self.set_page(self._page)
|
|
192
|
+
|
|
193
|
+
def set_page(self, page) -> None:
|
|
194
|
+
"""Render a page into the browser, images and all."""
|
|
195
|
+
self._page = page
|
|
196
|
+
render_into(
|
|
197
|
+
self._browser,
|
|
198
|
+
page,
|
|
199
|
+
device_pixel_ratio=self.devicePixelRatioF(),
|
|
200
|
+
text_color=self._text_color(),
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
def _text_color(self) -> str:
|
|
204
|
+
"""Rasterise math in the palette's own text colour.
|
|
205
|
+
|
|
206
|
+
Equations are images, so they do not follow a stylesheet the way the
|
|
207
|
+
surrounding prose does. Black math on a dark theme would be unreadable
|
|
208
|
+
only because its colour had been fixed at render time.
|
|
209
|
+
"""
|
|
210
|
+
return self.palette().windowText().color().name()
|
|
@@ -60,6 +60,7 @@ __all__ = [
|
|
|
60
60
|
"to_html",
|
|
61
61
|
"png_size",
|
|
62
62
|
"math_dpi",
|
|
63
|
+
"inline_metrics",
|
|
63
64
|
"menu_label",
|
|
64
65
|
"INLINE_DPI",
|
|
65
66
|
"DISPLAY_DPI",
|
|
@@ -130,13 +131,23 @@ class _Collector:
|
|
|
130
131
|
device_pixel_ratio: float
|
|
131
132
|
color: str
|
|
132
133
|
render: Callable[..., bytes | None]
|
|
134
|
+
axis_pt: float | None = None
|
|
135
|
+
min_height_pt: float = 0.0
|
|
133
136
|
images: list[MathImage] = field(default_factory=list)
|
|
134
137
|
|
|
135
138
|
def add(self, latex: str, *, display: bool) -> str:
|
|
136
139
|
"""Render one math span; return the HTML that stands for it."""
|
|
137
140
|
base = self.display_dpi if display else self.inline_dpi
|
|
138
141
|
dpi = max(1, round(base * self.device_pixel_ratio))
|
|
139
|
-
|
|
142
|
+
# Only inline math is set against a line of text. A display
|
|
143
|
+
# equation sits on its own line, so there is no baseline to meet.
|
|
144
|
+
centred = not display and self.axis_pt is not None
|
|
145
|
+
extra = (
|
|
146
|
+
{"axis_pt": self.axis_pt, "min_height_pt": self.min_height_pt}
|
|
147
|
+
if centred
|
|
148
|
+
else {}
|
|
149
|
+
)
|
|
150
|
+
png = self.render(latex, dpi=dpi, color=self.color, **extra)
|
|
140
151
|
|
|
141
152
|
if png is None:
|
|
142
153
|
delimiter = "$$" if display else "$"
|
|
@@ -156,7 +167,11 @@ class _Collector:
|
|
|
156
167
|
height=max(1, round(height / self.device_pixel_ratio)),
|
|
157
168
|
)
|
|
158
169
|
)
|
|
159
|
-
|
|
170
|
+
align = ' style="vertical-align:middle"' if centred else ""
|
|
171
|
+
image = (
|
|
172
|
+
f'<img src="{key}" width="{self.images[-1].width}" '
|
|
173
|
+
f'height="{self.images[-1].height}"{align}>'
|
|
174
|
+
)
|
|
160
175
|
return f"<br>{image}<br>" if display else image
|
|
161
176
|
|
|
162
177
|
|
|
@@ -168,6 +183,8 @@ def render_markdown(
|
|
|
168
183
|
device_pixel_ratio: float = 1.0,
|
|
169
184
|
color: str = "black",
|
|
170
185
|
render: Callable[..., bytes | None] | None = None,
|
|
186
|
+
axis_pt: float | None = None,
|
|
187
|
+
min_height_pt: float = 0.0,
|
|
171
188
|
) -> RichPage:
|
|
172
189
|
"""Convert markdown with `$...$` math to Qt-displayable HTML.
|
|
173
190
|
|
|
@@ -176,6 +193,11 @@ def render_markdown(
|
|
|
176
193
|
``None``. It defaults to a cached
|
|
177
194
|
:func:`~gratinglab.gui.mathtext.render_math_png`. A page carries roughly
|
|
178
195
|
forty equations, and the cache allows the viewer to reopen immediately.
|
|
196
|
+
|
|
197
|
+
``axis_pt`` and ``min_height_pt`` put inline math on the text's
|
|
198
|
+
baseline; see :func:`~gratinglab.gui.mathtext.render_math_png` and
|
|
199
|
+
:func:`inline_metrics`. Left ``None``, an inline equation's bottom edge
|
|
200
|
+
sits on the baseline instead.
|
|
179
201
|
"""
|
|
180
202
|
collector = _Collector(
|
|
181
203
|
inline_dpi=inline_dpi,
|
|
@@ -183,6 +205,8 @@ def render_markdown(
|
|
|
183
205
|
device_pixel_ratio=device_pixel_ratio,
|
|
184
206
|
color=color,
|
|
185
207
|
render=render if render is not None else _cached_render,
|
|
208
|
+
axis_pt=axis_pt,
|
|
209
|
+
min_height_pt=min_height_pt,
|
|
186
210
|
)
|
|
187
211
|
parts = [_block_html(block, collector) for block in _blocks(markdown)]
|
|
188
212
|
return RichPage(html="\n".join(p for p in parts if p), images=tuple(collector.images))
|
|
@@ -439,6 +463,19 @@ def png_size(data: bytes) -> tuple[int, int] | None:
|
|
|
439
463
|
)
|
|
440
464
|
|
|
441
465
|
|
|
466
|
+
def inline_metrics(font_pixel_size: float, x_height: float) -> dict[str, float]:
|
|
467
|
+
"""``axis_pt`` and ``min_height_pt`` for inline math beside this font.
|
|
468
|
+
|
|
469
|
+
Qt puts a middle-aligned image's centre a quarter of the x-height above
|
|
470
|
+
the baseline, provided the image is at least three x-heights tall. Both
|
|
471
|
+
are returned in points of the math render: mathtext renders at 10 pt and
|
|
472
|
+
:func:`math_dpi` scales that to the font's pixel size, so a pixel of the
|
|
473
|
+
text is ``10 / font_pixel_size`` points.
|
|
474
|
+
"""
|
|
475
|
+
points = 10.0 / font_pixel_size
|
|
476
|
+
return {"axis_pt": x_height / 4.0 * points, "min_height_pt": 3.0 * x_height * points}
|
|
477
|
+
|
|
478
|
+
|
|
442
479
|
def math_dpi(font_pixel_size: float, *, display: bool = False) -> int:
|
|
443
480
|
"""DPI at which mathtext output matches text of the given size.
|
|
444
481
|
|
|
@@ -464,9 +501,18 @@ def menu_label(title: str, suffix: str = "") -> str:
|
|
|
464
501
|
|
|
465
502
|
|
|
466
503
|
@lru_cache(maxsize=512)
|
|
467
|
-
def _cached_render(
|
|
504
|
+
def _cached_render(
|
|
505
|
+
latex: str,
|
|
506
|
+
*,
|
|
507
|
+
dpi: int,
|
|
508
|
+
color: str,
|
|
509
|
+
axis_pt: float | None = None,
|
|
510
|
+
min_height_pt: float = 0.0,
|
|
511
|
+
) -> bytes | None:
|
|
468
512
|
"""Rasterise with caching.
|
|
469
513
|
|
|
470
514
|
Without the cache, reopening the viewer would re-render every equation on
|
|
471
515
|
the page, roughly forty of them, and more at 2x."""
|
|
472
|
-
return mathtext.render_math_png(
|
|
516
|
+
return mathtext.render_math_png(
|
|
517
|
+
latex, dpi=dpi, color=color, axis_pt=axis_pt, min_height_pt=min_height_pt
|
|
518
|
+
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""File formats other programs read or write.
|
|
2
|
+
|
|
3
|
+
Each module handles one format, and nothing is imported here, so a caller
|
|
4
|
+
names the module it wants (``from gratinglab.io.ggp import read_ggp``):
|
|
5
|
+
|
|
6
|
+
``ggp``
|
|
7
|
+
PCGrate's ``.ggp`` groove profiles, read in all five border types and
|
|
8
|
+
written in the polygonal one.
|
|
9
|
+
``boundary``
|
|
10
|
+
A measured groove profile saved with its period and provenance, as
|
|
11
|
+
gratinglab's own ``.json``, which reads back, or a write-only ``.csv``.
|
|
12
|
+
A ``.ggp`` cannot carry the period, which is why this exists beside it.
|
|
13
|
+
``efficiency_table``
|
|
14
|
+
Efficiency tables exported by PCGrate-SX, read for comparison.
|
|
15
|
+
``txl``
|
|
16
|
+
Raith ``.txl`` layouts of a grating, written for electron-beam writing.
|
|
17
|
+
"""
|