diff --git a/Examples/Document Configuration/report.pdf b/Examples/Document Configuration/report.pdf index f870560..238f7dd 100644 Binary files a/Examples/Document Configuration/report.pdf and b/Examples/Document Configuration/report.pdf differ diff --git a/Examples/Document Configuration/report.yml b/Examples/Document Configuration/report.yml index e53fa7c..aaf624e 100644 --- a/Examples/Document Configuration/report.yml +++ b/Examples/Document Configuration/report.yml @@ -28,14 +28,6 @@ Customizing your document configuration and styling: - The document template (_doc): - > -<<<<<<< HEAD - The template controls settings such as - - _ul: - - Page size - - Margins - - PDF background file path - - And whether or not you have a separate set of the above settings applying to the first page of your document or not. -======= The `_doc` configuration controls the document-wide page size and orientation, along with one or more named `page templates`. Each page template controls settings such as - - Margins @@ -46,7 +38,6 @@ Customizing your document configuration and styling: can switch between page templates using the `_pagebreak` block (pass it the name or index of the template to use for the following pages) or the `_nextpagetemplate` block (which arms the template to switch to at the next page break). ->>>>>>> adb6527 (feat: implement multiple named page templates) - > Currently, the document template is internally configured to only use a single content frame. This limitation would only affect you if you wanted to do something like a two-column layout (where text flows into the second column on the same page once the first one is full). @@ -72,6 +63,12 @@ Customizing your document configuration and styling: - Font family - Text color - Typographic ratio (in musical intervals, e.g. "minor second" or "major third") + - Alignment (left, center, right, justify) and underline + + - > + Beyond the default `body` and `headings`, you can define named `styles` — alternate text + families you switch between within the document using the `_textstyle` block. Each named + style is a sparse override that inherits everything you don't change from the defaults. - Table styles (_tablestyle): - > You can control how your tables are displayed by modifying the table style. With the _tablestyle key, you can control diff --git a/docs/_static/examples/document-configuration-1.png b/docs/_static/examples/document-configuration-1.png index 2cdd09e..62ef6e7 100644 Binary files a/docs/_static/examples/document-configuration-1.png and b/docs/_static/examples/document-configuration-1.png differ diff --git a/docs/_static/examples/document-configuration-2.png b/docs/_static/examples/document-configuration-2.png index 5a4022d..6171a1d 100644 Binary files a/docs/_static/examples/document-configuration-2.png and b/docs/_static/examples/document-configuration-2.png differ diff --git a/docs/_static/examples/text-styles.png b/docs/_static/examples/text-styles.png new file mode 100644 index 0000000..ceb4bdd Binary files /dev/null and b/docs/_static/examples/text-styles.png differ diff --git a/docs/examples.md b/docs/examples.md index ab6712a..2875fde 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -81,6 +81,27 @@ block in the document front matter. See [Configuration](guide/configuration.md). --- +## Scoped text styles + +Define named text styles under `_style.styles`, then switch the active family part-way through +the document with the `_textstyle` block. Here a `fine-print` style (smaller, grey) and a +`callout` style (larger, blue, centred) are switched in and out — each swap restyles the body, +headings, and lists that follow it, and reverts when the section ends. See +[Configuration → Named text styles](#style-named). + +```{literalinclude} ../Examples/Text Styles/report.yml +:language: yaml +``` + +::::{container} ym-single-shot +```{image} _static/examples/text-styles.png +:alt: Scoped text styles — default, fine-print, and callout families switched within one document +:class: ym-page-shot +``` +:::: + +--- + ## Document variables Define `_vars`, render them into text with Jinja (`{{ "{{var}}" }}`), and reach into nested diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 2cc117e..5c76f3e 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -127,7 +127,9 @@ Report: (cfg-style)= ## Text styles — `_style` -Controls body and heading typography. +Controls body and heading typography. Alongside the default `body` and `headings` families, +`_style` can define **named text styles** under `styles` — alternate families you switch +between within the document with the [`_textstyle`](#style-switching) block. ```yaml _style: @@ -135,11 +137,13 @@ _style: font: Helvetica color: "#222222" ratio: Major Third # typographic scale as a musical interval + underline: false # underline every heading body: font: Helvetica color: black size: 10 # points spacing: 1.7 # line spacing ratio + align: left # left | center | right | justify bullets: font: Helvetica size: 10 @@ -148,6 +152,17 @@ _style: spacing: 10 indent-bullet: 20 indent-text: 40 + styles: # named styles (see "Named text styles" below) + fine-print: + body: + size: 8 + color: "#666666" + callout: + body: + size: 12 + align: center + headings: + ratio: major third ``` ### Headings @@ -157,6 +172,8 @@ _style: | `font` | Heading font family (see [Fonts](../reference/fonts.md)). | | `color` | Heading colour, hex or name. | | `ratio` | Typographic scale expressed as a **musical interval** — e.g. `minor third`, `major second`, `Major Third`. Larger intervals produce a bigger jump between heading levels. | +| `align` | Text alignment — `left`, `center`, `right`, or `justify` (default `left`). | +| `underline` | `true` to underline every heading (default `false`). | ### Body @@ -166,8 +183,81 @@ _style: | `color` | Body text colour. | | `size` | Body font size in points. | | `spacing` | Line-spacing ratio. | +| `align` | Text alignment — `left`, `center`, `right`, or `justify` (default `left`). | +| `underline` | `true` to underline body paragraphs (default `false`). | | `bullets` | Bullet styling — glyph hierarchy (`symbols`), colour, size, and indentation of the bullet (`indent-bullet`) and its text (`indent-text`). | +:::{note} +`align` and `underline` are available on `body`, `headings`, and every named style. Both are +inherited by named styles like any other field. +::: + +(style-named)= +### Named text styles + +Under `styles`, declare any number of **named families**. Each name maps to a **sparse +override** of the default family — you specify only what differs, and every unspecified field +(fonts, colours, bullets, alignment …) is inherited from `body`/`headings`. This mirrors the +inherit-from-defaults behaviour of the config system itself. + +```yaml +_style: + body: { font: NotoSans, size: 10, color: black, spacing: 1.7 } + headings: { font: AppleGaramond, ratio: minor third, color: "#dd9922" } + styles: + fine-print: # a smaller, grey family + body: + size: 8 + color: "#666666" + callout: # a larger, centred family with bigger headings + body: + size: 12 + align: center + headings: + ratio: major third +``` + +Switching to a named style swaps the **whole family** — body paragraphs, bullet lists, and +the derived `h1`…`h6` headings all follow the active style. Because heading sizes are derived +from `body.size` (`body.size × ratio`), a named style with a smaller `body.size` also shrinks +its headings. + +The special name **`default`** is always available and refers to the top-level `body`/ +`headings`. Switching to an undeclared name raises an error. + +(style-switching)= +### Switching text styles + +Within your report content, change the active family with the `_textstyle` block: + +- `_textstyle: ` — activate the named style (or `default`) **from that point onward + within its section**, and for every nested descendant. It renders nothing of its own. + +`_textstyle` is **scoped to the list it appears in**: it restyles every following sibling in +that frame plus their descendants, and reverts automatically when the section (list) ends. +There is no need to switch back manually unless you want to change style *within* the same +section. (This differs from page templates, which persist across sections until you switch +them again.) + +```yaml +Legal disclaimer: + - _textstyle: fine-print # applies from here down in THIS section + - This paragraph is fine-print. + - Sub-clause: + - Inherited fine-print (a nested descendant). + - _textstyle: default # switch back within the same section + - Back to the default style. + +Next section: + - Body style again — the disclaimer's scope ended with its list. +``` + +:::{note} +Named styles apply to **paragraphs, headings, and bullet/numbered lists**. Other blocks +(admonitions, quotes, code, images, tables) keep their own styling regardless of the active +text style. +::: + (cfg-tablestyle)= ## Table styles — `_tablestyle` diff --git a/docs/reference/blocks.md b/docs/reference/blocks.md index 38f9be0..aca027a 100644 --- a/docs/reference/blocks.md +++ b/docs/reference/blocks.md @@ -38,14 +38,11 @@ The suffix does not affect how the block is executed. It is simply an optional i | [`_code`](#block-code) | A non-executable code block. | | [`_py`](#block-py) | Execute Python and optionally show the syntax-highlighted source. | | [`_loadjson`](#block-loadjson) | Load variables into the document from a JSON file. | -<<<<<<< HEAD | [`_ul`](#block-ul) | An unordered (bulleted) list. | | [`_ol`](#block-ol) | An ordered (numbered) list. | -| [`_pagebreak`](#block-pagebreak) | Force a page break. | -======= | [`_pagebreak`](#block-pagebreak) | Force a page break, optionally switching page template. | | [`_nextpagetemplate`](#block-nextpagetemplate) | Arm the page template to switch to at the next break. | ->>>>>>> adb6527 (feat: implement multiple named page templates) +| [`_textstyle`](#block-textstyle) | Switch the active named text style for the rest of the section. | | [`_hrule`](#block-hrule) | A customizable horizontal rule. | | [`_spacer`](#block-spacer) | Insert vertical whitespace. | @@ -298,7 +295,7 @@ Recommended actions: ## `_pagebreak` — Page breaks Force a page break. Called with no value, it just breaks the page and keeps the current -[page template](configuration.md#cfg-doc). Pass the **name or 0-based index** of a page +[page template](#cfg-doc). Pass the **name or 0-based index** of a page template to switch to it for the pages that follow. ```yaml @@ -317,7 +314,7 @@ Report: (block-nextpagetemplate)= ## `_nextpagetemplate` — Switch template at the next break -Arm the [page template](configuration.md#cfg-doc) to switch to at the **next** page break, +Arm the [page template](#cfg-doc) to switch to at the **next** page break, without inserting a break itself. Useful when the break is produced elsewhere (for example, by content overflowing the page). Takes the template **name or 0-based index**. @@ -330,6 +327,36 @@ Report: --- +(block-textstyle)= +## `_textstyle` — Switch text style + +Activate a [named text style](#style-named) for the rest of the section it +appears in, and for every nested descendant. Takes a style **name** (declared under +`_style.styles`) or the special name `default`. It renders nothing of its own. + +The switch is **scoped to its list**: it restyles every following sibling plus their +descendants, then reverts automatically when the section ends — no manual switch-back is +needed unless you want to change style again within the same section. Switching swaps the +whole family, so body paragraphs, bullet lists, and derived headings all follow the active +style. An undeclared style name raises an error. + +```yaml +Legal disclaimer: + - _textstyle: fine-print # applies from here down in THIS section + - This paragraph is fine-print. + - Sub-clause: + - Inherited fine-print (a nested descendant). + - _textstyle: default # switch back within the same section + - Back to the default style. + +Next section: + - Body style again — the disclaimer's scope ended with its list. +``` + +See [Switching text styles](#style-switching) for the full behaviour. + +--- + (block-hrule)= ## `_hrule` — Horizontal rules diff --git a/src/ymprint/config/config_loaders.py b/src/ymprint/config/config_loaders.py index 5ace8b2..dd5bb21 100644 --- a/src/ymprint/config/config_loaders.py +++ b/src/ymprint/config/config_loaders.py @@ -38,7 +38,9 @@ def load_report_config(source_data: Optional[dict] = None, report_config_path: O raise FileNotFoundError(f"The supplied path for the report_config_path does not exist. Here is what you passed:\n\n{str(report_config_path.resolve())}") if report_config_path is not None and report_config_path.is_file(): - config_data = load_yaml(report_config_path) + # load_yaml returns None when the file exists but is empty; treat that as + # "no config here" rather than crashing on the .get() lookups below. + config_data = load_yaml(report_config_path) or {} config_styles = config_data.get("_style", {}) config_tablestyles = config_data.get("_tablestyle", {}) config_doctemplate = config_data.get("_doc", {}) diff --git a/tests/test_loadconfig.py b/tests/test_loadconfig.py index c03756a..84dbfee 100644 --- a/tests/test_loadconfig.py +++ b/tests/test_loadconfig.py @@ -40,6 +40,23 @@ def test_load_report_config(): # assert doc['_doc']['first-page']['cat'] == 'here' +def test_load_report_config_empty_file(tmp_path): + """An empty config file must be treated as 'no config', not crash. + + load_yaml returns None for an empty file; load_report_config should coalesce + that to an empty mapping and fall back to the internal defaults. + """ + empty_config = tmp_path / "doc.ymprint.yml" + empty_config.write_text("") + + styles, tablestyles, doc = load_report_config(None, empty_config) + + # Falls back to internal defaults rather than raising AttributeError. + assert doc['_doc']['page-size'] == 'a4' + assert styles['_style']['body']['size'] == 10 + assert tablestyles['_tablestyle']['headers']['text']['bold'] is True + + def test_load_doc_config(): source_data = { "_style": {