diff --git a/docs/CARD.md b/docs/CARD.md index 81ee67b..a1c766d 100644 --- a/docs/CARD.md +++ b/docs/CARD.md @@ -143,6 +143,17 @@ rozmiaru: - `label` jest stabilnym identyfikatorem używanym przez `figure_refs`; - `full_size_link` domyślnie włącza otwieranie zrzutu HTML w pełnym rozmiarze. +Jeśli każda figura ma odpowiadać osobnej stronie wydruku, sekcja deklaruje +`"asset_page_mode": "one-per-page"`. Generator wtedy wstawia jawne podziały +stron w TeX i buduje osobne płótna A4 w React/HTML. Pierwsza strona zachowuje +nagłówek sekcji, a kolejne są stronami kontynuacji z powtórzoną tabliczką +zasobu. Widok ekranowy rozdziela płótna odstępem `5cm`; reguła nie wpływa na +wydruk. + +Końcowy słownik jest domyślnie obecny w React/HTML dla zgodności. Karta, która +ma zawierać wyłącznie jawnie wymienione strony, ustawia na poziomie głównym +`"render_dictionary": false`. + ### Zadania `tasks` jest słownikiem zadań, a `tasks_order` określa ich kolejność. Każde diff --git a/react/src/main.tsx b/react/src/main.tsx index 15e23c4..de1c942 100644 --- a/react/src/main.tsx +++ b/react/src/main.tsx @@ -163,6 +163,7 @@ type Section = { id: string; index: number; title: string; + show_heading: boolean; header_html: string; left_margin_html: string; right_margin_html: string; @@ -182,7 +183,7 @@ type CardModel = { total_pages: number; front: any; sections: Section[]; - dictionary: { header_html: string; content_html: string }; + dictionary: { header_html: string; content_html: string } | null; toc: { id: string; label: string }[]; }; type NvimPanelSummary = { @@ -2714,10 +2715,12 @@ function SectionPage({ className="section-sheet" >
-

- {section.index} - {section.title} -

+ {section.show_heading && ( +

+ {section.index} + {section.title} +

+ )} {section.content_html && }
@@ -3117,13 +3120,15 @@ function App({ model }: { model: CardModel }) { /> ); })} - -

Słownik WE/EN/EK/KW

- -
+ {model.dictionary && ( + +

Słownik WE/EN/EK/KW

+ +
+ )} setShortcutsOpen(false)} /> diff --git a/react/src/react.css b/react/src/react.css index 89ac63d..3778a07 100644 --- a/react/src/react.css +++ b/react/src/react.css @@ -1,3 +1,23 @@ +.paper { + display: flex; + flex-direction: column; + gap: 5cm; +} +.paper > .sheet { + height: 297mm; + min-height: 297mm; + max-height: 297mm; + margin: 0 auto; + overflow: hidden; +} +.paper > .sheet > .sheet-content { + height: 297mm; + min-height: 297mm; + max-height: 297mm; + overflow-x: hidden; + overflow-y: auto; + scrollbar-gutter: stable; +} .viewer-chrome { position: sticky; top: 0; @@ -1471,6 +1491,17 @@ text.uml-step-element-active { } } @media print { + .paper { + display: block; + gap: 0; + } + .paper > .sheet, + .paper > .sheet > .sheet-content { + height: auto; + min-height: 0; + max-height: none; + overflow: visible; + } .viewer-chrome { display: none !important; } diff --git a/schemas/card-source.schema.json b/schemas/card-source.schema.json index 062f669..73c8004 100644 --- a/schemas/card-source.schema.json +++ b/schemas/card-source.schema.json @@ -31,6 +31,10 @@ "debug_checkpoints": { "$ref": "#/$defs/debugCheckpoints" }, + "render_dictionary": { + "type": "boolean", + "description": "Czy React/HTML ma dodać końcową stronę słownika WE/EN/EK/KW." + }, "template": { "type": "string", "minLength": 1 @@ -547,6 +551,10 @@ "content_tex": { "type": "string" }, + "asset_page_mode": { + "enum": ["flow", "one-per-page"], + "description": "Sposób paginacji assetów. one-per-page wymusza osobną kartkę A4 w HTML i PDF." + }, "area_tree_refs": { "$ref": "#/$defs/stringList" }, diff --git a/tools/render_card.py b/tools/render_card.py index 7feaa02..0d2412d 100644 --- a/tools/render_card.py +++ b/tools/render_card.py @@ -6901,7 +6901,10 @@ def tex_asset_path(data: dict, asset_path: str) -> str: def render_section_assets_tex(section: dict, data: dict) -> list[str]: lines: list[str] = [] - for asset in section.get("assets", []) or []: + one_per_page = section.get("asset_page_mode") == "one-per-page" + for asset_index, asset in enumerate(section.get("assets", []) or []): + if one_per_page and asset_index > 0: + lines.append(r"\clearpage") path = tex_asset_path(data, str(asset.get("path", ""))) width = float(asset.get("width", 1.0)) caption = tex_escape(str(asset.get("caption", ""))) @@ -7050,14 +7053,40 @@ def react_task_model( def react_card_model(data: dict) -> dict: equation_numbers, figure_numbers = collect_numbered_labels(data) sections = ordered_sections(data) - page_specs: list[tuple[dict, str | None]] = [] + page_specs: list[dict] = [] for section in sections: task_refs = section.get("task_refs", []) or [] if section.get("content_kind") == "tasks" and task_refs: - page_specs.extend((section, str(task_ref)) for task_ref in task_refs) + page_specs.extend( + { + "section": section, + "task_ref": str(task_ref), + "asset_indexes": None, + "include_body": True, + } + for task_ref in task_refs + ) + elif section.get("asset_page_mode") == "one-per-page" and section.get("assets"): + page_specs.extend( + { + "section": section, + "task_ref": None, + "asset_indexes": [asset_index], + "include_body": asset_index == 0, + } + for asset_index, _asset in enumerate(section.get("assets", []) or []) + ) else: - page_specs.append((section, None)) - total_pages = 2 + len(page_specs) + page_specs.append( + { + "section": section, + "task_ref": None, + "asset_indexes": None, + "include_body": True, + } + ) + include_dictionary = bool(data.get("render_dictionary", True)) + total_pages = 1 + len(page_specs) + int(include_dictionary) front_left, front_right = render_dual_margin_html( learning_tree_tags(data, column="ogolne", effect_color="green!50!black"), learning_tree_tags(data, column="zawodowe", effect_color="orange!85!black"), @@ -7092,7 +7121,10 @@ def react_card_model(data: dict) -> dict: ], } section_models: list[dict] = [] - for index, (section, task_ref) in enumerate(page_specs, start=1): + for index, page_spec in enumerate(page_specs, start=1): + section = page_spec["section"] + task_ref = page_spec["task_ref"] + include_body = bool(page_spec["include_body"]) left_margin, right_margin = render_section_margins_html(section, data) task_refs = section.get("task_refs", []) or [] tasks = ( @@ -7102,7 +7134,11 @@ def react_card_model(data: dict) -> dict: ) assets = [] include_section_assets = not task_ref or task_ref == str(task_refs[0]) - for asset in (section.get("assets", []) or []) if include_section_assets else []: + section_assets = (section.get("assets", []) or []) if include_section_assets else [] + asset_indexes = page_spec["asset_indexes"] + if asset_indexes is not None: + section_assets = [section_assets[asset_index] for asset_index in asset_indexes] + for asset in section_assets: source_path = str(asset.get("html_path") or asset.get("path", "")) interactive = copy.deepcopy(asset.get("interactive")) if interactive: @@ -7137,11 +7173,12 @@ def react_card_model(data: dict) -> dict: "id": html_id(f'section-{index}-{task_ref or "content"}'), "index": index, "title": title, + "show_heading": include_body, "header_html": render_page_header_html(data, index + 1, total_pages), "left_margin_html": left_margin, "right_margin_html": right_margin, "content_html": "" - if section.get("content_kind") == "tasks" + if section.get("content_kind") == "tasks" or not include_body else render_latex_fragment_html( str(section.get("content_tex", "")), equation_numbers, figure_numbers ), @@ -7150,7 +7187,7 @@ def react_card_model(data: dict) -> dict: "steps": [ {"id": str(step.get("id", "")), "title": str(step.get("title", ""))} for step in section.get("steps", []) or [] - ] if (not task_ref or task_ref == str(task_refs[0])) else [], + ] if include_body and (not task_ref or task_ref == str(task_refs[0])) else [], } ) return { @@ -7162,10 +7199,10 @@ def react_card_model(data: dict) -> dict: "dictionary": { "header_html": render_page_header_html(data, total_pages, total_pages), "content_html": render_learning_tree_overview_html(data), - }, + } if include_dictionary else None, "toc": [ {"id": section["id"], "label": f'{section["index"]}. {section["title"]}'} - for section in section_models + for section in section_models if section["show_heading"] ], }