streamtex 0.7.44__tar.gz → 0.7.45__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.
- {streamtex-0.7.44/streamtex.egg-info → streamtex-0.7.45}/PKG-INFO +1 -1
- {streamtex-0.7.44 → streamtex-0.7.45}/pyproject.toml +1 -1
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/ai/history.py +7 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/book.py +65 -9
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/deploy_cmd.py +10 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/export.py +31 -2
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/image.py +0 -1
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/image_editor.py +9 -11
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/media_overlay.py +18 -1
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/pdf_export.py +32 -2
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/static/default.css +3 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/styles/__init__.py +1 -1
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/styles/base.py +16 -14
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/styles/container.py +63 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/styles/text.py +12 -12
- {streamtex-0.7.44 → streamtex-0.7.45/streamtex.egg-info}/PKG-INFO +1 -1
- {streamtex-0.7.44 → streamtex-0.7.45}/LICENSE +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/MANIFEST.in +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/README.md +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/setup.cfg +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/__init__.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/__main__.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/ai/__init__.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/ai/config.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/ai/generate.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/ai/metadata.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/ai/providers/__init__.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/ai/providers/base.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/ai/providers/fal.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/ai/providers/google.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/ai/providers/openai.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/ai/providers/registry.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/auth.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/banner.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/bib.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/bib_preview.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/block_helpers.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/blocks.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/browser.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/__init__.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/_claude_files.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/_divergence.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/_install_local_packs.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/_shared.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/_stx_toml.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/_toml_helpers.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/artifact_cmd.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/bib_cmd.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/build_check.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/cache_cmd.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/claude_cmd.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/claude_global.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/claude_project.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/commands.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/component_cmd.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/console.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/coolify.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/deploy_auth_cmd.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/dev_cmd.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/dev_config.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/ds_cmd.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/export_cmd.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/install_cmd.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/kit_cmd.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/main.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/migrations/__init__.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/migrations/base.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/migrations/compat.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/migrations/registry.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/migrations/v0_4_0.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/migrations/v0_4_6.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/pack_cmd.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/project_cmd.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/project_rules.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/publish_cmd.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/published_check.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/run_cmd.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/run_set.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/screenshot_cmd.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/shortcuts.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/status_cmd.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/sync_cmd.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/upgrade_cmd.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/validate_cmd.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/cli/workspace_cmd.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/code.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/collection.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/constants.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/container.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/__init__.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/__init__.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/_frontmatter.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/agent.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/ai_prompt.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/archetype.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/asset.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/discovery.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/guideline.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/install.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/integration.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/loaders.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/palette.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/skill.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/spec.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/artifacts/validation.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/contracts.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/discovery.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/core/validation.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/deeplink.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/deploy/__init__.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/deploy/stx_auth.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/enums.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/export_enrich.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/export_widgets.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/facts.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/grid.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/gsheet.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/hover_tooltip.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/i18n.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/image_crop.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/image_utils.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/inspector.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/kept.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/latex.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/latex_utils.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/link_config.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/link_preview.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/list.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/loading.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/markdown.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/marker.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/marker_runtime.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/mermaid.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/overlay.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/plantuml.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/presentation.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/presentation_profile.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/runtime_flags.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/scripts/__init__.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/scripts/migrate_curves_to_relative.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/search.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/slide.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/space.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/spacing.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/static/css/stx_global.css +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/static/js/stx_marker_observer.js +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/static/js/stx_scroll_spy.js +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/static/logo-stx-tiny.png +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/static/logo-stx.png +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/styles/core.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/styles/scale.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/styles/scale_curves.toml +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/styles/visibility.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/tikz.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/toc.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/utils.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/watch.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/write.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex/zoom.py +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex.egg-info/SOURCES.txt +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex.egg-info/dependency_links.txt +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex.egg-info/entry_points.txt +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex.egg-info/requires.txt +0 -0
- {streamtex-0.7.44 → streamtex-0.7.45}/streamtex.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: streamtex
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.45
|
|
4
4
|
Summary: AI-powered content framework for Streamlit — create presentations, courses, and web-books with Claude or Cursor, no coding required.
|
|
5
5
|
Author-email: Nicolas Guelfi <nicolas.guelfi@laposte.net>
|
|
6
6
|
License-Expression: BUSL-1.1
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "streamtex"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.45"
|
|
8
8
|
description = "AI-powered content framework for Streamlit — create presentations, courses, and web-books with Claude or Cursor, no coding required."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -111,6 +111,13 @@ def save_version(
|
|
|
111
111
|
base_image=base_image,
|
|
112
112
|
revised_prompt=revised_prompt,
|
|
113
113
|
)
|
|
114
|
+
if existing_meta:
|
|
115
|
+
# The display settings belong to the slot, not to one image: a new
|
|
116
|
+
# version keeps the author's zoom and size (#18).
|
|
117
|
+
meta.display_zoom = existing_meta.display_zoom
|
|
118
|
+
meta.display_width = existing_meta.display_width
|
|
119
|
+
meta.display_height = existing_meta.display_height
|
|
120
|
+
meta.display_keep_ratio = existing_meta.display_keep_ratio
|
|
114
121
|
save_metadata(meta, current_json)
|
|
115
122
|
|
|
116
123
|
return current
|
|
@@ -21,6 +21,7 @@ from .enums import Tags
|
|
|
21
21
|
from .export import (
|
|
22
22
|
ExportConfig,
|
|
23
23
|
ExportMode,
|
|
24
|
+
apply_view_to_export_html,
|
|
24
25
|
build_export_filename,
|
|
25
26
|
generate_export_html,
|
|
26
27
|
get_asset_collector,
|
|
@@ -215,7 +216,8 @@ def _resolve_pdf_base_url() -> str:
|
|
|
215
216
|
|
|
216
217
|
def _offer_export_downloads(html: str, base_name: str,
|
|
217
218
|
pdf_config: PdfConfig | None = None,
|
|
218
|
-
paginated: bool = False
|
|
219
|
+
paginated: bool = False,
|
|
220
|
+
html_zoom: int = 0) -> None:
|
|
219
221
|
"""Render a 'Download as...' expander with export options and download buttons.
|
|
220
222
|
|
|
221
223
|
Format checkboxes (HTML / PDF) live **outside** the form so they trigger
|
|
@@ -232,7 +234,7 @@ def _offer_export_downloads(html: str, base_name: str,
|
|
|
232
234
|
# widget keys change, forcing Streamlit to create fresh widgets with
|
|
233
235
|
# the new default values. Old orphaned keys are harmless.
|
|
234
236
|
_cfg_str = (f"{defaults.format}|{defaults.landscape}|{defaults.print_background}|"
|
|
235
|
-
f"{defaults.page_numbers}|{defaults.scale}|{defaults.content_width}|"
|
|
237
|
+
f"{defaults.page_numbers}|{defaults.scale}|{defaults.content_width}|{defaults.zoom}|"
|
|
236
238
|
f"{defaults.margin_top}|{defaults.margin_bottom}|"
|
|
237
239
|
f"{defaults.margin_left}|{defaults.margin_right}")
|
|
238
240
|
_cfg_fp = hashlib.md5(_cfg_str.encode()).hexdigest()[:6]
|
|
@@ -287,7 +289,16 @@ def _offer_export_downloads(html: str, base_name: str,
|
|
|
287
289
|
# ── HTML enrichment options ─────────────────────────
|
|
288
290
|
enrich_nav = False
|
|
289
291
|
enrich_title = ""
|
|
292
|
+
html_zoom_pct = 0
|
|
290
293
|
if want_html:
|
|
294
|
+
html_zoom_pct = st.number_input(
|
|
295
|
+
"Zoom HTML (%)", min_value=0, max_value=400,
|
|
296
|
+
value=max(0, min(400, int(html_zoom or 0))), step=10,
|
|
297
|
+
key=f"_stx_export_html_zoom_{_pk}_{html_zoom}",
|
|
298
|
+
help="0 = the HTML file follows the view zoom (Settings › Zoom %). "
|
|
299
|
+
"A value fixes the zoom of the HTML file whatever the screen "
|
|
300
|
+
"shows; it replaces the view zoom (the PDF zoom multiplies it).",
|
|
301
|
+
)
|
|
291
302
|
st.markdown("**HTML navigation**")
|
|
292
303
|
enrich_nav = st.checkbox(
|
|
293
304
|
"Enriched navigation (TOC sidebar, markers, search)",
|
|
@@ -316,6 +327,7 @@ def _offer_export_downloads(html: str, base_name: str,
|
|
|
316
327
|
pdf_background = defaults.print_background
|
|
317
328
|
pdf_page_numbers = defaults.page_numbers
|
|
318
329
|
pdf_scale = defaults.scale
|
|
330
|
+
pdf_zoom = defaults.zoom
|
|
319
331
|
pdf_content_width = defaults.content_width
|
|
320
332
|
m_top = _margin_mm(defaults.margin_top)
|
|
321
333
|
m_bottom = _margin_mm(defaults.margin_bottom)
|
|
@@ -326,7 +338,8 @@ def _offer_export_downloads(html: str, base_name: str,
|
|
|
326
338
|
_cur_w = st.session_state.get(_PAGE_WIDTH_KEY, 100)
|
|
327
339
|
_cur_z = st.session_state.get(_ZOOM_KEY, 100)
|
|
328
340
|
st.markdown("**PDF layout**")
|
|
329
|
-
st.caption(f"Current view:
|
|
341
|
+
st.caption(f"Current view: Zoom {_cur_z}% (carried into the PDF, times Zoom below) · "
|
|
342
|
+
f"Width {_cur_w}% (HTML only; the PDF uses Content width)")
|
|
330
343
|
_FORMAT_OPTIONS = [
|
|
331
344
|
"16:9", "16:10", "4:3",
|
|
332
345
|
"A4", "A3", "Letter", "Legal", "Tabloid",
|
|
@@ -370,8 +383,19 @@ def _offer_export_downloads(html: str, base_name: str,
|
|
|
370
383
|
"Scale (%)", min_value=10, max_value=500,
|
|
371
384
|
value=_scale_default, step=10,
|
|
372
385
|
key=f"_stx_pdf_scale_{_pk}",
|
|
386
|
+
help="Chrome's print scale: shrinks sizes in pt/px and lets the "
|
|
387
|
+
"text reflow; sizes in vw/vh stay proportional to the page. "
|
|
388
|
+
"To shrink everything, use Zoom.",
|
|
373
389
|
)
|
|
374
390
|
pdf_scale = _pdf_scale_pct / 100.0
|
|
391
|
+
pdf_zoom = st.number_input(
|
|
392
|
+
"Zoom (%)", min_value=10, max_value=400,
|
|
393
|
+
value=max(10, min(400, int(defaults.zoom))), step=10,
|
|
394
|
+
key=f"_stx_pdf_zoom_{_pk}",
|
|
395
|
+
help="Shrinks or enlarges everything in the PDF, sizes in vw/vh "
|
|
396
|
+
"included; multiplies with the view zoom (view 80 % x 70 % = 56 %). "
|
|
397
|
+
"Scale only shrinks pt/px sizes.",
|
|
398
|
+
)
|
|
375
399
|
_cw_default = max(10, min(100, defaults.content_width))
|
|
376
400
|
pdf_content_width = st.number_input(
|
|
377
401
|
"Content width (%)", min_value=10, max_value=100,
|
|
@@ -404,11 +428,26 @@ def _offer_export_downloads(html: str, base_name: str,
|
|
|
404
428
|
rebuild_fn()
|
|
405
429
|
st.rerun()
|
|
406
430
|
if full_book_html:
|
|
407
|
-
|
|
431
|
+
# Built once per session with the view of that moment:
|
|
432
|
+
# carry the current view zoom and width (#110).
|
|
433
|
+
effective_html = apply_view_to_export_html(
|
|
434
|
+
full_book_html,
|
|
435
|
+
built_view=st.session_state.get(_STX_FULL_EXPORT_VIEW_KEY),
|
|
436
|
+
page_width=f"{st.session_state.get(_PAGE_WIDTH_KEY, 100)}%",
|
|
437
|
+
zoom=st.session_state.get(_ZOOM_KEY, 100) / 100,
|
|
438
|
+
)
|
|
408
439
|
|
|
409
440
|
if want_html:
|
|
410
441
|
# Enrich a separate copy — keep effective_html raw for PDF
|
|
411
442
|
html_download = effective_html
|
|
443
|
+
if html_zoom_pct:
|
|
444
|
+
# A fixed zoom for the HTML file only (#110); the PDF
|
|
445
|
+
# keeps the view zoom times its own Zoom.
|
|
446
|
+
html_download = apply_view_to_export_html(
|
|
447
|
+
html_download,
|
|
448
|
+
page_width=f"{st.session_state.get(_PAGE_WIDTH_KEY, 100)}%",
|
|
449
|
+
zoom=html_zoom_pct / 100,
|
|
450
|
+
)
|
|
412
451
|
if enrich_nav:
|
|
413
452
|
try:
|
|
414
453
|
from .export_enrich import enrich_export_html
|
|
@@ -464,6 +503,7 @@ def _offer_export_downloads(html: str, base_name: str,
|
|
|
464
503
|
margin_right=f"{m_right}mm",
|
|
465
504
|
print_background=pdf_background,
|
|
466
505
|
scale=pdf_scale,
|
|
506
|
+
zoom=pdf_zoom,
|
|
467
507
|
content_width=pdf_content_width,
|
|
468
508
|
page_numbers=pdf_page_numbers,
|
|
469
509
|
header_template=defaults.header_template,
|
|
@@ -482,6 +522,9 @@ def _offer_export_downloads(html: str, base_name: str,
|
|
|
482
522
|
report=_report)
|
|
483
523
|
if _report.timed_out: # shown with the download button
|
|
484
524
|
results["pdf_warning"] = _report.message(cfg.load_timeout_ms)
|
|
525
|
+
_view_z = st.session_state.get(_ZOOM_KEY, 100)
|
|
526
|
+
results["pdf_zoom_note"] = (
|
|
527
|
+
f"PDF zoom: view {_view_z:g}% × {pdf_zoom:g}% = {_view_z * pdf_zoom / 100:g}%")
|
|
485
528
|
except Exception as exc:
|
|
486
529
|
st.error(f"PDF generation failed after {time.monotonic() - _t0:.0f} s: {exc}")
|
|
487
530
|
st.session_state[state_key] = results
|
|
@@ -505,6 +548,8 @@ def _offer_export_downloads(html: str, base_name: str,
|
|
|
505
548
|
key=f"_stx_dl_html_{base_name}",
|
|
506
549
|
)
|
|
507
550
|
if "pdf" in results:
|
|
551
|
+
if results.get("pdf_zoom_note"):
|
|
552
|
+
st.caption(results["pdf_zoom_note"])
|
|
508
553
|
if results.get("pdf_warning"):
|
|
509
554
|
st.warning(results["pdf_warning"])
|
|
510
555
|
st.download_button(
|
|
@@ -639,7 +684,8 @@ def _st_book_impl(module_list, toc_config: TOCConfig = None, marker_config: Mark
|
|
|
639
684
|
scale=None,
|
|
640
685
|
block_args: tuple = (),
|
|
641
686
|
block_kwargs: dict | None = None,
|
|
642
|
-
lang: str | None = None
|
|
687
|
+
lang: str | None = None,
|
|
688
|
+
html_export_zoom: int = 0):
|
|
643
689
|
"""Generates a web page e-book from a list of block modules.
|
|
644
690
|
|
|
645
691
|
:param separator: Optional module with a build() function, rendered between each block.
|
|
@@ -663,6 +709,10 @@ def _st_book_impl(module_list, toc_config: TOCConfig = None, marker_config: Mark
|
|
|
663
709
|
:param page_width: Page width as % of browser width (default 90).
|
|
664
710
|
:param zoom: Default zoom level as % (default 100).
|
|
665
711
|
:param loading: If True (default), show a full-screen overlay with progress during loading.
|
|
712
|
+
:param html_export_zoom: Starting value of the "Zoom HTML (%)" field of the
|
|
713
|
+
export panel (#110). ``0`` (default) = the exported HTML follows the
|
|
714
|
+
view zoom (Settings › Zoom %); a value fixes the zoom of the HTML
|
|
715
|
+
file whatever the screen shows.
|
|
666
716
|
:param scale: Optional ``ScaleConfig`` to override the indexed responsive
|
|
667
717
|
font scale for this document only. When provided, an inline ``<style>``
|
|
668
718
|
block is emitted after the default CSS load, redefining
|
|
@@ -908,6 +958,7 @@ def _st_book_impl(module_list, toc_config: TOCConfig = None, marker_config: Mark
|
|
|
908
958
|
pdf_config=pdf_config, exports=exports,
|
|
909
959
|
inspector=inspector,
|
|
910
960
|
page_width=page_width, zoom=zoom,
|
|
961
|
+
html_export_zoom=html_export_zoom,
|
|
911
962
|
profile_names=profile_names,
|
|
912
963
|
active_profile=active_name,
|
|
913
964
|
profile_modified=_profile_modified,
|
|
@@ -1098,7 +1149,8 @@ def _st_book_impl(module_list, toc_config: TOCConfig = None, marker_config: Mark
|
|
|
1098
1149
|
)
|
|
1099
1150
|
if _show_manual:
|
|
1100
1151
|
with st.sidebar:
|
|
1101
|
-
_offer_export_downloads(full_html, base_name, pdf_config=pdf_config
|
|
1152
|
+
_offer_export_downloads(full_html, base_name, pdf_config=pdf_config,
|
|
1153
|
+
html_zoom=html_export_zoom)
|
|
1102
1154
|
|
|
1103
1155
|
# --- Inspector panel (opt-in, rendered into reserved sidebar placeholder) ---
|
|
1104
1156
|
if _inspector_placeholder is not None and st.session_state.get("_stx_inspector_open", False):
|
|
@@ -1612,6 +1664,7 @@ def _isolate_widget_keys():
|
|
|
1612
1664
|
|
|
1613
1665
|
|
|
1614
1666
|
_STX_FULL_EXPORT_HTML_KEY = "_stx_full_export_html"
|
|
1667
|
+
_STX_FULL_EXPORT_VIEW_KEY = "_stx_full_export_view" # (page_width, zoom) it was built with
|
|
1615
1668
|
|
|
1616
1669
|
|
|
1617
1670
|
# Streamlit primitives whose DOM emission is suppressed during the
|
|
@@ -1789,6 +1842,7 @@ def _build_page_cache(module_list, toc_config, marker_config, separator,
|
|
|
1789
1842
|
full_html = generate_export_html()
|
|
1790
1843
|
if full_html:
|
|
1791
1844
|
st.session_state[_STX_FULL_EXPORT_HTML_KEY] = full_html
|
|
1845
|
+
st.session_state[_STX_FULL_EXPORT_VIEW_KEY] = (export_config.page_width, export_config.zoom)
|
|
1792
1846
|
reset_export_buffer(ExportConfig(enabled=False))
|
|
1793
1847
|
|
|
1794
1848
|
search_index = stop_collector() if use_search else None
|
|
@@ -2422,7 +2476,8 @@ def _paginated_book(module_list, toc_config, marker_config, separator,
|
|
|
2422
2476
|
profile_modified=False, all_profiles=None,
|
|
2423
2477
|
loading=False,
|
|
2424
2478
|
block_args: tuple = (),
|
|
2425
|
-
block_kwargs: dict | None = None
|
|
2479
|
+
block_kwargs: dict | None = None,
|
|
2480
|
+
html_export_zoom: int = 0):
|
|
2426
2481
|
"""Paginated rendering — only renders one block per rerun."""
|
|
2427
2482
|
start_time = time.time()
|
|
2428
2483
|
logger.debug("Starting st_book (paginated)...")
|
|
@@ -2725,7 +2780,8 @@ def _paginated_book(module_list, toc_config, marker_config, separator,
|
|
|
2725
2780
|
if _show_manual:
|
|
2726
2781
|
with st.sidebar:
|
|
2727
2782
|
_offer_export_downloads(section_html, base_name,
|
|
2728
|
-
pdf_config=pdf_config, paginated=True
|
|
2783
|
+
pdf_config=pdf_config, paginated=True,
|
|
2784
|
+
html_zoom=html_export_zoom)
|
|
2729
2785
|
|
|
2730
2786
|
# --- Marker navigation widget (ALL markers, cross-page aware) ---
|
|
2731
2787
|
# Skip the widget if the current page has no markers (e.g. footer)
|
|
@@ -2803,7 +2859,7 @@ def _paginated_book(module_list, toc_config, marker_config, separator,
|
|
|
2803
2859
|
#: configuration only — nothing that reaches the content of a block.
|
|
2804
2860
|
BOOK_DEFAULT_KEYS = frozenset({
|
|
2805
2861
|
"paginate", "page_width", "zoom", "export", "export_title", "loading",
|
|
2806
|
-
"chrome_banner", "banner_color", "doc_version", "lang",
|
|
2862
|
+
"chrome_banner", "banner_color", "doc_version", "lang", "html_export_zoom",
|
|
2807
2863
|
})
|
|
2808
2864
|
|
|
2809
2865
|
|
|
@@ -113,6 +113,16 @@ sys.exit(1) if i < r else sys.exit(0)" || \\
|
|
|
113
113
|
# Copy project files
|
|
114
114
|
COPY . .
|
|
115
115
|
|
|
116
|
+
# LaTeX for st_tikz (latex -> dvisvgm), only when the project's code uses it
|
|
117
|
+
# (#17): about 540 MB. The image's own .venv also mentions st_tikz, hence
|
|
118
|
+
# --exclude-dir.
|
|
119
|
+
RUN if grep -rqs --include='*.py' --exclude-dir=.venv "st_tikz" . ; then \\
|
|
120
|
+
apt-get update && apt-get install -y --no-install-recommends \\
|
|
121
|
+
texlive-latex-base texlive-latex-recommended texlive-latex-extra \\
|
|
122
|
+
texlive-pictures dvisvgm ghostscript \\
|
|
123
|
+
&& rm -rf /var/lib/apt/lists/* ; \\
|
|
124
|
+
fi
|
|
125
|
+
|
|
116
126
|
# Install any [[packs]] type="local" declared in stx.toml with a relative path.
|
|
117
127
|
# Absolute paths are skipped — handle them upstream via `stx deploy preflight`
|
|
118
128
|
# or by promoting the pack to git (`stx component promote`).
|
|
@@ -465,8 +465,12 @@ class HtmlExportBuffer:
|
|
|
465
465
|
body = self.get_body_html()
|
|
466
466
|
css = _load_default_css()
|
|
467
467
|
c = self.config
|
|
468
|
-
|
|
469
|
-
|
|
468
|
+
# ExportConfig > the session's theme (kept by st_book, as the PDF
|
|
469
|
+
# uses it; a runtime light/dark toggle will write there, #20) > config.
|
|
470
|
+
bg = (c.theme_bg or _session_theme_color("_stx_theme_bg")
|
|
471
|
+
or _get_theme_color("theme.backgroundColor", "#fff"))
|
|
472
|
+
text = (c.theme_text or _session_theme_color("_stx_theme_text")
|
|
473
|
+
or _get_theme_color("theme.textColor", "#333"))
|
|
470
474
|
link = c.theme_primary or _get_theme_color("theme.primaryColor", "#1155cc")
|
|
471
475
|
zoom_css = f" zoom: {c.zoom};\n" if c.zoom != 1.0 else ""
|
|
472
476
|
return (
|
|
@@ -552,6 +556,15 @@ def _escape_lang(lang: str | None) -> str:
|
|
|
552
556
|
return "en"
|
|
553
557
|
|
|
554
558
|
|
|
559
|
+
def _session_theme_color(key: str) -> str | None:
|
|
560
|
+
"""A theme colour ``st_book`` keeps in session state, or None (no session)."""
|
|
561
|
+
try:
|
|
562
|
+
value = st.session_state.get(key)
|
|
563
|
+
except Exception: # noqa: BLE001 — outside a Streamlit session
|
|
564
|
+
return None
|
|
565
|
+
return value if isinstance(value, str) and value else None
|
|
566
|
+
|
|
567
|
+
|
|
555
568
|
def _get_theme_color(option: str, fallback: str) -> str:
|
|
556
569
|
"""Read a Streamlit theme color from config, with fallback.
|
|
557
570
|
|
|
@@ -678,6 +691,22 @@ def export_pop_wrapper(close_tag: str) -> None:
|
|
|
678
691
|
_buffer.pop_wrapper(close_tag)
|
|
679
692
|
|
|
680
693
|
|
|
694
|
+
def apply_view_to_export_html(html: str, *, built_view: Optional[tuple] = None,
|
|
695
|
+
page_width: str, zoom: float) -> str:
|
|
696
|
+
"""Give an already generated export the current view's page width and zoom.
|
|
697
|
+
|
|
698
|
+
The full-book HTML is built once per session; when the view changed
|
|
699
|
+
since (*built_view* = ``(page_width, zoom)`` it was built with), a later
|
|
700
|
+
rule on ``.streamtex-page`` replaces the old values (#110). Blocks never
|
|
701
|
+
read the view's width or zoom, only this page rule does, so no rebuild
|
|
702
|
+
is needed. Unchanged when the view is the same.
|
|
703
|
+
"""
|
|
704
|
+
if built_view is not None and tuple(built_view) == (page_width, zoom):
|
|
705
|
+
return html
|
|
706
|
+
css = f".streamtex-page {{ max-width: {page_width}; zoom: {zoom:g}; }}"
|
|
707
|
+
return html.replace("</head>", f"<style>{css}</style>\n</head>", 1)
|
|
708
|
+
|
|
709
|
+
|
|
681
710
|
def generate_export_html() -> Optional[str]:
|
|
682
711
|
"""Return the full HTML document, or None when export is inactive."""
|
|
683
712
|
if _buffer is None:
|
|
@@ -168,7 +168,6 @@ def st_image(
|
|
|
168
168
|
_zoom = _st.session_state.get(f"{_prefix}_zoom") if _st else None
|
|
169
169
|
_dw = _st.session_state.get(f"{_prefix}_width") if _st else None
|
|
170
170
|
_dh = _st.session_state.get(f"{_prefix}_height") if _st else None
|
|
171
|
-
# logger.warning("[DIAG:APPLY] '%s' already_init=%s, zoom=%s, dw=%s, dh=%s, original_width=%s", name, _already_init, _zoom, _dw, _dh, width)
|
|
172
171
|
if _dw:
|
|
173
172
|
width = _dw
|
|
174
173
|
elif _zoom is not None and _zoom != 100:
|
|
@@ -473,17 +473,17 @@ def _load_display_from_metadata(name: str, prefix: str) -> None:
|
|
|
473
473
|
"""Load persisted display settings from metadata JSON into session_state.
|
|
474
474
|
|
|
475
475
|
Called once per session — if session_state already has values, skip.
|
|
476
|
+
A load that fails is retried on the next run (#18): the flag is set
|
|
477
|
+
only once the metadata was read, or found absent.
|
|
476
478
|
"""
|
|
477
479
|
init_key = f"{prefix}_initialized"
|
|
478
480
|
if st.session_state.get(init_key):
|
|
479
481
|
return
|
|
480
|
-
st.session_state[init_key] = True
|
|
481
482
|
|
|
482
483
|
try:
|
|
483
484
|
from .ai.history import get_current_metadata
|
|
484
485
|
meta = get_current_metadata(name)
|
|
485
486
|
if meta:
|
|
486
|
-
# logger.warning("[DIAG:LOAD] '%s' zoom=%s width=%s", name, meta.display_zoom, meta.display_width)
|
|
487
487
|
if meta.display_zoom is not None:
|
|
488
488
|
st.session_state.setdefault(f"{prefix}_zoom", meta.display_zoom)
|
|
489
489
|
if meta.display_width is not None:
|
|
@@ -492,12 +492,11 @@ def _load_display_from_metadata(name: str, prefix: str) -> None:
|
|
|
492
492
|
st.session_state.setdefault(f"{prefix}_height", meta.display_height)
|
|
493
493
|
if meta.display_keep_ratio is not None:
|
|
494
494
|
st.session_state.setdefault(f"{prefix}_keep_ratio", meta.display_keep_ratio)
|
|
495
|
+
st.session_state[init_key] = True
|
|
495
496
|
except Exception:
|
|
496
|
-
# Best-effort load:
|
|
497
|
-
# the
|
|
498
|
-
|
|
499
|
-
# enhancement, not a correctness requirement.
|
|
500
|
-
pass
|
|
497
|
+
# Best-effort load: the image renders with its default display
|
|
498
|
+
# settings this run, and the load is tried again on the next one.
|
|
499
|
+
logger.debug("Display settings of '%s' not loaded; will retry", name, exc_info=True)
|
|
501
500
|
|
|
502
501
|
|
|
503
502
|
def _persist_display_to_metadata(name: str, prefix: str) -> None:
|
|
@@ -508,11 +507,11 @@ def _persist_display_to_metadata(name: str, prefix: str) -> None:
|
|
|
508
507
|
|
|
509
508
|
current = get_current(name)
|
|
510
509
|
if not current:
|
|
511
|
-
logger.warning("
|
|
510
|
+
logger.warning("Display settings of '%s' not saved: no current image", name)
|
|
512
511
|
return
|
|
513
512
|
meta = get_current_metadata(name)
|
|
514
513
|
if not meta:
|
|
515
|
-
logger.warning("
|
|
514
|
+
logger.warning("Display settings of '%s' not saved: no metadata", name)
|
|
516
515
|
return
|
|
517
516
|
|
|
518
517
|
meta.display_zoom = st.session_state.get(f"{prefix}_zoom")
|
|
@@ -521,9 +520,8 @@ def _persist_display_to_metadata(name: str, prefix: str) -> None:
|
|
|
521
520
|
meta.display_keep_ratio = st.session_state.get(f"{prefix}_keep_ratio", True)
|
|
522
521
|
json_path = metadata_path_for(current)
|
|
523
522
|
save_metadata(meta, json_path)
|
|
524
|
-
logger.warning("[DIAG:PERSIST] '%s' saved display_zoom=%s to %s", name, meta.display_zoom, json_path)
|
|
525
523
|
except Exception:
|
|
526
|
-
logger.warning("
|
|
524
|
+
logger.warning("Display settings of '%s' not saved", name, exc_info=True)
|
|
527
525
|
|
|
528
526
|
|
|
529
527
|
def _render_display_tab(*, name, width, height, key_base):
|
|
@@ -34,6 +34,11 @@ _BADGE_BASE_CSS = (
|
|
|
34
34
|
"pointer-events:none; z-index:2;"
|
|
35
35
|
)
|
|
36
36
|
|
|
37
|
+
# Opt-in proportional pill (#55): sized on the IMAGE width (container query
|
|
38
|
+
# units resolve against the media box, declared a size container).
|
|
39
|
+
_BADGE_PROPORTIONAL_CSS = _BADGE_BASE_CSS.replace(
|
|
40
|
+
"padding:2px 8px; font-size:12px;", "padding:0.15em 0.6em; font-size:clamp(9px, 7cqw, 22px);")
|
|
41
|
+
|
|
37
42
|
# margin declarations carrying an ``auto`` value (centering). They must be
|
|
38
43
|
# transferred from the media style onto the wrapper — an auto margin left on
|
|
39
44
|
# the media alone stops centering once the media is boxed in a wrapper.
|
|
@@ -51,12 +56,21 @@ class MediaOverlay:
|
|
|
51
56
|
overrides it in cascade order) — e.g. a project convention.
|
|
52
57
|
:param aria_label: Accessible disclosure text (e.g. AI-transparency
|
|
53
58
|
wording, art. 50 AI Act); emitted as ``aria-label`` when non-empty.
|
|
59
|
+
:param proportional: Size the badge on the image rather than the
|
|
60
|
+
viewport (#55): the media box becomes a size container and the
|
|
61
|
+
default pill uses ``clamp(9px, 7cqw, 22px)``, so a thumbnail and a
|
|
62
|
+
hero get proportionate badges; ``cqw`` also works in ``css``. Needs
|
|
63
|
+
a definite width (``st_image``'s default ``100%`` or an explicit
|
|
64
|
+
one): with ``width="auto"`` the box is left as is (a size container
|
|
65
|
+
cannot shrink-wrap its image) and ``cqw`` falls back to the viewport.
|
|
66
|
+
Off by default: output byte-identical to earlier versions.
|
|
54
67
|
"""
|
|
55
68
|
|
|
56
69
|
text: str
|
|
57
70
|
position: str = "bottom-right"
|
|
58
71
|
css: str = ""
|
|
59
72
|
aria_label: str = ""
|
|
73
|
+
proportional: bool = False
|
|
60
74
|
|
|
61
75
|
def __post_init__(self):
|
|
62
76
|
if self.position not in _POSITION_CSS:
|
|
@@ -74,7 +88,8 @@ def build_overlay_span(overlay: MediaOverlay) -> str:
|
|
|
74
88
|
guarantees the badge never intercepts clicks (link wrapping stays fully
|
|
75
89
|
functional).
|
|
76
90
|
"""
|
|
77
|
-
|
|
91
|
+
base = _BADGE_PROPORTIONAL_CSS if overlay.proportional else _BADGE_BASE_CSS
|
|
92
|
+
badge_css = f"position:absolute; {_POSITION_CSS[overlay.position]} {base}"
|
|
78
93
|
if overlay.css:
|
|
79
94
|
badge_css = f"{badge_css} {overlay.css}"
|
|
80
95
|
aria_attr = (
|
|
@@ -122,6 +137,8 @@ def wrap_media_overlay(
|
|
|
122
137
|
f"position:relative; display:inline-block; width:{box_width}; "
|
|
123
138
|
f"max-width:100%; line-height:0;"
|
|
124
139
|
)
|
|
140
|
+
if overlay.proportional and box_width not in ("auto", "fit-content"):
|
|
141
|
+
box_css = f"{box_css} container-type:inline-size;"
|
|
125
142
|
return (
|
|
126
143
|
f'<span class="stx-media-box" style="{box_css}">'
|
|
127
144
|
f"{inner_html}{build_overlay_span(overlay)}</span>"
|
|
@@ -68,7 +68,20 @@ class PdfConfig:
|
|
|
68
68
|
"""Include background colors and gradients in the PDF."""
|
|
69
69
|
|
|
70
70
|
scale: float = 1.0
|
|
71
|
-
"""
|
|
71
|
+
"""Chromium's print scale (0.1–2.0), like "Scale" in Chrome's print dialog.
|
|
72
|
+
|
|
73
|
+
Shrinks what is sized in ``pt``/``px`` and lets the text reflow on the
|
|
74
|
+
gained width; what is sized in ``vw``/``vh`` stays proportional to the
|
|
75
|
+
page (Chromium resolves viewport units against the printed page). To
|
|
76
|
+
shrink everything uniformly, use :attr:`zoom`."""
|
|
77
|
+
|
|
78
|
+
zoom: int = 100
|
|
79
|
+
"""Zoom of the whole document in the PDF, in percent (10–400; #110).
|
|
80
|
+
|
|
81
|
+
A CSS ``zoom`` on ``body``, so it scales ``px``, ``pt``, ``vw`` and ``vh``
|
|
82
|
+
alike, and it multiplies with the view zoom the exported HTML already
|
|
83
|
+
carries (view 80 % × PDF 70 % = 56 %). The page still fills the paper
|
|
84
|
+
width; bookmarks and page breaks are unchanged. ``100`` = no change."""
|
|
72
85
|
|
|
73
86
|
content_width: int = 100
|
|
74
87
|
"""Content width as percentage of the page (10–100).
|
|
@@ -333,18 +346,34 @@ def _inject_content_width_css(html: str, content_width: int) -> str:
|
|
|
333
346
|
cw = max(10, min(100, content_width))
|
|
334
347
|
padding_pct = (100 - cw) / 2
|
|
335
348
|
|
|
349
|
+
# Left and right only (#110): a percentage padding resolves against the
|
|
350
|
+
# width, so on all four sides it also pushed the content down by the
|
|
351
|
+
# same amount (15 % of the page width at 70 %).
|
|
336
352
|
css = (
|
|
337
353
|
f".streamtex-page {{"
|
|
338
354
|
f" max-width: 100% !important;"
|
|
339
355
|
f" width: 100% !important;"
|
|
340
356
|
f" margin: 0 !important;"
|
|
341
|
-
f" padding: {padding_pct:.1f}% !important;"
|
|
357
|
+
f" padding: 0 {padding_pct:.1f}% !important;"
|
|
342
358
|
f" box-sizing: border-box !important;"
|
|
343
359
|
f"}}"
|
|
344
360
|
)
|
|
345
361
|
return html.replace("</head>", f"<style>{css}</style>\n</head>")
|
|
346
362
|
|
|
347
363
|
|
|
364
|
+
def _inject_zoom_css(html: str, zoom: int) -> str:
|
|
365
|
+
"""Zoom the whole document by *zoom* percent (CSS ``zoom`` on ``body``).
|
|
366
|
+
|
|
367
|
+
``body`` rather than ``.streamtex-page``: the page already carries the
|
|
368
|
+
view zoom, and zooms on nested elements multiply, where a second rule on
|
|
369
|
+
the same element would replace it. No-op at 100.
|
|
370
|
+
"""
|
|
371
|
+
z = max(10, min(400, int(zoom)))
|
|
372
|
+
if z == 100:
|
|
373
|
+
return html
|
|
374
|
+
return html.replace("</head>", f"<style>body {{ zoom: {z / 100:g}; }}</style>\n</head>")
|
|
375
|
+
|
|
376
|
+
|
|
348
377
|
# ---------------------------------------------------------------------------
|
|
349
378
|
# TOC → PDF Bookmarks
|
|
350
379
|
# ---------------------------------------------------------------------------
|
|
@@ -532,6 +561,7 @@ def export_pdf(
|
|
|
532
561
|
|
|
533
562
|
# Inject content-width CSS if narrower than 100%
|
|
534
563
|
html = _inject_content_width_css(html, cfg.content_width)
|
|
564
|
+
html = _inject_zoom_css(html, cfg.zoom)
|
|
535
565
|
|
|
536
566
|
# Inject TOC headings for PDF bookmark generation
|
|
537
567
|
use_outline = False
|
|
@@ -68,6 +68,7 @@ a {
|
|
|
68
68
|
--stx-GIANT-size: 196pt;
|
|
69
69
|
--stx-Giant-size: 128pt;
|
|
70
70
|
--stx-giant-size: 112pt;
|
|
71
|
+
--stx-HUGE-size: 104pt;
|
|
71
72
|
--stx-Huge-size: 96pt;
|
|
72
73
|
--stx-huge-size: 80pt;
|
|
73
74
|
--stx-LARGE-size: 64pt;
|
|
@@ -130,6 +131,7 @@ a {
|
|
|
130
131
|
--stx-GIANT-size: 128pt;
|
|
131
132
|
--stx-Giant-size: 84pt;
|
|
132
133
|
--stx-giant-size: 74pt;
|
|
134
|
+
--stx-HUGE-size: 69pt;
|
|
133
135
|
--stx-Huge-size: 64pt;
|
|
134
136
|
--stx-huge-size: 54pt;
|
|
135
137
|
--stx-LARGE-size: 48pt;
|
|
@@ -192,6 +194,7 @@ a {
|
|
|
192
194
|
--stx-GIANT-size: 80pt;
|
|
193
195
|
--stx-Giant-size: 56pt;
|
|
194
196
|
--stx-giant-size: 48pt;
|
|
197
|
+
--stx-HUGE-size: 45pt;
|
|
195
198
|
--stx-Huge-size: 42pt;
|
|
196
199
|
--stx-huge-size: 36pt;
|
|
197
200
|
--stx-LARGE-size: 32pt;
|
|
@@ -26,6 +26,8 @@ class StxStyles:
|
|
|
26
26
|
reset_bold = text.weights.normal_weight
|
|
27
27
|
italic = text.decors.italic_text
|
|
28
28
|
center_txt = text.alignments.center_align
|
|
29
|
+
cell_center = container.cells.center
|
|
30
|
+
"""Grid cell content centered both ways (``Container.cells.center_middle``)."""
|
|
29
31
|
|
|
30
32
|
reset = (text.colors.reset + BackgroundColors.reset_bg
|
|
31
33
|
+ text.weights.normal_weight + text.sizes.medium_size + text.alignments.left_align)
|
|
@@ -35,33 +37,33 @@ class StxStyles:
|
|
|
35
37
|
``st_mermaid``, or ``st_tikz`` to make diagrams readable on dark pages."""
|
|
36
38
|
|
|
37
39
|
GIANT = text.sizes.GIANT_size
|
|
38
|
-
"""196pt"""
|
|
40
|
+
"""196pt on desktop (default.css)"""
|
|
39
41
|
Giant = text.sizes.Giant_size
|
|
40
|
-
"""
|
|
42
|
+
"""128pt on desktop (default.css)"""
|
|
41
43
|
giant = text.sizes.giant_size
|
|
42
|
-
"""
|
|
44
|
+
"""112pt on desktop (default.css)"""
|
|
43
45
|
HUGE = text.sizes.HUGE_size
|
|
44
|
-
"""
|
|
46
|
+
"""104pt on desktop (default.css)"""
|
|
45
47
|
Huge = text.sizes.Huge_size
|
|
46
|
-
"""
|
|
48
|
+
"""96pt on desktop (default.css)"""
|
|
47
49
|
huge = text.sizes.huge_size
|
|
48
|
-
"""
|
|
50
|
+
"""80pt on desktop (default.css)"""
|
|
49
51
|
LARGE = text.sizes.LARGE_size
|
|
50
|
-
"""
|
|
52
|
+
"""64pt on desktop (default.css)"""
|
|
51
53
|
Large = text.sizes.Large_size
|
|
52
|
-
"""
|
|
54
|
+
"""48pt on desktop (default.css)"""
|
|
53
55
|
large = text.sizes.large_size
|
|
54
|
-
"""
|
|
56
|
+
"""32pt on desktop (default.css)"""
|
|
55
57
|
big = text.sizes.big_size
|
|
56
|
-
"""
|
|
58
|
+
"""24pt on desktop (default.css)"""
|
|
57
59
|
medium = text.sizes.medium_size
|
|
58
|
-
"""
|
|
60
|
+
"""16pt on desktop (default.css)"""
|
|
59
61
|
little = text.sizes.little_size
|
|
60
|
-
"""
|
|
62
|
+
"""12pt on desktop (default.css)"""
|
|
61
63
|
small = text.sizes.small_size
|
|
62
|
-
"""
|
|
64
|
+
"""8pt on desktop (default.css)"""
|
|
63
65
|
tiny = text.sizes.tiny_size
|
|
64
|
-
"""4pt"""
|
|
66
|
+
"""4pt on desktop (default.css)"""
|
|
65
67
|
|
|
66
68
|
# --- Indexed responsive scale (subscript access) ---
|
|
67
69
|
class _IndexedScale:
|