From 474977572f2cb4750465ec3d8785b2dbd4d7d94b Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 20:46:18 +0200 Subject: [PATCH 001/129] Add project-summary-rendering ADR suggestion --- .../suggestions/project-summary-rendering.md | 1878 +++++++++++++++++ 1 file changed, 1878 insertions(+) create mode 100644 docs/dev/adrs/suggestions/project-summary-rendering.md diff --git a/docs/dev/adrs/suggestions/project-summary-rendering.md b/docs/dev/adrs/suggestions/project-summary-rendering.md new file mode 100644 index 000000000..c4bb0b4c5 --- /dev/null +++ b/docs/dev/adrs/suggestions/project-summary-rendering.md @@ -0,0 +1,1878 @@ +# ADR: Project Summary Rendering + +**Status:** Proposed +**Date:** 2026-05-26 + +Defines the **non-CIF** human-readable rendering surface for a project: +what the terminal/Jupyter summary, the auto-generated HTML report, the +on-demand journal-style LaTeX export, and (eventually) the GUI Summary +tab all consume and emit. + +Runs alongside, and **extends**, the accepted +[`iucr-cif-tag-alignment.md`](../accepted/iucr-cif-tag-alignment.md) +ADR (landed as PR #184). The alignment ADR established: + +- A new `project.report` facade slot (replaces the unimplemented + `project.summary` placeholder), with `save()` and `check()` + methods. +- A single `reports/` directory at project root. +- A `project.save(report=True)` opt-in flag for the IUCr CIF. + +That ADR currently scopes `project.report` to **CIF only** — the +multi-datablock IUCr submission CIF written to +`reports/.cif`. This ADR keeps the facade and adds a +**`project.report` configuration category** with six scalar +persisted fields (`cif`, `html`, `tex`, `pdf`, `style`, +`html_offline`) on `project.cif`, plus ad-hoc per-format +methods (`save_html()`, `save_cif()`, `save_tex()`, +`save_pdf()`). The Python-side `project.report.formats` is a +convenience property view over the four format booleans. The +accepted IUCr `project.save(report=True)` flag is **removed**; +reports come from the config category, not from boolean flags. +All four format booleans default to `False` so `project.save()` +writes nothing under `reports/` until the user configures +otherwise, preserving the "no surprise files" property. + +Coordination points with the alignment ADR (no blocking conflicts; +its Open Questions section is empty): + +- **Software-stack identification** — the alignment ADR's §2.3a-i + defines `_easydiffraction_software.{framework, calculator, minimizer}` + as the structured CIF emission, plus a concatenated + `_computing.structure_refinement` free-text string. This ADR's §4 + adopts the same three-role triple as the Python-side attribute + layout so the same data flows into both write paths. +- **Spec-compliance validation** — the alignment ADR's §2.5 added + `project.report.check()` (gemmi-based) and a `check=True` + flag on `project.report.save()`. This ADR **removes the public + surface** and moves the gemmi pass to an internal pre-write + step **inside the CIF emission paths only** — `save_cif()` and + the `cif` branch under `project.save()` (§1.4). HTML, TeX, and + PDF outputs are not gemmi-validatable and get no pre-write + validation; LaTeX errors surface at PDF-compile time via the + TeX engine. A writer that emits non-compliant CIF raises + `EasyDiffractionWriterError` instead. This ADR's deferred + `check_completeness()` (publication-side completeness) is a + separate concern that stays in Deferred Work. +- **Publication metadata source** — the alignment ADR's Deferred + Work proposes a user-supplied `reports/publ_info.{toml,json}` + to replace `?` placeholders. Both write paths read the same + Python attribute, **`project.publication`** — a new top-level + on `Project`, sibling to `project.info` and `project.analysis`. + The schema is defined in §5 of this ADR: six CIF-aligned + sibling categories (`journal`, `journal_date`, `journal_coeditor`, + `contact_author`, `body`, `authors`) with full IUCr-tag fidelity. + The loader accepts TOML (primary) and JSON (fallback); selection + is by file extension. + +Also touches: + +- [`analysis-cif-fit-state.md`](../accepted/analysis-cif-fit-state.md) + — adds an `analysis.software` provenance category that serialises + through the analysis tier. +- [`minimizer-input-output-split.md`](../accepted/minimizer-input-output-split.md) + — the new provenance category lives alongside the existing + minimizer/fit-result pairing, not inside it. +- [`project-facade-and-persistence.md`](../accepted/project-facade-and-persistence.md) + — two changes: `project.report` gains a persisted + configuration category (`_report.*` in `project.cif`, see §1.3), + turning the facade into a hybrid of helper methods plus + persisted config; and a new top-level `project.publication` + owner is added alongside the existing `project.info`, + `project.structures`, `project.experiments`, + `project.analysis`, `project.report` facade slots (see §5). +- [`python-cif-category-correspondence.md`](python-cif-category-correspondence.md) + — owns the Python↔CIF correspondence rule for **two** new + project-level singleton surfaces: + `project.report.* ↔ _report.*` (six scalar items, §1.3) and + `project.publication.*` sibling categories ↔ `_journal.*`, + `_publ_author.*`, `_publ_contact_author.*`, etc. (§5). + +## Context + +The library today has four shapes of summary output: + +- `Report.show_report()` and friends — terminal/Jupyter rendering of + project metadata, crystallographic data per phase, experimental + configuration, and fit metrics + ([report.py](../../../../src/easydiffraction/report/report.py)). + (Pre-PR #184 this was `Summary.show_report()` on + `project.summary`; the IUCr alignment ADR replaced the unimplemented + placeholder.) +- `summary.cif` — was written into the project root on every + `project.save()` as the literal string `"To be added..."` until + PR #184 removed both the writer call and the placeholder method. + Not a valid CIF block in any version that shipped. +- The old GUI's "Summary" tab — a single page listing project info, + crystal data, data collection, refinement engine + goodness-of-fit, + with an "Export summary" panel (Name, Format = HTML, Location). +- An eventual journal manuscript — currently produced by hand from the + scientist's notes and the values shown in the GUI Summary tab. + +These four are renderings of the **same** logical view. Every field +the GUI shows is already reachable from the live Python objects; the +summary is not a source of truth and has no field of its own that +isn't computable from `project`, its `structures`, its `experiments`, +and `analysis.fit_results`. The exception is software provenance — +which calculation engine and minimizer (with versions) produced the +fit — which the library does not currently capture anywhere. + +Two pressures act on the design: + +- **GUI consistency.** The library and the GUI must show the same + numbers from the same data flow. The GUI Summary tab needs a + programmatic API, not a CIF or an HTML file to re-parse. +- **Submission-grade output.** Scientists publish in IUCr journals, + Phys. Rev. B, J. Appl. Cryst., and others. The CIF side of that is + covered by the alignment ADR's IUCr export. The **manuscript** + side (refinement tables formatted to journal style) is not. + +The default-save `summary.cif` placeholder was the visible artefact +of the unresolved design question. The alignment ADR has since +replaced the unimplemented `project.summary` slot with a +`project.report` facade scoped to IUCr CIF generation +(`reports/.cif`). That resolves the CIF half of the +question but leaves the GUI Summary tab, the terminal +`show_report()`, the human-readable HTML, and the manuscript-bound +LaTeX/PDF without a definition. This ADR fills the gap by extending +the same `project.report` facade with non-CIF rendering surfaces. + +## Scope + +In scope: + +- Extend the alignment ADR's `project.report` facade with + terminal/Jupyter, HTML, and LaTeX rendering surfaces, a + configuration category (six scalar fields — + `project.report.{cif, html, tex, pdf, style, html_offline}` — + persisted in `project.cif`; `project.report.formats` is a + property view over the four booleans), and ad-hoc per-format + save methods. **All report formats are opt-in via the + configuration; every format defaults to `False` so + `project.save()` writes nothing under `reports/` until a + format is enabled** — see §1 and §2 for the rationale. +- Define the shared "summary data context" (one dictionary) that + terminal, HTML, LaTeX, and GUI renderers all consume. +- Add a Python-side software-provenance category on `analysis` + (`analysis.software`) recording calculation-engine and + minimization-engine name + version + URL stamped at fit time. + Persisted in `analysis/analysis.cif` (amends the IUCr ADR's + "Analysis — unchanged" stance for these fields; see §4 and the + ADRs-amended list). +- Add a new top-level `project.publication` owner on `Project` + (sibling to `project.info`, `project.structures`, + `project.experiments`, `project.analysis`, `project.report`) + carrying the `_publ_*` / `_journal_*` publication metadata + the IUCr writer otherwise emits as `?` placeholders. See §5; + amends `project-facade-and-persistence.md` and complements + `python-cif-category-correspondence.md`. +- Sketch the journal-style selector hook for LaTeX (start with one + style; add more by template, not by code change). + +Out of scope: + +- CIF tag-name decisions for any serialised field. Those are the + alignment ADR's job; this ADR notes recommended mappings and + cross-references. +- The IUCr CIF submission export tag policy and multi-datablock + layout. Covered by the alignment ADR; the output file lives at + `reports/.cif` and is opt-in via + `project.report.formats = ['cif', ...]`. +- Pre-existing project-level singleton categories (`_info.*`, + `_chart.*`, `_table.*`, `_verbosity.*`). Covered by the + in-flight + [`python-cif-category-correspondence.md`](python-cif-category-correspondence.md). + This ADR **does** add one new project-level singleton + category, `_report.*`, alongside them (see §1.3 and the + ADRs-amended list); that surface is not delegated to the + correspondence ADR. +- Static-image PDF generation. HTML prints from any browser; LaTeX + compiles to PDF locally. No bundled PDF writer. +- Markdown export. Trivial follow-on if the Jinja base templates are + in place, but no current user requirement. + +## Design Philosophy: Summary as a View + +Summary is a **derived view**, not a persisted artifact. The same +data dictionary feeds every renderer: + +``` +project, structures[], experiments[], analysis.fit_results, +analysis.software (new — see §4) + │ + ▼ + ReportDataContext (in-memory dict, single source of truth) + │ + ├──► terminal/Jupyter renderer (existing show_*; on demand) + ├──► HTML renderer (Jinja; opt-in via html_report) + ├──► LaTeX renderer (Jinja; opt-in via tex_report/pdf_report, style-selectable) + └──► GUI Summary tab (programmatic; eventual) +``` + +Render targets never duplicate the data — they consume the same +context. New summary fields are added in one place (the context +builder); every renderer picks them up. + +## Decision + +### 1. Extend `project.report` with rendering methods and a config category + +The alignment ADR has already created the `project.report` facade +(replacing the unimplemented `project.summary` placeholder). This +ADR extends it along two axes: + +- A new **configuration category** on `project.report` — + persisted in `project.cif`, matching the existing + `project.chart`, `project.table`, `project.verbosity` config + pattern — that records *which* report formats `project.save()` + emits and *how*. +- A new set of **ad-hoc per-format methods** for explicit + one-off writes that bypass the configuration. + +The accepted IUCr `project.save(report=True)` flag and the +public `project.report.check()` method are both **removed** (see +the ADRs-amended list). Reports come from configuration, not +boolean flags; dictionary-spec validation runs internally +before every CIF write (and only before CIF writes — HTML, TeX, +and PDF have no spec to validate against; see §1.4) and +surfaces as an error on writer bugs, not as a user opt-in. + +#### 1.1 Configuration category — `project.report.*` + +Persisted fields on `project.report`, populated by the user +once and read by `project.save()` thereafter: + +| Field | Type | Default | Effect | +| --------------------------- | ------------ | --------- | ------------------------------------------------------------------------------------------------------ | +| `project.report.cif` | `bool` | `False` | When `True`, `project.save()` writes `reports/.cif`. | +| `project.report.html` | `bool` | `False` | When `True`, `project.save()` writes `reports/.html`. | +| `project.report.tex` | `bool` | `False` | When `True`, `project.save()` writes `reports/tex/{.tex, figures/, styles/}`. | +| `project.report.pdf` | `bool` | `False` | When `True`, `project.save()` writes `reports/.pdf` (and `tex/` as a side-effect). | +| `project.report.style` | `str` (Enum) | `'iucr'` | LaTeX style. Values: `'iucr'`, `'revtex'`. Only meaningful when `tex` or `pdf` is `True`. | +| `project.report.html_offline` | `bool` | `False` | When `True`, the HTML report inline-bundles Plotly (~3 MB extra). Otherwise loads Plotly from CDN. | + +Four per-format scalar booleans (`cif`, `html`, `tex`, `pdf`) +plus two scalars (`style`, `html_offline`) — six fields total, +all single-row in CIF. Matches the existing `project.chart`, +`project.table`, `project.verbosity` scalar-config shape +verbatim. All booleans default to `False`, so an unconfigured +project produces no `reports/` directory at all. + +For convenience, `project.report.formats` is exposed as a +**property view** — reading it returns a list of the +currently-`True` formats; assigning a list flips the four +booleans to match: + +```python +project.report.formats # → [] +project.report.formats = ['cif', 'html'] +project.report.cif # → True +project.report.html # → True +project.report.tex # → False +project.report.formats # → ['cif', 'html'] +``` + +The list view is the more idiomatic surface for "set of enabled +formats"; the booleans are what CIF persists. Both spellings are +equivalent and round-trip cleanly. + +```python +import easydiffraction as ed + +project = ed.Project() +# … set up structures, experiments, run fit … + +# Configure once — persisted in project.cif (see §1.3 below). +# Either spelling works: +project.report.formats = ['cif', 'html'] +# or, equivalently: +# project.report.cif = True +# project.report.html = True +project.report.style = 'iucr' +project.report.html_offline = False + +# Every subsequent save now emits the configured reports too. +project.save() +# → project.cif (with _report.* config block) +# → structures/<...>.cif, experiments/<...>.cif, analysis/analysis.cif +# → reports/.cif (because project.report.cif is True) +# → reports/.html (because project.report.html is True) +``` + +##### Enum backing per the closed-values ADR + +Both the format set and the style selector are finite closed +sets, so per the accepted +[`enum-backed-closed-values.md`](../accepted/enum-backed-closed-values.md) +contract they are represented internally as `(str, Enum)`: + +```python +class ReportFormatEnum(str, Enum): + CIF = 'cif' + HTML = 'html' + TEX = 'tex' + PDF = 'pdf' + +class ReportStyleEnum(str, Enum): + IUCR = 'iucr' + REVTEX = 'revtex' +``` + +The four per-format booleans (`project.report.cif`, `.html`, +`.tex`, `.pdf`) carry one `ReportFormatEnum` member each as a +class-level constant identifying which format they enable. The +`project.report.style` setter accepts either an Enum member or +the string value (the project's existing convenience pattern); +dispatch and equality checks use enum members, not raw strings. +The `formats` property view returns a list of +`ReportFormatEnum` members (`[ReportFormatEnum.CIF, +ReportFormatEnum.HTML]`), which compare equal to the bare +string values for ergonomic user code (`'cif' in +project.report.formats` still works because `(str, Enum)` +inherits string equality). + +CIF serialisation uses the enum string values verbatim +(`_report.style iucr`); the writer rejects any value not in the +declared enum at write-time, matching the gemmi pass's +dictionary-spec check. + +#### 1.2 Ad-hoc per-format methods + +Each format has its own explicit write method on the facade, +independent of `project.report.formats`. Use when a user wants +to produce a one-off artifact without changing the persistent +configuration. + +```python +project.report.save_cif() # writes reports/.cif +project.report.save_html(offline: bool = False) # writes reports/.html +project.report.save_tex(style: str = 'iucr') # writes reports/tex/{.tex, ...} +project.report.save_pdf(style: str = 'iucr') # writes reports/.pdf (compiles TeX too) + +# Convenience: write everything currently in project.report.formats. +# Raises ValueError if no formats are configured (see below). +project.report.save() # reads config, no flags + +# Ad-hoc string returns (unchanged from the earlier draft): +project.report.as_html(offline: bool = False) -> str +project.report.as_tex(style: str = 'iucr') -> str + +# Shared data context (for GUI Summary tab + Jinja templates): +project.report.data_context() -> dict + +# Terminal / Jupyter renderers (existing methods, migrated to +# project.report by PR #184 — names preserved): +project.report.show_report() # full report — sections below +project.report.show_project_info() +project.report.show_crystallographic_data() +project.report.show_experimental_data() +project.report.show_fitting_details() +``` + +Per-format method signatures only carry the args that apply to +that format — `save_pdf(style='revtex')` is unambiguous; there +is no `save_html(style=...)` because HTML has no journal style. +The cross-format mixing that the earlier flag-based draft had +(`html_offline` ignored when `html=False`, `style=` ignored +without `tex=True`) is gone. + +`project.save()` itself takes no report-related arguments. The +accepted IUCr `project.save(report=True)` flag is removed (see +ADRs amended); reports are configured on `project.report.*`. + +```python +project.save() # writes project files + whatever is in project.report.formats +``` + +`Summary.as_cif()` and `summary_to_cif()` were already deleted +by the alignment ADR; this ADR's removal of `project.save(report=True)` +finishes the flag-cleanup. + +**Empty-configuration behaviour split.** + +The two entry points behave differently when no formats are +enabled — a deliberate split: `project.save()` writes the +project regardless (reports are a side-effect of configuration, +not the point of the call); `project.report.save()` is *only* +about reports, so calling it with nothing configured is a user +error. + +```python +# project.report.formats == [] (default — unconfigured) + +project.save() +# → writes project.cif + structures/ + experiments/ + analysis/ +# → reports/ is NOT created (no formats enabled — correct default +# behaviour, no error, no warning). + +project.report.save() +# → raises: +# ValueError( +# "project.report.save() called with no formats enabled. " +# "Set project.report.{cif,html,tex,pdf} = True (or assign a " +# "list via project.report.formats), or call a per-format " +# "method directly (project.report.save_html(), etc.)." +# ) +``` + +The Python error matches the CLI's existing behaviour for +`ed save-report` with no flags (§7) — both surfaces refuse to +silently no-op when the user explicitly asked for a report. +`project.save()` keeps the no-report default because the user +asked to save the project, not the reports. + +The per-format methods (`save_cif()`, `save_html()`, etc.) +never inspect `project.report.formats` — they always write +their format unconditionally. They are explicit one-offs. + +#### 1.3 CIF persistence of the configuration + +The configuration category serialises to `project.cif` next to +the other project-level singleton categories (`_info.*`, +`_chart.*`, `_table.*`, `_verbosity.*`). The CIF tag prefix is +`_report.*` — a Set category with six scalar items, no loops: + +```text +data_ + +# ---- Project info (from project-facade-and-persistence ADR) ---- +_info.title 'Quartz at 300 K' +_info.description 'Refinement against XRD pattern xrd_300K.' +_info.created 2026-05-26T12:00:00 +_info.last_modified 2026-05-26T15:42:00 + +# ---- Chart / table / verbosity selectors (existing config) ---- +_chart.type plotly +_table.type plotly +_verbosity.fit short + +# ---- Report configuration (this ADR §1.3) ---- +_report.cif yes +_report.html yes +_report.tex no +_report.pdf no +_report.style iucr +_report.html_offline no +``` + +All six items are scalar DDLm dotted entries — the category is +declared `_definition.class Set` so a single value per item, no +loops permitted. Matches the existing `_chart.*`, `_table.*`, +`_verbosity.*` category shape exactly. The `yes`/`no` boolean +encoding follows the project's existing CIF boolean convention. + +The default unconfigured state writes four explicit `no` values +(not an absent or empty representation), so the "no formats +enabled" condition is always a concrete CIF value, never an +empty loop or missing block: + +```text +# Default (project.report.formats = []): +_report.cif no +_report.html no +_report.tex no +_report.pdf no +_report.style iucr +_report.html_offline no +``` + +Load semantics are symmetric: every `no` reads back as `False` +on its descriptor; the `formats` property view returns `[]`. + +The four per-format booleans give the IUCr-aware tooling +(`gemmi`, `publCIF`) a typed, validatable view of the +configuration — each format is a known enum item with type +`Boolean`, not a parsed string. The Python list view +(`project.report.formats`) is a convenience computed from these +four booleans at access time; it has no separate CIF storage. + +Adding a new format in the future (e.g. `markdown`) is a +one-line schema extension: add `_report.markdown` to the +dictionary and a `project.report.markdown` boolean to the +descriptor. The list property picks it up automatically. + +Loading a `project.cif` populates `project.report.*` per the +project-facade-and-persistence contract; on the next +`project.save()`, the configured formats emit automatically with +no further user action. + +##### Why not its own CIF file? + +The project already has two distinct facade patterns for +top-level `project.*` slots, used deliberately for different +purposes. `project.report` is **Pattern A** — lightweight +project-level singleton config — not Pattern B — heavy datablock +owner with its own CIF file. The split is summarised below. + +| Slot | Pattern | CIF location | Python shape | +| -------------------------------------- | ------- | ------------------------------------------- | ----------------------------------------------------------- | +| `project.info` | A | `project.cif` (`_info.*`) | small `CategoryItem` | +| `project.chart` | A | `project.cif` (`_chart.*`) | `CategoryItem` (one field) | +| `project.table` | A | `project.cif` (`_table.*`) | `CategoryItem` (one field) | +| `project.verbosity` | A | `project.cif` (`_verbosity.*`) | `CategoryItem` (one field) | +| **`project.report`** (this ADR) | **A** | **`project.cif` (`_report.*`)** | **`CategoryItem` (six fields) plus action methods** | +| `project.publication` (this ADR, §5) | A | `project.cif` (`_publ_*` / `_journal_*`) | `CategoryOwner` of six sibling categories | +| `project.analysis` | B | `analysis/analysis.cif` | `CategoryOwner` (heavy datablock) | +| `project.structures[name]` | B | `structures/.cif` | `CategoryOwner` (heavy datablock) | +| `project.experiments[name]` | B | `experiments/.cif` | `CategoryOwner` (heavy datablock) | + +Reasons `project.report` is Pattern A, not Pattern B: + +- Six scalar config items do not justify a separate file + (`reports/report.cif` would be a tiny file holding six lines). +- A `reports/report.cif` would force the `reports/` directory to + exist even when every format boolean is `False` and no reports + are written — breaks the "no surprise files" property the + design is built around. +- Splits report configuration from chart / table / verbosity + configuration, which already share `project.cif` for the same + reason — they are all project-level preferences, not domain + data. + +What makes `project.report` look heavier than `project.chart` / +`project.table` / `project.verbosity` is the action methods on +the facade (`save_cif()`, `save_html()`, `show_report()`, +`data_context()`, etc.). Those live on the Python class +alongside the configuration fields, which is the facade-hybrid +amendment to `project-facade-and-persistence.md` already +recorded in the ADRs-amended list. The action methods do not +change where the configuration persists — that stays in +`project.cif`. + +#### 1.4 Validation moves internal — CIF only, writer-correctness only + +The accepted IUCr ADR §2.5 exposed `project.report.check()` and +a `check=True` flag for gemmi-based dictionary-spec validation. +Both are **removed** in favour of a pre-write self-check inside +the CIF emission paths only: + +```text +project.save() + └─ for each enabled format: + ├─ build the format content + ├─ if format ∈ {cif}: run gemmi against the content + │ └─ on failure → raise EasyDiffractionWriterError + │ pointing at the malformed tag, + │ with a "please file a bug" hint. + ├─ if format ∈ {html, tex, pdf}: no pre-write validation + │ — see scope below. + └─ atomically write the file +``` + +**Scope split — what is validated and how:** + +| Output | Validation | Failure mode | +| ----------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------- | +| `reports/.cif` | gemmi against `cif_core.dic` / `cif_pow.dic` | `EasyDiffractionWriterError` (writer bug — file an issue) | +| `reports/.html` | none at write time | n/a — HTML is a render of the data context, not a typed format | +| `reports/tex/` | none at write time | n/a — LaTeX errors surface at PDF-compile time, with the engine's message | +| `reports/.pdf` | TeX engine's own compilation (returns non-zero on error) | engine-specific message; the `.tex` and figures are still written | + +User-input validation (e.g., "is the email address syntactically +valid?", "is the ORCID well-formed?") happens **upstream** at +the descriptor's `value_spec` validator — the same boundary +where every other user input is checked. That's a separate +concern from the writer self-check above: descriptor validators +raise `typeguard.TypeCheckError` or the project's +`ValidationError` at *assignment time*, before any save is +attempted. By the time the writer runs, the values it receives +are already shape-correct; the gemmi pass on the CIF output +catches *writer* bugs (wrong tag, wrong type, malformed loop), +not user bugs. + +Rationale: dictionary compliance is a *writer-correctness* +property, not a user choice. A user can't fix a non-compliant +emission without modifying project state — and even then, the +writer should refuse to emit a malformed file in the first +place. Making validation a user-visible API surface invites +users to skip it; making it internal makes it impossible to +skip. Cached dictionary parsing keeps the overhead to a one-time +~200 ms session cost. The `EasyDiffractionWriterError` includes +the full gemmi diagnostic so bug reports are actionable. + +A separate, *completeness*-oriented check +(`project.report.check_completeness()`) — flagging unfilled +`_publ_*` / `_journal_*` placeholders for journal submission, +which is a publication-readiness question rather than a +writer-correctness one — is a different concern and stays in +Deferred Work. + +### 2. HTML report — config-driven via `project.report.formats` + +`'html' in project.report.formats` causes `project.save()` to +write `reports/.html`. The empty default +(`formats = []`) keeps `reports/` from being touched at all on +plain `project.save()`. For one-off HTML without changing the +persistent config, call `project.report.save_html()` directly. + +```python +# Persistent — every subsequent save writes the HTML report. +project.report.formats = ['html'] +project.report.html_offline = False # CDN-Plotly (default) +project.save() # → reports/.html + +# Persistent + air-gapped readers — inline-bundle Plotly. +project.report.html_offline = True +project.save() # → reports/.html (~3 MB) + +# One-off, ignoring config. +project.report.save_html() # CDN-Plotly +project.report.save_html(offline=True) # inline bundle +``` + +Plotly bundle modes: + +- **CDN mode (default)** — `include_plotlyjs='cdn'`. File size + ~50-300 KB depending on chart count. Requires internet to view. +- **Offline mode** (`project.report.html_offline = True` or + `save_html(offline=True)`) — `include_plotlyjs=True`. Adds + ~3 MB per HTML. Use when readers are air-gapped or when the + user wants to archive a fully self-contained report. + +`reports/` is created lazily — only when at least one format is +configured (or an ad-hoc method is called). A user iterating on +a fit with the default `formats = []` produces no extra files. + +Rationale for the config category (replacing the earlier +flag-based and "auto on every save" positions): + +- Reports are a *project preference*, not a per-call argument. + `project.chart.type`, `project.table.type`, + `project.verbosity.fit` follow the same pattern — set once, + persisted in `project.cif`, applied on every save. +- `project.save()` has one job: save the project. With + `formats = []` the report behaviour is unchanged from before + this ADR; with `formats = ['html']` HTML appears on every + save without needing a flag on each call. +- The GUI's Summary tab consumes `project.report.data_context()` + in-memory, not the HTML file — so the GUI-consistency story + does not depend on the HTML file existing at any particular + moment. +- No new dependencies for HTML: `plotly`, `jinja2`, `pandas` + are already declared in + [pyproject.toml](../../../../pyproject.toml). + +Content (one HTML page per project — per-project granularity matches +the IUCr "one CIF per article" convention): + +- Project info — title, description, phase count, experiment count. +- Crystal data per phase — phase id, space group, cell parameters, + atom-sites table. +- Data collection per experiment — experiment id, type fields, + measured range + number of points. Per-experiment sections are + anchor-linkable for navigation. +- Refinement — calculation engine + version + URL, minimization + engine + version + URL, goodness-of-fit, parameter counts + (total/free/fixed), constraint count. +- Fit charts per experiment — Plotly figures embedded inline via + `fig.to_html(include_plotlyjs=)`. Reuses the existing + `display/plotters/plotly.py` figures. +- Footer — EasyDiffraction version, save timestamp. + +### 3. LaTeX + figures + PDF — config-driven via `project.report.formats` + +LaTeX is a **publish-time** artifact. `'tex'` and `'pdf'` are +added to `project.report.formats` when the user wants them. +`project.report.style` selects the journal style — only +meaningful when `'tex'` or `'pdf'` is in `formats` (or when an +ad-hoc method is called); HTML and CIF have no style choice. + +```python +# Persistent — every save writes TeX + assets. +project.report.formats = ['tex'] +project.report.style = 'iucr' +project.save() # → reports/tex/{...} + +# Persistent — every save writes the compiled PDF too. +project.report.formats = ['tex', 'pdf'] +project.save() # → reports/tex/{...} + reports/.pdf + +# Persistent — switch style. +project.report.style = 'revtex' +project.save() # → reports/tex/{...} (REVTeX class) + +# One-off, ignoring config. +project.report.save_tex(style='iucr') # TeX + figures + styles only +project.report.save_pdf(style='revtex') # TeX + PDF (PDF implies TeX) + +# Ad-hoc string return. +project.report.as_tex(style='iucr') -> str +``` + +**`'pdf' in formats` implies the TeX source is also written** — +a PDF without the editable `.tex` source is useless if the user +wants to tweak before re-compiling. Asking for the PDF always +writes the TeX next to it. Equivalently, `save_pdf()` writes +the TeX assets as a side-effect. + +Future `project.report.html_style` (dark mode, journal-mimicking +HTML layout) can land separately without collision because it +lives in the config category, not in a method signature. + +#### 3.1 Folder layout + +Per-project filenames (`.{cif,html,pdf}`) share a root in +`reports/`; the LaTeX source plus its assets sit in `reports/tex/`. +Single-style today; multi-style ships all class files together so +the user can swap styles by editing one line in `.tex` and +rebuilding the PDF. + +**Full reports/ tree when all formats are configured.** +`project.report.formats = ['cif', 'html', 'tex', 'pdf']`: + +``` +/ + reports/ # populated by project.save() per config + .cif # ← 'cif' in project.report.formats (alignment ADR §2) + .html # ← 'html' in project.report.formats (this ADR §2) + .pdf # ← 'pdf' in project.report.formats (this ADR §3.4) + tex/ # ← 'tex' or 'pdf' in project.report.formats (this ADR §3) + .tex # main document; \input{}'s tables, \includegraphics figures + figures/ + fit_.pdf # one per experiment, vector PDF (kaleido) + styles/ # always-bundled — minimum to compile both styles + iucrjournals.cls # IUCr unified class (CC0 1.0) + harvard.sty # IUCr companion bibliography style + revtex4-2.cls # REVTeX class (LPPL 1.3c) + ltxgrid.sty # REVTeX page-grid dep + ltxutil.sty # REVTeX utilities dep + ltxfront.sty # REVTeX front-matter dep + ltxdocext.sty # REVTeX document-ext dep + revsymb4-2.sty # REVTeX symbols + aps4-2.rtx # REVTeX: APS journals (PRB, PRA, PRL, PRD) + aps10pt4-2.rtx # REVTeX: 10pt font size + aps11pt4-2.rtx # REVTeX: 11pt font size + aps12pt4-2.rtx # REVTeX: 12pt font size + # 12 files, ~420 KB. AIP/AAPM/SOR/RMP .rtx and + # all .bst BibTeX files excluded; see §3.2.1. +``` + +**Examples by configuration.** + +`project.report.formats = []` (default — nothing written): + +``` +/ + project.cif # _report.{cif,html,tex,pdf} = no + structures/<...>.cif + experiments/<...>.cif + analysis/analysis.cif + # reports/ directory does not exist +``` + +`project.report.formats = ['cif']` (journal-submission CIF only): + +``` +/ + project.cif + structures/<...>.cif + experiments/<...>.cif + analysis/analysis.cif + reports/ + .cif # IUCr-aligned, multi-datablock (alignment ADR §2.3) +``` + +`project.report.formats = ['html']` + `html_offline = True` +(self-contained inspection page): + +``` +/ + project.cif + structures/<...>.cif + experiments/<...>.cif + analysis/analysis.cif + reports/ + .html # ~3 MB, Plotly inlined +``` + +`project.report.formats = ['cif', 'html', 'pdf']` ++ `style = 'iucr'` (typical pre-submission bundle): + +``` +/ + project.cif + structures/<...>.cif + experiments/<...>.cif + analysis/analysis.cif + reports/ + .cif # journal-submission CIF + .html # interactive inspection page + .pdf # IUCr-style typeset refinement table + tex/ # source for the PDF (kept editable) + .tex + figures/fit_.pdf + styles/iucrjournals.cls # only the IUCr files are referenced; + styles/harvard.sty # the REVTeX files still ship in the bundle + ... # so the user can swap style with one edit +``` + +`reports/` is created lazily — only when at least one format +sits in `project.report.formats` (or an ad-hoc method is called). +The `tex/`, `tex/figures/`, and `tex/styles/` subfolders appear +only when `'tex'` or `'pdf'` is in `project.report.formats` (or +`save_tex()` / `save_pdf()` is invoked). + +The `` portion of every filename comes from +`project.info.name` verbatim (e.g. a project named +`La0.5Ba0.5CoO3_HRPT` produces `reports/La0.5Ba0.5CoO3_HRPT.html`). +Only filesystem-dangerous characters (path separators, control +chars) are sanitized; case, dots, underscores, and parentheses are +preserved so the user recognises their project name in the file +listing. + +#### 3.2 Style selection — two slugs (`iucr`, `revtex`), mixed bundling strategy + +Two style slugs ship in v1: + +| Slug | Class file | Default `\documentclass` | License | Distribution | +| ----------------- | --------------------- | ------------------------------------ | -------------- | ------------------------------------- | +| `iucr` (default) | `iucrjournals.cls` | `\documentclass{iucrjournals}` | CC0 1.0 | Bundled in wheel (no CTAN package) | +| `revtex` | `revtex4-2.cls` | `\documentclass[prb]{revtex4-2}` | LPPL 1.3c | Bundled in wheel (also on CTAN `revtex`) | + +The two styles differ in **how the sub-journal is selected**: + +- **`iucr` is a unified class.** `iucrjournals.cls` does not use + document-class options. The same class is used across all IUCr + journals (Acta Cryst E/B/C/D/F, J. Appl. Cryst., + J. Synchrotron Rad., IUCrData) — the sub-journal designation + is decided at submission via IUCr's web form, not in the `.tex`. +- **`revtex` uses an option letter.** `\documentclass[prb]{revtex4-2}` + for Phys. Rev. B; the user changes the letter (`pra`, `prl`, + `prd`, `reprint`, …) to swap sub-journal and rebuilds. The + library emits a comment block immediately above the + `\documentclass` line listing the alternatives: + + ```latex + % Change the option letter to target a different APS journal: + % prb = Phys. Rev. B [default] + % pra = Phys. Rev. A + % prl = Phys. Rev. Letters + % prd = Phys. Rev. D + % reprint = generic reprint format + \documentclass[prb]{revtex4-2} + ``` + +Unknown slugs raise `ValueError(f"Unknown style: {style!r}. " +"Supported: 'iucr', 'revtex'")` — explicit failure beats a silent +fallback. + +**Distribution: bundle every style every time.** + +`reports/tex/styles/` is populated with the **full set of supported +styles** on every report save — both `iucr` and `revtex` files, +regardless of which slug the user picked. The selected style's +class is `\input` by `.tex`'s `\documentclass{...}` line; +the other style's files sit alongside, ready for a one-line swap. + +Rationale: + +- Self-contained `reports/tex/` — can be zipped or emailed to a + co-author who doesn't have EasyDiffraction, rebuilt anywhere + with a TeX engine. +- Style swap is a `.tex` edit, not a library + round-trip. Matches the workflow established earlier in + ADR review. +- No dependency on user's TeX install having `revtex` installed, + no dependency on tectonic having internet access at compile + time. +- Single mental model: `tex/styles/` always contains the same set + of files; not "depends on what you asked for". + +Total footprint per save: ~420 KB across 12 files (IUCr ~20 KB + +REVTeX ~400 KB, default APS journal coverage only). Negligible +against the project's data files, the compiled PDF, and the HTML +report. See §3.2.1 for the file list and what's deliberately +excluded. + +#### 3.2.1 Source provenance and bundled files + +Both upstream sources are vendored under +`src/easydiffraction/report/styles/` in the repository and copied +into `reports/tex/styles/` on report save. Download URLs, dates, +licenses, and file lists below; the implementation plan refreshes +the vendored snapshot when upstream releases a new version. + +**IUCr** (`iucrjournals.cls`) + +- Source: https://journals.iucr.org/j/services/latexstyle.html +- Snapshot downloaded: 2026-05-26 +- License: CC0 1.0 Universal (public domain dedication); declared + in the `iucrjournals.cls` file header. +- Files included (2): + - `iucrjournals.cls` — unified IUCr class + (11 KB, dated 2024-12-02). + - `harvard.sty` — bibliography style; required by + `iucrjournals.cls` via `\RequirePackage{harvard}` + (9 KB, Peter Williams, 2001-10-25). +- Files excluded: `iucr.bib`, `iucr.bst`, `fig1.png`, + `template.tex` (bibliography / example assets, not needed for + the refinement-table use case). + +**REVTeX 4.2** (`revtex4-2.cls`) + +- Sources: + https://journals.aps.org/revtex (canonical, APS) and + https://ctan.org/pkg/revtex (CTAN mirror). +- Snapshot downloaded: 2026-05-26 (REVTeX 4.2f, 2022-06-05). +- License: LPPL 1.3c; declared in the `revtex4-2.cls` file + header (Copyright APS 1999–2022, derived from Arthur Ogawa's + original v4.0). +- Files included (10 — the minimum to compile the default PRB + output and let the user swap among PRB/PRA/PRL/PRD via + documentclass option): + - Class: `revtex4-2.cls` (203 KB). + - Companion `.sty` (all loaded by `revtex4-2.cls` via + `\RequirePackage`): `ltxgrid.sty` (74 KB), `ltxutil.sty` + (55 KB), `ltxfront.sty` (30 KB), `ltxdocext.sty` (10 KB), + `revsymb4-2.sty` (6 KB). + - APS journal config: `aps4-2.rtx` (16 KB) — covers + PRB / PRA / PRL / PRD via documentclass options. + - Font-size configs: `aps10pt4-2.rtx`, `aps11pt4-2.rtx`, + `aps12pt4-2.rtx` (5 KB each). +- Files excluded: + - `apsrmp4-2.rtx` (Rev. Mod. Phys. — defer until a user + asks). + - `aip4-2.rtx`, `aapm4-2.rtx`, `sor4-2.rtx` (AIP, AAPM, + Society of Rheology — out of the typical condensed-matter / + diffraction audience; defer). + - `bibtex/bst/revtex/*.bst` (BibTeX styles — refinement + appendix has no citations). + - `doc/latex/revtex/*` (documentation). + +Both license texts (CC0 1.0 and LPPL 1.3c) are copied into the +package's licensing documentation alongside the wheel's +BSD-3-Clause `LICENSE`, with attribution to APS and Peter Williams +where the file headers carry it. + +Template content for `style='iucr'` (unified IUCr class): + +- Refinement-data table in the journal's conventional layout (cell + parameters with uncertainties, space group, refinement + statistics, parameter counts, R-factors). +- Atom-site fractional-coordinate table. +- Anisotropic ADP table (when anisotropic ADPs present). +- Per-experiment fit figure via + `\includegraphics{figures/fit_.pdf}` with the figure + caption rendered from the experiment metadata. + +#### 3.3 Static-image generation — kaleido (same Plotly figures as HTML) + +LaTeX figures are produced by the **same Plotly figure objects** +that the HTML report embeds, rendered to vector PDF via Plotly's +official static-image backend `kaleido`: + +```python +fig = build_fit_figure(experiment, fit_results) # existing plotly path +fig.write_html(...) # → reports/.html (interactive) +fig.write_image('reports/tex/figures/fit_.pdf') # → kaleido → vector PDF +``` + +The HTML's interactive Plotly chart and the LaTeX's static PDF +come from the **same Plotly figure specification rendered by +the same Plotly engine** (kaleido shares Plotly.js with the +browser-side renderer). They are visually consistent — same +trace shapes, same colour mapping, same axis layout — though +exact pixel equality across an interactive HTML target and a +static PDF export cannot be guaranteed (font hinting, anti- +aliasing, and DPI handling differ between the browser and +kaleido's headless Chromium). Sharing the figure spec is +nevertheless the core argument for picking kaleido over a +second rendering toolkit: any visual difference is constrained +to rendering-stack quirks, not data choices. + +**`kaleido` v1.0+ is the chosen release line.** Rationale: + +- v1 is the actively maintained line upstream. Bug fixes and + security patches land here; the v0.2 line is in legacy + maintenance. +- Pinning a project's long-term static-image pipeline to a legacy + release just to dodge a runtime browser dependency is the wrong + trade-off — the migration debt accumulates and the runtime gap + closes naturally as Chrome/Chromium becomes near-universal. +- v1's ~30 MB footprint is genuinely smaller than v0.2's ~80 MB + bundled-Chromium build. +- The runtime browser requirement is solved cleanly via + conda-forge: `pixi add chromium` (or `conda install -c conda-forge chromium`) + on machines that don't already have a browser. Same pattern as + the LaTeX-engine install in §3.4. + +Practical install matrix: + +| Environment | kaleido v1 install | Browser already present? | Extra step | +| ------------------------ | ---------------------- | ------------------------ | ---------------------------- | +| Developer laptop | `pip install kaleido` | Yes (Chrome/Edge/Safari) | None | +| `pixi` dev shell | added to `pixi.toml` | Add `chromium` to pixi | One line in `pixi.toml` | +| CI runner (GitHub, etc.) | added to `pixi.toml` | Install via pixi/conda | Already managed by pixi.lock | +| Bare HPC node | `pip install kaleido` | Usually no | Install Chromium separately | + +**Dependencies named by this ADR.** The implementation plan must +name three dependencies before any `/draft-impl-1` or +`/draft-impl-2` invocation edits `pyproject.toml`, `pixi.toml`, or +`pixi.lock`: + +- `kaleido` (v1.0+) — Python package, runtime dependency for + rasterising Plotly figures to vector PDF for LaTeX inclusion. +- `chromium` — pixi/conda package, head-of-Plotly's static-image + pipeline. Goes into the project's dev/docs feature group so + `pixi run script-tests` can validate end-to-end PDF figure + generation. +- `tectonic` — pixi/conda package, lightweight TeX engine for §3.4 + PDF compilation in the project dev environment. + +Per AGENTS.md §Architecture, "an accepted plan that **names the +specific dependency** … combined with the user invoking +`/draft-impl-1` or `/draft-impl-2` for that plan … counts as +pre-approval." This ADR does **not** itself pre-approve the +dependency edits; the plan does. The ADR names them here so the +plan author has the canonical list and the implementer can edit +dependency files autonomously once the plan is accepted and the +implementation shortcut is invoked. + +#### 3.4 PDF compilation — opportunistic subprocess call + +No pure-Python LaTeX compiler exists in practice (TeX is a large C +codebase; reimplementing it pure-Python is not realistic). The +library calls out to an external TeX engine if one is available on +`PATH`, in this preference order: + +1. **`tectonic`** — modern Rust-based single-binary TeX engine, + auto-downloads packages on first use, available on conda-forge. + First-class fit for `pixi`/`conda` users. +2. **`latexmk`** — TeX Live's standard front-end; handles + multi-pass compilation. Conda-forge package + `texlive-core` ships it on Linux/macOS. +3. **`pdflatex`** — bare TeX Live engine, used as a single-pass + fallback. The Acta Cryst E refinement-table template has no + bibliography, so one pass suffices. + +Behaviour: + +- If any of the three is on `PATH`, the PDF is compiled from + `reports/tex/.tex` and written to + `reports/.pdf` (one directory up from the .tex source). + Promoting the compiled artifact to `reports/` keeps the + filename-root trio (`.cif`, `.html`, `.pdf`) co-located. +- If none is found, the `.tex`, `figures/`, and `styles/` are still + written; the save log emits a single clear warning, for example: + + ``` + PDF skipped: no TeX engine on PATH. + Install one with: + pixi add tectonic # recommended (conda-forge) + conda install -c conda-forge tectonic + # or any TeX Live distribution (latexmk / pdflatex) + Then re-run project.save() (with 'pdf' in project.report.formats) + or project.report.save_pdf(). + ``` + +The library does not bundle a TeX distribution — TeX Live is +multi-GB and pulling it through pip is not feasible. Tectonic is +the lightest realistic install (~50 MB single binary; downloads +packages on demand into a user cache). + +**Project-side dev environment.** The project's own `pixi.toml` +gains `tectonic` in a `[feature.docs.dependencies]` (or similar) +group so CI and `pixi run script-tests` can validate PDF +generation end-to-end. End-users picking the library up via plain +`pip install easydiffraction` get the warning path until they +install a TeX engine themselves. + +### 4. Software-provenance category on `analysis` + +New category `analysis.software`, stamped at fit time, recording the +runtime engine identities. Structure mirrors the alignment ADR's +`_easydiffraction_software.{framework, calculator, minimizer}` triple +so one Python attribute feeds both default save and IUCr export: + +```python +analysis.software.framework.name # str, 'EasyDiffraction' +analysis.software.framework.version # str, e.g. '0.17.0' +analysis.software.framework.url # str, project URL + +analysis.software.calculator.name # str, e.g. 'cryspy' +analysis.software.calculator.version # str, e.g. '1.2.3' +analysis.software.calculator.url # str + +analysis.software.minimizer.name # str, e.g. 'lmfit' +analysis.software.minimizer.version # str, e.g. '1.0.0' +analysis.software.minimizer.url # str + +analysis.software.timestamp # ISO-8601, UTC +``` + +These are populated automatically by `Analysis.fit()` immediately +before the fit returns success — never by the user. + +**Persistence and rendering paths (split):** + +- **Default save** (`analysis/analysis.cif`) — written as the + `analysis.software` category with all nine identity fields plus + the timestamp. Round-trippable on load. This is the change to + the accepted IUCr ADR's "Analysis — unchanged" stance on + software identification; see the ADRs-amended list below for + the explicit amendment. +- **IUCr export** (`reports/.cif`, alignment ADR §2.3a-i) + — the `name + version` portion of each role is read **from** + `analysis.software` (instead of being constructed inline from + project state, as the alignment ADR's §2.3a-i originally + specified) and surfaces as + `_easydiffraction_software.{framework, calculator, minimizer}` + in `data_global`, plus the concatenated + `_computing.structure_refinement` free-text string. The + `timestamp` surfaces as a project-extension + `_easydiffraction_software.fit_datetime` — **not** + `_audit.creation_date`, which the accepted IUCr writer already + uses for the report file's own creation time (see + `_iso_creation_datetime` in + [`iucr_writer.py`](../../../../src/easydiffraction/io/cif/iucr_writer.py)). + Fit time and report-generation time are different events and + must not collide on the same tag. **URLs are not part of the IUCr export** — they're + a rendering concern, not a publication-CIF field. Output goes + to `reports/.cif` (the alignment ADR's canonical + location; the directory is `reports/`, not `iucr/`). +- **Rendered documents** (`reports/.html`, + `reports/tex/.tex`) — render the full + `name — version — url` triple per role, with URLs as hyperlinks in + HTML and as `\href{}{}` in LaTeX. Matches the old GUI's + "Calculation engine: CrysPy — https://www.cryspy.fr" row layout. + +**Engine `url` is a class-level constant on each backend.** Both +`CalculatorBase` and `MinimizerCategoryBase` subclasses declare a +`url: str` class attribute pointing at the upstream library's +documentation. Explicit, no surprises, no runtime lookup needed. + +```python +class CryspyCalculator(CalculatorBase): + url = 'https://www.cryspy.fr' + # ... + +class CrysfmlCalculator(CalculatorBase): + url = 'https://code.ill.fr/scientific-software/crysfml' + # ... + +class PdffitCalculator(CalculatorBase): + url = 'https://www.diffpy.org/products/pdffit2.html' + # ... + +class LmfitMinimizer(MinimizerCategoryBase): + url = 'https://lmfit.github.io/lmfit-py' + # ... + +class EmceeMinimizer(MinimizerCategoryBase): + url = 'https://emcee.readthedocs.io' + # ... +``` + +`Analysis.fit()` reads `experiment.calculator.url` and +`analysis.minimizer.url` to populate the +`analysis.software.{calculator,minimizer}.url` fields. Version +strings come from `.__version__` at fit time. Both are +recorded once, in the snapshot — the engine library can upgrade +later without rewriting the recorded fit's provenance. + +Persistence: + +- Default save: written to `analysis/analysis.cif` under category + names this ADR proposes the alignment ADR adopts in its analysis + tier (see below). Round-trippable; surfaces as `analysis.software` + on load. +- HTML/LaTeX rendering: the `Refinement` section reads `analysis.software` + directly and prints ` ` per the + old GUI's row format. + +**CIF serialisation — reuses the accepted IUCr ADR's tag set, +plus one new fit-time-stamp tag.** Existing IUCr-export tag +names are owned by the alignment ADR §2.3a-i and are reused +here verbatim. One additional project-extension tag — +`_easydiffraction_software.fit_datetime` — is introduced by +this ADR to avoid colliding with the writer's existing +`_audit.creation_date` (which records report-generation time, +not fit time); it is registered as an IUCr amendment in the +ADRs-amended list below. + +- Framework, calculator, minimizer roles → + `_easydiffraction_software.{framework, calculator, minimizer}` + in `data_global`, plus the concatenated + `_computing.structure_refinement` free-text string. See + [`cif_core.dic`](../../../../tmp/iucr-dicts/cif_core.dic) for + the standard `_computing.structure_refinement` slot. +- Fit-time timestamp → `_easydiffraction_software.fit_datetime` + (project-extension tag in `data_global`). The IUCr writer's + own `_audit.creation_date` keeps its existing report-creation + semantics from + [`iucr_writer.py`](../../../../src/easydiffraction/io/cif/iucr_writer.py) + and is not overwritten. +- EasyDiffraction's own version is bundled into the + framework string and into the + `_computing.structure_refinement` rendering per the IUCr + convention; not a separate tag. + +No new minimization-engine tag (e.g. +`_easydiffraction_computing.minimization_engine`) is introduced +— the alignment ADR's existing `…software.minimizer` already +covers that role. The only new IUCr-export item is +`_easydiffraction_software.fit_datetime` (see the CIF-serialisation +preamble above and the ADRs-amended list); it extends the +existing `_easydiffraction_software.*` category, not a new +top-level extension namespace. The Python-side `analysis.software` +category feeds both the established triple and the new fit-time +tag; the IUCr-export emission stays under the alignment ADR's +control. + +#### 4.1 Missing-provenance behaviour + +`analysis.software` is populated by `Analysis.fit()` just before +a successful return — pre-fit calls, failed fits, and projects +loaded from a save predating this ADR all start out **without** +the snapshot. The public API surface treats missing provenance +uniformly: + +- **Rendering (`project.report.show_report()`, HTML, TeX).** Each + role-row prints `"(not available)"` for `name`, omits version + and URL, and adds a one-line footer "Software-provenance + snapshot not yet recorded — call `Analysis.fit()` once to + populate." No warning, no exception; the report still renders + end-to-end so users iterating on a configuration before fitting + see the rest of the page. +- **IUCr CIF export (`'cif' in project.report.formats`).** The + `_easydiffraction_software.{framework, calculator, minimizer}` + triple emits `?` placeholders consistent with the IUCr ADR's + unset-field convention. The derived + `_computing.structure_refinement` string falls back to + `"EasyDiffraction "` (framework only). The + `_easydiffraction_software.fit_datetime` tag is omitted + entirely (no `?` — the absence is the signal). +- **Internal validation (the §1.4 pre-write gemmi pass).** Does + **not** detect missing provenance. The gemmi pass validates + that emitted tags and types match the IUCr core / pdCIF + dictionaries, and it explicitly skips the + `_easydiffraction_*` extension namespace. So the + fallback-filled `_computing.structure_refinement` + (`"EasyDiffraction "`) is not flagged as missing — + it's a valid string — and the `?` placeholders on + `_easydiffraction_software.{framework, calculator, + minimizer}` are not flagged either, because gemmi does not + validate the extension namespace. Detecting "publication-grade + provenance is incomplete" is a different concern from + dictionary-spec compliance and falls to the deferred + `project.report.check_completeness()` listed in Deferred + Work. Users who must guarantee complete provenance before + submission should run that check (once it lands) or inspect + the rendered report manually. +- **Old projects.** Loading a project saved before this ADR + produces an `analysis.software` with all fields unset and the + timestamp `None`. No migration step is run; the user populates + the snapshot by re-running the fit. + +This rule applies wholesale — no flag toggles it, no targeted +exception is raised. Publication-grade users who need the +provenance can re-run the fit; users producing draft / preview +reports keep working without interruption. + +### 5. Publication-metadata category on `project` + +New top-level category on `Project`, sibling to `project.info` and +`project.analysis`. Populated by the user (directly in Python, or +loaded from a `publ_info.{toml,json}` file). Feeds the `_publ_*` / +`_journal_*` / `_publ_author.*` placeholders that the alignment +ADR's `data_global` block currently emits as `?` (alignment ADR +§2.3a). + +#### 5.1 Structure — CIF-aligned sibling categories + +`Publication` is a category-owner (like `Experiment`) hosting +sibling sub-categories that map **1:1 to CIF category prefixes**. +No artificial groupings; the CIF dictionaries already provide the +natural shape: + +| Python attribute | CIF category | Audience | +| -------------------------------------- | -------------------------- | ------------------------------------------------------------------------- | +| `publication.journal` | `_journal.*` | User-set at submission (`name_full`, `paper_category`); editor-set post-acceptance (`year`, `volume`, `issue`, `page_*`, `paper_doi`) | +| `publication.journal_date` | `_journal_date.*` | Editor (`accepted`, `from_coeditor`, `printers_final`) | +| `publication.journal_coeditor` | `_journal_coeditor.*` | Editor (`code`, `name`, `notes`) | +| `publication.contact_author` | `_publ_contact_author.*` | User (`name`, `address`, `email`, `phone`, `id_orcid`, `id_iucr`) | +| `publication.body` | `_publ_body.*` | User (`title`, `synopsis`, `abstract`, `keywords`) | +| `publication.authors` | `_publ_author.*` (loop) | User (per author: `name`, `address`, `footnote`, `id_orcid`, `id_iucr`) | + +Access pattern: + +```python +project.publication.journal.name_full = "Acta Crystallographica E" +project.publication.journal.paper_category = "structure-report" +project.publication.journal.paper_doi = "10.1107/S2056989026..." # post-acceptance +project.publication.contact_author.name = "Jane Doe" +project.publication.contact_author.email = "jane@example.com" +project.publication.contact_author.id_orcid = "0000-0001-..." +project.publication.body.title = "Crystal structure of ..." +project.publication.body.synopsis = "Short summary..." +project.publication.body.abstract = "..." +project.publication.body.keywords = ["powder diffraction", "Rietveld", ...] +project.publication.authors.add(name="Jane Doe", id_orcid="0000-0001-...") +project.publication.authors.add(name="John Smith", id_orcid="0000-0002-...") +``` + +Python attributes are lowercase snake_case (`id_orcid`); CIF tags +retain dictionary casing (`_publ_contact_author.id_ORCID`). Loops +use the project's existing `CategoryCollection` pattern (`add()`, +indexed access, etc.). + +The editor-side categories (`journal_date`, `journal_coeditor`) +exist so the schema can carry editor-supplied fields when a user +manually copies them in (typically by editing +`project.publication.*` in Python after a referee round) — not +because users typically fill them at submission time. Defaults are +`None`; the IUCr writer emits `?` for unset fields. Round-trip is +on the **`project.cif`** axis only (`project.publication` reads +and writes there per the project-facade-and-persistence +contract); **the report CIF at `reports/.cif` stays +export-only** per the accepted IUCr ADR. A reader for +`reports/.cif` is explicitly out of scope. + +#### 5.2 Discrete `body` fields, not a markdown blob + +`_publ_body.*` in coreCIF supports nested section content via an +`element` / `format` / `contents` trio. For the refinement-table +appendix use case (user pastes content into a full manuscript +later), discrete top-level slots are more discoverable than a +generic markdown blob: + +- `publication.body.title` — manuscript title (single string) +- `publication.body.synopsis` — short summary (IUCr Acta E requires this) +- `publication.body.abstract` — abstract text +- `publication.body.keywords` — list of strings (loop on CIF side) + +A future v2 could add free-form section support +(`publication.body.sections[]` with element/format/contents trios) +when users produce full manuscripts from the library. Out of +scope for v1. + +#### 5.3 Input mechanism — TOML primary, JSON fallback + +Two import paths, both writing into the same in-memory +`project.publication` object: + +```python +# Direct Python edit (preferred for notebook / interactive use): +project.publication.contact_author.email = "jane@example.com" + +# File-based load (preferred for collaborative / batch workflows): +project.publication.load("reports/publ_info.toml") # TOML, by extension +project.publication.load("reports/publ_info.json") # JSON, by extension +``` + +TOML is the primary format: + +- **Comment support** — users can document why a field is set / unset. +- **Multi-line strings** — abstracts, addresses, and synopses without escaping. +- **Familiar** — the project already uses `pyproject.toml` and `pixi.toml`. +- **Standard library** `tomllib` (Python 3.11+); no new dependency. + +JSON is supported as a fallback for programmatic generation (e.g. +a user script that dumps publication data from a database; an +external tool that produces JSON output). Standard library `json`. + +Format selection is by file extension. Unknown extensions raise +`ValueError("Unsupported publication-info format: . " +"Use .toml or .json.")`. No YAML support — adds a dependency for +no gain. + +Reading from the report CIF (`reports/.cif`) back into +`project.publication` is **explicitly out of scope** here and +remains the accepted IUCr ADR's "Export only — no round-trip" +contract. Users who edit the report file by hand should also +update `project.publication` (or its TOML/JSON source) so the +next save reflects the edits; the library does not auto-import. + +### 6. Shared `ReportDataContext` + Jinja templates + +One context-builder method on the `project.report` facade: + +```python +def data_context(self) -> dict: + return { + 'project': { + 'title': self.project.info.title, + 'description': self.project.info.description, + 'n_phases': len(self.project.structures), + 'n_experiments': len(self.project.experiments), + }, + 'structures': [ + { + 'id': s.name, + 'space_group': s.space_group.name_h_m.value, + 'cell': { + 'a': s.cell.length_a.value, + 'b': s.cell.length_b.value, + ... + }, + 'atom_sites': [...], + } + for s in self.project.structures.values() + ], + 'experiments': [...], + 'refinement': { + 'calculation_engine': {...}, + 'minimization_engine': {...}, + 'goodness_of_fit': ..., + 'parameters': {'total': ..., 'free': ..., 'fixed': ...}, + 'constraints': ..., + }, + 'software': {...}, # from §4 + 'publication': { # from project.publication (§5); unset fields → None + 'journal': {...}, # _journal.* + 'journal_date': {...}, # _journal_date.* (editor-side) + 'journal_coeditor': {...}, # _journal_coeditor.* (editor-side) + 'contact_author': {...}, # _publ_contact_author.* + 'body': {...}, # _publ_body.{title, synopsis, abstract, keywords} + 'authors': [...], # _publ_author.* loop + }, + 'figures': { + 'fit_per_experiment': {expt_id: plotly_html_div, ...}, + }, + 'metadata': { + 'easydiffraction_version': ..., + 'generated_at': ..., + }, + } +``` + +Templates live under `src/easydiffraction/report/templates/`, keyed by +style slug: + +``` +templates/ + base.j2 # shared macros (parameter row, uncertainty fmt) + html/ + report.html.j2 + style.css + tex/ + iucr.tex.j2 # ships in v1; emits \documentclass{iucrjournals} + revtex.tex.j2 # ships in v1; emits \documentclass[prb]{revtex4-2} +``` + +GUI consumes `project.report.data_context()` directly — no CIF +parsing, no HTML scraping. This is the consistency guarantee. + +### 7. CLI surface mirrors the Python split + +Two subcommands match the Python `project.save()` vs +`project.report.save_*()` split: + +```bash +ed save # project files + whatever is in project.report.formats +ed save-report --html # one-off — write reports/.html only +ed save-report --cif --tex --pdf # one-off — full LaTeX bundle + CIF +ed save-report --cif --tex --pdf --style iucr +``` + +`ed save-report` with no `--cif`/`--html`/`--tex`/`--pdf` exits +with a clear error pointing the user at the configuration +category (`ed config project.report.formats html cif`). +`--pdf` implies `--tex` so the user always gets the editable +source next to the PDF. + +For users who want to **persist** the choice across runs, the +configuration category is set the usual way — interactively +through `ed config project.report.formats html cif`, by editing +`project.cif` directly, or programmatically — and `ed save` +picks it up on every subsequent save. + +CLI flags are short (no `_report` suffix; the subcommand name +`save-report` already scopes them). The Python and CLI surfaces +stay symmetric: + +| Python (config — persisted) | Python (ad-hoc — one-off) | CLI (one-off subcommand) | +| ----------------------------------------- | ------------------------------------ | ------------------------------------- | +| `project.report.formats = ['html']` | `project.report.save_html()` | `ed save-report --html` | +| `project.report.formats = ['cif']` | `project.report.save_cif()` | `ed save-report --cif` | +| `project.report.formats = ['tex']` | `project.report.save_tex(style=…)` | `ed save-report --tex --style iucr` | +| `project.report.formats = ['pdf']` | `project.report.save_pdf(style=…)` | `ed save-report --pdf --style iucr` | +| `project.report.style = 'iucr'` | (passed as `style=…` per-call) | `--style iucr` | +| `project.report.html_offline = True` | `save_html(offline=True)` | `--html --offline` | + +### 8. Fields the library currently lacks + +The HTML/LaTeX renderers need three fields the library does not +expose today; this ADR scopes them as in-scope work: + +- `structures[i].crystal_system` — derivable from + `space_group.name_h_m`, but not currently exposed as a property. +- `experiments[i].measured_range` — `min`, `max`, `inc` triple from + the underlying data arrays. Not currently exposed as a property on + `Experiment`. +- `analysis.parameter_counts` — total / free / fixed / constrained. + Free and fixed are derivable from + `project.free_parameters`; total and constrained need a single + aggregating helper. + +All three are pure derived properties — no new state, no persistence +beyond what already exists. + +## Consequences + +### Positive + +- `summary.cif` placeholder goes away (alignment ADR + this ADR + jointly retire it); no more "To be added..." on disk. +- HTML can be auto-regenerated on every save by adding `'html'` + to `project.report.formats` once (or via the GUI's "Export" + panel). Zero-friction inspection for users who want it, no + surprise file writes for users who don't. +- LaTeX export covers the "send the refinement table to my + co-author" workflow that today requires manual transcription. +- Engine name + version + URL are captured at fit time, ending the + current provenance gap. Maps cleanly to coreCIF + `_computing.structure_refinement` for journal-bound CIFs. +- The GUI Summary tab consumes the same Python data context as the + HTML renderer. Library and GUI cannot drift on "what numbers are + shown". +- New summary fields are added in one place + (`project.report.data_context()`); all renderers pick them up. +- Style selector is a template registration, not a code path. + Adding `'prb'`, `'jac'`, etc. is a Jinja file + one-line + registration. + +### Trade-offs + +- All report outputs are opt-in. Default + `project.report.formats = []` means daily `project.save()` + calls write only project files — `reports/` isn't created + until a format is configured (or an ad-hoc method is called). +- HTML is small (~50–300 KB CDN-mode, ~few MB offline); users + who want it on every save add `'html'` to + `project.report.formats` once. +- LaTeX bundle (`reports/tex/` + `reports/.pdf`) is + several files (`.tex`, figures, compiled PDF, full styles + directory) — only written when `'tex'` or `'pdf'` is in + `project.report.formats` (or an ad-hoc `save_tex()` / + `save_pdf()` call is made). The `tex/styles/` directory + always contains + every supported style's files (12 files, ~420 KB) so the user + can swap styles by editing one line in `.tex` and + rebuilding, without re-running the library. Negligible against + project data files, the PDF, and the HTML report. +- Two upstream snapshots vendored in the repository + (`iucrjournals.cls` family CC0 1.0; REVTeX 4.2 family LPPL + 1.3c). The plan refreshes the snapshot when upstream releases + a new version; license texts are copied into the package's + licensing documentation alongside the wheel's BSD-3-Clause + `LICENSE` with attribution. +- Adds **kaleido v1.0+** as a direct dependency (~30 MB). + Justified by the visual-consistency win: the HTML and LaTeX + figures come from the same Plotly figure objects rendered by + the same Plotly engine (interactive in HTML, static PDF via + kaleido), so any visual difference is bounded by + browser/runtime/export quirks rather than two unrelated + rendering toolkits drifting on data choices. v1 relies on the + host + browser for rendering; the project's `pixi.toml` adds + `chromium` alongside `tectonic` to its dev/docs feature so CI + has a known-good Chromium. End users on machines without a + browser get a clear install hint at first use. +- PDF compilation is opportunistic — works when `tectonic`, + `latexmk`, or `pdflatex` is on `PATH`; otherwise the `.tex` and + figures are still written and the user gets a clear one-line + install hint (`pixi add tectonic` is the recommended path). +- The `data_context` dict is a public API surface. Renaming a key + affects every renderer. Treated like a public method signature. +- `analysis.software` adds a new category to persist on every save. + Small (8 string fields) but visible in `analysis.cif` diffs. +- Two ADRs (this one + alignment) must move in lockstep on the + `_computing.*` mapping and on publication-metadata sourcing. + Cross-references in both should make the coupling explicit. + +### ADRs amended by this ADR + +- [`iucr-cif-tag-alignment.md`](../accepted/iucr-cif-tag-alignment.md) + — five amendments: + 1. **`project.save()` flag removal.** The accepted + `project.save(report=True)` flag is **removed**. Reports + come from the new `project.report` configuration category + (§1.1, §1.3) — six scalar items persisted to `project.cif` + (`_report.cif`, `_report.html`, `_report.tex`, `_report.pdf`, + `_report.style`, `_report.html_offline`). The Python-side + `project.report.formats` is a property view over the four + format booleans. Set the configuration once; `project.save()` + applies it on every save thereafter. Replaces the flag with + persisted configuration, matching the existing + `project.chart`, `project.table`, `project.verbosity` + pattern. + 2. **`project.report.save()` surface redesigned.** The + accepted `project.report.save()` is now a no-argument + convenience that reads the configuration category (raises + `ValueError` when no formats are enabled — §1.2). Per-format + ad-hoc writes use new explicit methods: + `project.report.save_cif()`, `save_html(offline=False)`, + `save_tex(style='iucr')`, `save_pdf(style='iucr')`. The + earlier draft's `save(cif=True, html=True, tex=True, + pdf=True, style=, check=)` flag bundle is dropped. + `project.report.check()` and the `check=True` flag are + **removed**: dictionary-spec validation runs internally + **before every CIF write only** (`save_cif()` and the + `cif` branch under `project.save()`; HTML, TeX, and PDF + get no pre-write validation — see §1.4). A writer- + correctness failure on the CIF path raises + `EasyDiffractionWriterError` instead of producing a + broken file. + 3. **Software-identification source.** The alignment ADR's + §2.3a-i described the `_easydiffraction_software.*` triple as + a "report-only projection … built inline by the IUCr writer + from existing state", with no default-save persistence. This + ADR introduces a persisted `analysis.software` category (§4) + and amends that stance: the triple is read **from** + `analysis.software` at IUCr-export time, not reconstructed + inline. The Current State table row that previously read + "`_calculator.type`, `_minimizer.type` … Analysis — + unchanged" gains an "Analysis — `analysis.software` + persisted" note. + 4. **New project-extension tag for fit time.** Adds + `_easydiffraction_software.fit_datetime` to the + `data_global` block in the IUCr export. The accepted ADR's + §2.3a-i listed `_easydiffraction_software.{framework, + calculator, minimizer}` only; `fit_datetime` extends that + same category prefix without introducing a new top-level + extension namespace. The writer's existing + `_audit.creation_date` keeps its + `_iso_creation_datetime()` source (report-generation time) + and is **not** overwritten — fit time and report time are + distinct events. + 5. **Publication metadata in the default save.** The alignment + ADR's Scope explicitly excluded "Adding new CIF categories + the project does not currently track (`_chemical.*`, + `_publ.*`, `_journal.*`) **for the default save**" + (alignment ADR §Scope, lines 101-110). This ADR adds + `project.publication.*` (§5) and persists it to + `project.cif` — a different file from + `reports/.cif`, but still a default-save change + that the alignment ADR did not anticipate. Specifically: + - `_publ_contact_author.*`, `_publ_author.*`, `_publ_body.*`, + `_journal.*`, `_journal_date.*`, `_journal_coeditor.*` are + now in scope for `project.cif`. + - The accepted IUCr export still reads these from + `project.publication.*` and emits them in `data_global` + per §2.3a; the `?` placeholder semantics for unset fields + are unchanged. + - The accepted "Export only — no round-trip" rule for + `reports/.cif` is **unaffected** — + `project.publication` round-trips through `project.cif`, + not through the report CIF (see §5.1 / §5.3). + + All other IUCr-export decisions in the alignment ADR + (multi-datablock layout, tag-name policy, gemmi as the + validation engine) are unaffected — only the public surface + for validation changes: `project.report.check()` and + `check=True` are removed; the gemmi pass moves inside the + writer **on the CIF emission paths only** (§1.4), running + before every CIF write (no pre-write validation for HTML, TeX, + or PDF — those formats have no spec to check against). +- [`analysis-cif-fit-state.md`](../accepted/analysis-cif-fit-state.md) + — adds `analysis.software` to the persisted analysis state. +- [`project-facade-and-persistence.md`](../accepted/project-facade-and-persistence.md) + — three changes: + 1. **`project.report` gains a persisted configuration category.** + The accepted ADR scoped `project.report` as a CIF-write helper + (single output: `reports/.cif`). This ADR extends + it with a configuration category (`_report.*` in + `project.cif`, six scalar items per §1.1 / §1.3) that the + existing `Project.save()` reads on every save. The facade + becomes a hybrid — helper methods (`save_*()`) **and** + persisted configuration on the same Python object. + 2. **New top-level `project.publication` facade slot (§5).** + Sibling to `project.info`, `project.structures`, + `project.experiments`, `project.analysis`, `project.report`. + Persisted to `project.cif` next to the other project-level + singleton categories. + 3. **Project-level singleton category enumeration extended.** + The accepted ADR enumerates `_info.*`, `_chart.*`, + `_table.*`, `_verbosity.*` as the project-level singleton + categories owned by `project.cif`. This ADR adds two more + to that enumeration: `_report.*` (this ADR §1.3) and + `_publication.*` family (this ADR §5; concrete sub-prefixes + are `_publ_*` and `_journal_*` per IUCr coreCIF). +- [`python-cif-category-correspondence.md`](python-cif-category-correspondence.md) + — owns the Python-to-CIF correspondence rule for two new + project-level singleton surfaces: + - `project.report.*` ↔ `_report.*` — six scalar items (four + format booleans, `style`, `html_offline`) per §1.3. + - `project.publication.*` ↔ `_publ_*` / `_journal_*` sibling + categories per §5. Python attributes are lowercase + snake_case (`id_orcid`); CIF tags retain dictionary casing + (`_publ_contact_author.id_ORCID`). + + Both follow the correspondence ADR's existing 1:1 mapping + pattern. The correspondence ADR's enumeration of "currently + persisted Python category surfaces" gains two rows for these + additions. + + No conflict with the correspondence ADR's project-level + category list because `_publ_*` / `_journal_*` are + publication-domain, not project-level singleton categories. + +## Open Questions + +- **GUI Export panel.** Two reasonable shapes: (a) checkboxes + edit `project.report.formats` directly + a "Save now" button + that calls `project.save()` (config-driven, matches the + Python surface) or (b) checkboxes drive ad-hoc per-format + calls (`save_html()`, `save_pdf()`) without changing the + persisted config (one-off ergonomics). Either fits the + configuration / ad-hoc split in §1; the exact GUI layout + (single dialog vs. inline checkboxes, button labels, + post-save action) decides at GUI-integration time, not here. + +## Alternatives Considered + +### A. Leave `project.report` scoped to CIF only + +Status of the alignment ADR before this ADR's amendment: facade +exists but only writes the IUCr CIF. The GUI Summary tab would +then need its own renderer, the everyday project folder would have +no human-readable artefact, and there'd be no LaTeX/PDF path. Auto +HTML and the GUI-consistency story both disappear. Rejected on UX +and consistency grounds. + +### B. HTML auto-saved on every `project.save()` + +An earlier revision of this ADR had HTML always-on, no opt-in +flag, the philosophy being "every artefact stays fresh". +Rejected in favour of the configuration approach: +`project.save()` does one job (save the project) and reads +`project.report.formats` to decide which reports come along. +Empty config = no reports. Users who want auto-fresh HTML add +`'html'` to `project.report.formats` once; the GUI consumes +`project.report.data_context()` in-memory, not the HTML file, so +freshness of the file does not affect GUI consistency. + +### C. Markdown as the primary rendered format + +Markdown is git-friendly and renders nicely in many viewers. But it +cannot embed interactive Plotly figures and has no journal-style +analogue for LaTeX. Could be added as a third renderer (the Jinja +base template makes it a few hundred lines) but not as the primary. +Deferred. + +### D. Build the HTML renderer inside the GUI, not the library + +Push HTML/LaTeX rendering into the GUI codebase; the library only +exposes the data context. Saves library complexity. But: CLI users +get nothing, notebook users get nothing, and the GUI's renderer is +not exercised by CI. Rejected for the consistency benefit of a +single renderer. + +### E. PDF as the primary rendered format + +Could be done via `weasyprint` (HTML→PDF) or directly via +`reportlab`. Adds a dependency, slows save, and most users print +HTML to PDF from a browser anyway. Deferred. + +## Deferred Work + +- Markdown rendering as a third Jinja target. +- Additional style slugs beyond v1's `iucr` and `revtex` + (e.g. ICDD, Elsevier `elsarticle`, Springer `svjour3`). Each new + slug ships as one new Jinja template + a registration row; no + code restructuring needed. +- **Bibliography support** (`iucr.bib` / `iucr.bst`, + REVTeX `*.bst` files). Both upstream distributions ship + bibliography styles for citing IUCr / APS publications. Not + bundled in v1 because the refinement-table appendix use case + has no citations. Add when users start producing full + manuscripts from the library. +- **Extended REVTeX journal coverage.** v1 bundles `aps4-2.rtx` + (covers PRB/PRA/PRL/PRD). The four upstream `.rtx` files for + Rev. Mod. Phys. (`apsrmp4-2.rtx`), AIP (`aip4-2.rtx`), AAPM + (`aapm4-2.rtx`), and Society of Rheology (`sor4-2.rtx`) total + ~76 KB and are skipped to stay minimal. Add them when users + in those communities ask. Users on the deferred journals can + install REVTeX from TeX Live (full coverage is in the default + install) and the swap-with-one-line-edit workflow then works. +- **Snapshot-refresh automation.** A small `pixi` task to + re-fetch upstream sources and diff against the vendored + snapshot would help track when IUCr or APS releases a new + version. v1 refreshes by hand during plan work. +- **Per-style subfolder layout.** v1 flattens all 12 style files + into one `reports/tex/styles/` directory. As more styles ship + (Elsevier `elsarticle`, Springer `svjour3`, …), a per-style + subfolder layout (`styles/iucr/*`, `styles/revtex/*`, + `styles/elsarticle/*`) becomes cleaner — needs `TEXINPUTS=./styles//:` + configured for the TeX engine. Defer until the file count + becomes uncomfortable. +- Tab/accordion navigation in HTML for projects with many + experiments. +- `project.report.check_completeness()` — complements the + internal gemmi pass from §1.4 (dictionary spec compliance, + enforced before every CIF write). The completeness check would + flag whether the user has filled in `_publ_*` / `_journal_*` + placeholders for their target journal, which dictionary + validation cannot determine. Different concern, different layer. +- A pinned-snapshot variant of `.html` (timestamped, kept + next to fit-run-specific artifacts) for users who want to track + refinement history visually across saves. +- Additional figure types beyond fit + difference (e.g. + cumulative-χ² plot, residual histogram, posterior corner plot + for Bayesian fits). Same data-context-driven pattern, new + matplotlib + Plotly renderers per type. + +## Suggested Pull Request + +**Title:** Extend `project.report` with HTML, TeX, and PDF outputs (opt-in) + +**Description:** + +Builds on the IUCr CIF alignment work by filling in the non-CIF +half of the publication bundle. The `project.report` facade +covers four output formats — CIF, HTML, TeX, PDF — chosen via a +configuration category on the project (persisted in +`project.cif`) and applied automatically on every save: + +```python +project.report.formats = ['cif', 'html'] # which formats project.save() emits +project.report.style = 'iucr' # LaTeX style (when 'tex' or 'pdf' is in formats) +project.report.html_offline = False # Plotly via CDN (default) or inlined + +project.save() +# → project.cif (with the _report.* config) +# → structures/, experiments/, analysis/ as before +# → reports/.cif and reports/.html per config +``` + +Per-format ad-hoc methods cover one-offs without changing the +persisted config: +`project.report.save_html(offline=False)`, +`save_cif()`, `save_tex(style='revtex')`, `save_pdf()`. +The CLI mirrors with a new subcommand, +`ed save-report --cif --html --tex --pdf --style iucr` (also a +one-off; `ed save` reads the persisted config). + +The LaTeX bundle ships `.tex`, vector-PDF figures, and +the full set of supported style files under `reports/tex/`. The +compiled `.pdf` is written one level up (next to the +CIF and HTML) when a TeX engine — `tectonic` (recommended, +conda-forge), `latexmk`, or `pdflatex` — is on `PATH`. v1 ships +two styles, both **fully bundled** in `reports/tex/styles/` on +every report save (~420 KB across 12 files): + +- `iucr` (default) — IUCr's `iucrjournals.cls` (CC0 1.0, + https://journals.iucr.org/j/services/latexstyle.html), unified + across Acta Cryst E/B/C/D/F, J. Appl. Cryst., J. Synchrotron + Rad., IUCrData. Sub-journal is decided at submission, not in + the `.tex`. +- `revtex` — APS's `revtex4-2.cls` family (LPPL 1.3c, + https://journals.aps.org/revtex / https://ctan.org/pkg/revtex). + PRB by default; user changes the documentclass option letter + to switch to PRA / PRL / PRD / Rev. Mod. Phys. / AIP / AAPM / + SOR / generic reprint. + +Bundling both styles regardless of the active selection makes +`reports/tex/` self-contained — the user (or a collaborator +without EasyDiffraction) can rebuild in any style by editing +one line in `.tex` and rerunning the TeX engine. + +Adds an `analysis.software` Python category — three-role triple +(framework / calculator / minimizer) matching the alignment ADR's +`_easydiffraction_software.*` CIF emission — recording engines, +versions, and URLs at fit time so every report carries +authoritative provenance. The library, the CLI, and the GUI all +consume the same in-memory report data (`project.report.data_context()`) +— no code path renders independently. This keeps the forthcoming +GUI Summary tab in lockstep with the library. From dc64630a4fb16de46ea90ede8b8721ed89d8fc9f Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 21:01:41 +0200 Subject: [PATCH 002/129] Add project-summary-rendering implementation plan --- docs/dev/plans/project-summary-rendering.md | 989 ++++++++++++++++++++ 1 file changed, 989 insertions(+) create mode 100644 docs/dev/plans/project-summary-rendering.md diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md new file mode 100644 index 000000000..c94f280b4 --- /dev/null +++ b/docs/dev/plans/project-summary-rendering.md @@ -0,0 +1,989 @@ +# Plan: Project Summary Rendering + +Implementation plan for the +[`project-summary-rendering`](../adrs/suggestions/project-summary-rendering.md) +ADR. Follows [`AGENTS.md`](../../../AGENTS.md) — no deliberate +exceptions to those instructions. + +## ADR cross-reference + +- **Primary ADR:** `project-summary-rendering.md` (currently in + `suggestions/`; this plan promotes it to `accepted/` as the + final P1 step before the review gate). +- **Amends** (per the ADR's "ADRs amended by this ADR" section): + - [`iucr-cif-tag-alignment.md`](../adrs/accepted/iucr-cif-tag-alignment.md) + — five amendments: removes `project.save(report=True)` + flag, redesigns `project.report.save()` (no-arg config + reader + per-format `save_*()` methods), removes public + `check()` + `check=True` (moves gemmi pass internal, + CIF-only), reads `_easydiffraction_software.*` from + persisted `analysis.software`, adds + `_easydiffraction_software.fit_datetime` extension tag. + - [`analysis-cif-fit-state.md`](../adrs/accepted/analysis-cif-fit-state.md) + — adds `analysis.software` to the persisted analysis state. + - [`project-facade-and-persistence.md`](../adrs/accepted/project-facade-and-persistence.md) + — `project.report` gains a persisted configuration category + (`_report.*` in `project.cif`); new top-level + `project.publication` owner; singleton-category enumeration + extended. + - [`python-cif-category-correspondence.md`](../adrs/suggestions/python-cif-category-correspondence.md) + — **proposed** (in `suggestions/`); this plan amends its + correspondence table to add the two new surfaces + (`project.report.* ↔ _report.*` and + `project.publication.* ↔ _publ_*` / `_journal_*`) but + does **not** promote it to `accepted/`. Promotion (with + its own review cycle) is a separate concern; downstream + code does not rely on it being accepted. + +## Branch and PR + +- **Branch:** `project-summary-rendering` (already checked out; + rebased onto latest `develop`, which includes the merged IUCr + CIF tag alignment work as PR #184). +- **PR target:** `develop`. +- Do not push the branch until both Phase 1 and Phase 2 review + cycles close. + +## Decisions already made (in the ADR) + +These are settled by the accepted ADR — the plan does not +re-litigate them, only implements them: + +- **Three-tier extension of `project.report`:** + - **Config category** (§1.1, §1.3): six scalar fields on + `project.report` (`cif`, `html`, `tex`, `pdf`, `style`, + `html_offline`) persisted to `project.cif` as + `_report.*`. `project.report.formats` is a property view + over the four format booleans. Pattern A (project-level + singleton config), not Pattern B (datablock owner) — see + §1.3 "Why not its own CIF file?". + - **Ad-hoc per-format methods** (§1.2): `save_cif()`, + `save_html(offline=False)`, `save_tex(style='iucr')`, + `save_pdf(style='iucr')` on the facade. Each ignores + `formats` and writes its format unconditionally. + - **Convenience save** (`project.report.save()`): reads + config, raises `ValueError` when no formats are enabled. +- **Validation moves internal — CIF only** (§1.4): the gemmi + dictionary-spec pass runs before every CIF write + (`save_cif()` and the `cif` branch under `project.save()`); + HTML/TeX/PDF get no pre-write validation. Public + `project.report.check()` and `check=True` are **removed**; + writer-correctness failures raise + `EasyDiffractionWriterError`. +- **Empty-config behaviour split**: `project.save()` with + `formats = []` writes the project as usual, no reports, no + error. `project.report.save()` with `formats = []` raises + `ValueError` pointing at the configuration category. +- **Enums per the accepted closed-values ADR**: + `ReportFormatEnum('cif', 'html', 'tex', 'pdf')` and + `ReportStyleEnum('iucr', 'revtex')`, both `(str, Enum)`. CIF + serialisation uses the enum string values verbatim. +- **`analysis.software` provenance category** (§4): persisted + to `analysis/analysis.cif` with framework, calculator, + minimizer sub-categories (each `name + version + url`) plus + a `fit_datetime` timestamp. Populated by `Analysis.fit()` + immediately before successful return. URLs declared as + class-level `url` constants on backend classes. +- **Missing-provenance behaviour** (§4.1): pre-fit, failed-fit, + and pre-ADR loaded projects render `"(not available)"` on + every surface; IUCr CIF emits `?` placeholders; no exception + raised; `_easydiffraction_software.fit_datetime` is omitted + when unset. +- **`project.publication` top-level owner** (§5): six sibling + CIF-aligned sub-categories (`journal`, `journal_date`, + `journal_coeditor`, `contact_author`, `body`, `authors`). + Persisted to `project.cif`. TOML primary / JSON fallback + loader (`project.publication.load(...)`). No reader for + `reports/.cif` — that file stays export-only per + the accepted IUCr ADR. +- **Shared `ReportDataContext`** (§6): one dict feeding all + renderers (terminal, HTML, TeX, GUI). New summary fields + are added in one place (`data_context()`); all renderers + pick them up. +- **CLI surface** (§7): `ed save` reads the persisted config; + `ed save-report --html` / `--cif` / `--tex` / `--pdf + --style iucr` is the one-off subcommand. `ed save-report` + with no flags raises a clear error. +- **Dependencies named by the ADR** (§3.2, §3.4): `kaleido` + (Python, runtime, `pyproject.toml`), `chromium` (pixi/conda, + dev/docs feature), `tectonic` (pixi/conda, dev/docs feature). + Per AGENTS.md §Architecture, naming them in this accepted + plan plus a `/draft-impl-1` invocation counts as + pre-approval for the implementation steps to edit + `pyproject.toml` / `pixi.toml` / `pixi.lock`. + +## Open questions to resolve during implementation + +- **`formats` property class.** The Python `formats` property + view derives a `list[ReportFormatEnum]` from the four + booleans. Implementer decides between a thin + `property` on `Report` versus a richer descriptor type — the + former is simpler and fits the existing pattern. +- **Pixi env gemmi version.** Confirm the pinned `gemmi` + supports the validation calls inherited from the + alignment ADR. If too old, bump the constraint in + `pyproject.toml` / `pixi.toml` / `pixi.lock` (pre-approved + because `gemmi` is already named in the alignment ADR and + this ADR's text). +- **LaTeX style file vendoring location.** Probable home: + `src/easydiffraction/report/templates/tex/styles/`. Confirm + during P1 by checking how other vendored assets are laid + out (the project already vendors plugin templates under + `src/easydiffraction/display/`). +- **TeX engine discovery order.** ADR §3.4 lists + `tectonic` → `latexmk` → `pdflatex`. Confirm the discovery + function and the "engine missing → write `.tex` + warning" + fallback during P1.20. +- **Tutorial coverage scope.** Identify which tutorials gain + `project.report` configuration examples (most likely + `ed-3.py`, `ed-5.py`, `ed-8.py`, `ed-14.py` — the same + tutorials touched by PR #184) and which gain a new + end-to-end "save HTML + PDF" demo. + +## Concrete files likely to change + +**Foundation (P1.1–P1.7):** +- `src/easydiffraction/report/enums.py` (new — `ReportFormatEnum`, + `ReportStyleEnum`). +- `src/easydiffraction/project/categories/report/` (new package: + `__init__.py`, `default.py` — the refactored `Report` class as + a `CategoryItem`; `factory.py` — `ReportFactory`). Mirrors the + existing `chart/`, `table/`, `verbosity/` layout under + `src/easydiffraction/project/categories/`. +- `src/easydiffraction/report/report.py` (existing — + **deleted**; the current plain-facade `Report` body is + migrated into the new package above. No shim file. + Existing public import path `from easydiffraction.report + import Report` continues to work via the next bullet.) +- `src/easydiffraction/report/__init__.py` (existing — + one-line update: `from easydiffraction.report.report import + Report` becomes `from + easydiffraction.project.categories.report.default import + Report`. The public `easydiffraction.report.Report` import + path is preserved.) +- `src/easydiffraction/project/project_config.py` (existing — + add `report` slot/property alongside `info`, `chart`, + `table`, `verbosity`; persistence is automatic via the + existing `category_owner_to_cif` walker). +- `src/easydiffraction/project/project.py` (existing — bind + `self._report = self._config.report`; drop `report=True` / + `check=True` flags from `Project.save()`). +- `src/easydiffraction/io/cif/iucr_writer.py` (existing — + internalise the gemmi validation step). +- `src/easydiffraction/report/check.py` (existing — remove or + re-purpose into the internal validator). + +**Analysis software provenance (P1.8–P1.10):** +- `src/easydiffraction/analysis/categories/software/` (new + package: `__init__.py`, `base.py`, `default.py`, + `factory.py`). +- `src/easydiffraction/analysis/analysis.py` (existing — wire + the new category into `Analysis`; populate at fit time). +- `src/easydiffraction/datablocks/experiment/categories/calculator/*.py` + and + `src/easydiffraction/analysis/categories/minimizer/*.py` + (existing — add `url` class constants). +- `src/easydiffraction/io/cif/iucr_writer.py` (existing — + read the triple from `analysis.software`; add + `_easydiffraction_software.fit_datetime` emission). +- `docs/dev/adrs/accepted/analysis-cif-fit-state.md` (amend). + +**IUCr ADR amendments (P1.11):** +- `docs/dev/adrs/accepted/iucr-cif-tag-alignment.md` (amend + with all five amendments). + +**`project.publication` category (P1.12–P1.15):** +- `src/easydiffraction/project/categories/publication/` (new + package: `__init__.py`, `journal.py`, `journal_date.py`, + `journal_coeditor.py`, `contact_author.py`, `body.py`, + `authors.py`, `default.py`, `factory.py`). +- `src/easydiffraction/project/project.py` (existing — add + `publication` facade slot). +- `src/easydiffraction/io/cif/iucr_writer.py` (existing — + read `data_global` `_publ_*` / `_journal_*` items from + `project.publication.*` instead of static `?` placeholders). +- `src/easydiffraction/project/publication_loader.py` (new — + TOML/JSON loader). +- `docs/dev/adrs/accepted/project-facade-and-persistence.md` + (amend). +- `docs/dev/adrs/suggestions/python-cif-category-correspondence.md` + (amend). + +**Shared data context + Jinja base (P1.16):** +- `src/easydiffraction/report/data_context.py` (new). +- `src/easydiffraction/report/templates/base.j2` (new). + +**HTML renderer (P1.17):** +- `src/easydiffraction/report/html_renderer.py` (new). +- `src/easydiffraction/report/templates/html/report.html.j2` + (new). +- `src/easydiffraction/report/templates/html/style.css` (new). + +**LaTeX + PDF (P1.18–P1.20):** +- `pyproject.toml` (add `kaleido`). +- `pixi.toml` (add `chromium`, `tectonic` to dev/docs feature). +- `pixi.lock` (regenerated). +- `src/easydiffraction/report/templates/tex/iucr.tex.j2` (new). +- `src/easydiffraction/report/templates/tex/revtex.tex.j2` (new). +- `src/easydiffraction/report/templates/tex/styles/` (new — + 12 vendored class/style files per ADR §3.2.1). +- `src/easydiffraction/report/tex_renderer.py` (new). +- `src/easydiffraction/report/pdf_compiler.py` (new — subprocess + wrapper over the TeX engine). + +**Library-side new properties (P1.21):** +- `src/easydiffraction/datablocks/structure/categories/space_group/default.py` + (add `crystal_system` property derived from + `_space_group.name_H-M_alt`). +- `src/easydiffraction/datablocks/experiment/item/base.py` (or + equivalent — add `measured_range` property). + +**CLI (P1.22):** +- `src/easydiffraction/cli/save.py` (existing — match + `project.save()` no-arg behaviour reading config). +- `src/easydiffraction/cli/save_report.py` (new — `ed save-report + --cif --html --tex --pdf --style iucr` subcommand). + +**Tutorials / docs (P1.23):** +- `docs/docs/tutorials/*.py` (audit for `project.report` usage; + add `project.report.formats` examples to ed-3, ed-5, ed-8, + ed-14 — confirm list during P1.23; regenerate notebooks). +- `docs/docs/user-guide/analysis-workflow/report.md` (existing + — extend with the configuration-category section). +- `docs/docs/api-reference/report.md` (existing — extend with + the new save methods, enums, configuration fields). + +**ADR promotion (P1.24):** +- `docs/dev/adrs/suggestions/project-summary-rendering.md` → + moved to `docs/dev/adrs/accepted/`. +- `docs/dev/adrs/index.md` (row updated to `accepted/`). + +## Commit discipline + +When an AI agent follows this plan, **every completed Phase 1 +implementation step must be staged with explicit paths and +committed locally before moving to the next implementation step +or the Phase 1 review gate.** Follow the rules in +[`AGENTS.md`](../../../AGENTS.md) → **Commits**. Keep commits +atomic, single-purpose, and aligned with the plan steps. Do not +include generated artifacts (data CIFs, project directories, +benchmark CSVs) unless the step explicitly produces them — see +**Workflow** in [`AGENTS.md`](../../../AGENTS.md) for the +generated-artifact exceptions. + +## Implementation steps (Phase 1) + +- [ ] **P1.1 — Add `ReportFormatEnum` and `ReportStyleEnum`** + - Files: new `src/easydiffraction/report/enums.py`. + - Define `ReportFormatEnum(str, Enum)` with members `CIF`, + `HTML`, `TEX`, `PDF` (string values `'cif'`, `'html'`, + `'tex'`, `'pdf'`). + - Define `ReportStyleEnum(str, Enum)` with members `IUCR`, + `REVTEX` (string values `'iucr'`, `'revtex'`). + - Re-export from `src/easydiffraction/report/__init__.py`. + - Commit: `Add ReportFormatEnum and ReportStyleEnum`. + +- [ ] **P1.2 — Refactor `Report` into a `CategoryItem` and register it on `ProjectConfig`** + - Files: existing `src/easydiffraction/report/report.py`; + new `src/easydiffraction/project/categories/report/` + package (`__init__.py`, `default.py`, `factory.py`); modify + `src/easydiffraction/project/project_config.py`. + - Refactor the current plain-facade `Report` class into a + `CategoryItem` subclass living at + `src/easydiffraction/project/categories/report/default.py` + (matching the layout of `chart/`, `table/`, + `verbosity/`). The class keeps **every existing rendering + method** (`show_report`, `show_project_info`, + `show_crystallographic_data`, `show_experimental_data`, + `show_fitting_details`, `help`) — this is the explicit + "facade-hybrid" amendment to + `project-facade-and-persistence.md` already documented in + the ADR. Static helper methods (`_fmt_row` etc.) move + with it. + - Add six descriptor fields to the new `Report` + (`CategoryItem`): `cif` (`BoolDescriptor`), `html` + (`BoolDescriptor`), `tex` (`BoolDescriptor`), `pdf` + (`BoolDescriptor`), `style` (`StringDescriptor` typed + against `ReportStyleEnum`), `html_offline` + (`BoolDescriptor`). Each gets + `CifHandler(names=['_report.'])`. + - Add a `formats` Python `@property` (getter returns + `list[ReportFormatEnum]` derived from the four booleans; + setter accepts a list/iterable of `ReportFormatEnum` + members or strings and updates the four booleans). This is + a pure-Python property — it has no CIF handler, no + descriptor — so the existing `as_cif` walker on + `CategoryItem` ignores it automatically. + - Add a `ReportFactory` per the `@Factory.register` contract. + - Register `Report` on `ProjectConfig` alongside `info`, + `chart`, `table`, `verbosity`: add `self._report = + ReportFactory.create(ReportFactory.default_tag())` in + `ProjectConfig.__init__` and a `report` property. + - Update `Project.__init__` to read `self._report = + self._config.report` (replacing the current + `self._report = Report(self)`); the `Project.report` + property is unchanged. + - **Wire the parent back-reference explicitly.** The current + `CategoryOwner` does not auto-assign `_parent`; the + project's `_attach_category_parents()` is the central + wiring point and currently handles `structures`, + `experiments`, `analysis`, `chart`, `table`. Add + `self._report._parent = self` to that method so the moved + rendering methods can still reach the project via + `self._parent`. + - **Add a `project` property on `Report`** that returns + `self._parent`. Every method that currently dereferences + `self.project` (across the show_*, as_*, save_* surface) + keeps working unchanged because `self.project` now resolves + to the same project object, just via the parent + back-reference instead of a constructor argument. No + method body needs to change; only the `self.project` + attribute initialisation in `Report.__init__` goes away + (it becomes the property). + - **Write side is automatic** — `category_owner_to_cif` + (`src/easydiffraction/io/cif/serialize.py:566-575`) walks + `ProjectConfig` and emits every registered `CategoryItem`, + so the six `_report.*` items appear under `data_` + next to `_info.*`, `_chart.*`, `_table.*`, `_verbosity.*` + automatically once `Report` is a `CategoryItem`. + - **Read side is NOT automatic** — `project_config_from_cif` + (`src/easydiffraction/io/cif/serialize.py:675-689`) + manually calls `chart.from_cif(block)`, + `table.from_cif(block)`, `verbosity.from_cif(block)` and + must be extended to call `report.from_cif(block)` too. P1.3 + owns that extension (see next step). + - Commit: `Refactor Report into a ProjectConfig CategoryItem`. + +- [ ] **P1.3 — Wire `_report.*` into the `project.cif` loader (with legacy defaults)** + - File: + `src/easydiffraction/io/cif/serialize.py` (the existing + `project_config_from_cif()` function at + lines 675-689). + - Add a `report = getattr(project, 'report', None); if + report is not None: report.from_cif(block)` block, matching + the existing pattern used for `chart`, `table`, and + `verbosity`. + - The `Report.from_cif(block)` implementation inherits from + the `CategoryItem` base class — no new override needed — + and it must treat **missing `_report.*` items as + not-present rather than as an error**. The base class + behaviour is already to leave descriptors at their + declared defaults when the matching CIF item is absent, + which gives us the documented defaults + (`cif=html=tex=pdf=False`, `style='iucr'`, + `html_offline=False`) on legacy `project.cif` files + automatically. Add a one-line docstring comment to the + new loader block summarising the missing-items → defaults + rule so a future reader doesn't tighten it into a strict + check. + - This step also formally verifies the round-trip claim + in P1.2: write side from `category_owner_to_cif` emits + `_report.*`; read side from `project_config_from_cif` + (after this step) restores it. + - Commit: + `Load _report.* from project.cif with legacy defaults`. + +- [ ] **P1.4 — Move gemmi validation internal (CIF only)** + - Files: `src/easydiffraction/io/cif/iucr_writer.py`, + `src/easydiffraction/report/check.py` (or move/rename). + - Refactor the existing `Report.check()` body into a private + pre-write helper (e.g. `_validate_iucr_cif(content)`) + invoked from inside the CIF emission path. + - On gemmi failure, raise + `EasyDiffractionWriterError()` — + a new exception in `src/easydiffraction/core/errors.py` + (or wherever project exceptions live) — instead of writing + a broken file. + - The validation function is **only** wired into the CIF + write paths (`save_cif()`, the `cif` branch under + `project.save()`). HTML/TeX/PDF paths do not call it. + - Cache the parsed dictionaries at module import so the cost + is paid once per session. + - Commit: `Move IUCr CIF validation inside the writer`. + +- [ ] **P1.5 — Remove public `check()` and `check=True`** + - Files: `src/easydiffraction/project/categories/report/default.py` + (the moved `Report` class — same target for P1.6, P1.7, + P1.20), + `src/easydiffraction/project/project.py`, + `src/easydiffraction/cli/` (any commands exposing + `--check`). + - Delete `Report.check()` method. + - Drop the `check=False` keyword from `Project.save()`. + - Update any docstrings / tutorials that reference the + removed surface (P1.23 will sweep tutorials). + - Commit: `Remove public Report.check() and check=True flag`. + +- [ ] **P1.6 — Per-format `save_*()` methods + `Report.save()`** + - Files: `src/easydiffraction/project/categories/report/default.py`, + `src/easydiffraction/project/project.py`. + - Add `save_cif()`, `save_html(offline=False)`, + `save_tex(style='iucr')`, `save_pdf(style='iucr')` to + `Report`. `save_cif()` invokes the existing alignment-ADR + IUCr writer; the other three are stubs that raise + `NotImplementedError('lands in P1.17 / P1.19 / P1.20')` + so the wiring is testable now without all renderers in + place. + - Add `Report.save()`: reads `self.formats`, dispatches to + the matching `save_*()` per enabled format; raises + `ValueError(...)` when `formats` is empty (matching CLI's + no-flag behaviour). + - Rewire `Project.save()`: drop the `report=True` kwarg; + after the regular project files are written, call + `Report._save_configured()` (a private "no-arg, no-error + on empty" sibling of `save()`) that emits each enabled + format, returning quietly when none are configured. + - Commit: `Add per-format save methods and Report.save dispatch`. + +- [ ] **P1.7 — Empty-config behaviour tests-of-intent** + - Files: `src/easydiffraction/project/categories/report/default.py` + (assertion docstrings / runtime checks). + - Add the explicit `ValueError` message from §1.2 of the + ADR to `Report.save()` so future readers see the same + text the CLI prints. + - Verify (no test commits in P1 — actual tests land in P2) + that: + - `project.save()` with no formats writes only project + files. + - `project.report.save()` with no formats raises. + - `project.report.save_cif()` always writes regardless of + config. + - This step is largely contract-locking — the behaviour was + introduced in P1.6, this step makes the contract visible + in code via the docstrings and the error message. + - Commit: `Lock empty-config behaviour in Report API`. + +- [ ] **P1.8 — Add `analysis.software` category + URL constants** + - Files: new + `src/easydiffraction/analysis/categories/software/` + package (`__init__.py`, `base.py`, `default.py`, + `factory.py`); modify backend classes for `url`. + - Define the `Software` category with three sibling + sub-categories: `framework`, `calculator`, `minimizer`. + Each carries `name` (string), `version` (string), `url` + (string). The top-level category also carries + `timestamp` (ISO-8601 datetime string, nullable). + - Add `url: str` class-level constants to existing + calculator backend classes (`CryspyCalculator`, + `CrysfmlCalculator`, `PdffitCalculator`) and minimizer + classes (`LmfitMinimizer`, `EmceeMinimizer`, …) per the + ADR §4 list. + - No fit-time population yet (that's P1.9). + - Commit: + `Add analysis.software category and engine URL constants`. + +- [ ] **P1.9 — Populate `analysis.software` at fit time + `fit_datetime` emission** + - Files: + `src/easydiffraction/analysis/analysis.py`, + `src/easydiffraction/io/cif/iucr_writer.py`. + - In `Analysis.fit()`, immediately before successful return, + populate `analysis.software.*` from the active calculator + / minimizer instances (read `name` from `type_info`, + `version` from the upstream library's `__version__`, `url` + from the class constant). Set `timestamp` to the current + UTC ISO-8601 string. + - Update the IUCr writer's `data_global` block builder to + read the `_easydiffraction_software.{framework, calculator, + minimizer}` triple **from** `analysis.software` (currently + constructed inline per the alignment ADR's §2.3a-i — + replace that inline construction). + - Add `_easydiffraction_software.fit_datetime` emission in + `data_global` when `analysis.software.timestamp` is set; + omit entirely when unset (per the missing-provenance + behaviour in §4.1). + - Update `_computing.structure_refinement` derivation to + fall back to `"EasyDiffraction "` (framework + only) when calculator / minimizer details are unset. + - Commit: `Populate analysis.software at fit time and emit fit_datetime`. + +- [ ] **P1.10 — Amend `analysis-cif-fit-state.md` ADR** + - File: `docs/dev/adrs/accepted/analysis-cif-fit-state.md`. + - Document the new `analysis.software` persisted category + (framework / calculator / minimizer triples + timestamp). + - Commit: `Amend analysis-cif-fit-state for analysis.software`. + +- [ ] **P1.11 — Amend `iucr-cif-tag-alignment.md` ADR** + - File: `docs/dev/adrs/accepted/iucr-cif-tag-alignment.md`. + - Five amendments per the ADR's amended-list entry: + (1) `project.save(report=True)` flag removal; + (2) `Report.save()` surface redesign (per-format methods + + no-arg convenience + `ValueError` on empty); + (3) `_easydiffraction_software.*` triple read from + `analysis.software`; + (4) `_easydiffraction_software.fit_datetime` extension tag + added to `data_global`; + (5) public `check()` + `check=True` removed; gemmi pass + moves internal (CIF emission paths only). + - Update the Current State table row for software + identification accordingly. + - Commit: `Amend iucr-cif-tag-alignment for project.report`. + +- [ ] **P1.12 — Add `project.publication` top-level owner** + - Files: new + `src/easydiffraction/project/categories/publication/` + package; modify `src/easydiffraction/project/project.py`. + - Define `Publication` as a `CategoryOwner` exposing six + sibling sub-categories per the ADR §5.1 table: + `journal`, `journal_date`, `journal_coeditor`, + `contact_author`, `body`, `authors` (loop). + - Each sub-category carries the items enumerated in §5.1 + (e.g. `journal.name_full`, `journal.paper_doi`, + `contact_author.name`, `contact_author.id_orcid`, + `contact_author.id_iucr`, …) with + `CifHandler(names=['_.'])` carrying the + **dictionary casing** for the CIF tag side + (`_journal.paper_DOI`, `_publ_contact_author.id_ORCID`, + `_publ_contact_author.id_IUCr`, etc.) while **Python + attributes stay lowercase snake_case** per the ADR §5.1 + contract. The Python ↔ CIF casing split is the same shape + PR #184 already locked in for the structure tier + (`atom_site.adp_type` Python attr ↔ `_atom_site.ADP_type` + CIF tag). + - `Project` gains a `publication` read-only facade property + returning the `Publication` instance. + - No CIF write wiring yet (that's P1.13). + - Commit: `Add project.publication facade and sub-categories`. + +- [ ] **P1.13 — Persist `project.publication.*` to `project.cif`** + - Files: `src/easydiffraction/project/project.py` (and the + project-CIF serializer). + - Extend the project-CIF write path to include the + `_publ_*` / `_journal_*` items after `_report.*`. Unset + fields write as `?` (the existing CIF unset-value + convention). + - Extend the read path to populate `project.publication.*` + from any `_publ_*` / `_journal_*` items found in + `project.cif`. Missing items leave the descriptors at + `None`. + - Commit: `Persist project.publication to project.cif`. + +- [ ] **P1.14 — TOML/JSON loader for publication metadata** + - File: new + `src/easydiffraction/project/publication_loader.py`. + - Implement `Publication.load(path: str | Path)`: + - Extension dispatch: `.toml` → `tomllib` (stdlib 3.11+), + `.json` → `json` (stdlib). + - Unknown extension → `ValueError("Unsupported publication-info + format: . Use .toml or .json.")`. + - Map flat keys (`journal_name_full`, + `contact_author_name`, …) to the matching + sub-category fields; unknown keys raise + `ValueError()`. + - Commit: `Add Publication.load TOML/JSON entry point`. + +- [ ] **P1.15 — Wire IUCr writer to read from `project.publication.*` + amend ADRs** + - Files: `src/easydiffraction/io/cif/iucr_writer.py`, + `docs/dev/adrs/accepted/project-facade-and-persistence.md`, + `docs/dev/adrs/suggestions/python-cif-category-correspondence.md`. + - Update the IUCr writer's `data_global` content (§2.3a of + the alignment ADR) to read `_publ_*` / `_journal_*` values + from `project.publication.*` instead of emitting static + `?` placeholders. Unset fields still emit `?`; set fields + emit the user's value. + - Amend `project-facade-and-persistence.md`: add + `project.publication` to the facade enumeration; add + `project.report` as a hybrid (config + actions); extend + the project-level singleton-category list with + `_report.*` and the `_publ_*` / `_journal_*` family. + - Amend `python-cif-category-correspondence.md`: add two + rows to the correspondence table for + `project.report.* ↔ _report.*` and + `project.publication.* ↔ _publ_* / _journal_*`. + - Commit: + `Read publication metadata from project.publication in IUCr writer`. + +- [ ] **P1.16 — `ReportDataContext` builder + Jinja base templates** + - Files: new `src/easydiffraction/report/data_context.py`; + new `src/easydiffraction/report/templates/base.j2`. + - `data_context()` builds the dict per §6 of the ADR + (project metadata, structures iterable, experiments + iterable, fit results, software triple, publication + metadata, save timestamp, EasyDiffraction version). + - `base.j2` defines shared macros (parameter row, uncertainty + formatting) for the HTML and TeX renderers. + - No HTML/TeX renderer yet; this step is the shared + foundation. + - Commit: `Add ReportDataContext builder and Jinja base macros`. + +- [ ] **P1.17 — HTML renderer + `save_html(offline=False)`** + - Files: new + `src/easydiffraction/report/html_renderer.py`; + new `src/easydiffraction/report/templates/html/report.html.j2`; + new `src/easydiffraction/report/templates/html/style.css`. + - Implement `Report.as_html(offline=False)`: reads + `data_context()`, renders the Jinja HTML template, embeds + Plotly figures via `fig.to_html(include_plotlyjs=)` + per the `offline` argument. + - Replace the `NotImplementedError` stub in `Report.save_html()` + with the real call (writes + `reports/.html`). + - No new dependencies — `plotly`, `jinja2`, `pandas` are + already declared. + - Commit: `Add HTML renderer with CDN and offline Plotly modes`. + +- [ ] **P1.18 — Vendor LaTeX styles + add `kaleido` / `chromium` / `tectonic` deps** + - Files: `pyproject.toml`, `pixi.toml`, `pixi.lock`; new + `src/easydiffraction/report/templates/tex/styles/` (12 + files: `iucrjournals.cls`, `harvard.sty`, `revtex4-2.cls`, + `ltxgrid.sty`, `ltxutil.sty`, `ltxfront.sty`, + `ltxdocext.sty`, `revsymb4-2.sty`, `aps4-2.rtx`, + `aps10pt4-2.rtx`, `aps11pt4-2.rtx`, `aps12pt4-2.rtx`); + new `src/easydiffraction/report/templates/tex/styles/LICENSES.md` + (vendored-license attributions); new + `THIRD_PARTY_LICENSES.md` at repository root. + - Add `kaleido` (v1.0+) to the runtime dependency list in + `pyproject.toml`. + - Add `chromium` and `tectonic` to the dev/docs feature + group in `pixi.toml`. + - Regenerate `pixi.lock`. + - Copy the 12 upstream style files into the vendored + directory. + - **Concrete licence destinations** (root `LICENSE` is + deliberately **not** edited — keep that file scoped to the + wheel's BSD-3-Clause text only): + - `src/easydiffraction/report/templates/tex/styles/LICENSES.md` — + ships with the wheel. Contains the full CC0 1.0 text for + the IUCr files (`iucrjournals.cls`, `harvard.sty`), the + full LPPL 1.3c text for the REVTeX files (`revtex4-2.cls`, + `ltx{grid,util,front,docext}.sty`, `revsymb4-2.sty`, + `aps{4-2,10pt4-2,11pt4-2,12pt4-2}.rtx`), per-file + attribution (upstream URL, version, original author / + project), and a header pointing readers back at + `THIRD_PARTY_LICENSES.md` at the repo root. + - `THIRD_PARTY_LICENSES.md` at repo root — short index + listing every vendored third-party asset and pointing + at the in-package `LICENSES.md` for the full texts. The + file is created in this PR; future vendored content + extends the same index. + - **Verify wheel packaging under hatchling.** The project + uses hatchling (`[build-system] build-backend = + 'hatchling.build'`); `[tool.hatch.build.targets.wheel]` + currently lists `packages = ['src/easydiffraction']` with + no explicit include/exclude rules. By hatchling's default + behaviour, every non-pyc file inside the listed package + ships with the wheel — so `.cls`, `.sty`, `.rtx`, and + `.md` files at + `src/easydiffraction/report/templates/tex/styles/` should + be picked up automatically. **Verify with the existing + project task**: after vendoring the files, run + `pixi run dist-build` (defined at + [`pixi.toml:284-285`](../../../pixi.toml) as + `python -m build --wheel --outdir dist`) and inspect the + resulting wheel with + `unzip -l dist/*.whl | grep styles/` to confirm the 12 + style files and `LICENSES.md` are inside. `dist/*.whl` + is a local verification artefact — **do not stage it**; + it is not part of the PR's source diff. If the style + files are missing from the wheel, extend + `[tool.hatch.build.targets.wheel]` with a + `force-include = { "src/easydiffraction/report/templates" + = "easydiffraction/report/templates" }` (or equivalent + `include` rule) and re-run `pixi run dist-build` to + re-verify. Do **not** add + `[tool.setuptools.package-data]` — this project does not + use setuptools. + - Commit: `Vendor LaTeX styles with licenses and add kaleido/chromium/tectonic deps`. + +- [ ] **P1.19 — LaTeX renderer + `save_tex(style='iucr')`** + - Files: new + `src/easydiffraction/report/tex_renderer.py`; + new `src/easydiffraction/report/templates/tex/iucr.tex.j2`; + new `src/easydiffraction/report/templates/tex/revtex.tex.j2`. + - `Report.as_tex(style='iucr')` reads `data_context()`, + renders the matching Jinja TeX template (driven by the + `ReportStyleEnum` value), emits a `.tex` that + `\input{}`'s table partials and `\includegraphics{}`'s + figures. + - `Report.save_tex(style='iucr')` writes the rendered TeX + plus its assets to `reports/tex/`: the main document, a + `figures/` directory with vector-PDF fits via `kaleido`, + and a `styles/` directory copied from the vendored + bundle (P1.18). + - Replace the `NotImplementedError` stub in + `Report.save_tex()` with the real call. + - Commit: `Add LaTeX renderer and save_tex with vendored styles`. + +- [ ] **P1.20 — PDF compilation + `save_pdf(style='iucr')`** + - Files: new + `src/easydiffraction/report/pdf_compiler.py`; + modify + `src/easydiffraction/project/categories/report/default.py`. + - Implement engine discovery: try `tectonic` first, then + `latexmk`, then `pdflatex` (per the ADR's order). + - `Report.save_pdf(style='iucr')` writes the TeX bundle + (calls `save_tex()` as a side-effect, per the ADR's + "PDF implies TeX" rule), compiles to PDF via subprocess + against the first available engine, writes + `reports/.pdf`. + - If no engine is found: still write the TeX bundle, + print the §3.4 install hint (`pixi add tectonic` etc.), + return without raising — matches the ADR's + "opportunistic" semantics. + - Replace the `NotImplementedError` stub in + `Report.save_pdf()` with the real call. + - Commit: `Add opportunistic PDF compilation via TeX subprocess`. + +- [ ] **P1.21 — Add `crystal_system` and `measured_range` properties** + - Files: + `src/easydiffraction/datablocks/structure/categories/space_group/default.py`, + `src/easydiffraction/datablocks/experiment/item/base.py`. + - Add a `crystal_system` property to `SpaceGroup` derived + from `name_H-M_alt` (mapping table maintained in the + descriptor). + - Add a `measured_range` property to the experiment base + class returning `(min, max, inc)` derived from the + underlying x-axis array (a single axis: `2theta_scan` for + CWL, `time_of_flight` for TOF — never both at once). + Boundary cases — **explicit contract** so renderers don't + need defensive checks: + - **Empty data** (no points loaded yet) → returns `None` + (not a tuple). Renderers display `"(no data)"`. + - **Single point** → `(value, value, None)`. `inc` is + `None` to signal "no spacing". + - **Nonuniform spacing** (max−min step deviates from the + median step by more than 1% of the median) → + `(min, max, None)`. `inc` is `None`; the renderers may + still show `min` / `max`. + - **Uniform spacing** → `(min, max, inc)` where `inc` is + the median step (a single representative value). + - **Units** match the experiment's x-axis units (`'deg'` + for `2theta_scan`, `'us'` for `time_of_flight`). + Returned as bare floats; the renderer prepends units via + `data_context()`. + - Both properties are read-only computed properties — no + CIF representation, no validation hooks. They exist + purely to feed the renderers via `data_context()`. + - Commit: `Add crystal_system and measured_range properties for reports`. + +- [ ] **P1.22 — CLI `ed save-report` subcommand** + - Files: new `src/easydiffraction/cli/save_report.py`; + modify `src/easydiffraction/cli/save.py`. + - Add an `ed save-report` subcommand accepting `--cif`, + `--html`, `--tex`, `--pdf`, `--style iucr`, + `--offline` flags. Dispatches to + `project.report.save_*()` methods per flag. + - `ed save-report` with no flags → clear error pointing at + the configuration category (matches the Python + `ValueError` from P1.6). + - `ed save` continues to read the persisted config; no flag + surface added on `ed save`. + - Commit: `Add ed save-report CLI subcommand`. + +- [ ] **P1.23 — Update tutorials and user-guide docs** + - Files: `docs/docs/tutorials/*.py`, + `docs/docs/tutorials/*.ipynb` (regenerated artefacts — + explicitly staged because `pixi run notebook-prepare` + emits them and they must travel with the `.py` edits), + `docs/docs/user-guide/analysis-workflow/report.md`, + `docs/docs/api-reference/report.md`. + - Audit tutorial sources for `project.report.save()` / + `project.report.check()` / `project.save(report=True)` + references; rewrite to use the configuration category + or per-format methods per the new contract. + - Add at least one tutorial demonstrating + `project.report.formats = ['html']` + `project.save()` + end-to-end with the resulting `reports/.html`. + - Extend the user-guide report page with the configuration + table (§1.1) and the worked examples (§3.1). + - Extend the API-reference report page with the new + `Report` class surface (six fields, per-format + `save_*()` methods, enums). + - Regenerate notebooks via `pixi run notebook-prepare`. + - Commit: + `Update tutorials and user-guide docs for project.report`. + +- [ ] **P1.24 — Promote ADR to `accepted/`** + - Files: move + `docs/dev/adrs/suggestions/project-summary-rendering.md` + to `docs/dev/adrs/accepted/`; update + `docs/dev/adrs/index.md`. + - Flip the ADR's `**Status:**` line from `Proposed` to + `Accepted`; update the date to today (Phase 1 acceptance + date). + - Index row: status `Suggestion` → `Accepted`; link target + changes from `suggestions/` to `accepted/`. + - Commit: + `Promote project-summary-rendering ADR to accepted`. + +- [ ] **P1.25 — Reach Phase 1 review gate** + - No-code step. Mark every `[ ]` above as `[x]`; commit the + plan-file update alone. + - Commit: `Reach Phase 1 review gate`. + +## Test plan (Phase 2) + +Per AGENTS.md §Testing, every new module, class, and bug fix +ships with tests; unit tests mirror the source tree. Before +running the verification commands below, add or update: + +- [ ] **`tests/unit/easydiffraction/report/test_enums.py`** — + `ReportFormatEnum` and `ReportStyleEnum` are `(str, Enum)` + per the closed-values ADR; membership tests; round-trip via + string values. P1.1 surface. +- [ ] **`tests/unit/easydiffraction/project/categories/report/test_default.py`** + (extend) — six descriptor fields read/write; `formats` + property view round-trip (list/iterable → booleans → + list); per-format `save_*()` methods called with no + config; `Report.save()` `ValueError` on empty config; + `Report.save()` dispatches to the right `save_*()` per + enabled format. P1.2–P1.7 surface. +- [ ] **`tests/unit/easydiffraction/project/test_project.py`** + (extend) — `project.report.*` round-trip through + `project.cif`; `Project.save()` honours the config when set; + `Project.save()` writes no reports when config is empty; + `report=True` and `check=True` removed (calling with them + raises `TypeError`). P1.3, P1.5, P1.6 surface. +- [ ] **`tests/unit/easydiffraction/io/cif/test_iucr_writer.py`** + (extend) — internal gemmi validation runs on CIF emission; + malformed-tag injection triggers `EasyDiffractionWriterError`; + HTML/TeX/PDF paths do not invoke gemmi. P1.4 surface. +- [ ] **`tests/unit/easydiffraction/analysis/categories/software/test_software.py`** + (new) — `Software` category shape; `framework`/ + `calculator`/`minimizer` sub-categories carry name + version + + url; `timestamp` ISO-8601 round-trip via `analysis.cif`. P1.8 + surface. +- [ ] **`tests/unit/easydiffraction/analysis/test_analysis.py`** + (extend) — `Analysis.fit()` populates `analysis.software` on + success; missing provenance renders `"(not available)"` / + `?` placeholders; `_easydiffraction_software.fit_datetime` + appears in the IUCr export only when timestamp is set. P1.9 + surface. +- [ ] **`tests/unit/easydiffraction/project/categories/publication/test_publication.py`** + (new) — `Publication` exposes six sibling sub-categories; + each item carries the right `_publ_*` / `_journal_*` CIF + tag with dictionary casing; loop semantics for + `publication.authors`. P1.12 surface. +- [ ] **`tests/unit/easydiffraction/project/categories/publication/test_publication_loader.py`** + (new) — `Publication.load()` accepts TOML and JSON; + unknown extension raises; unknown key raises with the key + name; flat-key → sub-category mapping is exhaustive. P1.14 + surface. +- [ ] **`tests/unit/easydiffraction/io/cif/test_iucr_writer.py`** + (further extend) — `_publ_*` / `_journal_*` items in + `data_global` read from `project.publication.*` instead of + static placeholders; unset fields still emit `?`. P1.15 + surface. +- [ ] **`tests/unit/easydiffraction/report/test_data_context.py`** + (new) — `data_context()` returns the expected dict shape + per ADR §6; missing software / missing publication / + pre-fit project state degrade gracefully. P1.16 surface. +- [ ] **`tests/unit/easydiffraction/report/test_html_renderer.py`** + (new) — golden-string snapshots for the rendered HTML + against a small fixture project; Plotly CDN vs offline + mode markers in the output. P1.17 surface. +- [ ] **`tests/unit/easydiffraction/report/test_tex_renderer.py`** + (new) — golden-string snapshots for the rendered TeX with + `style='iucr'` and `style='revtex'`; ensures both Jinja + templates compile to syntactically valid LaTeX (lexer-only + check, no engine compile). +- [ ] **`tests/unit/easydiffraction/report/test_pdf_compiler.py`** + (new) — engine discovery order respected; missing-engine + branch writes TeX and returns silently with the install + hint; engine non-zero exit raises a clear error. +- [ ] **`tests/unit/easydiffraction/datablocks/structure/categories/space_group/test_default.py`** + (extend) — `crystal_system` derived correctly from a set of + representative H-M symbols. P1.21 surface. +- [ ] **`tests/unit/easydiffraction/datablocks/experiment/item/test_base.py`** + (extend) — `measured_range` boundary cases per P1.21's + contract: empty data → `None`; single point → `(v, v, + None)`; nonuniform spacing (>1% deviation from median) → + `(min, max, None)`; uniform spacing → `(min, max, inc)` + with `inc` as the median step. Covers both CWL + (`2theta_scan`, `'deg'`) and TOF (`time_of_flight`, + `'us'`) x-axes. P1.21 surface. +- [ ] **`tests/unit/easydiffraction/cli/test_save_report.py`** + (new) — `ed save-report` dispatches to the matching + `save_*()` methods; `ed save-report` with no flags exits + with the expected error. P1.22 surface. +- [ ] **Integration / script-test coverage** — verify + `pixi run script-tests` exercises at least one tutorial + that sets `project.report.formats` and the resulting + `reports/.html` is non-empty and gemmi-validates. + Extend a tutorial if not. + +Use `pixi run test-structure-check` to confirm the unit-test +layout mirrors the source tree per AGENTS.md §Testing. + +## Verification commands (Phase 2) + +Per AGENTS.md §Workflow, save any required check output with the +zsh-safe pattern. Variable names per-task: + +```sh +pixi run fix > /tmp/easydiffraction-fix.log 2>&1; fix_exit_code=$?; tail -n 200 /tmp/easydiffraction-fix.log; exit $fix_exit_code +pixi run check > /tmp/easydiffraction-check.log 2>&1; check_exit_code=$?; tail -n 200 /tmp/easydiffraction-check.log; exit $check_exit_code +pixi run test-structure-check > /tmp/easydiffraction-test-structure-check.log 2>&1; test_structure_check_exit_code=$?; tail -n 200 /tmp/easydiffraction-test-structure-check.log; exit $test_structure_check_exit_code +pixi run unit-tests > /tmp/easydiffraction-unit-tests.log 2>&1; unit_tests_exit_code=$?; tail -n 200 /tmp/easydiffraction-unit-tests.log; exit $unit_tests_exit_code +pixi run integration-tests > /tmp/easydiffraction-integration-tests.log 2>&1; integration_tests_exit_code=$?; tail -n 200 /tmp/easydiffraction-integration-tests.log; exit $integration_tests_exit_code +pixi run script-tests > /tmp/easydiffraction-script-tests.log 2>&1; script_tests_exit_code=$?; tail -n 200 /tmp/easydiffraction-script-tests.log; exit $script_tests_exit_code +``` + +Run in order; each must complete clean before the next. Per +AGENTS.md §Workflow: `pixi run fix` regenerates +`docs/dev/package-structure/full.md` and `short.md` +automatically. The new vendored LaTeX style files under +`src/easydiffraction/report/templates/tex/styles/` are +intentional sources — included in commits. Benchmark CSVs +under `docs/dev/benchmarking/` produced by +`pixi run script-tests` are untracked verification artifacts; +do not stage them. + +## Suggested Pull Request + +**Title:** `[report] Add HTML, TeX, PDF outputs and publication metadata to project.report` + +**Description:** + +Builds on the IUCr CIF alignment work (PR #184) by filling in +the non-CIF half of the publication bundle. The `project.report` +facade now covers four output formats — CIF, HTML, TeX, PDF — +opt-in via a persisted configuration category on +`project.report` that's written to `project.cif` alongside +the existing project-level preferences (`project.info`, +`project.chart`, `project.table`, `project.verbosity`). + +A user configures their report preferences once: + +```python +project.report.formats = ['html', 'cif'] +project.report.style = 'iucr' +project.report.html_offline = False +``` + +and every subsequent `project.save()` writes the configured +reports under `reports/`. Per-format ad-hoc methods +(`project.report.save_html()`, `save_cif()`, `save_tex()`, +`save_pdf()`) cover one-offs without changing the persisted +config. The CLI mirrors the split: `ed save` reads the +configuration; `ed save-report --html --tex --pdf --style iucr` +is the one-off subcommand. + +LaTeX support ships with vendored IUCr and REVTeX style files +(12 files, ~420 KB) so users can swap journal styles with one +line in `.tex` and recompile. PDF compilation is +opportunistic — works when `tectonic`, `latexmk`, or `pdflatex` +is on `PATH`; otherwise the editable `.tex` and figures are +still written and the user gets an install hint. + +The PR also adds two new persisted categories: +**`analysis.software`** (calculator and minimizer name + +version + URL stamped at fit time, ending the long-standing +provenance gap), and **`project.publication`** +(journal/author/contact-author metadata that feeds the +`_publ_*` / `_journal_*` placeholders in journal-submission +CIFs, replaceable by a TOML or JSON file). + +The PR amends three accepted ADRs +(`iucr-cif-tag-alignment`, `analysis-cif-fit-state`, +`project-facade-and-persistence`) plus the in-flight proposed +ADR `python-cif-category-correspondence` (which stays in +`suggestions/`; this PR only updates its correspondence table +to include the new `project.report.*` / `project.publication.*` +surfaces, it does not promote it to accepted), and promotes the +`project-summary-rendering` ADR itself from `suggestions/` to +`accepted/`. + +**Scope label:** `[report]`. The headline feature is the +`project.report` rendering surface; the `project.publication` +addition and IUCr writer wiring are supporting changes for +the same user-visible deliverable. From 523f5e5a9d356dc02dfc3ad6f1f03c98fca6c2bc Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 21:02:22 +0200 Subject: [PATCH 003/129] Add ReportFormatEnum and ReportStyleEnum --- docs/dev/plans/project-summary-rendering.md | 2 +- src/easydiffraction/report/__init__.py | 2 ++ src/easydiffraction/report/enums.py | 33 +++++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 src/easydiffraction/report/enums.py diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index c94f280b4..894950115 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -273,7 +273,7 @@ generated-artifact exceptions. ## Implementation steps (Phase 1) -- [ ] **P1.1 — Add `ReportFormatEnum` and `ReportStyleEnum`** +- [x] **P1.1 — Add `ReportFormatEnum` and `ReportStyleEnum`** - Files: new `src/easydiffraction/report/enums.py`. - Define `ReportFormatEnum(str, Enum)` with members `CIF`, `HTML`, `TEX`, `PDF` (string values `'cif'`, `'html'`, diff --git a/src/easydiffraction/report/__init__.py b/src/easydiffraction/report/__init__.py index 3d90c3734..a92cba60a 100644 --- a/src/easydiffraction/report/__init__.py +++ b/src/easydiffraction/report/__init__.py @@ -4,4 +4,6 @@ from __future__ import annotations +from easydiffraction.report.enums import ReportFormatEnum +from easydiffraction.report.enums import ReportStyleEnum from easydiffraction.report.report import Report diff --git a/src/easydiffraction/report/enums.py b/src/easydiffraction/report/enums.py new file mode 100644 index 000000000..1398d5e99 --- /dev/null +++ b/src/easydiffraction/report/enums.py @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Enumeration types used by report components.""" + +from __future__ import annotations + +from enum import StrEnum + + +class ReportFormatEnum(StrEnum): + """Report output format.""" + + CIF = 'cif' + HTML = 'html' + TEX = 'tex' + PDF = 'pdf' + + @classmethod + def default(cls) -> ReportFormatEnum: + """Return the default report format.""" + return cls.CIF + + +class ReportStyleEnum(StrEnum): + """Report template style.""" + + IUCR = 'iucr' + REVTEX = 'revtex' + + @classmethod + def default(cls) -> ReportStyleEnum: + """Return the default report style.""" + return cls.IUCR From 0fb7189d758adf4b50b51bc19ada2741849bd4cf Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 21:07:30 +0200 Subject: [PATCH 004/129] Refactor Report into a ProjectConfig CategoryItem --- docs/dev/plans/project-summary-rendering.md | 2 +- .../project/categories/report/__init__.py | 8 + .../categories/report/default.py} | 163 ++++++++++++++++-- .../project/categories/report/factory.py | 17 ++ src/easydiffraction/project/project.py | 3 +- src/easydiffraction/project/project_config.py | 8 + src/easydiffraction/report/__init__.py | 11 +- 7 files changed, 199 insertions(+), 13 deletions(-) create mode 100644 src/easydiffraction/project/categories/report/__init__.py rename src/easydiffraction/{report/report.py => project/categories/report/default.py} (67%) create mode 100644 src/easydiffraction/project/categories/report/factory.py diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 894950115..92035b5a8 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -283,7 +283,7 @@ generated-artifact exceptions. - Re-export from `src/easydiffraction/report/__init__.py`. - Commit: `Add ReportFormatEnum and ReportStyleEnum`. -- [ ] **P1.2 — Refactor `Report` into a `CategoryItem` and register it on `ProjectConfig`** +- [x] **P1.2 — Refactor `Report` into a `CategoryItem` and register it on `ProjectConfig`** - Files: existing `src/easydiffraction/report/report.py`; new `src/easydiffraction/project/categories/report/` package (`__init__.py`, `default.py`, `factory.py`); modify diff --git a/src/easydiffraction/project/categories/report/__init__.py b/src/easydiffraction/project/categories/report/__init__.py new file mode 100644 index 000000000..ca0cde8d5 --- /dev/null +++ b/src/easydiffraction/project/categories/report/__init__.py @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Project report category exports.""" + +from __future__ import annotations + +from easydiffraction.project.categories.report.default import Report +from easydiffraction.project.categories.report.factory import ReportFactory diff --git a/src/easydiffraction/report/report.py b/src/easydiffraction/project/categories/report/default.py similarity index 67% rename from src/easydiffraction/report/report.py rename to src/easydiffraction/project/categories/report/default.py index 19b668494..c7ebe2626 100644 --- a/src/easydiffraction/report/report.py +++ b/src/easydiffraction/project/categories/report/default.py @@ -4,13 +4,24 @@ from __future__ import annotations +from collections.abc import Iterable from textwrap import wrap from typing import TYPE_CHECKING +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.metadata import TypeInfo +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import MembershipValidator +from easydiffraction.core.variable import BoolDescriptor +from easydiffraction.core.variable import StringDescriptor from easydiffraction.io.cif.iucr_writer import iucr_report_path from easydiffraction.io.cif.iucr_writer import write_iucr_cif +from easydiffraction.io.cif.handler import CifHandler +from easydiffraction.project.categories.report.factory import ReportFactory from easydiffraction.report.check import ReportCheckResult from easydiffraction.report.check import check_report +from easydiffraction.report.enums import ReportFormatEnum +from easydiffraction.report.enums import ReportStyleEnum from easydiffraction.utils.logging import console from easydiffraction.utils.logging import log from easydiffraction.utils.utils import render_object_help @@ -22,7 +33,11 @@ from easydiffraction.core.variable import Parameter -class Report: +REPORT_STYLE_OPTIONS = [member.value for member in ReportStyleEnum] + + +@ReportFactory.register +class Report(CategoryItem): """ Generates reports and exports results from the project. @@ -30,16 +45,144 @@ class Report: fitted model, experiments, and analysis results. """ - def __init__(self, project: object) -> None: - """ - Initialize the report with a reference to the project. + _category_code = 'report' - Parameters - ---------- - project : object - The Project instance this report belongs to. - """ - self.project = project + type_info = TypeInfo( + tag='default', + description='Project report category', + ) + + def __init__(self) -> None: + """Initialize report-output configuration descriptors.""" + super().__init__() + + self._cif = BoolDescriptor( + name='cif', + description='Whether to write CIF reports when saving.', + value_spec=AttributeSpec(default=False), + cif_handler=CifHandler(names=['_report.cif']), + ) + self._html = BoolDescriptor( + name='html', + description='Whether to write HTML reports when saving.', + value_spec=AttributeSpec(default=False), + cif_handler=CifHandler(names=['_report.html']), + ) + self._tex = BoolDescriptor( + name='tex', + description='Whether to write TeX reports when saving.', + value_spec=AttributeSpec(default=False), + cif_handler=CifHandler(names=['_report.tex']), + ) + self._pdf = BoolDescriptor( + name='pdf', + description='Whether to write PDF reports when saving.', + value_spec=AttributeSpec(default=False), + cif_handler=CifHandler(names=['_report.pdf']), + ) + self._style = StringDescriptor( + name='style', + description='Report template style.', + value_spec=AttributeSpec( + default=ReportStyleEnum.default().value, + validator=MembershipValidator(allowed=REPORT_STYLE_OPTIONS), + ), + cif_handler=CifHandler(names=['_report.style']), + ) + self._html_offline = BoolDescriptor( + name='html_offline', + description='Whether HTML reports should embed assets.', + value_spec=AttributeSpec(default=False), + cif_handler=CifHandler(names=['_report.html_offline']), + ) + + @property + def cif(self) -> BoolDescriptor: + """Whether to write CIF reports when saving.""" + return self._cif + + @cif.setter + def cif(self, value: bool) -> None: + self._cif.value = value + + @property + def html(self) -> BoolDescriptor: + """Whether to write HTML reports when saving.""" + return self._html + + @html.setter + def html(self, value: bool) -> None: + self._html.value = value + + @property + def tex(self) -> BoolDescriptor: + """Whether to write TeX reports when saving.""" + return self._tex + + @tex.setter + def tex(self, value: bool) -> None: + self._tex.value = value + + @property + def pdf(self) -> BoolDescriptor: + """Whether to write PDF reports when saving.""" + return self._pdf + + @pdf.setter + def pdf(self, value: bool) -> None: + self._pdf.value = value + + @property + def style(self) -> StringDescriptor: + """Report template style.""" + return self._style + + @style.setter + def style(self, value: str) -> None: + self._style.value = ReportStyleEnum(value).value + + @property + def html_offline(self) -> BoolDescriptor: + """Whether HTML reports should embed assets.""" + return self._html_offline + + @html_offline.setter + def html_offline(self, value: bool) -> None: + self._html_offline.value = value + + @property + def formats(self) -> list[ReportFormatEnum]: + """Enabled report-output formats.""" + formats = [] + if self._cif.value: + formats.append(ReportFormatEnum.CIF) + if self._html.value: + formats.append(ReportFormatEnum.HTML) + if self._tex.value: + formats.append(ReportFormatEnum.TEX) + if self._pdf.value: + formats.append(ReportFormatEnum.PDF) + return formats + + @formats.setter + def formats( + self, + formats: Iterable[ReportFormatEnum | str] | ReportFormatEnum | str, + ) -> None: + if isinstance(formats, (ReportFormatEnum, str)): + values = [formats] + else: + values = list(formats) + enabled = {ReportFormatEnum(value) for value in values} + self.cif = ReportFormatEnum.CIF in enabled + self.html = ReportFormatEnum.HTML in enabled + self.tex = ReportFormatEnum.TEX in enabled + self.pdf = ReportFormatEnum.PDF in enabled + + @property + def project(self) -> object: + """Project owning this report category.""" + return self._parent def help(self) -> None: """Print available report methods.""" diff --git a/src/easydiffraction/project/categories/report/factory.py b/src/easydiffraction/project/categories/report/factory.py new file mode 100644 index 000000000..05bef8349 --- /dev/null +++ b/src/easydiffraction/project/categories/report/factory.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Factory for project report categories.""" + +from __future__ import annotations + +from typing import ClassVar + +from easydiffraction.core.factory import FactoryBase + + +class ReportFactory(FactoryBase): + """Create project report category instances.""" + + _default_rules: ClassVar[dict] = { + frozenset(): 'default', + } diff --git a/src/easydiffraction/project/project.py b/src/easydiffraction/project/project.py index b6bfc018c..934ca8e6c 100644 --- a/src/easydiffraction/project/project.py +++ b/src/easydiffraction/project/project.py @@ -208,9 +208,9 @@ def __init__( object.__setattr__(self, '_chart', self._config.chart) object.__setattr__(self, '_table', self._config.table) object.__setattr__(self, '_verbosity', self._config.verbosity) + object.__setattr__(self, '_report', self._config.report) self._display = ProjectDisplay(self) self._analysis = Analysis(self) - self._report = Report(self) self._saved = False self._varname = 'project' if type(self)._loading else varname() type(self)._current_project = self @@ -223,6 +223,7 @@ def _attach_category_parents(self) -> None: self._analysis._parent = self self._chart._parent = self self._table._parent = self + self._report._parent = self @staticmethod def _supported_filters_for(category: object) -> dict[str, object]: diff --git a/src/easydiffraction/project/project_config.py b/src/easydiffraction/project/project_config.py index 725c07090..2c779c8a7 100644 --- a/src/easydiffraction/project/project_config.py +++ b/src/easydiffraction/project/project_config.py @@ -9,6 +9,8 @@ from easydiffraction.project.categories.chart import ChartFactory from easydiffraction.project.categories.info import ProjectInfo from easydiffraction.project.categories.info import ProjectInfoFactory +from easydiffraction.project.categories.report import Report +from easydiffraction.project.categories.report import ReportFactory from easydiffraction.project.categories.table import Table from easydiffraction.project.categories.table import TableFactory from easydiffraction.project.categories.verbosity import Verbosity @@ -32,6 +34,7 @@ def __init__( description=description, ) self._chart = ChartFactory.create(ChartFactory.default_tag()) + self._report = ReportFactory.create(ReportFactory.default_tag()) self._table = TableFactory.create(TableFactory.default_tag()) self._verbosity = VerbosityFactory.create(VerbosityFactory.default_tag()) @@ -45,6 +48,11 @@ def chart(self) -> Chart: """Chart configuration category.""" return self._chart + @property + def report(self) -> Report: + """Report configuration category.""" + return self._report + @property def table(self) -> Table: """Table configuration category.""" diff --git a/src/easydiffraction/report/__init__.py b/src/easydiffraction/report/__init__.py index a92cba60a..a7e8a175d 100644 --- a/src/easydiffraction/report/__init__.py +++ b/src/easydiffraction/report/__init__.py @@ -6,4 +6,13 @@ from easydiffraction.report.enums import ReportFormatEnum from easydiffraction.report.enums import ReportStyleEnum -from easydiffraction.report.report import Report + + +def __getattr__(name: str) -> object: + """Load report objects that would otherwise form import cycles.""" + if name == 'Report': + from easydiffraction.project.categories.report.default import Report + + return Report + msg = f"module {__name__!r} has no attribute {name!r}" + raise AttributeError(msg) From cbbba9221dd948bb0e778db297c72445e8a42f31 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 21:08:10 +0200 Subject: [PATCH 005/129] Load _report.* from project.cif with legacy defaults --- docs/dev/plans/project-summary-rendering.md | 2 +- src/easydiffraction/io/cif/serialize.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 92035b5a8..c6dae47ac 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -354,7 +354,7 @@ generated-artifact exceptions. owns that extension (see next step). - Commit: `Refactor Report into a ProjectConfig CategoryItem`. -- [ ] **P1.3 — Wire `_report.*` into the `project.cif` loader (with legacy defaults)** +- [x] **P1.3 — Wire `_report.*` into the `project.cif` loader (with legacy defaults)** - File: `src/easydiffraction/io/cif/serialize.py` (the existing `project_config_from_cif()` function at diff --git a/src/easydiffraction/io/cif/serialize.py b/src/easydiffraction/io/cif/serialize.py index 20841f3fc..35b379a17 100644 --- a/src/easydiffraction/io/cif/serialize.py +++ b/src/easydiffraction/io/cif/serialize.py @@ -684,6 +684,11 @@ def project_config_from_cif(project: object, cif_text: str) -> None: if chart is not None: chart.from_cif(block) + report = getattr(project, 'report', None) + if report is not None: + # Missing _report.* items intentionally keep legacy defaults. + report.from_cif(block) + table = getattr(project, 'table', None) if table is not None: table.from_cif(block) From dd6810c6e47c4ae151dffc61a77a9a68542772ac Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 21:11:14 +0200 Subject: [PATCH 006/129] Move IUCr CIF validation inside the writer --- docs/dev/plans/project-summary-rendering.md | 2 +- src/easydiffraction/core/errors.py | 13 ++++ src/easydiffraction/io/cif/iucr_writer.py | 5 +- src/easydiffraction/report/check.py | 79 +++++++++++++++++++++ 4 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 src/easydiffraction/core/errors.py diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index c6dae47ac..20aaff901 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -383,7 +383,7 @@ generated-artifact exceptions. - Commit: `Load _report.* from project.cif with legacy defaults`. -- [ ] **P1.4 — Move gemmi validation internal (CIF only)** +- [x] **P1.4 — Move gemmi validation internal (CIF only)** - Files: `src/easydiffraction/io/cif/iucr_writer.py`, `src/easydiffraction/report/check.py` (or move/rename). - Refactor the existing `Report.check()` body into a private diff --git a/src/easydiffraction/core/errors.py b/src/easydiffraction/core/errors.py new file mode 100644 index 000000000..e466a8892 --- /dev/null +++ b/src/easydiffraction/core/errors.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Project exception types.""" + +from __future__ import annotations + + +class EasyDiffractionError(Exception): + """Base class for EasyDiffraction exceptions.""" + + +class EasyDiffractionWriterError(EasyDiffractionError): + """Raised when EasyDiffraction cannot write a valid file.""" diff --git a/src/easydiffraction/io/cif/iucr_writer.py b/src/easydiffraction/io/cif/iucr_writer.py index d42b0c2bd..aed482950 100644 --- a/src/easydiffraction/io/cif/iucr_writer.py +++ b/src/easydiffraction/io/cif/iucr_writer.py @@ -16,6 +16,7 @@ from easydiffraction.io.cif.iucr_transformers import IucrCategoryTransformer from easydiffraction.io.cif.iucr_transformers import IucrItem from easydiffraction.io.cif.serialize import format_value +from easydiffraction.report.check import _validate_iucr_cif from easydiffraction.utils.utils import package_version _BLOCK_SEPARATOR = '#=====================================================' @@ -101,8 +102,10 @@ def write_iucr_cif( Path of the written report CIF. """ output_path = iucr_report_path(project, path) + content = _render_iucr_cif(project) + _validate_iucr_cif(content) output_path.parent.mkdir(parents=True, exist_ok=True) - output_path.write_text(_render_iucr_cif(project), encoding='utf-8') + output_path.write_text(content, encoding='utf-8') return output_path diff --git a/src/easydiffraction/report/check.py b/src/easydiffraction/report/check.py index 31b027b38..8aa0c2d1c 100644 --- a/src/easydiffraction/report/check.py +++ b/src/easydiffraction/report/check.py @@ -8,14 +8,18 @@ import re from dataclasses import dataclass from typing import TYPE_CHECKING +from typing import NoReturn import gemmi +from easydiffraction.core.errors import EasyDiffractionWriterError + if TYPE_CHECKING: from collections.abc import Iterable _REPORT_TAG_RE = re.compile(r'(?m)^\s*(_[A-Za-z][A-Za-z0-9_.-]*)\b') _DICT_SAVE_RE = re.compile(r'(?m)^save_(_[^\s]+)\s*$') +_WRITER_ERROR_HINT = 'Please file a bug with the full diagnostic.' @dataclass(frozen=True) @@ -88,6 +92,20 @@ def _dictionary_paths( return tuple(path for path in candidates if path.is_file()) +def _read_dictionary_documents( + dictionary_paths: tuple[pathlib.Path, ...], +) -> tuple[tuple[gemmi.cif.Document, ...], tuple[str, ...]]: + """Return parsed dictionaries and any load diagnostics.""" + documents = [] + errors = [] + for dictionary_path in dictionary_paths: + try: + documents.append(gemmi.cif.read_file(str(dictionary_path))) + except Exception as exc: # noqa: BLE001 + errors.append(f'Failed to load CIF dictionary {dictionary_path}: {exc}') + return tuple(documents), tuple(errors) + + def _gemmi_dictionary_warnings( document: gemmi.cif.Document, dictionary_paths: tuple[pathlib.Path, ...], @@ -104,6 +122,22 @@ def _gemmi_dictionary_warnings( return logger.messages +def _gemmi_dictionary_errors( + document: gemmi.cif.Document, + dictionary_documents: tuple[gemmi.cif.Document, ...], +) -> list[str]: + """Return gemmi dictionary-validation diagnostics.""" + logger = _GemmiLogger() + ddl = gemmi.cif.Ddl(logger, print_unknown_tags=False) + try: + for dictionary_document in dictionary_documents: + ddl.read_ddl(dictionary_document) + ddl.validate_cif(document) + except Exception as exc: # noqa: BLE001 + return [f'Gemmi dictionary validation failed: {exc}'] + return logger.messages + + def _unknown_tag_warnings( report_path: pathlib.Path, dictionary_paths: tuple[pathlib.Path, ...], @@ -122,6 +156,20 @@ def _unknown_tag_warnings( return [f'Unknown IUCr tag: {tag}' for tag in unknown_tags] +def _unknown_tag_errors(content: str, known_tags: set[str]) -> list[str]: + """Return unknown-tag diagnostics from CIF content.""" + if not known_tags: + return [] + + report_tags = set(_REPORT_TAG_RE.findall(content)) + unknown_tags = sorted( + tag + for tag in report_tags + if not tag.startswith('_easydiffraction_') and tag not in known_tags + ) + return [f'Unknown IUCr tag: {tag}' for tag in unknown_tags] + + def _known_dictionary_tags(dictionary_paths: tuple[pathlib.Path, ...]) -> set[str]: """Return item names declared by dictionary save frames.""" tags: set[str] = set() @@ -131,6 +179,37 @@ def _known_dictionary_tags(dictionary_paths: tuple[pathlib.Path, ...]) -> set[st return tags +_CACHED_DICTIONARY_PATHS = _dictionary_paths(None) +_CACHED_DICTIONARY_DOCUMENTS, _CACHED_DICTIONARY_LOAD_ERRORS = ( + _read_dictionary_documents(_CACHED_DICTIONARY_PATHS) +) +_CACHED_DICTIONARY_TAGS = _known_dictionary_tags(_CACHED_DICTIONARY_PATHS) + + +def _validate_iucr_cif(content: str) -> None: + """Validate IUCr CIF content before it is written.""" + try: + document = gemmi.cif.read_string(content) + except Exception as exc: # noqa: BLE001 + _raise_writer_error(f'Failed to parse generated IUCr CIF: {exc}') + + diagnostics = list(_CACHED_DICTIONARY_LOAD_ERRORS) + if _CACHED_DICTIONARY_DOCUMENTS: + diagnostics.extend( + _gemmi_dictionary_errors(document, _CACHED_DICTIONARY_DOCUMENTS) + ) + diagnostics.extend(_unknown_tag_errors(content, _CACHED_DICTIONARY_TAGS)) + + if diagnostics: + _raise_writer_error('\n'.join(diagnostics)) + + +def _raise_writer_error(diagnostic: str) -> NoReturn: + """Raise a writer error with bug-report guidance.""" + msg = f'Generated IUCr CIF failed validation. {_WRITER_ERROR_HINT}\n{diagnostic}' + raise EasyDiffractionWriterError(msg) + + class _GemmiLogger: """Collect gemmi validation messages.""" From 946063cae169b293d969493db1ad793f6244be18 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 21:12:13 +0200 Subject: [PATCH 007/129] Remove public Report.check() and check=True flag --- docs/dev/plans/project-summary-rendering.md | 2 +- .../project/categories/report/default.py | 38 +------------------ src/easydiffraction/project/project.py | 6 +-- 3 files changed, 5 insertions(+), 41 deletions(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 20aaff901..959ed1661 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -401,7 +401,7 @@ generated-artifact exceptions. is paid once per session. - Commit: `Move IUCr CIF validation inside the writer`. -- [ ] **P1.5 — Remove public `check()` and `check=True`** +- [x] **P1.5 — Remove public `check()` and `check=True`** - Files: `src/easydiffraction/project/categories/report/default.py` (the moved `Report` class — same target for P1.6, P1.7, P1.20), diff --git a/src/easydiffraction/project/categories/report/default.py b/src/easydiffraction/project/categories/report/default.py index c7ebe2626..8f3ad1769 100644 --- a/src/easydiffraction/project/categories/report/default.py +++ b/src/easydiffraction/project/categories/report/default.py @@ -14,16 +14,12 @@ from easydiffraction.core.validation import MembershipValidator from easydiffraction.core.variable import BoolDescriptor from easydiffraction.core.variable import StringDescriptor -from easydiffraction.io.cif.iucr_writer import iucr_report_path from easydiffraction.io.cif.iucr_writer import write_iucr_cif from easydiffraction.io.cif.handler import CifHandler from easydiffraction.project.categories.report.factory import ReportFactory -from easydiffraction.report.check import ReportCheckResult -from easydiffraction.report.check import check_report from easydiffraction.report.enums import ReportFormatEnum from easydiffraction.report.enums import ReportStyleEnum from easydiffraction.utils.logging import console -from easydiffraction.utils.logging import log from easydiffraction.utils.utils import render_object_help from easydiffraction.utils.utils import render_table @@ -391,43 +387,13 @@ def show_fitting_details(self) -> None: columns_data=fit_metrics, ) - def save(self, *, check: bool = False) -> pathlib.Path: + def save(self) -> pathlib.Path: """ Write the IUCr submission report. - Parameters - ---------- - check : bool, default=False - Whether to validate the written report. - Returns ------- pathlib.Path Path of the written report CIF. """ - report_path = write_iucr_cif(self.project) - if check: - self.check(path=report_path) - return report_path - - def check(self, path: str | pathlib.Path | None = None) -> ReportCheckResult: - """ - Validate the IUCr submission report. - - Parameters - ---------- - path : str | pathlib.Path | None, default=None - Report path. Defaults to ``reports/.cif``. - - Returns - ------- - ReportCheckResult - Validation result with errors and warnings. - """ - report_path = iucr_report_path(self.project, path) - result = check_report(report_path) - for warning in result.warnings: - log.warning(warning) - if result.errors: - log.error('\n'.join(result.errors), exc_type=ValueError) - return result + return write_iucr_cif(self.project) diff --git a/src/easydiffraction/project/project.py b/src/easydiffraction/project/project.py index 934ca8e6c..527739ec2 100644 --- a/src/easydiffraction/project/project.py +++ b/src/easydiffraction/project/project.py @@ -462,7 +462,7 @@ def _build_parameter_map(self) -> dict[str, object]: param_map[unique_name] = param return param_map - def save(self, *, report: bool = False, check: bool = False) -> None: + def save(self, *, report: bool = False) -> None: """ Save the project into the existing project directory. @@ -470,8 +470,6 @@ def save(self, *, report: bool = False, check: bool = False) -> None: ---------- report : bool, default=False Whether to write the IUCr submission report. - check : bool, default=False - Whether to validate the IUCr submission report. """ if self.info.path is None: log.error('Project path not specified. Use save_as() to define the path first.') @@ -533,7 +531,7 @@ def save(self, *, report: bool = False, check: bool = False) -> None: console.print(f'│ {branch} 📄 {file_name}') if report: - report_path = self.report.save(check=check) + report_path = self.report.save() console.print('└── 📁 reports/') console.print(f' └── 📄 {report_path.name}') From bfda9eac690312722039590a4dc699f9d0302897 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 21:13:47 +0200 Subject: [PATCH 008/129] Add per-format save methods and Report.save dispatch --- docs/dev/plans/project-summary-rendering.md | 2 +- .../project/categories/report/default.py | 108 +++++++++++++++++- src/easydiffraction/project/project.py | 17 ++- 3 files changed, 116 insertions(+), 11 deletions(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 959ed1661..dff338802 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -414,7 +414,7 @@ generated-artifact exceptions. removed surface (P1.23 will sweep tutorials). - Commit: `Remove public Report.check() and check=True flag`. -- [ ] **P1.6 — Per-format `save_*()` methods + `Report.save()`** +- [x] **P1.6 — Per-format `save_*()` methods + `Report.save()`** - Files: `src/easydiffraction/project/categories/report/default.py`, `src/easydiffraction/project/project.py`. - Add `save_cif()`, `save_html(offline=False)`, diff --git a/src/easydiffraction/project/categories/report/default.py b/src/easydiffraction/project/categories/report/default.py index 8f3ad1769..e3afe3f72 100644 --- a/src/easydiffraction/project/categories/report/default.py +++ b/src/easydiffraction/project/categories/report/default.py @@ -387,7 +387,7 @@ def show_fitting_details(self) -> None: columns_data=fit_metrics, ) - def save(self) -> pathlib.Path: + def save_cif(self) -> pathlib.Path: """ Write the IUCr submission report. @@ -395,5 +395,111 @@ def save(self) -> pathlib.Path: ------- pathlib.Path Path of the written report CIF. + + Raises + ------ + EasyDiffractionWriterError + If the generated IUCr CIF fails validation. """ return write_iucr_cif(self.project) + + def save_html(self, offline: bool = False) -> pathlib.Path: + """ + Write the HTML report. + + Parameters + ---------- + offline : bool, default=False + Whether to embed assets in the HTML report. + + Returns + ------- + pathlib.Path + Path of the written HTML report. + + Raises + ------ + NotImplementedError + Until the HTML writer lands in a later step. + """ + del offline + raise NotImplementedError('lands in P1.17 / P1.19 / P1.20') + + def save_tex(self, style: str = 'iucr') -> pathlib.Path: + """ + Write the TeX report. + + Parameters + ---------- + style : str, default='iucr' + Report template style. + + Returns + ------- + pathlib.Path + Path of the written TeX report. + + Raises + ------ + NotImplementedError + Until the TeX writer lands in a later step. + """ + del style + raise NotImplementedError('lands in P1.17 / P1.19 / P1.20') + + def save_pdf(self, style: str = 'iucr') -> pathlib.Path: + """ + Write the PDF report. + + Parameters + ---------- + style : str, default='iucr' + Report template style. + + Returns + ------- + pathlib.Path + Path of the written PDF report. + + Raises + ------ + NotImplementedError + Until the PDF writer lands in a later step. + """ + del style + raise NotImplementedError('lands in P1.17 / P1.19 / P1.20') + + def save(self) -> list[pathlib.Path]: + """ + Write all configured report formats. + + Returns + ------- + list[pathlib.Path] + Paths of written report files. + + Raises + ------ + ValueError + If no report formats are configured. + """ + report_paths = self._save_configured() + if not report_paths: + raise ValueError('No report formats configured.') + return report_paths + + def _save_configured(self) -> list[pathlib.Path]: + """Write enabled formats, returning quietly when none are set.""" + report_paths = [] + for report_format in self.formats: + if report_format is ReportFormatEnum.CIF: + report_paths.append(self.save_cif()) + elif report_format is ReportFormatEnum.HTML: + report_paths.append( + self.save_html(offline=bool(self.html_offline.value)) + ) + elif report_format is ReportFormatEnum.TEX: + report_paths.append(self.save_tex(style=str(self.style.value))) + elif report_format is ReportFormatEnum.PDF: + report_paths.append(self.save_pdf(style=str(self.style.value))) + return report_paths diff --git a/src/easydiffraction/project/project.py b/src/easydiffraction/project/project.py index 527739ec2..34fc7b8e9 100644 --- a/src/easydiffraction/project/project.py +++ b/src/easydiffraction/project/project.py @@ -462,14 +462,9 @@ def _build_parameter_map(self) -> dict[str, object]: param_map[unique_name] = param return param_map - def save(self, *, report: bool = False) -> None: + def save(self) -> None: """ Save the project into the existing project directory. - - Parameters - ---------- - report : bool, default=False - Whether to write the IUCr submission report. """ if self.info.path is None: log.error('Project path not specified. Use save_as() to define the path first.') @@ -530,10 +525,14 @@ def save(self, *, report: bool = False) -> None: branch = '└──' if index == len(analysis_file_names) - 1 else '├──' console.print(f'│ {branch} 📄 {file_name}') - if report: - report_path = self.report.save() + report_paths = self.report._save_configured() + if report_paths: + reports_dir = self.info.path / 'reports' console.print('└── 📁 reports/') - console.print(f' └── 📄 {report_path.name}') + for index, report_path in enumerate(report_paths): + branch = '└──' if index == len(report_paths) - 1 else '├──' + relative_path = report_path.relative_to(reports_dir) + console.print(f' {branch} 📄 {relative_path}') self.info.update_last_modified() self._saved = True From 3aa949bf99b86a9971e1e919ebc2403dd346c957 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 21:14:14 +0200 Subject: [PATCH 009/129] Lock empty-config behaviour in Report API --- docs/dev/plans/project-summary-rendering.md | 2 +- .../project/categories/report/default.py | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index dff338802..d575da64e 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -435,7 +435,7 @@ generated-artifact exceptions. format, returning quietly when none are configured. - Commit: `Add per-format save methods and Report.save dispatch`. -- [ ] **P1.7 — Empty-config behaviour tests-of-intent** +- [x] **P1.7 — Empty-config behaviour tests-of-intent** - Files: `src/easydiffraction/project/categories/report/default.py` (assertion docstrings / runtime checks). - Add the explicit `ValueError` message from §1.2 of the diff --git a/src/easydiffraction/project/categories/report/default.py b/src/easydiffraction/project/categories/report/default.py index e3afe3f72..4051559a3 100644 --- a/src/easydiffraction/project/categories/report/default.py +++ b/src/easydiffraction/project/categories/report/default.py @@ -30,6 +30,12 @@ REPORT_STYLE_OPTIONS = [member.value for member in ReportStyleEnum] +_NO_REPORT_FORMATS_MESSAGE = ( + 'project.report.save() called with no formats enabled. ' + 'Set project.report.{cif,html,tex,pdf} = True (or assign a ' + 'list via project.report.formats), or call a per-format ' + 'method directly (project.report.save_html(), etc.).' +) @ReportFactory.register @@ -481,11 +487,12 @@ def save(self) -> list[pathlib.Path]: Raises ------ ValueError - If no report formats are configured. + If no report formats are configured. ``project.save()`` + is the no-op-on-empty entry point. """ report_paths = self._save_configured() if not report_paths: - raise ValueError('No report formats configured.') + raise ValueError(_NO_REPORT_FORMATS_MESSAGE) return report_paths def _save_configured(self) -> list[pathlib.Path]: From 4b3bf061c93190a85c176342a352697702e89ac7 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 21:22:28 +0200 Subject: [PATCH 010/129] Add analysis.software category and engine URL constants --- docs/dev/plans/project-summary-rendering.md | 2 +- src/easydiffraction/analysis/analysis.py | 9 ++ .../analysis/calculators/crysfml.py | 3 + .../analysis/calculators/cryspy.py | 1 + .../analysis/calculators/pdffit.py | 3 + .../analysis/categories/minimizer/bumps.py | 1 + .../categories/minimizer/bumps_amoeba.py | 1 + .../analysis/categories/minimizer/bumps_de.py | 1 + .../categories/minimizer/bumps_dream.py | 1 + .../analysis/categories/minimizer/bumps_lm.py | 1 + .../analysis/categories/minimizer/dfols.py | 1 + .../analysis/categories/minimizer/emcee.py | 1 + .../analysis/categories/minimizer/lmfit.py | 1 + .../minimizer/lmfit_least_squares.py | 1 + .../categories/minimizer/lmfit_leastsq.py | 1 + .../analysis/categories/software/__init__.py | 9 ++ .../analysis/categories/software/base.py | 82 ++++++++++++++++++ .../analysis/categories/software/default.py | 85 +++++++++++++++++++ .../analysis/categories/software/factory.py | 17 ++++ src/easydiffraction/io/cif/serialize.py | 1 + 20 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 src/easydiffraction/analysis/categories/software/__init__.py create mode 100644 src/easydiffraction/analysis/categories/software/base.py create mode 100644 src/easydiffraction/analysis/categories/software/default.py create mode 100644 src/easydiffraction/analysis/categories/software/factory.py diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index d575da64e..4cd628edb 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -453,7 +453,7 @@ generated-artifact exceptions. in code via the docstrings and the error message. - Commit: `Lock empty-config behaviour in Report API`. -- [ ] **P1.8 — Add `analysis.software` category + URL constants** +- [x] **P1.8 — Add `analysis.software` category + URL constants** - Files: new `src/easydiffraction/analysis/categories/software/` package (`__init__.py`, `base.py`, `default.py`, diff --git a/src/easydiffraction/analysis/analysis.py b/src/easydiffraction/analysis/analysis.py index e67676a31..818e1c41e 100644 --- a/src/easydiffraction/analysis/analysis.py +++ b/src/easydiffraction/analysis/analysis.py @@ -29,6 +29,8 @@ from easydiffraction.analysis.categories.sequential_fit_extract import ( SequentialFitExtractCollection, ) +from easydiffraction.analysis.categories.software import Software +from easydiffraction.analysis.categories.software import SoftwareFactory from easydiffraction.analysis.enums import FitCorrelationSourceEnum from easydiffraction.analysis.enums import FitModeEnum from easydiffraction.analysis.enums import FitResultKindEnum @@ -477,6 +479,11 @@ def fit_parameter_correlations(self) -> FitParameterCorrelations: """Persisted fit-parameter correlation summaries.""" return self._fit_parameter_correlations + @property + def software(self) -> Software: + """Software-provenance snapshot for the latest successful fit.""" + return self._software + class Analysis( _AnalysisOwnerAccessorsMixin, @@ -521,6 +528,7 @@ def __init__(self, project: object) -> None: self._fit_parameters = FitParameters() self._fit_result = self._minimizer._fit_result_class() self._fit_parameter_correlations = FitParameterCorrelations() + self._software: Software = SoftwareFactory.create(SoftwareFactory.default_tag()) self._has_persisted_fit_state_data = False self._persisted_fit_state_sidecar: dict[str, object] = {} self._fitter = Fitter(self.minimizer.type) @@ -541,6 +549,7 @@ def _attach_category_parents(self) -> None: self._fit_parameters._parent = self self._fit_result._parent = self self._fit_parameter_correlations._parent = self + self._software._parent = self @staticmethod def _supported_filters_for(category: object) -> dict[str, object]: diff --git a/src/easydiffraction/analysis/calculators/crysfml.py b/src/easydiffraction/analysis/calculators/crysfml.py index 70231b02f..4774366f3 100644 --- a/src/easydiffraction/analysis/calculators/crysfml.py +++ b/src/easydiffraction/analysis/calculators/crysfml.py @@ -1,6 +1,8 @@ # SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause +from __future__ import annotations + from typing import Any import numpy as np @@ -70,6 +72,7 @@ class CrysfmlCalculator(CalculatorBase): description='CrysFML library for crystallographic calculations', ) engine_imported: bool = cfml_py_utilities is not None + url: str = 'https://code.ill.fr/scientific-software/crysfml' @property def name(self) -> str: diff --git a/src/easydiffraction/analysis/calculators/cryspy.py b/src/easydiffraction/analysis/calculators/cryspy.py index 7bac27385..c39db75d5 100644 --- a/src/easydiffraction/analysis/calculators/cryspy.py +++ b/src/easydiffraction/analysis/calculators/cryspy.py @@ -55,6 +55,7 @@ class CryspyCalculator(CalculatorBase): description='CrysPy library for crystallographic calculations', ) engine_imported: bool = cryspy is not None + url: str = 'https://www.cryspy.fr' @property def name(self) -> str: diff --git a/src/easydiffraction/analysis/calculators/pdffit.py b/src/easydiffraction/analysis/calculators/pdffit.py index a9c3b3697..19f5021b7 100644 --- a/src/easydiffraction/analysis/calculators/pdffit.py +++ b/src/easydiffraction/analysis/calculators/pdffit.py @@ -7,6 +7,8 @@ silences stdio on import to avoid noisy output in notebooks and logs. """ +from __future__ import annotations + import os import re from pathlib import Path @@ -54,6 +56,7 @@ class PdffitCalculator(CalculatorBase): description='PDFfit2 for pair distribution function calculations', ) engine_imported: bool = PdfFit is not None + url: str = 'https://www.diffpy.org/products/pdffit2.html' @property def name(self) -> str: diff --git a/src/easydiffraction/analysis/categories/minimizer/bumps.py b/src/easydiffraction/analysis/categories/minimizer/bumps.py index 198c3155e..3f6ec06a9 100644 --- a/src/easydiffraction/analysis/categories/minimizer/bumps.py +++ b/src/easydiffraction/analysis/categories/minimizer/bumps.py @@ -20,6 +20,7 @@ class BumpsMinimizer(LeastSquaresMinimizerBase): 'optimizer_name': 'bumps', 'method_name': 'lm', } + url: str = 'https://bumps.readthedocs.io' type_info = TypeInfo( tag=MinimizerTypeEnum.BUMPS, diff --git a/src/easydiffraction/analysis/categories/minimizer/bumps_amoeba.py b/src/easydiffraction/analysis/categories/minimizer/bumps_amoeba.py index b27c8f7ea..cd58fd5f4 100644 --- a/src/easydiffraction/analysis/categories/minimizer/bumps_amoeba.py +++ b/src/easydiffraction/analysis/categories/minimizer/bumps_amoeba.py @@ -20,6 +20,7 @@ class BumpsAmoebaMinimizer(LeastSquaresMinimizerBase): 'optimizer_name': 'bumps (amoeba)', 'method_name': 'amoeba', } + url: str = 'https://bumps.readthedocs.io' type_info = TypeInfo( tag=MinimizerTypeEnum.BUMPS_AMOEBA, diff --git a/src/easydiffraction/analysis/categories/minimizer/bumps_de.py b/src/easydiffraction/analysis/categories/minimizer/bumps_de.py index 097f6bfea..218f7cd37 100644 --- a/src/easydiffraction/analysis/categories/minimizer/bumps_de.py +++ b/src/easydiffraction/analysis/categories/minimizer/bumps_de.py @@ -20,6 +20,7 @@ class BumpsDeMinimizer(LeastSquaresMinimizerBase): 'optimizer_name': 'bumps (de)', 'method_name': 'de', } + url: str = 'https://bumps.readthedocs.io' type_info = TypeInfo( tag=MinimizerTypeEnum.BUMPS_DE, diff --git a/src/easydiffraction/analysis/categories/minimizer/bumps_dream.py b/src/easydiffraction/analysis/categories/minimizer/bumps_dream.py index 13b844e86..8d7f1b638 100644 --- a/src/easydiffraction/analysis/categories/minimizer/bumps_dream.py +++ b/src/easydiffraction/analysis/categories/minimizer/bumps_dream.py @@ -26,6 +26,7 @@ class BumpsDreamMinimizer(BayesianMinimizerBase): 'optimizer_name': 'bumps (dream)', 'method_name': 'dream', } + url: str = 'https://bumps.readthedocs.io' type_info = TypeInfo( tag=MinimizerTypeEnum.BUMPS_DREAM, diff --git a/src/easydiffraction/analysis/categories/minimizer/bumps_lm.py b/src/easydiffraction/analysis/categories/minimizer/bumps_lm.py index 692081bea..bd552f9bc 100644 --- a/src/easydiffraction/analysis/categories/minimizer/bumps_lm.py +++ b/src/easydiffraction/analysis/categories/minimizer/bumps_lm.py @@ -20,6 +20,7 @@ class BumpsLmMinimizer(LeastSquaresMinimizerBase): 'optimizer_name': 'bumps (lm)', 'method_name': 'lm', } + url: str = 'https://bumps.readthedocs.io' type_info = TypeInfo( tag=MinimizerTypeEnum.BUMPS_LM, diff --git a/src/easydiffraction/analysis/categories/minimizer/dfols.py b/src/easydiffraction/analysis/categories/minimizer/dfols.py index 81978f2ee..d8a60cca1 100644 --- a/src/easydiffraction/analysis/categories/minimizer/dfols.py +++ b/src/easydiffraction/analysis/categories/minimizer/dfols.py @@ -20,6 +20,7 @@ class DfolsMinimizer(LeastSquaresMinimizerBase): 'optimizer_name': 'dfols', 'method_name': '', } + url: str = 'https://github.com/numericalalgorithmsgroup/dfols' type_info = TypeInfo( tag=MinimizerTypeEnum.DFOLS, diff --git a/src/easydiffraction/analysis/categories/minimizer/emcee.py b/src/easydiffraction/analysis/categories/minimizer/emcee.py index a890f5873..77143afca 100644 --- a/src/easydiffraction/analysis/categories/minimizer/emcee.py +++ b/src/easydiffraction/analysis/categories/minimizer/emcee.py @@ -41,6 +41,7 @@ class EmceeMinimizer(BayesianMinimizerBase): 'optimizer_name': 'emcee', 'method_name': 'de', } + url: str = 'https://emcee.readthedocs.io' _expected_descriptor_names: ClassVar[tuple[str, ...]] = ( *BayesianMinimizerBase._expected_descriptor_names, 'proposal_moves', diff --git a/src/easydiffraction/analysis/categories/minimizer/lmfit.py b/src/easydiffraction/analysis/categories/minimizer/lmfit.py index 99c07c64c..fc024ec23 100644 --- a/src/easydiffraction/analysis/categories/minimizer/lmfit.py +++ b/src/easydiffraction/analysis/categories/minimizer/lmfit.py @@ -20,6 +20,7 @@ class LmfitMinimizer(LeastSquaresMinimizerBase): 'optimizer_name': 'lmfit', 'method_name': 'leastsq', } + url: str = 'https://lmfit.github.io/lmfit-py' type_info = TypeInfo( tag=MinimizerTypeEnum.LMFIT, diff --git a/src/easydiffraction/analysis/categories/minimizer/lmfit_least_squares.py b/src/easydiffraction/analysis/categories/minimizer/lmfit_least_squares.py index ad3e43f79..dab687ee8 100644 --- a/src/easydiffraction/analysis/categories/minimizer/lmfit_least_squares.py +++ b/src/easydiffraction/analysis/categories/minimizer/lmfit_least_squares.py @@ -20,6 +20,7 @@ class LmfitLeastSquaresMinimizer(LeastSquaresMinimizerBase): 'optimizer_name': 'lmfit (least_squares)', 'method_name': 'least_squares', } + url: str = 'https://lmfit.github.io/lmfit-py' type_info = TypeInfo( tag=MinimizerTypeEnum.LMFIT_LEAST_SQUARES, diff --git a/src/easydiffraction/analysis/categories/minimizer/lmfit_leastsq.py b/src/easydiffraction/analysis/categories/minimizer/lmfit_leastsq.py index ef43d500e..e9ba7b732 100644 --- a/src/easydiffraction/analysis/categories/minimizer/lmfit_leastsq.py +++ b/src/easydiffraction/analysis/categories/minimizer/lmfit_leastsq.py @@ -20,6 +20,7 @@ class LmfitLeastsqMinimizer(LeastSquaresMinimizerBase): 'optimizer_name': 'lmfit (leastsq)', 'method_name': 'leastsq', } + url: str = 'https://lmfit.github.io/lmfit-py' type_info = TypeInfo( tag=MinimizerTypeEnum.LMFIT_LEASTSQ, diff --git a/src/easydiffraction/analysis/categories/software/__init__.py b/src/easydiffraction/analysis/categories/software/__init__.py new file mode 100644 index 000000000..715288d60 --- /dev/null +++ b/src/easydiffraction/analysis/categories/software/__init__.py @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Analysis software-provenance category exports.""" + +from __future__ import annotations + +from easydiffraction.analysis.categories.software.base import SoftwareRole +from easydiffraction.analysis.categories.software.default import Software +from easydiffraction.analysis.categories.software.factory import SoftwareFactory diff --git a/src/easydiffraction/analysis/categories/software/base.py b/src/easydiffraction/analysis/categories/software/base.py new file mode 100644 index 000000000..dcab532a6 --- /dev/null +++ b/src/easydiffraction/analysis/categories/software/base.py @@ -0,0 +1,82 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Shared software-provenance role helpers.""" + +from __future__ import annotations + +from easydiffraction.core.guard import GuardedBase +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.variable import StringDescriptor +from easydiffraction.io.cif.handler import CifHandler + + +class SoftwareRole(GuardedBase): + """Name, version, and URL for one software role.""" + + def __init__(self, *, role_name: str, description: str) -> None: + """ + Create descriptors for one software role. + + Parameters + ---------- + role_name : str + Role prefix used in persisted CIF item names. + description : str + Human-readable role description. + """ + super().__init__() + self._name = StringDescriptor( + name=f'{role_name}_name', + description=f'{description} name.', + value_spec=AttributeSpec(default=None, allow_none=True), + cif_handler=CifHandler(names=[f'_software.{role_name}_name']), + ) + self._version = StringDescriptor( + name=f'{role_name}_version', + description=f'{description} version.', + value_spec=AttributeSpec(default=None, allow_none=True), + cif_handler=CifHandler(names=[f'_software.{role_name}_version']), + ) + self._url = StringDescriptor( + name=f'{role_name}_url', + description=f'{description} URL.', + value_spec=AttributeSpec(default=None, allow_none=True), + cif_handler=CifHandler(names=[f'_software.{role_name}_url']), + ) + + @property + def name(self) -> StringDescriptor: + """Software name.""" + return self._name + + @name.setter + def name(self, value: str | None) -> None: + self._name.value = value + + @property + def version(self) -> StringDescriptor: + """Software version.""" + return self._version + + @version.setter + def version(self, value: str | None) -> None: + self._version.value = value + + @property + def url(self) -> StringDescriptor: + """Software project URL.""" + return self._url + + @url.setter + def url(self, value: str | None) -> None: + self._url.value = value + + @property + def parameters(self) -> list[StringDescriptor]: + """Descriptors owned by this software role.""" + return [self._name, self._version, self._url] + + @property + def as_cif(self) -> str: + """Return CIF representation of this software role.""" + return '\n'.join(param.as_cif for param in self.parameters) diff --git a/src/easydiffraction/analysis/categories/software/default.py b/src/easydiffraction/analysis/categories/software/default.py new file mode 100644 index 000000000..62430b8fc --- /dev/null +++ b/src/easydiffraction/analysis/categories/software/default.py @@ -0,0 +1,85 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Analysis software-provenance category.""" + +from __future__ import annotations + +from easydiffraction.analysis.categories.software.base import SoftwareRole +from easydiffraction.analysis.categories.software.factory import SoftwareFactory +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.metadata import TypeInfo +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.variable import StringDescriptor +from easydiffraction.io.cif.handler import CifHandler + + +@SoftwareFactory.register +class Software(CategoryItem): + """Software-provenance snapshot for the latest successful fit.""" + + _category_code = 'software' + + type_info = TypeInfo( + tag='default', + description='Analysis software provenance category', + ) + + def __init__(self) -> None: + super().__init__() + self._framework = SoftwareRole( + role_name='framework', + description='EasyDiffraction framework', + ) + self._calculator = SoftwareRole( + role_name='calculator', + description='Calculation engine', + ) + self._minimizer = SoftwareRole( + role_name='minimizer', + description='Minimization engine', + ) + self._timestamp = StringDescriptor( + name='timestamp', + description='UTC timestamp of the fit provenance snapshot.', + value_spec=AttributeSpec(default=None, allow_none=True), + cif_handler=CifHandler(names=['_software.timestamp']), + ) + + @property + def framework(self) -> SoftwareRole: + """EasyDiffraction framework provenance.""" + return self._framework + + @property + def calculator(self) -> SoftwareRole: + """Calculation-engine provenance.""" + return self._calculator + + @property + def minimizer(self) -> SoftwareRole: + """Minimization-engine provenance.""" + return self._minimizer + + @property + def timestamp(self) -> StringDescriptor: + """UTC timestamp of the fit provenance snapshot.""" + return self._timestamp + + @timestamp.setter + def timestamp(self, value: str | None) -> None: + self._timestamp.value = value + + @property + def parameters(self) -> list[StringDescriptor]: + """Descriptors owned by this software category.""" + return [ + *self._framework.parameters, + *self._calculator.parameters, + *self._minimizer.parameters, + self._timestamp, + ] + + @property + def as_cif(self) -> str: + """Return CIF representation of this software category.""" + return super().as_cif diff --git a/src/easydiffraction/analysis/categories/software/factory.py b/src/easydiffraction/analysis/categories/software/factory.py new file mode 100644 index 000000000..2916ada54 --- /dev/null +++ b/src/easydiffraction/analysis/categories/software/factory.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Factory for analysis software-provenance categories.""" + +from __future__ import annotations + +from typing import ClassVar + +from easydiffraction.core.factory import FactoryBase + + +class SoftwareFactory(FactoryBase): + """Create software-provenance categories.""" + + _default_rules: ClassVar[dict] = { + frozenset(): 'default', + } diff --git a/src/easydiffraction/io/cif/serialize.py b/src/easydiffraction/io/cif/serialize.py index 35b379a17..a670cb73d 100644 --- a/src/easydiffraction/io/cif/serialize.py +++ b/src/easydiffraction/io/cif/serialize.py @@ -721,6 +721,7 @@ def analysis_from_cif(analysis: object, cif_text: str) -> None: analysis._set_fitting_mode_type(_analysis_mode_from_cif_block(block)) analysis._set_minimizer_type(_analysis_minimizer_from_cif_block(block)) analysis.minimizer.from_cif(block) + analysis.software.from_cif(block) _restore_mode_specific_analysis_sections(analysis, block) # Restore aliases (loop) From cf198b84bc2d2a272a4eba38183564884a8fa33a Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 21:28:27 +0200 Subject: [PATCH 011/129] Populate analysis.software at fit time and emit fit_datetime --- docs/dev/plans/project-summary-rendering.md | 2 +- src/easydiffraction/analysis/analysis.py | 95 +++++++++++++++++++++ src/easydiffraction/io/cif/iucr_writer.py | 77 ++++++++++++----- 3 files changed, 150 insertions(+), 24 deletions(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 4cd628edb..0ef367fe1 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -472,7 +472,7 @@ generated-artifact exceptions. - Commit: `Add analysis.software category and engine URL constants`. -- [ ] **P1.9 — Populate `analysis.software` at fit time + `fit_datetime` emission** +- [x] **P1.9 — Populate `analysis.software` at fit time + `fit_datetime` emission** - Files: `src/easydiffraction/analysis/analysis.py`, `src/easydiffraction/io/cif/iucr_writer.py`. diff --git a/src/easydiffraction/analysis/analysis.py b/src/easydiffraction/analysis/analysis.py index 818e1c41e..56f6fe890 100644 --- a/src/easydiffraction/analysis/analysis.py +++ b/src/easydiffraction/analysis/analysis.py @@ -5,6 +5,8 @@ from contextlib import suppress from dataclasses import dataclass +from datetime import UTC +from datetime import datetime from itertools import combinations from math import isclose from pathlib import Path @@ -64,6 +66,7 @@ from easydiffraction.utils.utils import _help_method_rows from easydiffraction.utils.utils import _help_property_rows from easydiffraction.utils.utils import format_bulleted_warning +from easydiffraction.utils.utils import package_version from easydiffraction.utils.utils import render_cif from easydiffraction.utils.utils import render_object_help from easydiffraction.utils.utils import render_table @@ -81,6 +84,22 @@ _UNDO_ABS_TOL = 0.0 _GT_REFLECTION_THRESHOLD_SIGMA = 3.0 _GT_REFLECTION_THRESHOLD_EXPRESSION = r'I>3\s(I)' +_EASYDIFFRACTION_URL = 'https://github.com/easyscience/diffraction-lib' +_SOFTWARE_PACKAGE_BY_ENGINE = { + 'cryspy': 'cryspy', + 'crysfml': 'crysfml', + 'pdffit': 'diffpy.pdffit2', + 'lmfit': 'lmfit', + 'lmfit_leastsq': 'lmfit', + 'lmfit_least_squares': 'lmfit', + 'dfols': 'dfols', + 'bumps': 'bumps', + 'bumps_lm': 'bumps', + 'bumps_amoeba': 'bumps', + 'bumps_de': 'bumps', + 'bumps_dream': 'bumps', + 'emcee': 'emcee', +} @dataclass(frozen=True) @@ -564,6 +583,78 @@ def _supported_filters_for(category: object) -> dict[str, object]: del category return {} + @staticmethod + def _type_info_tag(obj: object) -> str: + """Return the object's factory tag as a string.""" + tag = getattr(getattr(obj, 'type_info', None), 'tag', '') + return str(getattr(tag, 'value', tag)) + + @staticmethod + def _software_version(name: str) -> str | None: + """Return the installed package version for one engine name.""" + package_name = _SOFTWARE_PACKAGE_BY_ENGINE.get(name) + if package_name is None: + return None + return package_version(package_name) + + def _software_values(self, engine: object) -> tuple[str, str | None, str | None]: + """Return name, version, and URL for one software engine.""" + name = self._type_info_tag(engine) + return name, self._software_version(name), getattr(engine, 'url', None) + + @staticmethod + def _combine_software_values( + values: list[tuple[str, str | None, str | None]], + ) -> tuple[str | None, str | None, str | None]: + """Return comma-joined unique software role values.""" + unique_values = sorted(set(values)) + if not unique_values: + return None, None, None + return tuple( + ', '.join(str(value[index]) for value in unique_values if value[index]) + or None + for index in range(3) + ) + + def _calculator_software_values(self) -> tuple[str | None, str | None, str | None]: + """Return name, version, and URL for active calculators.""" + values = [] + for experiment in self.project.experiments.values(): + calculator = experiment.calculator.calculator + values.append(self._software_values(calculator)) + return self._combine_software_values(values) + + @staticmethod + def _set_software_role( + role: object, + values: tuple[str | None, str | None, str | None], + ) -> None: + """Set one software role from a name, version, URL tuple.""" + name, version, url = values + role.name = name + role.version = version + role.url = url + + def _stamp_software_provenance(self) -> None: + """Record software identities for the latest successful fit.""" + self._set_software_role( + self.software.framework, + ( + 'EasyDiffraction', + package_version('easydiffraction'), + _EASYDIFFRACTION_URL, + ), + ) + self._set_software_role( + self.software.calculator, + self._calculator_software_values(), + ) + self._set_software_role( + self.software.minimizer, + self._software_values(self.minimizer), + ) + self.software.timestamp = datetime.now(tz=UTC).isoformat(timespec='seconds') + def _swap_minimizer(self, new_type: str) -> None: """Switch the active minimizer category.""" self._replace_minimizer(new_type, announce=True) @@ -2519,6 +2610,7 @@ def _run_single( experiments, fit_options=FitterFitOptions(resume=resume, extra_steps=extra_steps), ) + self._stamp_software_provenance() if self.project.info.path is not None: self.project.save() @@ -2545,6 +2637,7 @@ def _run_joint( experiments, fit_options=FitterFitOptions(resume=resume, extra_steps=extra_steps), ) + self._stamp_software_provenance() if self.project.info.path is not None: self.project.save() @@ -2583,6 +2676,8 @@ def _run_sequential(self) -> None: self.fitter.results = None self._clear_persisted_fit_state() + self._stamp_software_provenance() + if self.project.info.path is not None: self.project.save() diff --git a/src/easydiffraction/io/cif/iucr_writer.py b/src/easydiffraction/io/cif/iucr_writer.py index aed482950..ff32f820d 100644 --- a/src/easydiffraction/io/cif/iucr_writer.py +++ b/src/easydiffraction/io/cif/iucr_writer.py @@ -159,10 +159,10 @@ def _write_audit_section(lines: list[str]) -> None: def _write_computing_section(lines: list[str], project: object) -> None: """Append software-stack metadata.""" - framework = _software_label('EasyDiffraction', package_name='easydiffraction') - calculator = _calculator_label(project) - minimizer = _minimizer_label(project) - refinement = f'{framework} with {minimizer} minimizer and {calculator} calculator' + framework = _software_role_label(project, 'framework') + calculator = _software_role_label(project, 'calculator') + minimizer = _software_role_label(project, 'minimizer') + refinement = _structure_refinement_label(framework, calculator, minimizer) _section(lines, 'Computing') _write_item(lines, '_computing.structure_refinement', refinement) @@ -171,6 +171,9 @@ def _write_computing_section(lines: list[str], project: object) -> None: _write_item(lines, '_easydiffraction_software.framework', framework) _write_item(lines, '_easydiffraction_software.calculator', calculator) _write_item(lines, '_easydiffraction_software.minimizer', minimizer) + fit_datetime = _software_fit_datetime(project) + if fit_datetime is not None: + _write_item(lines, '_easydiffraction_software.fit_datetime', fit_datetime) def _write_publication_sections(lines: list[str]) -> None: @@ -812,29 +815,57 @@ def _software_label(name: str, *, package_name: str | None = None) -> str: return f'{name} {version}' if version else name -def _calculator_label(project: object) -> str: - """Return the active calculator label for the report.""" - names: list[str] = [] - for experiment in _collection_values(getattr(project, 'experiments', None)): - calculator = getattr(experiment, 'calculator', None) - calculator_name = _descriptor_value(getattr(calculator, 'type', None)) - if calculator_name not in {None, ''}: - names.append(str(calculator_name)) +def _analysis_software(project: object) -> object | None: + """Return the project's persisted software snapshot.""" + analysis = getattr(project, 'analysis', None) + return getattr(analysis, 'software', None) - unique_names = sorted(set(names)) - if not unique_names: - return '?' - return ', '.join(_software_label(name) for name in unique_names) +def _role_descriptor_value(role: object, attr_name: str) -> object: + """Return one software-role descriptor value.""" + return _descriptor_value(getattr(role, attr_name, None)) -def _minimizer_label(project: object) -> str: - """Return the active minimizer label for the report.""" - analysis = getattr(project, 'analysis', None) - minimizer = getattr(analysis, 'minimizer', None) - minimizer_name = _descriptor_value(getattr(minimizer, 'type', None)) - if minimizer_name in {None, ''}: + +def _software_role_label(project: object, role_name: str) -> str: + """Return a persisted software role label or CIF unknown.""" + software = _analysis_software(project) + role = getattr(software, role_name, None) + name = _role_descriptor_value(role, 'name') + if name in {None, ''}: return '?' - return _software_label(str(minimizer_name)) + + version = _role_descriptor_value(role, 'version') + if version in {None, ''}: + return str(name) + return f'{name} {version}' + + +def _software_fit_datetime(project: object) -> object | None: + """Return the persisted fit timestamp, if available.""" + software = _analysis_software(project) + timestamp = _descriptor_value(getattr(software, 'timestamp', None)) + if timestamp in {None, ''}: + return None + return timestamp + + +def _framework_refinement_label(framework: str) -> str: + """Return framework label for ``_computing`` fallback text.""" + if framework == '?': + return _software_label('EasyDiffraction', package_name='easydiffraction') + return framework + + +def _structure_refinement_label( + framework: str, + calculator: str, + minimizer: str, +) -> str: + """Return the free-text refinement software label.""" + framework_label = _framework_refinement_label(framework) + if calculator == '?' or minimizer == '?': + return framework_label + return f'{framework_label} with {minimizer} minimizer and {calculator} calculator' def _base_engine_name(name: str) -> str: From 8424777c9c731e1ef2566934239e301608e1bde7 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 21:29:20 +0200 Subject: [PATCH 012/129] Amend analysis-cif-fit-state for analysis.software --- .../adrs/accepted/analysis-cif-fit-state.md | 36 +++++++++++++++++-- docs/dev/plans/project-summary-rendering.md | 2 +- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/docs/dev/adrs/accepted/analysis-cif-fit-state.md b/docs/dev/adrs/accepted/analysis-cif-fit-state.md index b15b1ed52..9051c0ff4 100644 --- a/docs/dev/adrs/accepted/analysis-cif-fit-state.md +++ b/docs/dev/adrs/accepted/analysis-cif-fit-state.md @@ -25,6 +25,7 @@ Analysis-owned fit state needs to persist: - fit bounds and bound provenance - pre-fit scalar snapshots for recovery workflows - compact status metadata for the latest saved fit projection +- software-provenance snapshot for the latest successful fit - deterministic correlation summaries - minimizer-specific fit outputs on the paired `_fit_result.*` category - per-parameter posterior summaries on `_fit_parameter` @@ -104,6 +105,34 @@ round-trip project schema remains common. correlation summaries keyed by a persisted `id`. Only unique parameter pairs are stored. +### Software provenance + +`_software` stores the runtime software snapshot recorded after a +successful fit. It is part of the project save / load contract and feeds +report rendering plus the IUCr export software labels. It is not a user +configuration category. + +The category stores name, version, and URL triples for: + +- `framework` +- `calculator` +- `minimizer` + +Each role is persisted as scalar items on the same category: + +- `_name` +- `_version` +- `_url` + +The category also stores: + +- `timestamp` + +`timestamp` is an ISO-8601 UTC string for the fit that produced the +snapshot. Projects saved before this category existed load with all +software fields unset and `timestamp` set to `None`; rerunning a fit +populates the snapshot. + ### Minimizer fit projection The active `_minimizer.*` category stores user-selected solver inputs @@ -230,9 +259,10 @@ Load order is: 1. standard analysis configuration 2. `_minimizer.*` settings according to the active `_minimizer.type` -3. common and family-specific `_fit_result.*` fields on the paired class -4. `_fit_parameter` and `_fit_parameter_correlation` -5. posterior sidecar arrays when a Bayesian result is expected +3. `_software.*` provenance fields when present +4. common and family-specific `_fit_result.*` fields on the paired class +5. `_fit_parameter` and `_fit_parameter_correlation` +6. posterior sidecar arrays when a Bayesian result is expected Persist backend runtime objects, optimizer instances, and raw driver payloads nowhere in this design. diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 0ef367fe1..37482088c 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -496,7 +496,7 @@ generated-artifact exceptions. only) when calculator / minimizer details are unset. - Commit: `Populate analysis.software at fit time and emit fit_datetime`. -- [ ] **P1.10 — Amend `analysis-cif-fit-state.md` ADR** +- [x] **P1.10 — Amend `analysis-cif-fit-state.md` ADR** - File: `docs/dev/adrs/accepted/analysis-cif-fit-state.md`. - Document the new `analysis.software` persisted category (framework / calculator / minimizer triples + timestamp). From 1abc9bedfdf8049a705a740b5ebe7586e5ec5fbe Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 21:33:04 +0200 Subject: [PATCH 013/129] Amend iucr-cif-tag-alignment for project.report --- .../adrs/accepted/iucr-cif-tag-alignment.md | 112 ++++++++++-------- docs/dev/plans/project-summary-rendering.md | 2 +- 2 files changed, 66 insertions(+), 48 deletions(-) diff --git a/docs/dev/adrs/accepted/iucr-cif-tag-alignment.md b/docs/dev/adrs/accepted/iucr-cif-tag-alignment.md index 119bb677e..61640d433 100644 --- a/docs/dev/adrs/accepted/iucr-cif-tag-alignment.md +++ b/docs/dev/adrs/accepted/iucr-cif-tag-alignment.md @@ -142,20 +142,21 @@ observation drives the policy: coefficient loop indexed by integer `power`), and pdCIF has no parametric peak-shape items at all. File path scopes them; no prefix needed. -- **Reports** — a separate write path, `project.save(report=True)`, that - pulls live Python state and emits a single journal-submission CIF to - `reports/.cif`. This path applies all IUCr renames, +- **Reports** — a separate `project.report` facade that pulls live + Python state and emits journal report artifacts under `reports/`. + The IUCr CIF one-off method is `project.report.save_cif()`; the + regular `project.save()` call emits configured reports from + `project.report.formats`. This path applies all IUCr renames, structural reshapings, multi-datablock layout, and project-extension - namespacing (`_easydiffraction_*`). Lives under the new - `project.report` facade slot (replaces the unimplemented - `project.summary` placeholder). **Export only — no round-trip.** + namespacing (`_easydiffraction_*`). It replaces the unimplemented + `project.summary` placeholder. **Export only — no round-trip.** ## Current State Project CIF categories audited against `cif_core.dic` v3.4.0 and `cif_pow.dic` v2.5.0. The "Default-save tier" column shows whether the category changes in the default save; the "IUCr export" column shows the -dotted DDLm tag emitted under `project.save(report=True)`. +dotted DDLm tag emitted by the IUCr CIF report writer. | Category (current) | IUCr dictionary | Default-save tier | IUCr export (dotted DDLm) | | ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -182,7 +183,8 @@ dotted DDLm tag emitted under `project.save(report=True)`. | `_extinction.*` | core (`_refine_ls.extinction_*` items) | Experiment — unchanged | `_easydiffraction_extinction.*` + dual emit `_refine_ls.extinction_{method,coef,expression}`. | | `_excluded_region.*` | pdCIF (`_pd_proc.info_excluded_regions` free-text) | Experiment — unchanged | `_easydiffraction_excluded_region.*` + `_pd_proc.info_excluded_regions` free-text rendering. | | `_expt_type.*` | none | Experiment — unchanged | `_easydiffraction_experiment_type.*`. | -| `_calculator.type`, `_minimizer.type` | none | Analysis — unchanged | Identification rolled into the `_easydiffraction_software.{framework, calculator, minimizer}` category; `_computing.structure_refinement` carries the same info as IUCr-standard free text. | +| `_calculator.type`, `_minimizer.type` | none | Analysis — unchanged | Selection fields remain settings only; identity is read from `analysis.software` for `_easydiffraction_software.{framework, calculator, minimizer}` and `_computing.structure_refinement`. | +| `_software.*` | none | Analysis — new provenance category | Source for `_easydiffraction_software.{framework, calculator, minimizer}`, `_easydiffraction_software.fit_datetime`, and `_computing.structure_refinement` in `data_global`. | | `_minimizer.*` settings (tolerances, max_iter, …) | none | Analysis — unchanged | `_easydiffraction_minimizer.*` (settings only, separate from the identification triple). | | `_fitting_mode.type`, `_background.type` | none | Analysis / Experiment — unchanged | `_easydiffraction_fitting_mode.type`, `_easydiffraction_background.type` selectors. | | `_fit_result.reduced_chi_square`, `n_data_points`, `n_parameters` | core (`_refine_ls.*`) and pdCIF (`_pd_proc_ls.*`) | Analysis — unchanged (topology-neutral) | Shape-shifting per topology: see §1.2 and §3 transformers. | @@ -193,7 +195,7 @@ dotted DDLm tag emitted under `project.save(report=True)`. | `_joint_fit`, `_sequential_fit*` | none | Analysis — unchanged | `_easydiffraction_joint_fit*`, `_easydiffraction_sequential_fit*`. | | reflection-set aggregates | core (`_reflns.*`) | Analysis — new fields | `_reflns.number_total`, `_reflns.number_gt`, `_reflns.threshold_expression` (e.g. `'I>3\s(I)'`). | | publication metadata | core (`_journal.*`, `_publ_author.*`, `_publ_contact_author.*`, `_audit.*`) | (not emitted today) | Emitted in `data_global` block per §2.3a with `?` placeholders. | -| analysis-stack identification | core (`_computing.structure_refinement`) | (not emitted today) | `_easydiffraction_software.{framework, calculator, minimizer}` triple + `_computing.structure_refinement` derived string in `data_global` (see §2.3a-i). | +| analysis-stack identification | core (`_computing.structure_refinement`) | Analysis — `_software.*` persisted | `_easydiffraction_software.{framework, calculator, minimizer}` triple + `_easydiffraction_software.fit_datetime` + `_computing.structure_refinement` derived from `analysis.software`. | ## Decision @@ -298,18 +300,19 @@ Specifically: #### 2.1 API ```python -project.save() # regular project save only -project.save(report=True) # regular save + reports/.cif -project.report.save() # write reports only, no regular save -project.report.check() # validate reports (see §2.5) +project.save() # project files + configured reports +project.report.save_cif() # one-off reports/.cif +project.report.save() # write configured reports only ``` `project.summary` (currently an unimplemented placeholder) is removed -and replaced by `project.report` — a new facade slot that owns the -journal-submission CIF generation and validation. The slot is named -generically because the same path can host additional report types in -the future (mmCIF export, figure bundles, etc.); the IUCr CIF is the -only kind shipped today. +and replaced by `project.report` — a facade slot that owns journal +report generation. `project.report.formats` controls which reports +`project.save()` emits. Per-format methods (`save_cif()`, +`save_html()`, `save_tex()`, `save_pdf()`) write one-off artifacts +without changing that configuration. The no-arg `project.report.save()` +uses `project.report.formats` and raises `ValueError` when no formats +are enabled. #### 2.2 Output location @@ -330,7 +333,7 @@ example files in the corpus). pd_xray.cif analysis/ analysis.cif - reports/ # written by save(report=True) + reports/ # written by report config or save_cif() .cif # single multi-block IUCr CIF ``` @@ -504,13 +507,15 @@ the project has source data, otherwise `?`. - `_audit.creation_method 'EasyDiffraction '`, `_audit.creation_date `. -- `_computing.structure_refinement` (single string concatenating the - framework + calculator + minimizer names and versions, e.g. +- `_computing.structure_refinement` (single string derived from + `analysis.software`; when calculator or minimizer provenance is unset + it falls back to the framework label only, e.g. `'EasyDiffraction 0.17.0 with lmfit 1.0.0 minimizer and cryspy 1.2.3 calculator'`). coreCIF standard channel for advertising the analysis-software stack to IUCr-aware tooling. - `_easydiffraction_software.*` triple holding the same three roles in - structured form (see §2.3a-i below). + structured form, plus `_easydiffraction_software.fit_datetime` when a + fit timestamp is available (see §2.3a-i below). - `_journal.*` placeholders, written as `?` when the project has no source data: `_journal.name_full`, `_journal.year`, `_journal.volume`, `_journal.issue`, `_journal.page_first`, `_journal.page_last`, @@ -540,13 +545,15 @@ similar) is deferred — see Deferred Work. #### 2.3a-i `_easydiffraction_software` framework The IUCr submission needs to identify the analysis stack. The project -emits one structured category in `data_global` carrying three role-keyed -strings: +emits one structured category in `data_global` from +`analysis.software`, carrying three role-keyed strings and an optional +fit timestamp: ``` _easydiffraction_software.framework 'EasyDiffraction 0.17.0' _easydiffraction_software.calculator 'cryspy 1.2.3' _easydiffraction_software.minimizer 'lmfit 1.0.0' +_easydiffraction_software.fit_datetime 2026-05-26T13:45:00+00:00 ``` - `_easydiffraction_software.framework` — EasyDiffraction itself, the @@ -556,6 +563,9 @@ _easydiffraction_software.minimizer 'lmfit 1.0.0' - `_easydiffraction_software.minimizer` — the active minimizer (lmfit, scipy-lstsq, dfo-ls, emcee, …) with version. Bayesian sampler runs use the sampler name and version here. +- `_easydiffraction_software.fit_datetime` — ISO-8601 UTC timestamp of + the successful fit that populated `analysis.software`. Omitted when no + timestamp is recorded. The same three values are concatenated into the `_computing.structure_refinement` free-text string for IUCr-tooling @@ -775,21 +785,18 @@ The IUCr writer pass differs from the default writer: #### 2.5 Submission-side validation -`project.report.check()` runs the generated `reports/.cif` -through `gemmi` (already a project dependency per `pyproject.toml`) for -dictionary-compliance validation before submission. - -```python -project.report.check() # validate reports/.cif -project.save(report=True, check=True) # save + validate in one step -``` +The IUCr CIF writer runs generated content through `gemmi` (already a +project dependency per `pyproject.toml`) before writing +`reports/.cif`. Public `project.report.check()` and +`check=True` entry points are removed; dictionary compliance is a writer +self-check, not a user choice. Validation checks performed by `gemmi`: - Every emitted tag exists in `cif_core.dic` or `cif_pow.dic` (the shipped reference dictionaries, or fresh copies fetched on demand). Unknown tags outside the project's `_easydiffraction_*` namespace - produce a warning. + raise `EasyDiffractionWriterError`. - Value types match the dictionary's `_type.contents` declaration (Real, Integer, Code, Text, …). - Required category keys (`_category_key.name` per `_pd_calib_d_to_tof`, @@ -803,7 +810,7 @@ Validation does **not** cover: - Crystallographic sanity checks (bond lengths, void volumes, density plausibility, missed-symmetry detection, anisotropic-ADP positive-definiteness). These need a full `checkCIF` implementation, - which `gemmi` does not provide. Treat `project.report.check()` as a + which `gemmi` does not provide. Treat the internal gemmi pass as a "spec compliance" pass, not a "scientific sanity" pass — a separate IUCr-server upload remains the final check before submission. - Verifying that `?` placeholders in `_journal.*` / `_publ_*` have been @@ -811,8 +818,8 @@ Validation does **not** cover: which are mandatory per journal). Flagged as a separate concern. The `_easydiffraction_*` project-extension namespace is excluded from -the unknown-tag warning by passing `gemmi`'s validator a prefix-skip -list. +unknown-tag failures by a prefix-skip rule in the writer validation +helper. ### 3. Handler mechanism — `iucr_name` + `IucrCategoryTransformer` @@ -976,10 +983,11 @@ Policy: recognisable to scientists familiar with `_refine_ls.*` / `_pd_proc_ls.*` from Rietveld publications; the IUCr export carries the matching dictionary-canonical category prefixes per topology. -- IUCr submission becomes a single command, with no manual editing - required: `project.save(report=True)` produces an upload-ready file at - `reports/.cif` matching the multi-datablock publication - convention. +- IUCr submission becomes a single explicit report command, with no + manual editing required: `project.report.save_cif()` produces an + upload-ready file at `reports/.cif` matching the + multi-datablock publication convention. Users who want CIF reports on + every project save can set `project.report.formats = ['cif']`. - Publication-metadata placeholders are emitted as `?` in `data_global` so users know where to fill in journal-required info before submission. @@ -1005,8 +1013,8 @@ Policy: `hb8169.cif` at 50K lines (DDL1 form; DDLm form would be of comparable size). - IUCr export is one-way. A user who hand-edits a file in `reports/` - loses those edits on the next `project.save(report=True)`. Documented - as such; treat `reports/` as generated output. + loses those edits on the next configured report save. Documented as + such; treat `reports/` as generated output. - Some external tooling chains (publCIF, journal in-house scripts) may still expect DDL1 underscore form. The dotted DDLm form is the dictionary spec; if real submissions surface a problem, a downstream @@ -1020,14 +1028,18 @@ Policy: function descriptors, reflns aggregates). `_fit_result.*` stays topology-neutral in `analysis/analysis.cif`; per-topology renaming to `_refine_ls.*` / `_pd_proc_ls.*` happens only in the IUCr export - (§1.2, §3 transformers). + (§1.2, §3 transformers). A later project-report amendment adds + `_software.*` as the persisted source for report software provenance. - [`minimizer-input-output-split.md`](minimizer-input-output-split.md) — `_fit_result.*` examples updated for the new fields. - [`project-facade-and-persistence.md`](project-facade-and-persistence.md) — `project.summary` facade slot is removed and replaced by - `project.report`. `summary.cif` is no longer written by default - `Project.save()`; the slot is repurposed for IUCr / journal report - generation in `reports/.cif` (see §2). The unimplemented + `project.report`. The accepted `project.save(report=True)` flag is + superseded by `project.report.formats` for configured reports and + `project.report.save_cif()` for the IUCr CIF one-off path. + `summary.cif` is no longer written by default `Project.save()`; the + slot is repurposed for IUCr / journal report generation in + `reports/.cif` (see §2). The unimplemented `summary_to_cif()` placeholder code path ([`project.py:464`](../../../../src/easydiffraction/project/project.py)) is removed as part of the implementation plan; no summary content @@ -1038,6 +1050,12 @@ Policy: and replaced by `project.report.help()` (same responsibilities, new slot name). All other entries in the help-surface table are unaffected. +- [`project-summary-rendering.md`](../suggestions/project-summary-rendering.md) + — amends this ADR's report API: public `check()` / `check=True` are + removed, gemmi validation moves inside CIF write paths, the + `_easydiffraction_software.*` triple is read from `analysis.software`, + and `_easydiffraction_software.fit_datetime` is added when fit + provenance has a timestamp. ## Open Questions @@ -1104,7 +1122,7 @@ it. - **Publication-metadata override hook.** A user-supplied `reports/publ_info.json` (or `publ_info.toml`) read by - `project.save(report=True)` to replace the `?` placeholders in + the IUCr report writer to replace the `?` placeholders in `data_global` (`_journal.*`, `_publ_*`, `_publ_author.*` loop entries). Out of scope for the first pass; revisit once the IUCr export is shipping and users have feedback on workflow friction. diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 37482088c..b793cb6d0 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -502,7 +502,7 @@ generated-artifact exceptions. (framework / calculator / minimizer triples + timestamp). - Commit: `Amend analysis-cif-fit-state for analysis.software`. -- [ ] **P1.11 — Amend `iucr-cif-tag-alignment.md` ADR** +- [x] **P1.11 — Amend `iucr-cif-tag-alignment.md` ADR** - File: `docs/dev/adrs/accepted/iucr-cif-tag-alignment.md`. - Five amendments per the ADR's amended-list entry: (1) `project.save(report=True)` flag removal; From 6e29305652a88d52b3df43a20fc67d69ab950b78 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 21:38:31 +0200 Subject: [PATCH 014/129] Add project.publication facade and sub-categories --- docs/dev/plans/project-summary-rendering.md | 2 +- .../categories/publication/__init__.py | 15 + .../project/categories/publication/default.py | 621 ++++++++++++++++++ .../project/categories/publication/factory.py | 17 + src/easydiffraction/project/project.py | 9 + 5 files changed, 663 insertions(+), 1 deletion(-) create mode 100644 src/easydiffraction/project/categories/publication/__init__.py create mode 100644 src/easydiffraction/project/categories/publication/default.py create mode 100644 src/easydiffraction/project/categories/publication/factory.py diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index b793cb6d0..ba5b34e38 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -518,7 +518,7 @@ generated-artifact exceptions. identification accordingly. - Commit: `Amend iucr-cif-tag-alignment for project.report`. -- [ ] **P1.12 — Add `project.publication` top-level owner** +- [x] **P1.12 — Add `project.publication` top-level owner** - Files: new `src/easydiffraction/project/categories/publication/` package; modify `src/easydiffraction/project/project.py`. diff --git a/src/easydiffraction/project/categories/publication/__init__.py b/src/easydiffraction/project/categories/publication/__init__.py new file mode 100644 index 000000000..f1aa7a046 --- /dev/null +++ b/src/easydiffraction/project/categories/publication/__init__.py @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Project publication metadata exports.""" + +from __future__ import annotations + +from easydiffraction.project.categories.publication.default import Publication +from easydiffraction.project.categories.publication.default import PublicationAuthor +from easydiffraction.project.categories.publication.default import PublicationAuthors +from easydiffraction.project.categories.publication.default import PublicationBody +from easydiffraction.project.categories.publication.default import PublicationContactAuthor +from easydiffraction.project.categories.publication.default import PublicationJournal +from easydiffraction.project.categories.publication.default import PublicationJournalCoeditor +from easydiffraction.project.categories.publication.default import PublicationJournalDate +from easydiffraction.project.categories.publication.factory import PublicationFactory diff --git a/src/easydiffraction/project/categories/publication/default.py b/src/easydiffraction/project/categories/publication/default.py new file mode 100644 index 000000000..a3a583df1 --- /dev/null +++ b/src/easydiffraction/project/categories/publication/default.py @@ -0,0 +1,621 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Project publication metadata categories.""" + +from __future__ import annotations + +from easydiffraction.core.category import CategoryCollection +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.category_owner import CategoryOwner +from easydiffraction.core.metadata import TypeInfo +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.variable import StringDescriptor +from easydiffraction.io.cif.handler import CifHandler +from easydiffraction.project.categories.publication.factory import PublicationFactory + + +class PublicationItemBase(CategoryItem): + """Base for optional publication metadata scalar categories.""" + + def _optional_string( + self, + *, + name: str, + cif_name: str, + description: str, + ) -> StringDescriptor: + """Create a nullable publication string descriptor.""" + return StringDescriptor( + name=name, + description=description, + value_spec=AttributeSpec(default=None, allow_none=True), + cif_handler=CifHandler(names=[cif_name]), + ) + + +class PublicationJournal(PublicationItemBase): + """Journal metadata for publication reports.""" + + _category_code = 'journal' + + def __init__(self) -> None: + super().__init__() + self._name_full = self._optional_string( + name='name_full', + cif_name='_journal.name_full', + description='Full journal name.', + ) + self._year = self._optional_string( + name='year', + cif_name='_journal.year', + description='Journal publication year.', + ) + self._volume = self._optional_string( + name='volume', + cif_name='_journal.volume', + description='Journal volume.', + ) + self._issue = self._optional_string( + name='issue', + cif_name='_journal.issue', + description='Journal issue.', + ) + self._page_first = self._optional_string( + name='page_first', + cif_name='_journal.page_first', + description='First journal page.', + ) + self._page_last = self._optional_string( + name='page_last', + cif_name='_journal.page_last', + description='Last journal page.', + ) + self._paper_category = self._optional_string( + name='paper_category', + cif_name='_journal.paper_category', + description='Journal paper category.', + ) + self._paper_doi = self._optional_string( + name='paper_doi', + cif_name='_journal.paper_DOI', + description='Journal paper DOI.', + ) + self._coden_astm = self._optional_string( + name='coden_astm', + cif_name='_journal.coden_ASTM', + description='Journal CODEN ASTM identifier.', + ) + self._suppl_publ_number = self._optional_string( + name='suppl_publ_number', + cif_name='_journal.suppl_publ_number', + description='Supplementary publication number.', + ) + + @property + def name_full(self) -> StringDescriptor: + """Full journal name.""" + return self._name_full + + @name_full.setter + def name_full(self, value: str | None) -> None: + self._name_full.value = value + + @property + def year(self) -> StringDescriptor: + """Journal publication year.""" + return self._year + + @year.setter + def year(self, value: str | None) -> None: + self._year.value = value + + @property + def volume(self) -> StringDescriptor: + """Journal volume.""" + return self._volume + + @volume.setter + def volume(self, value: str | None) -> None: + self._volume.value = value + + @property + def issue(self) -> StringDescriptor: + """Journal issue.""" + return self._issue + + @issue.setter + def issue(self, value: str | None) -> None: + self._issue.value = value + + @property + def page_first(self) -> StringDescriptor: + """First journal page.""" + return self._page_first + + @page_first.setter + def page_first(self, value: str | None) -> None: + self._page_first.value = value + + @property + def page_last(self) -> StringDescriptor: + """Last journal page.""" + return self._page_last + + @page_last.setter + def page_last(self, value: str | None) -> None: + self._page_last.value = value + + @property + def paper_category(self) -> StringDescriptor: + """Journal paper category.""" + return self._paper_category + + @paper_category.setter + def paper_category(self, value: str | None) -> None: + self._paper_category.value = value + + @property + def paper_doi(self) -> StringDescriptor: + """Journal paper DOI.""" + return self._paper_doi + + @paper_doi.setter + def paper_doi(self, value: str | None) -> None: + self._paper_doi.value = value + + @property + def coden_astm(self) -> StringDescriptor: + """Journal CODEN ASTM identifier.""" + return self._coden_astm + + @coden_astm.setter + def coden_astm(self, value: str | None) -> None: + self._coden_astm.value = value + + @property + def suppl_publ_number(self) -> StringDescriptor: + """Supplementary publication number.""" + return self._suppl_publ_number + + @suppl_publ_number.setter + def suppl_publ_number(self, value: str | None) -> None: + self._suppl_publ_number.value = value + + +class PublicationJournalDate(PublicationItemBase): + """Journal editorial date metadata.""" + + _category_code = 'journal_date' + + def __init__(self) -> None: + super().__init__() + self._accepted = self._optional_string( + name='accepted', + cif_name='_journal_date.accepted', + description='Accepted date.', + ) + self._from_coeditor = self._optional_string( + name='from_coeditor', + cif_name='_journal_date.from_coeditor', + description='Date sent from coeditor.', + ) + self._printers_final = self._optional_string( + name='printers_final', + cif_name='_journal_date.printers_final', + description='Final printer date.', + ) + + @property + def accepted(self) -> StringDescriptor: + """Accepted date.""" + return self._accepted + + @accepted.setter + def accepted(self, value: str | None) -> None: + self._accepted.value = value + + @property + def from_coeditor(self) -> StringDescriptor: + """Date sent from coeditor.""" + return self._from_coeditor + + @from_coeditor.setter + def from_coeditor(self, value: str | None) -> None: + self._from_coeditor.value = value + + @property + def printers_final(self) -> StringDescriptor: + """Final printer date.""" + return self._printers_final + + @printers_final.setter + def printers_final(self, value: str | None) -> None: + self._printers_final.value = value + + +class PublicationJournalCoeditor(PublicationItemBase): + """Journal coeditor metadata.""" + + _category_code = 'journal_coeditor' + + def __init__(self) -> None: + super().__init__() + self._code = self._optional_string( + name='code', + cif_name='_journal_coeditor.code', + description='Journal coeditor code.', + ) + self._name = self._optional_string( + name='name', + cif_name='_journal_coeditor.name', + description='Journal coeditor name.', + ) + self._notes = self._optional_string( + name='notes', + cif_name='_journal_coeditor.notes', + description='Journal coeditor notes.', + ) + + @property + def code(self) -> StringDescriptor: + """Journal coeditor code.""" + return self._code + + @code.setter + def code(self, value: str | None) -> None: + self._code.value = value + + @property + def name(self) -> StringDescriptor: + """Journal coeditor name.""" + return self._name + + @name.setter + def name(self, value: str | None) -> None: + self._name.value = value + + @property + def notes(self) -> StringDescriptor: + """Journal coeditor notes.""" + return self._notes + + @notes.setter + def notes(self, value: str | None) -> None: + self._notes.value = value + + +class PublicationContactAuthor(PublicationItemBase): + """Publication contact-author metadata.""" + + _category_code = 'publ_contact_author' + + def __init__(self) -> None: + super().__init__() + self._name = self._optional_string( + name='name', + cif_name='_publ_contact_author.name', + description='Contact author name.', + ) + self._address = self._optional_string( + name='address', + cif_name='_publ_contact_author.address', + description='Contact author address.', + ) + self._email = self._optional_string( + name='email', + cif_name='_publ_contact_author.email', + description='Contact author email.', + ) + self._phone = self._optional_string( + name='phone', + cif_name='_publ_contact_author.phone', + description='Contact author phone.', + ) + self._id_orcid = self._optional_string( + name='id_orcid', + cif_name='_publ_contact_author.id_ORCID', + description='Contact author ORCID identifier.', + ) + self._id_iucr = self._optional_string( + name='id_iucr', + cif_name='_publ_contact_author.id_IUCr', + description='Contact author IUCr identifier.', + ) + + @property + def name(self) -> StringDescriptor: + """Contact author name.""" + return self._name + + @name.setter + def name(self, value: str | None) -> None: + self._name.value = value + + @property + def address(self) -> StringDescriptor: + """Contact author address.""" + return self._address + + @address.setter + def address(self, value: str | None) -> None: + self._address.value = value + + @property + def email(self) -> StringDescriptor: + """Contact author email.""" + return self._email + + @email.setter + def email(self, value: str | None) -> None: + self._email.value = value + + @property + def phone(self) -> StringDescriptor: + """Contact author phone.""" + return self._phone + + @phone.setter + def phone(self, value: str | None) -> None: + self._phone.value = value + + @property + def id_orcid(self) -> StringDescriptor: + """Contact author ORCID identifier.""" + return self._id_orcid + + @id_orcid.setter + def id_orcid(self, value: str | None) -> None: + self._id_orcid.value = value + + @property + def id_iucr(self) -> StringDescriptor: + """Contact author IUCr identifier.""" + return self._id_iucr + + @id_iucr.setter + def id_iucr(self, value: str | None) -> None: + self._id_iucr.value = value + + +class PublicationBody(PublicationItemBase): + """Publication body metadata.""" + + _category_code = 'publ_body' + + def __init__(self) -> None: + super().__init__() + self._title = self._optional_string( + name='title', + cif_name='_publ_body.title', + description='Publication title.', + ) + self._synopsis = self._optional_string( + name='synopsis', + cif_name='_publ_body.synopsis', + description='Publication synopsis.', + ) + self._abstract = self._optional_string( + name='abstract', + cif_name='_publ_body.abstract', + description='Publication abstract.', + ) + self._keywords = self._optional_string( + name='keywords', + cif_name='_publ_body.keywords', + description='Publication keywords.', + ) + + @property + def title(self) -> StringDescriptor: + """Publication title.""" + return self._title + + @title.setter + def title(self, value: str | None) -> None: + self._title.value = value + + @property + def synopsis(self) -> StringDescriptor: + """Publication synopsis.""" + return self._synopsis + + @synopsis.setter + def synopsis(self, value: str | None) -> None: + self._synopsis.value = value + + @property + def abstract(self) -> StringDescriptor: + """Publication abstract.""" + return self._abstract + + @abstract.setter + def abstract(self, value: str | None) -> None: + self._abstract.value = value + + @property + def keywords(self) -> list[str]: + """Publication keywords.""" + value = self._keywords.value + if value in {None, ''}: + return [] + return str(value).splitlines() + + @keywords.setter + def keywords(self, value: list[str]) -> None: + self._keywords.value = '\n'.join(value) + + +class PublicationAuthor(PublicationItemBase): + """Single publication author row.""" + + _category_code = 'publ_author' + _category_entry_name = 'name' + + def __init__(self) -> None: + super().__init__() + self._name = self._optional_string( + name='name', + cif_name='_publ_author.name', + description='Publication author name.', + ) + self._address = self._optional_string( + name='address', + cif_name='_publ_author.address', + description='Publication author address.', + ) + self._footnote = self._optional_string( + name='footnote', + cif_name='_publ_author.footnote', + description='Publication author footnote.', + ) + self._id_orcid = self._optional_string( + name='id_orcid', + cif_name='_publ_author.id_ORCID', + description='Publication author ORCID identifier.', + ) + self._id_iucr = self._optional_string( + name='id_iucr', + cif_name='_publ_author.id_IUCr', + description='Publication author IUCr identifier.', + ) + + @property + def name(self) -> StringDescriptor: + """Publication author name.""" + return self._name + + @name.setter + def name(self, value: str | None) -> None: + self._name.value = value + + @property + def address(self) -> StringDescriptor: + """Publication author address.""" + return self._address + + @address.setter + def address(self, value: str | None) -> None: + self._address.value = value + + @property + def footnote(self) -> StringDescriptor: + """Publication author footnote.""" + return self._footnote + + @footnote.setter + def footnote(self, value: str | None) -> None: + self._footnote.value = value + + @property + def id_orcid(self) -> StringDescriptor: + """Publication author ORCID identifier.""" + return self._id_orcid + + @id_orcid.setter + def id_orcid(self, value: str | None) -> None: + self._id_orcid.value = value + + @property + def id_iucr(self) -> StringDescriptor: + """Publication author IUCr identifier.""" + return self._id_iucr + + @id_iucr.setter + def id_iucr(self, value: str | None) -> None: + self._id_iucr.value = value + + +class PublicationAuthors(CategoryCollection): + """Publication author rows.""" + + def __init__(self) -> None: + """Create an empty publication-author collection.""" + super().__init__(item_type=PublicationAuthor) + + def add( + self, + *, + name: str, + address: str | None = None, + footnote: str | None = None, + id_orcid: str | None = None, + id_iucr: str | None = None, + ) -> PublicationAuthor: + """ + Add or replace one publication author row. + + Parameters + ---------- + name : str + Author name. + address : str | None, default=None + Author address. + footnote : str | None, default=None + Author footnote. + id_orcid : str | None, default=None + Author ORCID identifier. + id_iucr : str | None, default=None + Author IUCr identifier. + + Returns + ------- + PublicationAuthor + The inserted author row. + """ + author = PublicationAuthor() + author.name = name + author.address = address + author.footnote = footnote + author.id_orcid = id_orcid + author.id_iucr = id_iucr + super().add(author) + return author + + +@PublicationFactory.register +class Publication(CategoryOwner): + """Project publication metadata facade.""" + + type_info = TypeInfo( + tag='default', + description='Project publication metadata', + ) + + def __init__(self) -> None: + super().__init__() + self._journal = PublicationJournal() + self._journal_date = PublicationJournalDate() + self._journal_coeditor = PublicationJournalCoeditor() + self._contact_author = PublicationContactAuthor() + self._body = PublicationBody() + self._authors = PublicationAuthors() + + @property + def journal(self) -> PublicationJournal: + """Journal metadata.""" + return self._journal + + @property + def journal_date(self) -> PublicationJournalDate: + """Journal editorial date metadata.""" + return self._journal_date + + @property + def journal_coeditor(self) -> PublicationJournalCoeditor: + """Journal coeditor metadata.""" + return self._journal_coeditor + + @property + def contact_author(self) -> PublicationContactAuthor: + """Publication contact-author metadata.""" + return self._contact_author + + @property + def body(self) -> PublicationBody: + """Publication body metadata.""" + return self._body + + @property + def authors(self) -> PublicationAuthors: + """Publication author rows.""" + return self._authors diff --git a/src/easydiffraction/project/categories/publication/factory.py b/src/easydiffraction/project/categories/publication/factory.py new file mode 100644 index 000000000..0f2926339 --- /dev/null +++ b/src/easydiffraction/project/categories/publication/factory.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Factory for project publication metadata owners.""" + +from __future__ import annotations + +from typing import ClassVar + +from easydiffraction.core.factory import FactoryBase + + +class PublicationFactory(FactoryBase): + """Create project publication metadata owners.""" + + _default_rules: ClassVar[dict] = { + frozenset(): 'default', + } diff --git a/src/easydiffraction/project/project.py b/src/easydiffraction/project/project.py index 34fc7b8e9..910640563 100644 --- a/src/easydiffraction/project/project.py +++ b/src/easydiffraction/project/project.py @@ -23,6 +23,8 @@ from easydiffraction.io.cif.serialize import project_to_cif from easydiffraction.io.results_sidecar import read_analysis_results_sidecar from easydiffraction.io.results_sidecar import write_analysis_results_sidecar +from easydiffraction.project.categories.publication import Publication +from easydiffraction.project.categories.publication import PublicationFactory from easydiffraction.project.display import ProjectDisplay from easydiffraction.project.project_config import ProjectConfig from easydiffraction.report import Report @@ -209,6 +211,7 @@ def __init__( object.__setattr__(self, '_table', self._config.table) object.__setattr__(self, '_verbosity', self._config.verbosity) object.__setattr__(self, '_report', self._config.report) + self._publication = PublicationFactory.create(PublicationFactory.default_tag()) self._display = ProjectDisplay(self) self._analysis = Analysis(self) self._saved = False @@ -224,6 +227,7 @@ def _attach_category_parents(self) -> None: self._chart._parent = self self._table._parent = self self._report._parent = self + self._publication._parent = self @staticmethod def _supported_filters_for(category: object) -> dict[str, object]: @@ -333,6 +337,11 @@ def report(self) -> Report: """Submission report builder bound to the project.""" return self._report + @property + def publication(self) -> Publication: + """Publication metadata bound to the project.""" + return self._publication + @property def parameters(self) -> list: """Return parameters from all structures and experiments.""" From 0db4dc6e20a9eae4698d9865a7a7898e9d186f80 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 21:40:03 +0200 Subject: [PATCH 015/129] Persist project.publication to project.cif --- docs/dev/plans/project-summary-rendering.md | 2 +- src/easydiffraction/io/cif/serialize.py | 30 ++++++++++++------- .../project/categories/publication/default.py | 12 ++++++++ 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index ba5b34e38..986066021 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -544,7 +544,7 @@ generated-artifact exceptions. - No CIF write wiring yet (that's P1.13). - Commit: `Add project.publication facade and sub-categories`. -- [ ] **P1.13 — Persist `project.publication.*` to `project.cif`** +- [x] **P1.13 — Persist `project.publication.*` to `project.cif`** - Files: `src/easydiffraction/project/project.py` (and the project-CIF serializer). - Extend the project-CIF write path to include the diff --git a/src/easydiffraction/io/cif/serialize.py b/src/easydiffraction/io/cif/serialize.py index a670cb73d..e3080aa97 100644 --- a/src/easydiffraction/io/cif/serialize.py +++ b/src/easydiffraction/io/cif/serialize.py @@ -565,18 +565,22 @@ def _as_cif_text(section: object) -> str: def project_config_to_cif(project: object) -> str: """Render project-level configuration to ``project.cif`` text.""" - config = getattr(project, '_config', None) - if config is not None: - return category_owner_to_cif(config) + sections: list[str] = [] + for attr_name in ('info', 'chart', 'report'): + section = getattr(project, attr_name, None) + if section is not None: + sections.append(_as_cif_text(section)) - lines: list[str] = [_as_cif_text(project.info)] - chart = getattr(project, 'chart', None) - if chart is not None: - lines.extend(('', _as_cif_text(chart))) - table = getattr(project, 'table', None) - if table is not None: - lines.extend(('', _as_cif_text(table))) - return '\n'.join(lines) + publication = getattr(project, 'publication', None) + if publication is not None: + sections.append(category_owner_to_cif(publication)) + + for attr_name in ('table', 'verbosity'): + section = getattr(project, attr_name, None) + if section is not None: + sections.append(_as_cif_text(section)) + + return '\n\n'.join(section for section in sections if section) def project_to_cif(project: object) -> str: @@ -689,6 +693,10 @@ def project_config_from_cif(project: object, cif_text: str) -> None: # Missing _report.* items intentionally keep legacy defaults. report.from_cif(block) + publication = getattr(project, 'publication', None) + if publication is not None: + publication.from_cif(block) + table = getattr(project, 'table', None) if table is not None: table.from_cif(block) diff --git a/src/easydiffraction/project/categories/publication/default.py b/src/easydiffraction/project/categories/publication/default.py index a3a583df1..934b0e2c6 100644 --- a/src/easydiffraction/project/categories/publication/default.py +++ b/src/easydiffraction/project/categories/publication/default.py @@ -619,3 +619,15 @@ def body(self) -> PublicationBody: def authors(self) -> PublicationAuthors: """Publication author rows.""" return self._authors + + def from_cif(self, block: object) -> None: + """ + Populate publication metadata from a project CIF block. + + Parameters + ---------- + block : object + Parsed CIF block containing project-level publication tags. + """ + for category in self.categories: + category.from_cif(block) From 6f82337c5014cefbe518ee6d823eb442465941dd Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 21:42:35 +0200 Subject: [PATCH 016/129] Add Publication.load TOML JSON entry point --- docs/dev/plans/project-summary-rendering.md | 2 +- .../project/categories/publication/default.py | 19 ++ .../project/publication_loader.py | 203 ++++++++++++++++++ 3 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 src/easydiffraction/project/publication_loader.py diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 986066021..7d3641c8b 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -557,7 +557,7 @@ generated-artifact exceptions. `None`. - Commit: `Persist project.publication to project.cif`. -- [ ] **P1.14 — TOML/JSON loader for publication metadata** +- [x] **P1.14 — TOML/JSON loader for publication metadata** - File: new `src/easydiffraction/project/publication_loader.py`. - Implement `Publication.load(path: str | Path)`: diff --git a/src/easydiffraction/project/categories/publication/default.py b/src/easydiffraction/project/categories/publication/default.py index 934b0e2c6..83a9c6306 100644 --- a/src/easydiffraction/project/categories/publication/default.py +++ b/src/easydiffraction/project/categories/publication/default.py @@ -4,6 +4,8 @@ from __future__ import annotations +import pathlib + from easydiffraction.core.category import CategoryCollection from easydiffraction.core.category import CategoryItem from easydiffraction.core.category_owner import CategoryOwner @@ -12,6 +14,7 @@ from easydiffraction.core.variable import StringDescriptor from easydiffraction.io.cif.handler import CifHandler from easydiffraction.project.categories.publication.factory import PublicationFactory +from easydiffraction.project.publication_loader import load_publication class PublicationItemBase(CategoryItem): @@ -631,3 +634,19 @@ def from_cif(self, block: object) -> None: """ for category in self.categories: category.from_cif(block) + + def load(self, path: str | pathlib.Path) -> None: + """ + Load publication metadata from a TOML or JSON file. + + Parameters + ---------- + path : str | pathlib.Path + File path ending in ``.toml`` or ``.json``. + + Raises + ------ + ValueError + If the file extension, top-level shape, or any key is invalid. + """ + load_publication(self, path) diff --git a/src/easydiffraction/project/publication_loader.py b/src/easydiffraction/project/publication_loader.py new file mode 100644 index 000000000..1ce4c8aff --- /dev/null +++ b/src/easydiffraction/project/publication_loader.py @@ -0,0 +1,203 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Load publication metadata from TOML or JSON files.""" + +from __future__ import annotations + +import json +import pathlib +import tomllib +from collections.abc import Mapping +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from easydiffraction.project.categories.publication import Publication + +_FIELD_MAP = { + 'journal_name_full': ('journal', 'name_full'), + 'journal_year': ('journal', 'year'), + 'journal_volume': ('journal', 'volume'), + 'journal_issue': ('journal', 'issue'), + 'journal_page_first': ('journal', 'page_first'), + 'journal_page_last': ('journal', 'page_last'), + 'journal_paper_category': ('journal', 'paper_category'), + 'journal_paper_doi': ('journal', 'paper_doi'), + 'journal_coden_astm': ('journal', 'coden_astm'), + 'journal_suppl_publ_number': ('journal', 'suppl_publ_number'), + 'journal_date_accepted': ('journal_date', 'accepted'), + 'journal_date_from_coeditor': ('journal_date', 'from_coeditor'), + 'journal_date_printers_final': ('journal_date', 'printers_final'), + 'journal_coeditor_code': ('journal_coeditor', 'code'), + 'journal_coeditor_name': ('journal_coeditor', 'name'), + 'journal_coeditor_notes': ('journal_coeditor', 'notes'), + 'contact_author_name': ('contact_author', 'name'), + 'contact_author_address': ('contact_author', 'address'), + 'contact_author_email': ('contact_author', 'email'), + 'contact_author_phone': ('contact_author', 'phone'), + 'contact_author_id_orcid': ('contact_author', 'id_orcid'), + 'contact_author_id_iucr': ('contact_author', 'id_iucr'), + 'body_title': ('body', 'title'), + 'body_synopsis': ('body', 'synopsis'), + 'body_abstract': ('body', 'abstract'), +} + +_AUTHOR_FIELDS = { + 'name', + 'address', + 'footnote', + 'id_orcid', + 'id_iucr', +} + + +def _read_publication_data(path: pathlib.Path) -> Mapping[str, object]: + """Read a publication metadata file by extension.""" + ext = path.suffix.lower() + if ext == '.toml': + data = tomllib.loads(path.read_text()) + elif ext == '.json': + data = json.loads(path.read_text()) + else: + msg = f'Unsupported publication-info format: {ext}. Use .toml or .json.' + raise ValueError(msg) + + if not isinstance(data, Mapping): + msg = 'Publication-info file must contain a top-level object.' + raise ValueError(msg) + return data + + +def _optional_text(key: str, value: object) -> str | None: + """Validate an optional text field from the publication file.""" + if value is None: + return None + if isinstance(value, str): + return value + if isinstance(value, (int, float)) and not isinstance(value, bool): + return str(value) + msg = f'Publication-info field {key!r} must be a string or null.' + raise ValueError(msg) + + +def _required_text(key: str, value: object) -> str: + """Validate a required text field from the publication file.""" + text = _optional_text(key, value) + if text is None or not text: + msg = f'Publication-info field {key!r} must be a non-empty string.' + raise ValueError(msg) + return text + + +def _keywords(value: object) -> list[str]: + """Validate body keywords from the publication file.""" + if value is None: + return [] + if not isinstance(value, list): + msg = "Publication-info field 'body_keywords' must be a list of strings." + raise ValueError(msg) + + keywords: list[str] = [] + for idx, keyword in enumerate(value): + if not isinstance(keyword, str): + msg = f"Publication-info field 'body_keywords[{idx}]' must be a string." + raise ValueError(msg) + keywords.append(keyword) + return keywords + + +def _author_rows(value: object) -> list[dict[str, str | None]]: + """Validate publication author rows from the publication file.""" + if not isinstance(value, list): + msg = "Publication-info field 'authors' must be a list of objects." + raise ValueError(msg) + + rows: list[dict[str, str | None]] = [] + for idx, row in enumerate(value): + if not isinstance(row, Mapping): + msg = f"Publication-info field 'authors[{idx}]' must be an object." + raise ValueError(msg) + + for key in row: + if key not in _AUTHOR_FIELDS: + raise ValueError(f'authors.{key}') + + rows.append( + { + 'name': _required_text(f'authors[{idx}].name', row.get('name')), + 'address': _optional_text(f'authors[{idx}].address', row.get('address')), + 'footnote': _optional_text(f'authors[{idx}].footnote', row.get('footnote')), + 'id_orcid': _optional_text(f'authors[{idx}].id_orcid', row.get('id_orcid')), + 'id_iucr': _optional_text(f'authors[{idx}].id_iucr', row.get('id_iucr')), + } + ) + return rows + + +def _validate_publication_data( + data: Mapping[str, object], +) -> tuple[ + list[tuple[str, str, str | None]], + list[str] | None, + list[dict[str, str | None]] | None, +]: + """Validate publication data and return normalized updates.""" + updates: list[tuple[str, str, str | None]] = [] + keywords: list[str] | None = None + authors: list[dict[str, str | None]] | None = None + + for key, value in data.items(): + if key in _FIELD_MAP: + category_name, attr_name = _FIELD_MAP[key] + updates.append((category_name, attr_name, _optional_text(key, value))) + elif key == 'body_keywords': + keywords = _keywords(value) + elif key == 'authors': + authors = _author_rows(value) + else: + raise ValueError(key) + + return updates, keywords, authors + + +def _apply_author_rows( + publication: Publication, + authors: list[dict[str, str | None]], +) -> None: + """Replace the publication author collection with normalized rows.""" + publication.authors._adopt_items([]) + publication.authors._mark_parent_dirty() + for author in authors: + publication.authors.add( + name=author['name'], + address=author['address'], + footnote=author['footnote'], + id_orcid=author['id_orcid'], + id_iucr=author['id_iucr'], + ) + + +def load_publication(publication: Publication, path: str | pathlib.Path) -> None: + """ + Load publication metadata into a Publication object. + + Parameters + ---------- + publication : Publication + Publication metadata facade to populate. + path : str | pathlib.Path + TOML or JSON file containing flat publication metadata keys. + + Raises + ------ + ValueError + If the file extension, top-level shape, or any key is invalid. + """ + data = _read_publication_data(pathlib.Path(path)) + updates, keywords, authors = _validate_publication_data(data) + + for category_name, attr_name, value in updates: + setattr(getattr(publication, category_name), attr_name, value) + if keywords is not None: + publication.body.keywords = keywords + if authors is not None: + _apply_author_rows(publication, authors) From c83b4b4796cf8476e2fba60a672308e84cc35d45 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 21:46:12 +0200 Subject: [PATCH 017/129] Read publication metadata in IUCr writer --- .../project-facade-and-persistence.md | 24 ++- .../python-cif-category-correspondence.md | 4 + docs/dev/plans/project-summary-rendering.md | 2 +- src/easydiffraction/io/cif/iucr_writer.py | 169 ++++++++++++------ 4 files changed, 144 insertions(+), 55 deletions(-) diff --git a/docs/dev/adrs/accepted/project-facade-and-persistence.md b/docs/dev/adrs/accepted/project-facade-and-persistence.md index 17bcdc1a9..0f0389ff8 100644 --- a/docs/dev/adrs/accepted/project-facade-and-persistence.md +++ b/docs/dev/adrs/accepted/project-facade-and-persistence.md @@ -16,7 +16,8 @@ Persistence. `Project` is the top-level user facade. It owns project metadata, structures, experiments, rendering preferences, display helpers, -analysis, report helpers, verbosity, and save/load behavior. +analysis, report helpers, publication metadata, verbosity, and save/load +behavior. A later proposal considered renaming this facade to `Workspace` so that `project` could be reserved for the scientific project information @@ -50,9 +51,18 @@ through `project.report` and written only when requested, using `reports/.cif`; default project saves do not write `summary.cif`. -Expose submission-report helpers as `project.report`. The previous -`project.summary` placeholder and its `summary.cif` output are not part -of the persistence layout. +Expose submission-report helpers as `project.report`. This facade is a +hybrid surface: its scalar output configuration persists to +`project.cif` as `_report.*`, while its methods render report artifacts +under `reports/`. The previous `project.summary` placeholder and its +`summary.cif` output are not part of the persistence layout. + +Expose journal-submission metadata as `project.publication`. It is a +top-level owner with CIF-aligned sibling categories for `_journal.*`, +`_journal_date.*`, `_journal_coeditor.*`, +`_publ_contact_author.*`, `_publ_body.*`, and the `_publ_author.*` +loop. These singleton publication categories persist in `project.cif` +and feed report exports; `reports/.cif` remains export-only. Keep project information available as `project.info`. The Python name avoids a confusing `project.project` access path, while the persisted @@ -79,6 +89,12 @@ The saved project directory path is runtime file-I/O state, not a serialized project-information field. If the path is exposed in Python, it must not emit a `_project.path` CIF item. +The project-level singleton categories currently persisted in +`project.cif` are `_project.*`, `_chart.*`, `_report.*`, `_table.*`, +`_verbosity.*`, `_journal.*`, `_journal_date.*`, +`_journal_coeditor.*`, `_publ_contact_author.*`, `_publ_body.*`, and +the `_publ_author.*` loop. + ## Consequences The saved layout mirrors the current object graph while preserving the diff --git a/docs/dev/adrs/suggestions/python-cif-category-correspondence.md b/docs/dev/adrs/suggestions/python-cif-category-correspondence.md index 653d57fdd..7072b8d6c 100644 --- a/docs/dev/adrs/suggestions/python-cif-category-correspondence.md +++ b/docs/dev/adrs/suggestions/python-cif-category-correspondence.md @@ -56,6 +56,8 @@ to objects reached from the current `Project` root, for example | Current Python surface | Current saved location | Current CIF block form | Notes | | ------------------------------------------------ | ------------------------ | ---------------------- | ----------------------------------------------------------------------------------- | | `project.info`, `project.chart`, `project.table` | `project.cif` | bare categories | Project-level singleton config. | +| `project.report` | `project.cif` | bare category | Project-owned report-output config; report methods render artifacts under `reports/`. | +| `project.publication` | `project.cif` | bare categories + loop | Journal-submission metadata under `_journal_*` / `_publ_*` categories. | | `project.verbosity` | `project.cif` | bare category | Project-owned fit-output verbosity category backed by `VerbosityEnum`. | | `project.structures[name]` | `structures/.cif` | `data_` | Each structure is one CIF data block. | | `project.experiments[name]` | `experiments/.cif` | `data_` | Each experiment is one CIF data block. | @@ -75,6 +77,8 @@ to objects reached from the current `Project` root, for example | `project.info.last_modified` | `_project.last_modified` | Partly | Field name matches, category name does not. | | `project.info.path` | none | No | Runtime storage path, not a CIF field. | | `project.chart.type` | `_chart.type` | Yes | Direct category-owned selector mapping. | +| `project.report.*` | `_report.*` | Yes | Direct project-owned report-output configuration mapping. | +| `project.publication.*` | `_journal.*` / `_publ_*` | Partly | Python keeps one owner with sibling categories; CIF uses journal and publication dictionary names. | | `project.table.type` | `_table.type` | Yes | Direct category-owned selector mapping. | | `project.verbosity.fit` | `_verbosity.fit` | Yes | Direct category and field mapping for fitting process output verbosity. | diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 7d3641c8b..50cad4b12 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -571,7 +571,7 @@ generated-artifact exceptions. `ValueError()`. - Commit: `Add Publication.load TOML/JSON entry point`. -- [ ] **P1.15 — Wire IUCr writer to read from `project.publication.*` + amend ADRs** +- [x] **P1.15 — Wire IUCr writer to read from `project.publication.*` + amend ADRs** - Files: `src/easydiffraction/io/cif/iucr_writer.py`, `docs/dev/adrs/accepted/project-facade-and-persistence.md`, `docs/dev/adrs/suggestions/python-cif-category-correspondence.md`. diff --git a/src/easydiffraction/io/cif/iucr_writer.py b/src/easydiffraction/io/cif/iucr_writer.py index ff32f820d..7ed73bb9e 100644 --- a/src/easydiffraction/io/cif/iucr_writer.py +++ b/src/easydiffraction/io/cif/iucr_writer.py @@ -23,51 +23,52 @@ _TEXT_WRAP_WIDTH = 80 _ITEM_WIDTH = 38 -_JOURNAL_TAGS = ( - '_journal.name_full', - '_journal.year', - '_journal.volume', - '_journal.issue', - '_journal.page_first', - '_journal.page_last', - '_journal.paper_category', - '_journal.paper_DOI', - '_journal.coden_ASTM', - '_journal.suppl_publ_number', +_JOURNAL_ITEMS = ( + ('_journal.name_full', 'name_full'), + ('_journal.year', 'year'), + ('_journal.volume', 'volume'), + ('_journal.issue', 'issue'), + ('_journal.page_first', 'page_first'), + ('_journal.page_last', 'page_last'), + ('_journal.paper_category', 'paper_category'), + ('_journal.paper_DOI', 'paper_doi'), + ('_journal.coden_ASTM', 'coden_astm'), + ('_journal.suppl_publ_number', 'suppl_publ_number'), ) -_JOURNAL_DATE_TAGS = ( - '_journal_date.accepted', - '_journal_date.from_coeditor', - '_journal_date.printers_final', +_JOURNAL_DATE_ITEMS = ( + ('_journal_date.accepted', 'accepted'), + ('_journal_date.from_coeditor', 'from_coeditor'), + ('_journal_date.printers_final', 'printers_final'), ) -_JOURNAL_COEDITOR_TAGS = ( - '_journal_coeditor.code', - '_journal_coeditor.name', - '_journal_coeditor.notes', +_JOURNAL_COEDITOR_ITEMS = ( + ('_journal_coeditor.code', 'code'), + ('_journal_coeditor.name', 'name'), + ('_journal_coeditor.notes', 'notes'), ) -_PUBL_CONTACT_AUTHOR_TAGS = ( - '_publ_contact_author.name', - '_publ_contact_author.address', - '_publ_contact_author.email', - '_publ_contact_author.phone', - '_publ_contact_author.id_ORCID', - '_publ_contact_author.id_IUCr', +_PUBL_CONTACT_AUTHOR_ITEMS = ( + ('_publ_contact_author.name', 'name'), + ('_publ_contact_author.address', 'address'), + ('_publ_contact_author.email', 'email'), + ('_publ_contact_author.phone', 'phone'), + ('_publ_contact_author.id_ORCID', 'id_orcid'), + ('_publ_contact_author.id_IUCr', 'id_iucr'), ) -_PUBL_AUTHOR_TAGS = ( - '_publ_author.name', - '_publ_author.address', - '_publ_author.footnote', - '_publ_author.id_ORCID', - '_publ_author.id_IUCr', +_PUBL_AUTHOR_ITEMS = ( + ('_publ_author.name', 'name'), + ('_publ_author.address', 'address'), + ('_publ_author.footnote', 'footnote'), + ('_publ_author.id_ORCID', 'id_orcid'), + ('_publ_author.id_IUCr', 'id_iucr'), ) +_PUBL_AUTHOR_TAGS = tuple(tag for tag, _ in _PUBL_AUTHOR_ITEMS) -_PUBL_BODY_TAGS = ( - '_publ_body.title', - '_publ_body.contents', +_PUBL_BODY_ITEMS = ( + ('_publ_body.title', 'title'), + ('_publ_body.contents', 'contents'), ) _PACKAGE_BY_ENGINE = { @@ -144,7 +145,7 @@ def _write_global_block(project: object) -> str: lines = ['data_global'] _write_audit_section(lines) _write_computing_section(lines, project) - _write_publication_sections(lines) + _write_publication_sections(lines, project) _write_formula_section(lines, project) return '\n'.join(lines) @@ -176,21 +177,41 @@ def _write_computing_section(lines: list[str], project: object) -> None: _write_item(lines, '_easydiffraction_software.fit_datetime', fit_datetime) -def _write_publication_sections(lines: list[str]) -> None: - """Append publication placeholders.""" - _write_placeholder_items(lines, 'Journal', _JOURNAL_TAGS) - _write_placeholder_items(lines, 'Journal dates', _JOURNAL_DATE_TAGS) - _write_placeholder_items(lines, 'Journal coeditor', _JOURNAL_COEDITOR_TAGS) - _write_placeholder_items( +def _write_publication_sections(lines: list[str], project: object) -> None: + """Append publication metadata from the project publication owner.""" + publication = getattr(project, 'publication', None) + _write_publication_item_section( + lines, + 'Journal', + getattr(publication, 'journal', None), + _JOURNAL_ITEMS, + ) + _write_publication_item_section( + lines, + 'Journal dates', + getattr(publication, 'journal_date', None), + _JOURNAL_DATE_ITEMS, + ) + _write_publication_item_section( + lines, + 'Journal coeditor', + getattr(publication, 'journal_coeditor', None), + _JOURNAL_COEDITOR_ITEMS, + ) + _write_publication_item_section( lines, 'Publication contact author', - _PUBL_CONTACT_AUTHOR_TAGS, + getattr(publication, 'contact_author', None), + _PUBL_CONTACT_AUTHOR_ITEMS, ) _section(lines, 'Publication authors') - _write_loop(lines, _PUBL_AUTHOR_TAGS, [tuple('?' for _ in _PUBL_AUTHOR_TAGS)]) + _write_loop(lines, _PUBL_AUTHOR_TAGS, _publication_author_rows(publication)) - _write_placeholder_items(lines, 'Publication body', _PUBL_BODY_TAGS) + _write_publication_body_section( + lines, + getattr(publication, 'body', None), + ) def _write_formula_section(lines: list[str], project: object) -> None: @@ -682,15 +703,63 @@ def _write_tof_calibration_loop(lines: list[str], experiment: object) -> None: _write_loop(lines, loop.tags, loop.rows) -def _write_placeholder_items( +def _write_publication_item_section( lines: list[str], title: str, - tags: Iterable[str], + category: object, + items: Iterable[tuple[str, str]], ) -> None: - """Append one placeholder category section.""" + """Append one scalar publication metadata section.""" _section(lines, title) - for tag in tags: - _write_item(lines, tag, '?') + for tag, attr_name in items: + _write_item(lines, tag, _attribute_value(category, attr_name)) + + +def _write_publication_body_section(lines: list[str], body: object) -> None: + """Append publication body metadata.""" + _section(lines, 'Publication body') + for tag, attr_name in _PUBL_BODY_ITEMS: + _write_item(lines, tag, _publication_body_value(body, attr_name)) + + +def _publication_body_value(body: object, attr_name: str) -> object: + """Return one publication-body value.""" + if attr_name != 'contents': + return _attribute_value(body, attr_name) + + return _publication_body_contents(body) + + +def _publication_body_contents(body: object) -> str | None: + """Return IUCr publication body contents from discrete fields.""" + if body is None: + return None + + sections = [] + for attr_name in ('synopsis', 'abstract'): + value = _attribute_value(body, attr_name) + if value not in {None, ''}: + sections.append(str(value)) + + keywords = getattr(body, 'keywords', []) + if keywords: + sections.append(f'Keywords: {", ".join(keywords)}') + + if not sections: + return None + return '\n\n'.join(sections) + + +def _publication_author_rows(publication: object) -> list[tuple[object, ...]]: + """Return publication author rows or one empty placeholder row.""" + authors = getattr(publication, 'authors', None) + rows = [ + tuple(_attribute_value(author, attr_name) for _, attr_name in _PUBL_AUTHOR_ITEMS) + for author in _collection_values(authors) + ] + if rows: + return rows + return [tuple(None for _ in _PUBL_AUTHOR_ITEMS)] def _write_loop( From 1dad9947733cbb859927d20b535d885b89351ace Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 21:49:28 +0200 Subject: [PATCH 018/129] Add ReportDataContext builder and Jinja base macros --- docs/dev/plans/project-summary-rendering.md | 2 +- .../project/categories/report/default.py | 5 + src/easydiffraction/report/data_context.py | 325 ++++++++++++++++++ src/easydiffraction/report/templates/base.j2 | 26 ++ 4 files changed, 357 insertions(+), 1 deletion(-) create mode 100644 src/easydiffraction/report/data_context.py create mode 100644 src/easydiffraction/report/templates/base.j2 diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 50cad4b12..ac5618cd9 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -592,7 +592,7 @@ generated-artifact exceptions. - Commit: `Read publication metadata from project.publication in IUCr writer`. -- [ ] **P1.16 — `ReportDataContext` builder + Jinja base templates** +- [x] **P1.16 — `ReportDataContext` builder + Jinja base templates** - Files: new `src/easydiffraction/report/data_context.py`; new `src/easydiffraction/report/templates/base.j2`. - `data_context()` builds the dict per §6 of the ADR diff --git a/src/easydiffraction/project/categories/report/default.py b/src/easydiffraction/project/categories/report/default.py index 4051559a3..062c7754f 100644 --- a/src/easydiffraction/project/categories/report/default.py +++ b/src/easydiffraction/project/categories/report/default.py @@ -17,6 +17,7 @@ from easydiffraction.io.cif.iucr_writer import write_iucr_cif from easydiffraction.io.cif.handler import CifHandler from easydiffraction.project.categories.report.factory import ReportFactory +from easydiffraction.report.data_context import build_report_data_context from easydiffraction.report.enums import ReportFormatEnum from easydiffraction.report.enums import ReportStyleEnum from easydiffraction.utils.logging import console @@ -186,6 +187,10 @@ def project(self) -> object: """Project owning this report category.""" return self._parent + def data_context(self) -> dict[str, object]: + """Return shared report-rendering data.""" + return build_report_data_context(self.project) + def help(self) -> None: """Print available report methods.""" render_object_help(self) diff --git a/src/easydiffraction/report/data_context.py b/src/easydiffraction/report/data_context.py new file mode 100644 index 000000000..b1f8af1dc --- /dev/null +++ b/src/easydiffraction/report/data_context.py @@ -0,0 +1,325 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Build shared data for report renderers.""" + +from __future__ import annotations + +from collections.abc import Iterable +from datetime import UTC +from datetime import datetime + +from easydiffraction.utils.utils import package_version + +_STRUCTURE_CELL_FIELDS = ( + 'length_a', + 'length_b', + 'length_c', + 'angle_alpha', + 'angle_beta', + 'angle_gamma', +) +_EXPERIMENT_TYPE_FIELDS = ( + 'sample_form', + 'beam_mode', + 'radiation_probe', + 'scattering_type', +) +_FIT_RESULT_FIELDS = ( + 'result_kind', + 'success', + 'message', + 'iterations', + 'fitting_time', + 'reduced_chi_square', + 'n_data_points', + 'n_parameters', + 'n_free_parameters', + 'degrees_of_freedom', + 'r_factor_all', + 'wr_factor_all', + 'r_factor_gt', + 'wr_factor_gt', + 'prof_r_factor', + 'prof_wr_factor', + 'prof_wr_expected', + 'profile_function', + 'background_function', +) +_PUBLICATION_JOURNAL_FIELDS = ( + 'name_full', + 'year', + 'volume', + 'issue', + 'page_first', + 'page_last', + 'paper_category', + 'paper_doi', + 'coden_astm', + 'suppl_publ_number', +) +_PUBLICATION_JOURNAL_DATE_FIELDS = ( + 'accepted', + 'from_coeditor', + 'printers_final', +) +_PUBLICATION_JOURNAL_COEDITOR_FIELDS = ( + 'code', + 'name', + 'notes', +) +_PUBLICATION_CONTACT_AUTHOR_FIELDS = ( + 'name', + 'address', + 'email', + 'phone', + 'id_orcid', + 'id_iucr', +) +_PUBLICATION_BODY_FIELDS = ( + 'title', + 'synopsis', + 'abstract', +) +_PUBLICATION_AUTHOR_FIELDS = ( + 'name', + 'address', + 'footnote', + 'id_orcid', + 'id_iucr', +) + + +class ReportDataContext: + """ + Build renderer-neutral report data from a project. + + Parameters + ---------- + project : object + Project facade that owns structures, experiments, analysis, and + publication metadata. + """ + + def __init__(self, project: object) -> None: + self._project = project + + def build(self) -> dict[str, object]: + """ + Return the full report data context. + + Returns + ------- + dict[str, object] + Renderer-neutral data consumed by terminal, HTML, TeX, and + GUI report surfaces. + """ + project = self._project + structures = list(_collection_values(_safe_attr(project, 'structures'))) + experiments = list(_collection_values(_safe_attr(project, 'experiments'))) + return { + 'project': self._project_context(structures, experiments), + 'structures': [self._structure_context(structure) for structure in structures], + 'experiments': [ + self._experiment_context(experiment) for experiment in experiments + ], + 'refinement': self._refinement_context(), + 'software': self._software_context(), + 'publication': self._publication_context(), + 'figures': { + 'fit_per_experiment': {}, + }, + 'metadata': { + 'easydiffraction_version': package_version('easydiffraction'), + 'generated_at': datetime.now(tz=UTC).isoformat(timespec='seconds'), + }, + } + + def _project_context( + self, + structures: list[object], + experiments: list[object], + ) -> dict[str, object]: + """Return project metadata for report rendering.""" + info = _safe_attr(self._project, 'info') + return { + 'name': _safe_attr(self._project, 'name'), + 'title': _attr_value(info, 'title'), + 'description': _attr_value(info, 'description'), + 'n_phases': len(structures), + 'n_experiments': len(experiments), + } + + def _structure_context(self, structure: object) -> dict[str, object]: + """Return one structure summary.""" + space_group = _safe_attr(structure, 'space_group') + return { + 'id': _safe_attr(structure, 'name'), + 'space_group': _attr_value(space_group, 'name_h_m'), + 'crystal_system': _attr_value(space_group, 'crystal_system'), + 'cell': _field_values(_safe_attr(structure, 'cell'), _STRUCTURE_CELL_FIELDS), + 'atom_sites': [ + self._atom_site_context(atom_site) + for atom_site in _collection_values(_safe_attr(structure, 'atom_sites')) + ], + } + + def _atom_site_context(self, atom_site: object) -> dict[str, object]: + """Return one atom-site row.""" + return { + 'label': _attr_value(atom_site, 'label'), + 'type_symbol': _attr_value(atom_site, 'type_symbol'), + 'fract_x': _attr_value(atom_site, 'fract_x'), + 'fract_y': _attr_value(atom_site, 'fract_y'), + 'fract_z': _attr_value(atom_site, 'fract_z'), + 'occupancy': _attr_value(atom_site, 'occupancy'), + 'adp_type': _attr_value(atom_site, 'adp_type'), + 'adp_iso': _attr_value(atom_site, 'adp_iso'), + } + + def _experiment_context(self, experiment: object) -> dict[str, object]: + """Return one experiment summary.""" + calculator = _safe_attr(experiment, 'calculator') + return { + 'id': _safe_attr(experiment, 'name'), + 'type': _field_values( + _safe_attr(experiment, 'type'), + _EXPERIMENT_TYPE_FIELDS, + ), + 'calculator': { + 'type': _attr_value(calculator, 'type'), + }, + 'diffrn': { + 'ambient_temperature': _attr_value( + _safe_attr(experiment, 'diffrn'), + 'ambient_temperature', + ), + 'ambient_pressure': _attr_value( + _safe_attr(experiment, 'diffrn'), + 'ambient_pressure', + ), + }, + 'measured_range': _value(_safe_attr(experiment, 'measured_range')), + } + + def _refinement_context(self) -> dict[str, object]: + """Return refinement summary data.""" + analysis = _safe_attr(self._project, 'analysis') + fit_result = _safe_attr(analysis, 'fit_result') + fields = _field_values(fit_result, _FIT_RESULT_FIELDS) + total = fields.get('n_parameters') + free = fields.get('n_free_parameters') + fixed = total - free if isinstance(total, int) and isinstance(free, int) else None + return { + 'fit_result': fields, + 'parameters': { + 'total': total, + 'free': free, + 'fixed': fixed, + }, + 'constraints': len(list(_collection_values(_safe_attr(analysis, 'constraints')))), + } + + def _software_context(self) -> dict[str, object]: + """Return analysis software-provenance data.""" + analysis = _safe_attr(self._project, 'analysis') + software = _safe_attr(analysis, 'software') + return { + 'framework': _software_role_context(_safe_attr(software, 'framework')), + 'calculator': _software_role_context(_safe_attr(software, 'calculator')), + 'minimizer': _software_role_context(_safe_attr(software, 'minimizer')), + 'fit_datetime': _attr_value(software, 'timestamp'), + } + + def _publication_context(self) -> dict[str, object]: + """Return journal-publication metadata.""" + publication = _safe_attr(self._project, 'publication') + body = _safe_attr(publication, 'body') + return { + 'journal': _field_values( + _safe_attr(publication, 'journal'), + _PUBLICATION_JOURNAL_FIELDS, + ), + 'journal_date': _field_values( + _safe_attr(publication, 'journal_date'), + _PUBLICATION_JOURNAL_DATE_FIELDS, + ), + 'journal_coeditor': _field_values( + _safe_attr(publication, 'journal_coeditor'), + _PUBLICATION_JOURNAL_COEDITOR_FIELDS, + ), + 'contact_author': _field_values( + _safe_attr(publication, 'contact_author'), + _PUBLICATION_CONTACT_AUTHOR_FIELDS, + ), + 'body': { + **_field_values(body, _PUBLICATION_BODY_FIELDS), + 'keywords': list(_safe_attr(body, 'keywords') or []), + }, + 'authors': [ + _field_values(author, _PUBLICATION_AUTHOR_FIELDS) + for author in _collection_values(_safe_attr(publication, 'authors')) + ], + } + + +def build_report_data_context(project: object) -> dict[str, object]: + """ + Build renderer-neutral report data from a project. + + Parameters + ---------- + project : object + Project facade to summarize. + + Returns + ------- + dict[str, object] + Data context shared by all report renderers. + """ + return ReportDataContext(project).build() + + +def _safe_attr(owner: object, attr_name: str) -> object: + """Return a public attribute without triggering diagnostics.""" + if owner is None: + return None + public_attrs = getattr(type(owner), '_public_attrs', None) + if callable(public_attrs) and attr_name not in public_attrs(): + return None + return getattr(owner, attr_name, None) + + +def _value(value: object) -> object: + """Return descriptor values, otherwise return the object itself.""" + return getattr(value, 'value', value) + + +def _attr_value(owner: object, attr_name: str) -> object: + """Return one public attribute value.""" + return _value(_safe_attr(owner, attr_name)) + + +def _field_values(owner: object, fields: tuple[str, ...]) -> dict[str, object]: + """Return descriptor values for a fixed field list.""" + return {field: _attr_value(owner, field) for field in fields} + + +def _software_role_context(role: object) -> dict[str, object]: + """Return one software role context.""" + return { + 'name': _attr_value(role, 'name'), + 'version': _attr_value(role, 'version'), + 'url': _attr_value(role, 'url'), + } + + +def _collection_values(collection: object) -> Iterable[object]: + """Return collection values for project containers.""" + if collection is None: + return () + values = getattr(collection, 'values', None) + if callable(values): + return values() + if isinstance(collection, Iterable) and not isinstance(collection, str): + return collection + return (collection,) diff --git a/src/easydiffraction/report/templates/base.j2 b/src/easydiffraction/report/templates/base.j2 new file mode 100644 index 000000000..7492315df --- /dev/null +++ b/src/easydiffraction/report/templates/base.j2 @@ -0,0 +1,26 @@ +{# Shared report-rendering helpers. #} + +{% macro format_number(value, digits=6) -%} +{%- if value is number -%} +{{- ("%.{}g".format(digits)) | format(value) -}} +{%- elif value is none -%} +{%- else -%} +{{- value -}} +{%- endif -%} +{%- endmacro %} + +{% macro format_uncertainty(uncertainty, digits=2) -%} +{%- if uncertainty is number -%} +{{- ("%.{}g".format(digits)) | format(uncertainty) -}} +{%- elif uncertainty is none or uncertainty == "" -%} +{%- else -%} +{{- uncertainty -}} +{%- endif -%} +{%- endmacro %} + +{% macro parameter_row(label, value, uncertainty=none, unit=none, separator=" | ") -%} +{{- label -}}{{ separator -}} +{{- format_number(value) -}}{{ separator -}} +{{- format_uncertainty(uncertainty) -}} +{%- if unit -%}{{ separator }}{{ unit }}{%- endif -%} +{%- endmacro %} From 89d67210b70df477e901d03be5da95aa3a4adb25 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 21:52:13 +0200 Subject: [PATCH 019/129] Add HTML renderer with CDN and offline Plotly modes --- docs/dev/plans/project-summary-rendering.md | 2 +- .../project/categories/report/default.py | 30 ++- src/easydiffraction/report/html_renderer.py | 136 +++++++++++ .../report/templates/html/report.html.j2 | 225 ++++++++++++++++++ .../report/templates/html/style.css | 205 ++++++++++++++++ 5 files changed, 591 insertions(+), 7 deletions(-) create mode 100644 src/easydiffraction/report/html_renderer.py create mode 100644 src/easydiffraction/report/templates/html/report.html.j2 create mode 100644 src/easydiffraction/report/templates/html/style.css diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index ac5618cd9..15c20739e 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -605,7 +605,7 @@ generated-artifact exceptions. foundation. - Commit: `Add ReportDataContext builder and Jinja base macros`. -- [ ] **P1.17 — HTML renderer + `save_html(offline=False)`** +- [x] **P1.17 — HTML renderer + `save_html(offline=False)`** - Files: new `src/easydiffraction/report/html_renderer.py`; new `src/easydiffraction/report/templates/html/report.html.j2`; diff --git a/src/easydiffraction/project/categories/report/default.py b/src/easydiffraction/project/categories/report/default.py index 062c7754f..9232efb5c 100644 --- a/src/easydiffraction/project/categories/report/default.py +++ b/src/easydiffraction/project/categories/report/default.py @@ -428,13 +428,31 @@ def save_html(self, offline: bool = False) -> pathlib.Path: pathlib.Path Path of the written HTML report. - Raises - ------ - NotImplementedError - Until the HTML writer lands in a later step. """ - del offline - raise NotImplementedError('lands in P1.17 / P1.19 / P1.20') + from easydiffraction.report.html_renderer import html_report_path # noqa: PLC0415 + + output_path = html_report_path(self.project) + output_path.parent.mkdir(parents=True, exist_ok=True) + output_path.write_text(self.as_html(offline=offline), encoding='utf-8') + return output_path + + def as_html(self, offline: bool = False) -> str: + """ + Render the HTML report. + + Parameters + ---------- + offline : bool, default=False + Whether to embed Plotly JavaScript assets. + + Returns + ------- + str + Complete HTML report document. + """ + from easydiffraction.report.html_renderer import render_html_report # noqa: PLC0415 + + return render_html_report(self.data_context(), offline=offline) def save_tex(self, style: str = 'iucr') -> pathlib.Path: """ diff --git a/src/easydiffraction/report/html_renderer.py b/src/easydiffraction/report/html_renderer.py new file mode 100644 index 000000000..7e40fd81f --- /dev/null +++ b/src/easydiffraction/report/html_renderer.py @@ -0,0 +1,136 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Render project reports as HTML.""" + +from __future__ import annotations + +import pathlib +from importlib.resources import files + +from jinja2 import Environment +from jinja2 import PackageLoader + +_TEMPLATE_NAME = 'html/report.html.j2' + + +def html_report_path( + project: object, + path: str | pathlib.Path | None = None, +) -> pathlib.Path: + """ + Return the target HTML report path for a project. + + Parameters + ---------- + project : object + Project instance. + path : str | pathlib.Path | None, default=None + Explicit report path. + + Returns + ------- + pathlib.Path + Resolved report path. + + Raises + ------ + FileNotFoundError + If no path is supplied and the project has not been saved. + """ + if path is not None: + return pathlib.Path(path) + + project_path = getattr(getattr(project, 'info', None), 'path', None) + if project_path is None: + msg = 'Project has no saved path. Save the project first.' + raise FileNotFoundError(msg) + + project_name = getattr(project, 'name', 'project') + return pathlib.Path(project_path) / 'reports' / f'{project_name}.html' + + +def render_html_report( + context: dict[str, object], + *, + offline: bool = False, +) -> str: + """ + Render a report data context as HTML. + + Parameters + ---------- + context : dict[str, object] + Data returned by ``Report.data_context()``. + offline : bool, default=False + Whether Plotly figures should embed JavaScript assets. + + Returns + ------- + str + Complete HTML document. + """ + template_context = dict(context) + template_context['stylesheet'] = _stylesheet_text() + template_context['figures'] = _figure_html_context( + context.get('figures'), + offline=offline, + ) + return _environment().get_template(_TEMPLATE_NAME).render(**template_context) + + +def _environment() -> Environment: + """Return the Jinja environment for report templates.""" + return Environment( + loader=PackageLoader('easydiffraction.report', 'templates'), + autoescape=True, + trim_blocks=True, + lstrip_blocks=True, + ) + + +def _stylesheet_text() -> str: + """Return the embedded report stylesheet.""" + stylesheet = files('easydiffraction.report').joinpath( + 'templates', + 'html', + 'style.css', + ) + return stylesheet.read_text(encoding='utf-8') + + +def _figure_html_context( + figures: object, + *, + offline: bool, +) -> dict[str, object]: + """Return HTML snippets for figure slots.""" + if not isinstance(figures, dict): + return {'fit_per_experiment': {}} + + return { + 'fit_per_experiment': _figure_map_html( + figures.get('fit_per_experiment'), + offline=offline, + ), + } + + +def _figure_map_html(figures: object, *, offline: bool) -> dict[str, str]: + """Convert one figure mapping into HTML snippets.""" + if not isinstance(figures, dict): + return {} + + include_plotlyjs: bool | str = True if offline else 'cdn' + rendered: dict[str, str] = {} + for key, figure in figures.items(): + rendered[str(key)] = _figure_html(figure, include_plotlyjs=include_plotlyjs) + include_plotlyjs = False + return rendered + + +def _figure_html(figure: object, *, include_plotlyjs: bool | str) -> str: + """Return an HTML snippet for one figure-like object.""" + to_html = getattr(figure, 'to_html', None) + if callable(to_html): + return to_html(full_html=False, include_plotlyjs=include_plotlyjs) + return str(figure) diff --git a/src/easydiffraction/report/templates/html/report.html.j2 b/src/easydiffraction/report/templates/html/report.html.j2 new file mode 100644 index 000000000..057e52fa1 --- /dev/null +++ b/src/easydiffraction/report/templates/html/report.html.j2 @@ -0,0 +1,225 @@ +{% import "base.j2" as base %} +{% macro metadata_table(title, values) -%} + +{%- endmacro %} + + + + + + {{ project.title or project.name or "EasyDiffraction report" }} + + + +
+
+

EasyDiffraction report

+

{{ project.title or project.name or "Untitled project" }}

+ {% if project.description %} +

{{ project.description }}

+ {% endif %} +
+
+
Phases
+
{{ project.n_phases }}
+
+
+
Experiments
+
{{ project.n_experiments }}
+
+
+
Generated
+
{{ metadata.generated_at }}
+
+
+
Version
+
{{ metadata.easydiffraction_version or "not available" }}
+
+
+
+ +
+

Refinement

+
+
+ Reduced chi-square + {{ base.format_number(refinement.fit_result.reduced_chi_square) }} +
+
+ Parameters + + {{ refinement.parameters.free or 0 }} / + {{ refinement.parameters.total or 0 }} + +
+
+ Constraints + {{ refinement.constraints }} +
+
+ + + {% for key, value in refinement.fit_result.items() if value is not none %} + + + + + {% endfor %} + +
{{ key | replace("_", " ") }}{{ value }}
+
+ +
+

Software

+
+ {% for role_name, role in software.items() if role_name != "fit_datetime" %} +
+

{{ role_name | replace("_", " ") }}

+

{{ role.name or "not available" }}

+ {% if role.version %}

Version {{ role.version }}

{% endif %} + {% if role.url %}

{{ role.url }}

{% endif %} +
+ {% endfor %} +
+ {% if software.fit_datetime %} +

Fit timestamp: {{ software.fit_datetime }}

+ {% endif %} +
+ +
+

Publication

+
+ {{ metadata_table("Journal", publication.journal) }} + {{ metadata_table("Contact author", publication.contact_author) }} + {{ metadata_table("Body", publication.body) }} +
+ {% if publication.authors %} +

Authors

+ + + + + + + + + + {% for author in publication.authors %} + + + + + + {% endfor %} + +
NameORCIDIUCr
{{ author.name }}{{ author.id_orcid or "" }}{{ author.id_iucr or "" }}
+ {% endif %} +
+ +
+

Structures

+ {% for structure in structures %} +
+
+

{{ structure.id }}

+

{{ structure.space_group or "space group not available" }}

+
+ + + {% for key, value in structure.cell.items() %} + + + + + {% endfor %} + +
{{ key | replace("_", " ") }}{{ base.format_number(value) }}
+ {% if structure.atom_sites %} +
+ + + + + + + + + + + + + + {% for atom in structure.atom_sites %} + + + + + + + + + + {% endfor %} + +
LabelTypexyzOcc.ADP
{{ atom.label }}{{ atom.type_symbol }}{{ base.format_number(atom.fract_x) }}{{ base.format_number(atom.fract_y) }}{{ base.format_number(atom.fract_z) }}{{ base.format_number(atom.occupancy) }}{{ base.format_number(atom.adp_iso) }}
+
+ {% endif %} +
+ {% endfor %} +
+ +
+

Experiments

+ {% for experiment in experiments %} +
+
+

{{ experiment.id }}

+

+ {{ experiment.type.sample_form or "sample form not available" }}, + {{ experiment.type.radiation_probe or "probe not available" }}, + {{ experiment.type.beam_mode or "beam mode not available" }} +

+
+ + + + + + + + + + + + + + + +
Calculator{{ experiment.calculator.type or "not available" }}
Temperature{{ base.format_number(experiment.diffrn.ambient_temperature) }}
Pressure{{ base.format_number(experiment.diffrn.ambient_pressure) }}
+ {% if figures.fit_per_experiment[experiment.id] %} +
{{ figures.fit_per_experiment[experiment.id] | safe }}
+ {% endif %} +
+ {% endfor %} +
+
+ + diff --git a/src/easydiffraction/report/templates/html/style.css b/src/easydiffraction/report/templates/html/style.css new file mode 100644 index 000000000..d5cae344d --- /dev/null +++ b/src/easydiffraction/report/templates/html/style.css @@ -0,0 +1,205 @@ +:root { + color-scheme: light; + --bg: #f7f8f6; + --surface: #ffffff; + --ink: #1c2430; + --muted: #657182; + --rule: #d8ded6; + --accent: #1d6f68; + --accent-strong: #124d49; + --table: #eef3f1; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + background: var(--bg); + color: var(--ink); + font: 16px/1.55 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; +} + +.report-shell { + width: min(1120px, calc(100% - 32px)); + margin: 0 auto; + padding: 32px 0 56px; +} + +.report-header { + padding: 28px 0 20px; + border-bottom: 2px solid var(--accent); +} + +.eyebrow { + margin: 0 0 8px; + color: var(--accent-strong); + font-size: 0.78rem; + font-weight: 700; + letter-spacing: 0; + text-transform: uppercase; +} + +h1, +h2, +h3, +p { + margin-top: 0; +} + +h1 { + max-width: 820px; + margin-bottom: 14px; + font-size: 2.25rem; + line-height: 1.1; +} + +h2 { + margin-bottom: 16px; + font-size: 1.35rem; +} + +h3 { + margin-bottom: 8px; + font-size: 1rem; +} + +.lede { + max-width: 780px; + color: var(--muted); +} + +section { + padding: 28px 0; + border-bottom: 1px solid var(--rule); +} + +.summary-grid, +.metrics, +.software-grid, +.publication-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 12px; +} + +.summary-grid { + margin: 24px 0 0; +} + +.summary-grid div, +.metrics div, +.software-grid article, +.metadata-card, +.record { + border: 1px solid var(--rule); + border-radius: 8px; + background: var(--surface); +} + +.summary-grid div, +.metrics div { + padding: 14px 16px; +} + +.summary-grid dt, +.metrics span { + color: var(--muted); + font-size: 0.82rem; +} + +.summary-grid dd { + margin: 4px 0 0; + font-size: 1.2rem; + font-weight: 700; +} + +.metrics strong { + display: block; + margin-top: 4px; + font-size: 1.2rem; +} + +.software-grid article, +.metadata-card, +.record { + padding: 16px; +} + +.record + .record { + margin-top: 16px; +} + +.record header { + display: flex; + flex-wrap: wrap; + align-items: baseline; + justify-content: space-between; + gap: 8px 18px; +} + +.muted, +.record header p { + color: var(--muted); +} + +table { + width: 100%; + border-collapse: collapse; + margin-top: 12px; + font-size: 0.93rem; +} + +th, +td { + padding: 8px 10px; + border-bottom: 1px solid var(--rule); + text-align: left; + vertical-align: top; +} + +th { + width: 34%; + background: var(--table); + color: var(--accent-strong); + font-weight: 650; +} + +tbody tr:last-child th, +tbody tr:last-child td { + border-bottom: 0; +} + +.table-scroll { + overflow-x: auto; +} + +.figure { + margin-top: 16px; + min-height: 280px; +} + +@media (max-width: 640px) { + body { + font-size: 15px; + } + + .report-shell { + width: min(100% - 20px, 1120px); + padding-top: 18px; + } + + h1 { + font-size: 1.75rem; + } + + .record header { + display: block; + } + + th, + td { + padding: 7px 8px; + } +} From bc6eae39109d420e61396caf8dc00e424d56172d Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 22:13:23 +0200 Subject: [PATCH 020/129] Vendor LaTeX styles with licenses and add report deps --- THIRD_PARTY_LICENSES.md | 23 + .../suggestions/project-summary-rendering.md | 31 +- docs/dev/plans/project-summary-rendering.md | 20 +- pixi.lock | 1195 ++- pixi.toml | 1 + pyproject.toml | 1 + .../report/templates/tex/styles/LICENSES.md | 568 ++ .../templates/tex/styles/aps10pt4-2.rtx | 186 + .../templates/tex/styles/aps11pt4-2.rtx | 178 + .../templates/tex/styles/aps12pt4-2.rtx | 178 + .../report/templates/tex/styles/aps4-2.rtx | 678 ++ .../report/templates/tex/styles/harvard.sty | 270 + .../templates/tex/styles/iucrjournals.cls | 385 + .../report/templates/tex/styles/ltxdocext.sty | 296 + .../report/templates/tex/styles/ltxfront.sty | 1160 +++ .../report/templates/tex/styles/ltxgrid.sty | 2754 ++++++ .../report/templates/tex/styles/ltxutil.sty | 2108 +++++ .../templates/tex/styles/revsymb4-2.sty | 167 + .../report/templates/tex/styles/revtex4-2.cls | 7681 +++++++++++++++++ 19 files changed, 17840 insertions(+), 40 deletions(-) create mode 100644 THIRD_PARTY_LICENSES.md create mode 100644 src/easydiffraction/report/templates/tex/styles/LICENSES.md create mode 100644 src/easydiffraction/report/templates/tex/styles/aps10pt4-2.rtx create mode 100644 src/easydiffraction/report/templates/tex/styles/aps11pt4-2.rtx create mode 100644 src/easydiffraction/report/templates/tex/styles/aps12pt4-2.rtx create mode 100644 src/easydiffraction/report/templates/tex/styles/aps4-2.rtx create mode 100644 src/easydiffraction/report/templates/tex/styles/harvard.sty create mode 100644 src/easydiffraction/report/templates/tex/styles/iucrjournals.cls create mode 100644 src/easydiffraction/report/templates/tex/styles/ltxdocext.sty create mode 100644 src/easydiffraction/report/templates/tex/styles/ltxfront.sty create mode 100644 src/easydiffraction/report/templates/tex/styles/ltxgrid.sty create mode 100644 src/easydiffraction/report/templates/tex/styles/ltxutil.sty create mode 100644 src/easydiffraction/report/templates/tex/styles/revsymb4-2.sty create mode 100644 src/easydiffraction/report/templates/tex/styles/revtex4-2.cls diff --git a/THIRD_PARTY_LICENSES.md b/THIRD_PARTY_LICENSES.md new file mode 100644 index 000000000..8cc374aa1 --- /dev/null +++ b/THIRD_PARTY_LICENSES.md @@ -0,0 +1,23 @@ +# Third-Party Licenses + +This file indexes third-party assets vendored in this repository. + +## Report LaTeX Styles + +The full license texts and attribution details are in +`src/easydiffraction/report/templates/tex/styles/LICENSES.md`. + +| File | Source | License | +| --- | --- | --- | +| `src/easydiffraction/report/templates/tex/styles/iucrjournals.cls` | IUCr LaTeX template | CC0-1.0 | +| `src/easydiffraction/report/templates/tex/styles/harvard.sty` | IUCr LaTeX template | CC0-1.0 | +| `src/easydiffraction/report/templates/tex/styles/revtex4-2.cls` | REVTeX 4.2f | LPPL-1.3c | +| `src/easydiffraction/report/templates/tex/styles/ltxgrid.sty` | REVTeX 4.2f | LPPL-1.3c | +| `src/easydiffraction/report/templates/tex/styles/ltxutil.sty` | REVTeX 4.2f | LPPL-1.3c | +| `src/easydiffraction/report/templates/tex/styles/ltxfront.sty` | REVTeX 4.2f | LPPL-1.3c | +| `src/easydiffraction/report/templates/tex/styles/ltxdocext.sty` | REVTeX 4.2f | LPPL-1.3c | +| `src/easydiffraction/report/templates/tex/styles/revsymb4-2.sty` | REVTeX 4.2f | LPPL-1.3c | +| `src/easydiffraction/report/templates/tex/styles/aps4-2.rtx` | REVTeX 4.2f | LPPL-1.3c | +| `src/easydiffraction/report/templates/tex/styles/aps10pt4-2.rtx` | REVTeX 4.2f | LPPL-1.3c | +| `src/easydiffraction/report/templates/tex/styles/aps11pt4-2.rtx` | REVTeX 4.2f | LPPL-1.3c | +| `src/easydiffraction/report/templates/tex/styles/aps12pt4-2.rtx` | REVTeX 4.2f | LPPL-1.3c | diff --git a/docs/dev/adrs/suggestions/project-summary-rendering.md b/docs/dev/adrs/suggestions/project-summary-rendering.md index c4bb0b4c5..b9eeda9c6 100644 --- a/docs/dev/adrs/suggestions/project-summary-rendering.md +++ b/docs/dev/adrs/suggestions/project-summary-rendering.md @@ -991,34 +991,38 @@ to rendering-stack quirks, not data choices. closes naturally as Chrome/Chromium becomes near-universal. - v1's ~30 MB footprint is genuinely smaller than v0.2's ~80 MB bundled-Chromium build. -- The runtime browser requirement is solved cleanly via - conda-forge: `pixi add chromium` (or `conda install -c conda-forge chromium`) - on machines that don't already have a browser. Same pattern as - the LaTeX-engine install in §3.4. +- The runtime browser requirement is a host prerequisite rather + than a project-level Pixi dependency: conda-forge does not + provide a `chromium` package for the workspace's supported + platforms. Machines without Chrome/Chromium get a clear install + hint from the report path. This differs from the LaTeX-engine + install in §3.4, where `tectonic` is available on conda-forge. Practical install matrix: | Environment | kaleido v1 install | Browser already present? | Extra step | | ------------------------ | ---------------------- | ------------------------ | ---------------------------- | | Developer laptop | `pip install kaleido` | Yes (Chrome/Edge/Safari) | None | -| `pixi` dev shell | added to `pixi.toml` | Add `chromium` to pixi | One line in `pixi.toml` | -| CI runner (GitHub, etc.) | added to `pixi.toml` | Install via pixi/conda | Already managed by pixi.lock | +| `pixi` dev shell | added through editable install | System browser required | Install Chrome/Chromium if absent | +| CI runner (GitHub, etc.) | added through editable install | Runner browser required | Install Chrome/Chromium in CI image | | Bare HPC node | `pip install kaleido` | Usually no | Install Chromium separately | **Dependencies named by this ADR.** The implementation plan must -name three dependencies before any `/draft-impl-1` or +name two dependencies before any `/draft-impl-1` or `/draft-impl-2` invocation edits `pyproject.toml`, `pixi.toml`, or `pixi.lock`: - `kaleido` (v1.0+) — Python package, runtime dependency for rasterising Plotly figures to vector PDF for LaTeX inclusion. -- `chromium` — pixi/conda package, head-of-Plotly's static-image - pipeline. Goes into the project's dev/docs feature group so - `pixi run script-tests` can validate end-to-end PDF figure - generation. - `tectonic` — pixi/conda package, lightweight TeX engine for §3.4 PDF compilation in the project dev environment. +`chromium` is deliberately not a dependency: conda-forge has no +package with that name for the supported workspace platforms. The +implementation treats Chrome/Chromium as a host-level Kaleido +prerequisite and reports a clear install hint when static-image +export cannot find a browser. + Per AGENTS.md §Architecture, "an accepted plan that **names the specific dependency** … combined with the user invoking `/draft-impl-1` or `/draft-impl-2` for that plan … counts as @@ -1566,8 +1570,9 @@ beyond what already exists. rendering toolkits drifting on data choices. v1 relies on the host browser for rendering; the project's `pixi.toml` adds - `chromium` alongside `tectonic` to its dev/docs feature so CI - has a known-good Chromium. End users on machines without a + `tectonic`, while Chrome/Chromium remains a host-level + prerequisite because conda-forge has no `chromium` package for + the workspace platforms. End users on machines without a browser get a clear install hint at first use. - PDF compilation is opportunistic — works when `tectonic`, `latexmk`, or `pdflatex` is on `PATH`; otherwise the `.tex` and diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 15c20739e..bb6992fb2 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -105,8 +105,12 @@ re-litigate them, only implements them: --style iucr` is the one-off subcommand. `ed save-report` with no flags raises a clear error. - **Dependencies named by the ADR** (§3.2, §3.4): `kaleido` - (Python, runtime, `pyproject.toml`), `chromium` (pixi/conda, - dev/docs feature), `tectonic` (pixi/conda, dev/docs feature). + (Python, runtime, `pyproject.toml`) and `tectonic` + (pixi/conda, dev/docs feature). `chromium` was investigated + during implementation but is not added because conda-forge + provides no package for the workspace platforms; Kaleido uses a + host browser and the report path raises a clear install hint + when one is missing. Per AGENTS.md §Architecture, naming them in this accepted plan plus a `/draft-impl-1` invocation counts as pre-approval for the implementation steps to edit @@ -221,7 +225,7 @@ re-litigate them, only implements them: **LaTeX + PDF (P1.18–P1.20):** - `pyproject.toml` (add `kaleido`). -- `pixi.toml` (add `chromium`, `tectonic` to dev/docs feature). +- `pixi.toml` (add `tectonic` to dev/docs feature). - `pixi.lock` (regenerated). - `src/easydiffraction/report/templates/tex/iucr.tex.j2` (new). - `src/easydiffraction/report/templates/tex/revtex.tex.j2` (new). @@ -621,7 +625,7 @@ generated-artifact exceptions. already declared. - Commit: `Add HTML renderer with CDN and offline Plotly modes`. -- [ ] **P1.18 — Vendor LaTeX styles + add `kaleido` / `chromium` / `tectonic` deps** +- [x] **P1.18 — Vendor LaTeX styles + add `kaleido` / `tectonic` deps** - Files: `pyproject.toml`, `pixi.toml`, `pixi.lock`; new `src/easydiffraction/report/templates/tex/styles/` (12 files: `iucrjournals.cls`, `harvard.sty`, `revtex4-2.cls`, @@ -633,8 +637,10 @@ generated-artifact exceptions. `THIRD_PARTY_LICENSES.md` at repository root. - Add `kaleido` (v1.0+) to the runtime dependency list in `pyproject.toml`. - - Add `chromium` and `tectonic` to the dev/docs feature - group in `pixi.toml`. + - Add `tectonic` to the dev/docs feature group in + `pixi.toml`. Do not add `chromium`: `pixi search chromium` + found no conda-forge package for the workspace platforms, so + the browser remains a host-level Kaleido prerequisite. - Regenerate `pixi.lock`. - Copy the 12 upstream style files into the vendored directory. @@ -682,7 +688,7 @@ generated-artifact exceptions. re-verify. Do **not** add `[tool.setuptools.package-data]` — this project does not use setuptools. - - Commit: `Vendor LaTeX styles with licenses and add kaleido/chromium/tectonic deps`. + - Commit: `Vendor LaTeX styles with licenses and add report deps`. - [ ] **P1.19 — LaTeX renderer + `save_tex(style='iucr')`** - Files: new diff --git a/pixi.lock b/pixi.lock index d69e22de0..7ed59343e 100644 --- a/pixi.lock +++ b/pixi.lock @@ -17,9 +17,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py314h3de4e8d_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-he90730b_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py314h4a8dc5f_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py314h42812f9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.18.0-h27c8c51_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.3-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.14-hecca717_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gsl-2.8-hbf7d49c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-14.2.0-h6083320_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda @@ -34,36 +39,54 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.3-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.3-h73754d4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.88.1-h0d30a3d_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.33-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.58-h421ea60_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.22-h280c20c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.1-h0c1763c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.1-h5347b49_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.52.1-h280c20c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py314h67df5f8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py314h5bd0f2a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-26.2.0-he4ff34a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py314h0f05182_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.5-habeac84_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py314h2e6c369_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tectonic-0.16.9-ha39f199_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py314h5bd0f2a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.13-he1eb515_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.7-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h09e67af_11.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda @@ -88,6 +111,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda @@ -220,6 +249,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/46/b4/0887c88ddfaba1d7140ea335144eb904af97550786ee58bdb295ff10d255/crysfml-0.6.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/6aa79ba3570bddd1bf7e951c6123f806751e58e8cce736bad77b2cf348d7/logistro-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/58/e0/f1871f520c359e4e3a2eb7437c9e7e792bb6c356414e8617937561167caf/pycifrw-5.0.1.tar.gz - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl @@ -230,9 +260,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/dc/0decaf5da92a7a969374474025787102d811d42aed1d32191fa338620e15/python_socketio-5.16.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/3e/c0b690253f0b82d86e99949af13533363acfb5432ecb5d53dd5b3bce9c34/orjson-3.11.9-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/96/4efd6fa5c62c85426a0c19077a586258ebc3a2a146ff2493e4312a697a22/greenlet-3.5.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz + - pypi: https://files.pythonhosted.org/packages/78/91/3635cdb13318cb0a328abaa69e2b91251caad39d6779aa308098f341f6cb/simplejson-4.1.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-py3-none-any.whl @@ -257,6 +289,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/b9/a6d8bb7d228940f01885bd9f327ab7f9d366a9be775c4bf366bf9d9477ae/kaleido-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl @@ -276,6 +309,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/28/180bfc5c95e83d40cb2abce512684ccad44e4819ec899fc36cb404a19061/python_engineio-4.13.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ba/6c/ff8bf52315064dbeb55cb5067e191120a5b2e58bb648d0d34cf7969dc2c2/choreographer-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/c9/989f4034fb46841208de7aeeac2c6d8300745ab4f28c42f629ba77c2d916/aiohttp-3.13.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl @@ -341,6 +375,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda @@ -430,9 +470,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py314h3daef5d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cairo-1.18.4-he0f2337_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py314h44086f9_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py314he609de1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fontconfig-2.18.0-h2b252f5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/freetype-2.14.3-hce30654_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/graphite2-1.3.14-hec049ff_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gsl-2.8-h8d0574d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/harfbuzz-14.2.0-h3103d1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h385eeb1_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda @@ -446,13 +491,19 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.1-hf6b4638_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype-2.14.3-hce30654_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype6-2.14.3-hdfa99f5_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_19.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_19.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libglib-2.88.1-ha08bb59_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libintl-0.25.1-h493aca8_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.33-openmp_he657e61_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpng-1.6.58-h132b30e_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.22-h1a92334_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.1-h1b79a29_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.52.1-h1a92334_0.conda @@ -463,6 +514,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-26.2.0-h7039424_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.2-hd24854e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.47-h30297fc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pixman-0.46.4-h81086ad_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py314ha14b1ff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py314h3a4d195_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py314h36abed7_0.conda @@ -471,10 +524,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py314haad56a0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tectonic-0.16.9-hab3abff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py314h6c2aa35_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h10816f8_11.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: . - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -521,6 +576,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/51/ac/b9d68ebddfe1b02c77af5bf81120e12b036b4432dc6af7a303d90e2bc38b/chardet-7.4.3-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/6aa79ba3570bddd1bf7e951c6123f806751e58e8cce736bad77b2cf348d7/logistro-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/9e/d13e40f83b8d0a94430e6778ce1d94a43b38cf2efe63278bdd2b4c65abbf/ruff-0.15.14-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/58/e0/f1871f520c359e4e3a2eb7437c9e7e792bb6c356414e8617937561167caf/pycifrw-5.0.1.tar.gz - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl @@ -531,6 +587,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/b1/4260d67d6bd85e58a66b72d54ce15d5de789b6f3870cc6bedf8ff9667401/propcache-0.5.2-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/68/10/bf2d6738d72748b961a3751ab89522d58c54efc36a8e1a12161216cd45cf/pandas-3.0.3-cp314-cp314-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/68/31/9a5432c433a7671107182cdc9a20ea78a70f99c4e5334aa54b6d4d0d79ed/simplejson-4.1.1-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/6a/b7/9366ed44ced9b7ef357ab48c94205280276db9d7f064aa3012a97227e966/h5py-3.16.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl @@ -549,6 +606,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/5a/7fd1b784a87e96e0078f49a0a13a98b4c5f644ba5597a4a3b70a2ba3e613/py3dmol-2.5.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/eb/5da01e356015aee6ecfa1187ced87aef51364e306f5e695dd52719bf0e78/orjson-3.11.9-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl @@ -561,6 +619,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/91/cc8cc78a111826c54743d88651e1687008133c37e5ee615fee9b57990fac/aiohttp-3.13.5-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/b9/a6d8bb7d228940f01885bd9f327ab7f9d366a9be775c4bf366bf9d9477ae/kaleido-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl @@ -580,6 +639,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/28/180bfc5c95e83d40cb2abce512684ccad44e4819ec899fc36cb404a19061/python_engineio-4.13.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ba/6c/ff8bf52315064dbeb55cb5067e191120a5b2e58bb648d0d34cf7969dc2c2/choreographer-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ba/b1/5297bb6a7df4782f7605bffc43b31f5044070935fbbcaa6c705a07e6ac65/yarl-1.24.2-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl @@ -644,6 +704,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda @@ -730,23 +796,34 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py314h5a2d7ad_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py314he701e3d_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cairo-1.18.4-h477c42c_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py314h5a2d7ad_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py314hb98de8c_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/fontconfig-2.18.0-hd47e2ca_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/freetype-2.14.3-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/graphite2-1.3.14-hac47afa_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/gsl-2.8-h5b8d9c4_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/harfbuzz-14.2.0-h5a1b470_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/icu-78.3-h637d24d_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-7_h8455456_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-7_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.1-hac47afa_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libfreetype-2.14.3-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libfreetype6-2.14.3-hdbac1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libglib-2.88.1-h7ce1215_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.13.0-default_h049141e_1000.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libintl-0.22.5-h5728263_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.58-h7351971_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.22-h6a83c73_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.1-hf5d6505_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.3-h692994f_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.3-hbc0d294_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.3-h3cfd58e_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.3-h8ef44ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.6-h4fa8253_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.3-py314h2359020_1.conda @@ -755,6 +832,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-26.2.0-h80d1838_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/onemkl-license-2026.0.0-h57928b3_908.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.2-hf411b9b_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pcre2-10.47-hd2b5f0e_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pixman-0.46.4-h5112557_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py314hc5dbbe4_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.14.5-h4b44e0e_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py314hcaaf0b2_2.conda @@ -763,6 +842,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py312h343a6d4_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.30.0-py314h9f07db2_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2023.0.0-hd3d4ead_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tectonic-0.16.9-h18ecd4e_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py314h5a2d7ad_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda @@ -772,6 +852,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/winpty-0.4.3-4.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h3a581c9_11.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: . - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -784,6 +865,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/72/ec1b5cbdcb140c132e6c7bdf99bd73e4f675439e77126c88f472fcffa09c/simplejson-4.1.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1d/77/928ea2e70641ca177a11140062cc5840d421795f2e82749d408d0cce900a/narwhals-2.21.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl @@ -815,6 +897,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/6aa79ba3570bddd1bf7e951c6123f806751e58e8cce736bad77b2cf348d7/logistro-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/58/e0/f1871f520c359e4e3a2eb7437c9e7e792bb6c356414e8617937561167caf/pycifrw-5.0.1.tar.gz - pypi: https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl @@ -856,6 +939,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/b9/a6d8bb7d228940f01885bd9f327ab7f9d366a9be775c4bf366bf9d9477ae/kaleido-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl @@ -876,6 +960,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/28/180bfc5c95e83d40cb2abce512684ccad44e4819ec899fc36cb404a19061/python_engineio-4.13.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ba/6c/ff8bf52315064dbeb55cb5067e191120a5b2e58bb648d0d34cf7969dc2c2/choreographer-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl @@ -904,6 +989,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f3/c3/0c6798456bade745c75c452342dabacce5798196483e77e643be1f53877d/orjson-3.11.9-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl @@ -930,9 +1016,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py312hdb49522_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-he90730b_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py312h460c074_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py312h8285ef7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.18.0-h27c8c51_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.3-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.14-hecca717_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gsl-2.8-hbf7d49c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-14.2.0-h6083320_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda @@ -947,20 +1038,26 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.3-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.3-h73754d4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.88.1-h0d30a3d_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.33-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.58-h421ea60_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.22-h280c20c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.1-h0c1763c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.1-h5347b49_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.52.1-h280c20c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py312h8a5da7c_1.conda @@ -968,16 +1065,28 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-26.2.0-he4ff34a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py312h5253ce2_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.13-hd63d673_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py312h8a5da7c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py312h868fb18_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tectonic-0.16.9-ha39f199_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py312h4c3975b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.13-he1eb515_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.7-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h09e67af_11.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda @@ -1001,6 +1110,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda @@ -1089,8 +1204,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0b/f9/f15c95d6b200167cb22c5eca5eecfa9d28a8ee3f74095f1cd2345c71f2f9/pydoclint-0.8.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/b6/156a8de1e1b47694f0e7de6675866936608d45dc68388fd017d36f8693be/simplejson-4.1.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/0d/12/bbce9472f489cb5c4c23b0d13e5c59c37c1aab11b7ac637dfe6bbdccebe7/copier-9.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/a4/82b7a2fe5d8a67a59ed831b24d59a3d46ea7d207b66e1602d376541d94a6/orjson-3.11.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl @@ -1129,6 +1246,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/6aa79ba3570bddd1bf7e951c6123f806751e58e8cce736bad77b2cf348d7/logistro-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/d8/8d44036d7eb7b6a8ec4c5494ea0c8c8b94fbc0ed3991c1a7adf230df03bf/aiohttp-3.13.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl @@ -1173,6 +1291,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/b9/a6d8bb7d228940f01885bd9f327ab7f9d366a9be775c4bf366bf9d9477ae/kaleido-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/e9/1a19e42cd43cc1365e127db6aae85e1c671da1d9a5d746f4d34a50edb577/h5py-3.16.0-cp312-cp312-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl @@ -1193,6 +1312,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/28/180bfc5c95e83d40cb2abce512684ccad44e4819ec899fc36cb404a19061/python_engineio-4.13.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ba/6c/ff8bf52315064dbeb55cb5067e191120a5b2e58bb648d0d34cf7969dc2c2/choreographer-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl @@ -1253,6 +1373,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda @@ -1343,9 +1469,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py312h0dfefe5_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cairo-1.18.4-he0f2337_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py312h1b4d9a2_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py312h6510ced_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fontconfig-2.18.0-h2b252f5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/freetype-2.14.3-hce30654_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/graphite2-1.3.14-hec049ff_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gsl-2.8-h8d0574d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/harfbuzz-14.2.0-h3103d1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h385eeb1_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda @@ -1359,12 +1490,18 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.1-hf6b4638_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype-2.14.3-hce30654_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype6-2.14.3-hdfa99f5_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_19.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_19.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libglib-2.88.1-ha08bb59_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libintl-0.25.1-h493aca8_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.33-openmp_he657e61_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpng-1.6.58-h132b30e_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.22-h1a92334_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.1-h1b79a29_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.52.1-h1a92334_0.conda @@ -1375,6 +1512,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-26.2.0-h7039424_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.2-hd24854e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.47-h30297fc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pixman-0.46.4-h81086ad_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py312hb3ab3e3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py312h19bbe71_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py312h1de3e18_0.conda @@ -1383,10 +1522,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py312h6ef9ec0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tectonic-0.16.9-hab3abff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py312h2bbb03f_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h10816f8_11.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: . - pypi: https://files.pythonhosted.org/packages/01/5c/87b5fefdd3c4b157c8a16833f2236723136806814584c4589610217252f0/diffpy_pdffit2-1.6.0-cp312-cp312-macosx_11_0_arm64.whl @@ -1401,6 +1542,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/1d/9f9e30d76300b0150afaa8b37fab9a0194d44fd4f6b1e5038aca4a1440ed/crysfml-0.6.2-cp312-cp312-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/16/6d/11867a3ffa3a3608d84a4de51ef4dd0896d6b5cc9132fbe1daf593e677bc/orjson-3.11.9-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl - pypi: https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1d/77/928ea2e70641ca177a11140062cc5840d421795f2e82749d408d0cce900a/narwhals-2.21.2-py3-none-any.whl @@ -1437,6 +1579,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/6aa79ba3570bddd1bf7e951c6123f806751e58e8cce736bad77b2cf348d7/logistro-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/9e/d13e40f83b8d0a94430e6778ce1d94a43b38cf2efe63278bdd2b4c65abbf/ruff-0.15.14-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl @@ -1474,6 +1617,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/9c/2f/4c5af01fd1a7506a1d5375403d68925eac70289229492db5aa68b58103d8/chardet-7.4.3-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/b9/a6d8bb7d228940f01885bd9f327ab7f9d366a9be775c4bf366bf9d9477ae/kaleido-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl @@ -1496,6 +1640,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/28/180bfc5c95e83d40cb2abce512684ccad44e4819ec899fc36cb404a19061/python_engineio-4.13.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/78/fc060d2e3b13c6ec59288574b8efac64075e316b2afba4396a56b2422f78/simplejson-4.1.1-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/ba/6c/ff8bf52315064dbeb55cb5067e191120a5b2e58bb648d0d34cf7969dc2c2/choreographer-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/be/b0/a9d19b43f38f878b1278bca5b00b909f7540d41494396dd2561f9ad0956d/sqlalchemy-2.0.50-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl @@ -1555,6 +1701,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda @@ -1642,22 +1794,33 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.5.0-py312h06d0912_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py312hc6d9e41_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cairo-1.18.4-h477c42c_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py312he06e257_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py312ha1a9051_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/fontconfig-2.18.0-hd47e2ca_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/freetype-2.14.3-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/graphite2-1.3.14-hac47afa_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/gsl-2.8-h5b8d9c4_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/harfbuzz-14.2.0-h5a1b470_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/icu-78.3-h637d24d_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-7_h8455456_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-7_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.1-hac47afa_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libfreetype-2.14.3-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libfreetype6-2.14.3-hdbac1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libglib-2.88.1-h7ce1215_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.13.0-default_h049141e_1000.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libintl-0.22.5-h5728263_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.58-h7351971_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.22-h6a83c73_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.1-hf5d6505_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.3-h692994f_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.3-hbc0d294_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.3-h3cfd58e_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.3-h8ef44ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.6-h4fa8253_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.3-py312h05f76fc_1.conda @@ -1666,6 +1829,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-26.2.0-h80d1838_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/onemkl-license-2026.0.0-h57928b3_908.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.2-hf411b9b_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pcre2-10.47-hd2b5f0e_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pixman-0.46.4-h5112557_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py312he5662c2_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.12.13-h0159041_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py312h829343e_2.conda @@ -1674,6 +1839,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py312h343a6d4_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.30.0-py312hdabe01f_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2023.0.0-hd3d4ead_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tectonic-0.16.9-h18ecd4e_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py312he06e257_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda @@ -1683,6 +1849,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/winpty-0.4.3-4.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h3a581c9_11.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: . - pypi: https://files.pythonhosted.org/packages/03/c1/0976b235cf29ead553e22f2fb6385a8252b533715e00d0ae52ed7b900582/h5py-3.16.0-cp312-cp312-win_amd64.whl @@ -1729,6 +1896,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/6aa79ba3570bddd1bf7e951c6123f806751e58e8cce736bad77b2cf348d7/logistro-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl @@ -1769,6 +1937,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/b9/a6d8bb7d228940f01885bd9f327ab7f9d366a9be775c4bf366bf9d9477ae/kaleido-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl @@ -1793,7 +1962,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b9/c4/90de06b2d8737c68c05ff9274113f854dbf6a5f28b7a955212111672cb57/simplejson-4.1.1-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b9/c5/fc1b368f303087d20e8c9bf3d6ceb186263cfac0ade735cd938538bea839/pandas-3.0.3-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b9/d5/973a43fc9c55e20f2051e9830997649f669be0cb3ca52192087c0143f118/orjson-3.11.9-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/ba/6c/ff8bf52315064dbeb55cb5067e191120a5b2e58bb648d0d34cf7969dc2c2/choreographer-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl @@ -1840,9 +2012,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py314h3de4e8d_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-he90730b_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py314h4a8dc5f_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py314h42812f9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.18.0-h27c8c51_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.3-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.14-hecca717_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gsl-2.8-hbf7d49c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-14.2.0-h6083320_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda @@ -1857,36 +2034,54 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.3-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.3-h73754d4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.88.1-h0d30a3d_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.33-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.58-h421ea60_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.22-h280c20c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.1-h0c1763c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.1-h5347b49_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.52.1-h280c20c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py314h67df5f8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py314h5bd0f2a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-26.2.0-he4ff34a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py314h0f05182_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.5-habeac84_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py314h2e6c369_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tectonic-0.16.9-ha39f199_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py314h5bd0f2a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.13-he1eb515_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.7-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h09e67af_11.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda @@ -1911,6 +2106,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda @@ -2043,6 +2244,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/46/b4/0887c88ddfaba1d7140ea335144eb904af97550786ee58bdb295ff10d255/crysfml-0.6.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/6aa79ba3570bddd1bf7e951c6123f806751e58e8cce736bad77b2cf348d7/logistro-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/58/e0/f1871f520c359e4e3a2eb7437c9e7e792bb6c356414e8617937561167caf/pycifrw-5.0.1.tar.gz - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl @@ -2053,9 +2255,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/dc/0decaf5da92a7a969374474025787102d811d42aed1d32191fa338620e15/python_socketio-5.16.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/3e/c0b690253f0b82d86e99949af13533363acfb5432ecb5d53dd5b3bce9c34/orjson-3.11.9-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/96/4efd6fa5c62c85426a0c19077a586258ebc3a2a146ff2493e4312a697a22/greenlet-3.5.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz + - pypi: https://files.pythonhosted.org/packages/78/91/3635cdb13318cb0a328abaa69e2b91251caad39d6779aa308098f341f6cb/simplejson-4.1.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-py3-none-any.whl @@ -2080,6 +2284,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/b9/a6d8bb7d228940f01885bd9f327ab7f9d366a9be775c4bf366bf9d9477ae/kaleido-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl @@ -2099,6 +2304,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/28/180bfc5c95e83d40cb2abce512684ccad44e4819ec899fc36cb404a19061/python_engineio-4.13.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ba/6c/ff8bf52315064dbeb55cb5067e191120a5b2e58bb648d0d34cf7969dc2c2/choreographer-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/c9/989f4034fb46841208de7aeeac2c6d8300745ab4f28c42f629ba77c2d916/aiohttp-3.13.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl @@ -2164,6 +2370,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda @@ -2253,9 +2465,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py314h3daef5d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cairo-1.18.4-he0f2337_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py314h44086f9_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py314he609de1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fontconfig-2.18.0-h2b252f5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/freetype-2.14.3-hce30654_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/graphite2-1.3.14-hec049ff_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gsl-2.8-h8d0574d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/harfbuzz-14.2.0-h3103d1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h385eeb1_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda @@ -2269,13 +2486,19 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.1-hf6b4638_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype-2.14.3-hce30654_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype6-2.14.3-hdfa99f5_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_19.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_19.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libglib-2.88.1-ha08bb59_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libintl-0.25.1-h493aca8_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.33-openmp_he657e61_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpng-1.6.58-h132b30e_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.22-h1a92334_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.1-h1b79a29_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.52.1-h1a92334_0.conda @@ -2286,6 +2509,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-26.2.0-h7039424_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.2-hd24854e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.47-h30297fc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pixman-0.46.4-h81086ad_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py314ha14b1ff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py314h3a4d195_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py314h36abed7_0.conda @@ -2294,10 +2519,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py314haad56a0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tectonic-0.16.9-hab3abff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py314h6c2aa35_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h10816f8_11.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: . - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -2344,6 +2571,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/51/ac/b9d68ebddfe1b02c77af5bf81120e12b036b4432dc6af7a303d90e2bc38b/chardet-7.4.3-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/6aa79ba3570bddd1bf7e951c6123f806751e58e8cce736bad77b2cf348d7/logistro-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/9e/d13e40f83b8d0a94430e6778ce1d94a43b38cf2efe63278bdd2b4c65abbf/ruff-0.15.14-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/58/e0/f1871f520c359e4e3a2eb7437c9e7e792bb6c356414e8617937561167caf/pycifrw-5.0.1.tar.gz - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl @@ -2354,6 +2582,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/b1/4260d67d6bd85e58a66b72d54ce15d5de789b6f3870cc6bedf8ff9667401/propcache-0.5.2-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/68/10/bf2d6738d72748b961a3751ab89522d58c54efc36a8e1a12161216cd45cf/pandas-3.0.3-cp314-cp314-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/68/31/9a5432c433a7671107182cdc9a20ea78a70f99c4e5334aa54b6d4d0d79ed/simplejson-4.1.1-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/6a/b7/9366ed44ced9b7ef357ab48c94205280276db9d7f064aa3012a97227e966/h5py-3.16.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl @@ -2372,6 +2601,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/5a/7fd1b784a87e96e0078f49a0a13a98b4c5f644ba5597a4a3b70a2ba3e613/py3dmol-2.5.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/eb/5da01e356015aee6ecfa1187ced87aef51364e306f5e695dd52719bf0e78/orjson-3.11.9-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl @@ -2384,6 +2614,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/91/cc8cc78a111826c54743d88651e1687008133c37e5ee615fee9b57990fac/aiohttp-3.13.5-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/b9/a6d8bb7d228940f01885bd9f327ab7f9d366a9be775c4bf366bf9d9477ae/kaleido-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl @@ -2403,6 +2634,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/28/180bfc5c95e83d40cb2abce512684ccad44e4819ec899fc36cb404a19061/python_engineio-4.13.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ba/6c/ff8bf52315064dbeb55cb5067e191120a5b2e58bb648d0d34cf7969dc2c2/choreographer-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ba/b1/5297bb6a7df4782f7605bffc43b31f5044070935fbbcaa6c705a07e6ac65/yarl-1.24.2-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl @@ -2467,6 +2699,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda @@ -2553,23 +2791,34 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py314h5a2d7ad_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py314he701e3d_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cairo-1.18.4-h477c42c_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py314h5a2d7ad_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py314hb98de8c_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/fontconfig-2.18.0-hd47e2ca_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/freetype-2.14.3-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/graphite2-1.3.14-hac47afa_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/gsl-2.8-h5b8d9c4_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/harfbuzz-14.2.0-h5a1b470_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/icu-78.3-h637d24d_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-7_h8455456_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-7_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.1-hac47afa_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libfreetype-2.14.3-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libfreetype6-2.14.3-hdbac1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libglib-2.88.1-h7ce1215_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.13.0-default_h049141e_1000.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libintl-0.22.5-h5728263_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.58-h7351971_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.22-h6a83c73_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.1-hf5d6505_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.3-h692994f_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.3-hbc0d294_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.3-h3cfd58e_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.3-h8ef44ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.6-h4fa8253_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.3-py314h2359020_1.conda @@ -2578,6 +2827,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-26.2.0-h80d1838_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/onemkl-license-2026.0.0-h57928b3_908.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.2-hf411b9b_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pcre2-10.47-hd2b5f0e_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pixman-0.46.4-h5112557_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py314hc5dbbe4_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.14.5-h4b44e0e_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py314hcaaf0b2_2.conda @@ -2586,6 +2837,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py312h343a6d4_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.30.0-py314h9f07db2_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2023.0.0-hd3d4ead_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tectonic-0.16.9-h18ecd4e_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py314h5a2d7ad_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda @@ -2595,6 +2847,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/winpty-0.4.3-4.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h3a581c9_11.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: . - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -2607,6 +2860,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/72/ec1b5cbdcb140c132e6c7bdf99bd73e4f675439e77126c88f472fcffa09c/simplejson-4.1.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1d/77/928ea2e70641ca177a11140062cc5840d421795f2e82749d408d0cce900a/narwhals-2.21.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl @@ -2638,6 +2892,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/6aa79ba3570bddd1bf7e951c6123f806751e58e8cce736bad77b2cf348d7/logistro-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/58/e0/f1871f520c359e4e3a2eb7437c9e7e792bb6c356414e8617937561167caf/pycifrw-5.0.1.tar.gz - pypi: https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl @@ -2679,6 +2934,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/b9/a6d8bb7d228940f01885bd9f327ab7f9d366a9be775c4bf366bf9d9477ae/kaleido-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl @@ -2699,6 +2955,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/28/180bfc5c95e83d40cb2abce512684ccad44e4819ec899fc36cb404a19061/python_engineio-4.13.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ba/6c/ff8bf52315064dbeb55cb5067e191120a5b2e58bb648d0d34cf7969dc2c2/choreographer-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl @@ -2727,6 +2984,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f3/c3/0c6798456bade745c75c452342dabacce5798196483e77e643be1f53877d/orjson-3.11.9-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl @@ -3505,6 +3763,33 @@ packages: purls: [] size: 207882 timestamp: 1765214722852 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-he90730b_1.conda + sha256: 06525fa0c4e4f56e771a3b986d0fdf0f0fc5a3270830ee47e127a5105bde1b9a + md5: bb6c4808bfa69d6f7f6b07e5846ced37 + depends: + - __glibc >=2.17,<3.0.a0 + - fontconfig >=2.15.0,<3.0a0 + - fonts-conda-ecosystem + - icu >=78.1,<79.0a0 + - libexpat >=2.7.3,<3.0a0 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - libgcc >=14 + - libglib >=2.86.3,<3.0a0 + - libpng >=1.6.53,<1.7.0a0 + - libstdcxx >=14 + - libxcb >=1.17.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - pixman >=0.46.4,<1.0a0 + - xorg-libice >=1.1.2,<2.0a0 + - xorg-libsm >=1.2.6,<2.0a0 + - xorg-libx11 >=1.8.12,<2.0a0 + - xorg-libxext >=1.3.6,<2.0a0 + - xorg-libxrender >=0.9.12,<0.10.0a0 + license: LGPL-2.1-only or MPL-1.1 + purls: [] + size: 989514 + timestamp: 1766415934926 - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py312h460c074_1.conda sha256: 7dafe8173d5f94e46cf9cd597cc8ff476a8357fbbd4433a8b5697b2864845d9c md5: 648ee28dcd4e07a1940a17da62eccd40 @@ -3567,6 +3852,44 @@ packages: - pkg:pypi/debugpy?source=hash-mapping size: 2886804 timestamp: 1769744977998 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.18.0-h27c8c51_0.conda + sha256: e798086d8a65d55dc4c51f5746705639c9a5f2eeb0b8fc50e6152cfc0d69a4e8 + md5: 06965b2f9854d0b15e0443ee81fe83dc + depends: + - __glibc >=2.17,<3.0.a0 + - libexpat >=2.8.1,<3.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libgcc >=14 + - libuuid >=2.42.1,<3.0a0 + - libzlib >=1.3.2,<2.0a0 + license: MIT + license_family: MIT + purls: [] + size: 280882 + timestamp: 1779421631622 +- conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.3-ha770c72_0.conda + sha256: c934c385889c7836f034039b43b05ccfa98f53c900db03d8411189892ced090b + md5: 8462b5322567212beeb025f3519fb3e2 + depends: + - libfreetype 2.14.3 ha770c72_0 + - libfreetype6 2.14.3 h73754d4_0 + license: GPL-2.0-only OR FTL + purls: [] + size: 173839 + timestamp: 1774298173462 +- conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.14-hecca717_2.conda + sha256: 25ba37da5c39697a77fce2c9a15e48cf0a84f1464ad2aafbe53d8357a9f6cc8c + md5: 2cd94587f3a401ae05e03a6caf09539d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: LGPL-2.0-or-later + license_family: LGPL + purls: [] + size: 99596 + timestamp: 1755102025473 - conda: https://conda.anaconda.org/conda-forge/linux-64/gsl-2.8-hbf7d49c_1.conda sha256: f923af07c3a3db746d3be8efebdaa9c819a6007ee3cc12445cee059641611e05 md5: 04e128d2adafe3c844cde58f103c481b @@ -3580,6 +3903,26 @@ packages: purls: [] size: 2486744 timestamp: 1737621160295 +- conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-14.2.0-h6083320_0.conda + sha256: 232c95b56d16d33d8256026a3b1ad34f7f9a75c179d388854be0fd624ddba9e3 + md5: e194f6a2f498f0c7b1e6498bd0b12645 + depends: + - __glibc >=2.17,<3.0.a0 + - cairo >=1.18.4,<2.0a0 + - graphite2 >=1.3.14,<2.0a0 + - icu >=78.3,<79.0a0 + - libexpat >=2.7.5,<3.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libgcc >=14 + - libglib >=2.86.4,<3.0a0 + - libstdcxx >=14 + - libzlib >=1.3.2,<2.0a0 + license: MIT + license_family: MIT + purls: [] + size: 2333599 + timestamp: 1776778392713 - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda sha256: fbf86c4a59c2ed05bbffb2ba25c7ed94f6185ec30ecb691615d42342baa1a16a md5: c80d8a3b84358cb967fa81e7075fbc8a @@ -3761,6 +4104,29 @@ packages: purls: [] size: 58592 timestamp: 1769456073053 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.3-ha770c72_0.conda + sha256: 38f014a7129e644636e46064ecd6b1945e729c2140e21d75bb476af39e692db2 + md5: e289f3d17880e44b633ba911d57a321b + depends: + - libfreetype6 >=2.14.3 + license: GPL-2.0-only OR FTL + purls: [] + size: 8049 + timestamp: 1774298163029 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.3-h73754d4_0.conda + sha256: 16f020f96da79db1863fcdd8f2b8f4f7d52f177dd4c58601e38e9182e91adf1d + md5: fb16b4b69e3f1dcfe79d80db8fd0c55d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libpng >=1.6.55,<1.7.0a0 + - libzlib >=1.3.2,<2.0a0 + constrains: + - freetype >=2.14.3 + license: GPL-2.0-only OR FTL + purls: [] + size: 384575 + timestamp: 1774298162622 - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda sha256: 8e0a3b5e41272e5678499b5dfc4cddb673f9e935de01eb0767ce857001229f46 md5: 57736f29cc2b0ec0b6c2952d3f101b6a @@ -3810,6 +4176,22 @@ packages: purls: [] size: 2483673 timestamp: 1778269025089 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.88.1-h0d30a3d_2.conda + sha256: 33eb5d5310a5c2c0a4707a0afa644801c2e08c8f70c45e1f62f354116dfe0970 + md5: 17d484ab9c8179c6a6e5b7dbb5065afc + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libffi >=3.5.2,<3.6.0a0 + - pcre2 >=10.47,<10.48.0a0 + - libzlib >=1.3.2,<2.0a0 + - libiconv >=1.18,<2.0a0 + constrains: + - glib >2.66 + license: LGPL-2.1-or-later + purls: [] + size: 4754097 + timestamp: 1778508800134 - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda sha256: 5abe4ab9d93f6c9757d654f1969ae2267d4505315c1f2f8fe705fd60af084f1b md5: faac990cb7aedc7f3a2224f2c9b0c26c @@ -3820,6 +4202,16 @@ packages: purls: [] size: 603817 timestamp: 1778268942614 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda + sha256: c467851a7312765447155e071752d7bf9bf44d610a5687e32706f480aad2833f + md5: 915f5995e94f60e9a4826e0b0920ee88 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: LGPL-2.1-only + purls: [] + size: 790176 + timestamp: 1754908768807 - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda sha256: ec30e52a3c1bf7d0425380a189d209a52baa03f22fb66dd3eb587acaa765bd6d md5: b88d90cad08e6bc8ad540cb310a761fb @@ -3886,6 +4278,17 @@ packages: purls: [] size: 5931919 timestamp: 1776993658641 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.58-h421ea60_0.conda + sha256: 377cfe037f3eeb3b1bf3ad333f724a64d32f315ee1958581fc671891d63d3f89 + md5: eba48a68a1a2b9d3c0d9511548db85db + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libzlib >=1.3.2,<2.0a0 + license: zlib-acknowledgement + purls: [] + size: 317729 + timestamp: 1776315175087 - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.22-h280c20c_1.conda sha256: b677bbf1c339d894757c3dcfbb2f88649e499e4991d70ae09a1466da9a6c92d6 md5: 965e4d531b588b2e42f66fd8e48b056c @@ -3942,6 +4345,20 @@ packages: purls: [] size: 419935 timestamp: 1779396012261 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda + sha256: 666c0c431b23c6cec6e492840b176dde533d48b7e6fb8883f5071223433776aa + md5: 92ed62436b625154323d40d5f2f11dd7 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - pthread-stubs + - xorg-libxau >=1.0.11,<2.0a0 + - xorg-libxdmcp + license: MIT + license_family: MIT + purls: [] + size: 395888 + timestamp: 1727278577118 - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda sha256: 6ae68e0b86423ef188196fff6207ed0c8195dd84273cb5623b85aa08033a410c md5: 5aa797f8787fe7a17d1b0821485b5adc @@ -4070,6 +4487,32 @@ packages: purls: [] size: 3167099 timestamp: 1775587756857 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda + sha256: 5e6f7d161356fefd981948bea5139c5aa0436767751a6930cb1ca801ebb113ff + md5: 7a3bff861a6583f1889021facefc08b1 + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - libgcc >=14 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 1222481 + timestamp: 1763655398280 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_1.conda + sha256: 43d37bc9ca3b257c5dd7bf76a8426addbdec381f6786ff441dc90b1a49143b6a + md5: c01af13bdc553d1a8fbfff6e8db075f0 + depends: + - libgcc >=14 + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: MIT + license_family: MIT + purls: [] + size: 450960 + timestamp: 1754665235234 - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py312h5253ce2_0.conda sha256: d834fd656133c9e4eaf63ffe9a117c7d0917d86d89f7d64073f4e3a0020bd8a7 md5: dd94c506b119130aef5a9382aed648e7 @@ -4098,6 +4541,17 @@ packages: - pkg:pypi/psutil?source=hash-mapping size: 231303 timestamp: 1769678156552 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda + sha256: 9c88f8c64590e9567c6c80823f0328e58d3b1efb0e1c539c0315ceca764e0973 + md5: b3c17d95b5a10c6e64a21fa17573e70e + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + purls: [] + size: 8252 + timestamp: 1726802366959 - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.13-hd63d673_0_cpython.conda sha256: a44655c1c3e1d43ed8704890a91e12afd68130414ea2c0872e154e5633a13d7e md5: 7eccb41177e15cc672e1babe9056018e @@ -4245,6 +4699,40 @@ packages: - pkg:pypi/rpds-py?source=hash-mapping size: 376121 timestamp: 1764543122774 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tectonic-0.16.9-ha39f199_0.conda + sha256: b67e0a9768271baa16aa8dac9d777b9bad86c69709c81432cdeda91d7ca15aeb + md5: d598c6d0184cedbc447e37bf71790696 + depends: + - fontconfig + - freetype + - graphite2 + - harfbuzz + - icu + - libpng + - openssl + - zlib + - libgcc >=14 + - libstdcxx >=14 + - __glibc >=2.17,<3.0.a0 + - graphite2 >=1.3.14,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - fontconfig >=2.17.1,<3.0a0 + - fonts-conda-ecosystem + - harfbuzz >=14.1.0 + - icu >=78.3,<79.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libexpat >=2.7.5,<3.0a0 + - openssl >=3.5.6,<4.0a0 + - libpng >=1.6.58,<1.7.0a0 + - libglib >=2.86.4,<3.0a0 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + purls: [] + size: 5522587 + timestamp: 1776736894555 - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda sha256: cafeec44494f842ffeca27e9c8b0c27ed714f93ac77ddadc6aaf726b5554ebac md5: cffd3bdd58090148f4cfcd831f4b26ab @@ -4287,6 +4775,88 @@ packages: - pkg:pypi/tornado?source=hash-mapping size: 912476 timestamp: 1774358032579 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda + sha256: c12396aabb21244c212e488bbdc4abcdef0b7404b15761d9329f5a4a39113c4b + md5: fb901ff28063514abb6046c9ec2c4a45 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + purls: [] + size: 58628 + timestamp: 1734227592886 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda + sha256: 277841c43a39f738927145930ff963c5ce4c4dacf66637a3d95d802a64173250 + md5: 1c74ff8c35dcadf952a16f752ca5aa49 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libuuid >=2.38.1,<3.0a0 + - xorg-libice >=1.1.2,<2.0a0 + license: MIT + license_family: MIT + purls: [] + size: 27590 + timestamp: 1741896361728 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.13-he1eb515_0.conda + sha256: 516d4060139dbb4de49a4dcdc6317a9353fb39ebd47789c14e6fe52de0deee42 + md5: 861fb6ccbc677bb9a9fb2468430b9c6a + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libxcb >=1.17.0,<2.0a0 + license: MIT + license_family: MIT + purls: [] + size: 839652 + timestamp: 1770819209719 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda + sha256: 6bc6ab7a90a5d8ac94c7e300cc10beb0500eeba4b99822768ca2f2ef356f731b + md5: b2895afaf55bf96a8c8282a2e47a5de0 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 15321 + timestamp: 1762976464266 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda + sha256: 25d255fb2eef929d21ff660a0c687d38a6d2ccfbcbf0cc6aa738b12af6e9d142 + md5: 1dafce8548e38671bea82e3f5c6ce22f + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 20591 + timestamp: 1762976546182 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.7-hb03c661_0.conda + sha256: 79c60fc6acfd3d713d6340d3b4e296836a0f8c51602327b32794625826bd052f + md5: 34e54f03dfea3e7a2dcf1453a85f1085 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - xorg-libx11 >=1.8.12,<2.0a0 + license: MIT + license_family: MIT + purls: [] + size: 50326 + timestamp: 1769445253162 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda + sha256: 044c7b3153c224c6cedd4484dd91b389d2d7fd9c776ad0f4a34f099b3389f4a1 + md5: 96d57aba173e878a2089d5638016dc5e + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - xorg-libx11 >=1.8.10,<2.0a0 + license: MIT + license_family: MIT + purls: [] + size: 33005 + timestamp: 1734229037766 - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda sha256: 6d9ea2f731e284e9316d95fa61869fe7bbba33df7929f82693c121022810f4ad md5: a77f85f77be52ff59391544bfe73390a @@ -4312,6 +4882,17 @@ packages: purls: [] size: 311184 timestamp: 1779123989774 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda + sha256: 245c9ee8d688e23661b95e3c6dd7272ca936fabc03d423cdb3cdee1bbcf9f2f2 + md5: c2a01a08fc991620a74b32420e97868a + depends: + - __glibc >=2.17,<3.0.a0 + - libzlib 1.3.2 h25fd6f3_2 + license: Zlib + license_family: Other + purls: [] + size: 95931 + timestamp: 1774072620848 - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda sha256: 68f0206ca6e98fea941e5717cec780ed2873ffabc0e1ed34428c061e2c6268c7 md5: 4a13eeac0b5c8e5b8ab496e6c4ddd829 @@ -4643,6 +5224,61 @@ packages: - pkg:pypi/executing?source=hash-mapping size: 30753 timestamp: 1756729456476 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + sha256: 58d7f40d2940dd0a8aa28651239adbf5613254df0f75789919c4e6762054403b + md5: 0c96522c6bdaed4b1566d11387caaf45 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 397370 + timestamp: 1566932522327 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + sha256: c52a29fdac682c20d252facc50f01e7c2e7ceac52aa9817aaf0bb83f7559ec5c + md5: 34893075a5c9e55cdafac56607368fc6 + license: OFL-1.1 + license_family: Other + purls: [] + size: 96530 + timestamp: 1620479909603 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + sha256: 00925c8c055a2275614b4d983e1df637245e19058d79fc7dd1a93b8d9fb4b139 + md5: 4d59c254e01d9cde7957100457e2d5fb + license: OFL-1.1 + license_family: Other + purls: [] + size: 700814 + timestamp: 1620479612257 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + sha256: 2821ec1dc454bd8b9a31d0ed22a7ce22422c0aef163c59f49dfdf915d0f0ca14 + md5: 49023d73832ef61042f6a237cb2687e7 + license: LicenseRef-Ubuntu-Font-Licence-Version-1.0 + license_family: Other + purls: [] + size: 1620504 + timestamp: 1727511233259 +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + sha256: a997f2f1921bb9c9d76e6fa2f6b408b7fa549edd349a77639c9fe7a23ea93e61 + md5: fee5683a3f04bd15cbd8318b096a27ab + depends: + - fonts-conda-forge + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 3667 + timestamp: 1566974674465 +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda + sha256: 54eea8469786bc2291cc40bca5f46438d3e062a399e8f53f013b6a9f50e98333 + md5: a7970cd949a077b7cb9696379d338681 + depends: + - font-ttf-ubuntu + - font-ttf-inconsolata + - font-ttf-dejavu-sans-mono + - font-ttf-source-code-pro + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 4059 + timestamp: 1762351264405 - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda sha256: 2509992ec2fd38ab27c7cdb42cf6cadc566a1cc0d1021a2673475d9fa87c6276 md5: d3549fd50d450b6d9e7dddff25dd2110 @@ -6075,6 +6711,26 @@ packages: purls: [] size: 180327 timestamp: 1765215064054 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cairo-1.18.4-he0f2337_1.conda + sha256: cde9b79ee206fe3ba6ca2dc5906593fb7a1350515f85b2a1135a4ce8ec1539e3 + md5: 36200ecfbbfbcb82063c87725434161f + depends: + - __osx >=11.0 + - fontconfig >=2.15.0,<3.0a0 + - fonts-conda-ecosystem + - icu >=78.1,<79.0a0 + - libcxx >=19 + - libexpat >=2.7.3,<3.0a0 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - libglib >=2.86.3,<3.0a0 + - libpng >=1.6.53,<1.7.0a0 + - libzlib >=1.3.1,<2.0a0 + - pixman >=0.46.4,<1.0a0 + license: LGPL-2.1-only or MPL-1.1 + purls: [] + size: 900035 + timestamp: 1766416416791 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py312h1b4d9a2_1.conda sha256: 597e986ac1a1bd1c9b29d6850e1cdea4a075ce8292af55568952ec670e7dd358 md5: 503ac138ad3cfc09459738c0f5750705 @@ -6137,6 +6793,42 @@ packages: - pkg:pypi/debugpy?source=hash-mapping size: 2778080 timestamp: 1769745040206 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/fontconfig-2.18.0-h2b252f5_0.conda + sha256: 938d18fca474d5a7ed716b88dbc4f962f9a44ac9f2fe29393d7d9d04ac6cbf15 + md5: cf31c25b5770548a394478bb6180ddbc + depends: + - __osx >=11.0 + - libexpat >=2.8.1,<3.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libintl >=0.25.1,<1.0a0 + - libzlib >=1.3.2,<2.0a0 + license: MIT + license_family: MIT + purls: [] + size: 247593 + timestamp: 1779422088582 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/freetype-2.14.3-hce30654_0.conda + sha256: 5952bd9db12207a18a112e8924aa2ce8c2f9d57b62584d58a97d2f6afe1ea324 + md5: 6dcc75ba2e04c555e881b72793d3282f + depends: + - libfreetype 2.14.3 hce30654_0 + - libfreetype6 2.14.3 hdfa99f5_0 + license: GPL-2.0-only OR FTL + purls: [] + size: 173313 + timestamp: 1774298702053 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/graphite2-1.3.14-hec049ff_2.conda + sha256: c507ae9989dbea7024aa6feaebb16cbf271faac67ac3f0342ef1ab747c20475d + md5: 0fc46fee39e88bbcf5835f71a9d9a209 + depends: + - __osx >=11.0 + - libcxx >=19 + license: LGPL-2.0-or-later + license_family: LGPL + purls: [] + size: 81202 + timestamp: 1755102333712 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gsl-2.8-h8d0574d_1.conda sha256: f11d8f2007f6591022afa958d8fe15afbe4211198d1603c0eb886bc21a9eb19e md5: cc261442bead590d89ca9f96884a344f @@ -6149,6 +6841,25 @@ packages: purls: [] size: 1862134 timestamp: 1737621413640 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/harfbuzz-14.2.0-h3103d1b_0.conda + sha256: 40ccd6a589c60a4cedb2f9921dfa60ea5845b5ce323477b042b6f90218b239f6 + md5: ea75b03886981362d93bb4708ee14811 + depends: + - __osx >=11.0 + - cairo >=1.18.4,<2.0a0 + - graphite2 >=1.3.14,<2.0a0 + - icu >=78.3,<79.0a0 + - libcxx >=19 + - libexpat >=2.7.5,<3.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libglib >=2.86.4,<3.0a0 + - libzlib >=1.3.2,<2.0a0 + license: MIT + license_family: MIT + purls: [] + size: 2023669 + timestamp: 1776779039314 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda sha256: 3a7907a17e9937d3a46dfd41cffaf815abad59a569440d1e25177c15fd0684e5 md5: f1182c91c0de31a7abd40cedf6a5ebef @@ -6304,6 +7015,28 @@ packages: purls: [] size: 40979 timestamp: 1769456747661 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype-2.14.3-hce30654_0.conda + sha256: a047a2f238362a37d484f9620e8cba29f513a933cd9eb68571ad4b270d6f8f3e + md5: f73b109d49568d5d1dda43bb147ae37f + depends: + - libfreetype6 >=2.14.3 + license: GPL-2.0-only OR FTL + purls: [] + size: 8091 + timestamp: 1774298691258 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype6-2.14.3-hdfa99f5_0.conda + sha256: ff764608e1f2839e95e2cf9b243681475f8778c36af7a42b3f78f476fdbb1dd3 + md5: e98ba7b5f09a5f450eca083d5a1c4649 + depends: + - __osx >=11.0 + - libpng >=1.6.55,<1.7.0a0 + - libzlib >=1.3.2,<2.0a0 + constrains: + - freetype >=2.14.3 + license: GPL-2.0-only OR FTL + purls: [] + size: 338085 + timestamp: 1774298689297 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_19.conda sha256: 06644fa4d34d57c9e48f4d84b1256f9e5f654fdb37f43acc8a58a396952d42b7 md5: 644058123986582db33aebd4ae2ca184 @@ -6341,6 +7074,41 @@ packages: purls: [] size: 599691 timestamp: 1778273075448 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libglib-2.88.1-ha08bb59_2.conda + sha256: 3b32a7a710132d509f2ea38b2f0384414c863533e0fc7ac71b6a0763e4c67424 + md5: 62d6f3b832d7d79ae0c0aa1bb3c325fa + depends: + - __osx >=11.0 + - libintl >=0.25.1,<1.0a0 + - libffi >=3.5.2,<3.6.0a0 + - pcre2 >=10.47,<10.48.0a0 + - libiconv >=1.18,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + constrains: + - glib >2.66 + license: LGPL-2.1-or-later + purls: [] + size: 4439458 + timestamp: 1778508895255 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda + sha256: de0336e800b2af9a40bdd694b03870ac4a848161b35c8a2325704f123f185f03 + md5: 4d5a7445f0b25b6a3ddbb56e790f5251 + depends: + - __osx >=11.0 + license: LGPL-2.1-only + purls: [] + size: 750379 + timestamp: 1754909073836 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libintl-0.25.1-h493aca8_0.conda + sha256: 99d2cebcd8f84961b86784451b010f5f0a795ed1c08f1e7c76fbb3c22abf021a + md5: 5103f6a6b210a3912faf8d7db516918c + depends: + - __osx >=11.0 + - libiconv >=1.18,<2.0a0 + license: LGPL-2.1-or-later + purls: [] + size: 90957 + timestamp: 1751558394144 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda sha256: 34878d87275c298f1a732c6806349125cebbf340d24c6c23727268184bba051e md5: b1fd823b5ae54fbec272cea0811bd8a9 @@ -6393,6 +7161,16 @@ packages: purls: [] size: 4304965 timestamp: 1776995497368 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpng-1.6.58-h132b30e_0.conda + sha256: 66eae34546df1f098a67064970c92aa14ae7a7505091889e00468294d2882c36 + md5: 2259ae0949dbe20c0665850365109b27 + depends: + - __osx >=11.0 + - libzlib >=1.3.2,<2.0a0 + license: zlib-acknowledgement + purls: [] + size: 289546 + timestamp: 1776315246750 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.22-h1a92334_1.conda sha256: 202be45db5726757a8ea1f374f85aacc18c504f5ff15b2558496dff4c8779c48 md5: 9ed5ab909c449bdcae72322e44875a18 @@ -6551,6 +7329,29 @@ packages: purls: [] size: 3106008 timestamp: 1775587972483 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.47-h30297fc_0.conda + sha256: 5e2e443f796f2fd92adf7978286a525fb768c34e12b1ee9ded4000a41b2894ba + md5: 9b4190c4055435ca3502070186eba53a + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 850231 + timestamp: 1763655726735 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pixman-0.46.4-h81086ad_1.conda + sha256: 29c9b08a9b8b7810f9d4f159aecfd205fce051633169040005c0b7efad4bc718 + md5: 17c3d745db6ea72ae2fce17e7338547f + depends: + - __osx >=11.0 + - libcxx >=19 + license: MIT + license_family: MIT + purls: [] + size: 248045 + timestamp: 1754665282033 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py312hb3ab3e3_0.conda sha256: 6d0e21c76436374635c074208cfeee62a94d3c37d0527ad67fd8a7615e546a05 md5: fd856899666759403b3c16dcba2f56ff @@ -6780,6 +7581,35 @@ packages: - pkg:pypi/rpds-py?source=hash-mapping size: 350976 timestamp: 1764543169524 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tectonic-0.16.9-hab3abff_0.conda + sha256: e28bcb2028cef87d9d368811105767f395f55c5ecbe859b45bacaa8b7cd1d279 + md5: 3acf25ab281eeef47f059873f61faa58 + depends: + - freetype + - graphite2 + - harfbuzz + - icu + - libpng + - openssl + - zlib + - __osx >=11.0 + - libcxx >=19 + - openssl >=3.5.6,<4.0a0 + - icu >=78.3,<79.0a0 + - graphite2 >=1.3.14,<2.0a0 + - libglib >=2.86.4,<3.0a0 + - harfbuzz >=14.1.0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libpng >=1.6.58,<1.7.0a0 + - libzlib >=1.3.2,<2.0a0 + constrains: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 4990654 + timestamp: 1776737058933 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda sha256: 799cab4b6cde62f91f750149995d149bc9db525ec12595e8a1d91b9317f038b3 md5: a9d86bc62f39b94c4661716624eb21b0 @@ -6842,6 +7672,17 @@ packages: purls: [] size: 245404 timestamp: 1779124076307 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda + sha256: 8dd2ac25f0ba714263aac5832d46985648f4bfb9b305b5021d702079badc08d2 + md5: f1c0bce276210bed45a04949cfe8dc20 + depends: + - __osx >=11.0 + - libzlib 1.3.2 h8088a28_2 + license: Zlib + license_family: Other + purls: [] + size: 81123 + timestamp: 1774072974535 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda sha256: 9485ba49e8f47d2b597dd399e88f4802e100851b27c21d7525625b0b4025a5d9 md5: ab136e4c34e97f34fb621d2592a393d8 @@ -6946,6 +7787,27 @@ packages: purls: [] size: 56115 timestamp: 1771350256444 +- conda: https://conda.anaconda.org/conda-forge/win-64/cairo-1.18.4-h477c42c_1.conda + sha256: 9ee4ad706c5d3e1c6c469785d60e3c2b263eec569be0eac7be33fbaef978bccc + md5: 52ea1beba35b69852d210242dd20f97d + depends: + - fontconfig >=2.15.0,<3.0a0 + - fonts-conda-ecosystem + - icu >=78.1,<79.0a0 + - libexpat >=2.7.3,<3.0a0 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - libglib >=2.86.3,<3.0a0 + - libpng >=1.6.53,<1.7.0a0 + - libzlib >=1.3.1,<2.0a0 + - pixman >=0.46.4,<1.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: LGPL-2.1-only or MPL-1.1 + purls: [] + size: 1537783 + timestamp: 1766416059188 - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py312he06e257_1.conda sha256: 3e3bdcb85a2e79fe47d9c8ce64903c76f663b39cb63b8e761f6f884e76127f82 md5: 46f7dccfee37a52a97c0ed6f33fcf0a3 @@ -7008,6 +7870,46 @@ packages: - pkg:pypi/debugpy?source=hash-mapping size: 4026404 timestamp: 1769745008861 +- conda: https://conda.anaconda.org/conda-forge/win-64/fontconfig-2.18.0-hd47e2ca_0.conda + sha256: b1bd6a9a15517d32ffa3165f3562808c6b02319ffed0b49d39a7d15381fb0527 + md5: ea543431a836ea08ccdce00bb55c8585 + depends: + - libexpat >=2.8.1,<3.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libiconv >=1.18,<2.0a0 + - libintl >=0.22.5,<1.0a0 + - libzlib >=1.3.2,<2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + size: 201700 + timestamp: 1779421777219 +- conda: https://conda.anaconda.org/conda-forge/win-64/freetype-2.14.3-h57928b3_0.conda + sha256: 70815dbae6ccdfbb0a47269101a260b0a2e11a2ab5c0f7209f325d01bdb18fb7 + md5: 507b36518b5a595edda64066c820a6ef + depends: + - libfreetype 2.14.3 h57928b3_0 + - libfreetype6 2.14.3 hdbac1cb_0 + license: GPL-2.0-only OR FTL + purls: [] + size: 185640 + timestamp: 1774300487600 +- conda: https://conda.anaconda.org/conda-forge/win-64/graphite2-1.3.14-hac47afa_2.conda + sha256: 5f1714b07252f885a62521b625898326ade6ca25fbc20727cfe9a88f68a54bfd + md5: b785694dd3ec77a011ccf0c24725382b + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: LGPL-2.0-or-later + license_family: LGPL + purls: [] + size: 96336 + timestamp: 1755102441729 - conda: https://conda.anaconda.org/conda-forge/win-64/gsl-2.8-h5b8d9c4_1.conda sha256: 87a3468e09cc1ee0268e8639debad6a5b440090ef8cb1d2ee5eed66c86085528 md5: a47cf810b7c03955139a150b228b93ca @@ -7022,6 +7924,38 @@ packages: purls: [] size: 1528970 timestamp: 1737622367981 +- conda: https://conda.anaconda.org/conda-forge/win-64/harfbuzz-14.2.0-h5a1b470_0.conda + sha256: 82abc468768c5130b45e4025fe289e0c84ea015d7fa23059503da212c89097cb + md5: b8862b83b5c899f5b65bcba0298b8478 + depends: + - cairo >=1.18.4,<2.0a0 + - graphite2 >=1.3.14,<2.0a0 + - icu >=78.3,<79.0a0 + - libexpat >=2.7.5,<3.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libglib >=2.86.4,<3.0a0 + - libzlib >=1.3.2,<2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: MIT + license_family: MIT + purls: [] + size: 1322557 + timestamp: 1776778816190 +- conda: https://conda.anaconda.org/conda-forge/win-64/icu-78.3-h637d24d_0.conda + sha256: 1bda728d70a619731b278c859eda364146cb5b4b8c739a64da8128353d81d1c4 + md5: 0097b24800cb696915c3dbd1f5335d3f + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + size: 14954024 + timestamp: 1773822508646 - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda sha256: eb60f1ad8b597bcf95dee11bc11fe71a8325bc1204cf51d2bb1f2120ffd77761 md5: 4432f52dc0c8eb6a7a6abc00a037d93c @@ -7092,6 +8026,48 @@ packages: purls: [] size: 45831 timestamp: 1769456418774 +- conda: https://conda.anaconda.org/conda-forge/win-64/libfreetype-2.14.3-h57928b3_0.conda + sha256: 71fae9ae05563ceec70adceb7bc66faa326a81a6590a8aac8a5074019070a2d8 + md5: d9f70dd06674e26b6d5a657ddd22b568 + depends: + - libfreetype6 >=2.14.3 + license: GPL-2.0-only OR FTL + purls: [] + size: 8379 + timestamp: 1774300468411 +- conda: https://conda.anaconda.org/conda-forge/win-64/libfreetype6-2.14.3-hdbac1cb_0.conda + sha256: 497e9ab7c80f579e1b2850523740d6a543b8020f6b43be6bd6e83b3a6fb7fb32 + md5: f9975a0177ee6cdda10c86d1db1186b0 + depends: + - libpng >=1.6.55,<1.7.0a0 + - libzlib >=1.3.2,<2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - freetype >=2.14.3 + license: GPL-2.0-only OR FTL + purls: [] + size: 340180 + timestamp: 1774300467879 +- conda: https://conda.anaconda.org/conda-forge/win-64/libglib-2.88.1-h7ce1215_2.conda + sha256: f61277e224e9889c221bb2eac0f57d5aeeb82fc45d3dc326957d251c97444f7c + md5: 5fb838786a8317ebb38056bbe236d3ff + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libiconv >=1.18,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - pcre2 >=10.47,<10.48.0a0 + - libintl >=0.22.5,<1.0a0 + - libffi >=3.5.2,<3.6.0a0 + constrains: + - glib >2.66 + license: LGPL-2.1-or-later + purls: [] + size: 4522891 + timestamp: 1778508851933 - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.13.0-default_h049141e_1000.conda sha256: 2ee12e37223dfcd0acd050c80a91150c482b6e2899198521e1800dce66662467 md5: 6a01c986e30292c715038d2788aa1385 @@ -7118,6 +8094,15 @@ packages: purls: [] size: 696926 timestamp: 1754909290005 +- conda: https://conda.anaconda.org/conda-forge/win-64/libintl-0.22.5-h5728263_3.conda + sha256: c7e4600f28bcada8ea81456a6530c2329312519efcf0c886030ada38976b0511 + md5: 2cf0cf76cc15d360dfa2f17fd6cf9772 + depends: + - libiconv >=1.17,<2.0a0 + license: LGPL-2.1-or-later + purls: [] + size: 95568 + timestamp: 1723629479451 - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_0.conda sha256: d636d1a25234063642f9c531a7bb58d84c1c496411280a36ea000bd122f078f1 md5: 8f83619ab1588b98dd99c90b0bfc5c6d @@ -7143,6 +8128,18 @@ packages: purls: [] size: 89411 timestamp: 1769482314283 +- conda: https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.58-h7351971_0.conda + sha256: 218913aeee391460bd0e341b834dbd9c6fa6ae0a4276c0c300266cc99a816a28 + md5: 52f1280563f3b48b5f75414cd2d15dd1 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libzlib >=1.3.2,<2.0a0 + license: zlib-acknowledgement + purls: [] + size: 385227 + timestamp: 1776315248638 - conda: https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.22-h6a83c73_1.conda sha256: de45b71224da77a1c3a7dd48d8885eb957c9f05455d4f0828463293e7144330f md5: 7d5abf7ca1bd00b43d273f44d93d05dc @@ -7177,10 +8174,11 @@ packages: purls: [] size: 36621 timestamp: 1759768399557 -- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.3-h692994f_0.conda - sha256: 8038084c60eda2006d0122d05e3364fe8db0a18935ca6ed0168b5ba5aa33f904 - md5: f7d6fcda29570e20851b78d92ea2154e +- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.3-h3cfd58e_0.conda + sha256: 3b61ee3caba702d2ff432fa3920835db963026e5c99c4e6fdca0c6114f59e7ce + md5: 9e8dd0d90ed830107b2c36801035b7db depends: + - icu >=78.3,<79.0a0 - libiconv >=1.18,<2.0a0 - liblzma >=5.8.3,<6.0a0 - libzlib >=1.3.2,<2.0a0 @@ -7189,30 +8187,28 @@ packages: - vc14_runtime >=14.44.35208 constrains: - libxml2 2.15.3 - - icu <0.0a0 license: MIT license_family: MIT purls: [] - size: 518869 - timestamp: 1776376971242 -- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.3-hbc0d294_0.conda - sha256: da68af9d9d28d65a6916db1bef68f8a25c64c4fdcf759f32a2d2f2f143220adf - md5: e3b5acbb857a12f5d59e8d174bc536c0 + size: 519871 + timestamp: 1776376969852 +- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.3-h8ef44ab_0.conda + sha256: a4599c6bbbbdd7db570896e520c557eec8e66d94e839a59d17dc1f24a3d5f82b + md5: 95591ca5671d2213f5b2d5aa7818420d depends: + - icu >=78.3,<79.0a0 - libiconv >=1.18,<2.0a0 - liblzma >=5.8.3,<6.0a0 - - libxml2-16 2.15.3 h692994f_0 + - libxml2-16 2.15.3 h3cfd58e_0 - libzlib >=1.3.2,<2.0a0 - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 - constrains: - - icu <0.0a0 license: MIT license_family: MIT purls: [] - size: 43916 - timestamp: 1776376994334 + size: 43684 + timestamp: 1776376992865 - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda sha256: 88609816e0cc7452bac637aaf65783e5edf4fee8a9f8e22bdc3a75882c536061 md5: dbabbd6234dea34040e631f87676292f @@ -7350,6 +8346,35 @@ packages: purls: [] size: 9410183 timestamp: 1775589779763 +- conda: https://conda.anaconda.org/conda-forge/win-64/pcre2-10.47-hd2b5f0e_0.conda + sha256: 3e9e02174edf02cb4bcdd75668ad7b74b8061791a3bc8bdb8a52ae336761ba3e + md5: 77eaf2336f3ae749e712f63e36b0f0a1 + depends: + - bzip2 >=1.0.8,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 995992 + timestamp: 1763655708300 +- conda: https://conda.anaconda.org/conda-forge/win-64/pixman-0.46.4-h5112557_1.conda + sha256: 246fce4706b3f8b247a7d6142ba8d732c95263d3c96e212b9d63d6a4ab4aff35 + md5: 08c8fa3b419df480d985e304f7884d35 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: MIT + license_family: MIT + purls: [] + size: 542795 + timestamp: 1754665193489 - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py312he5662c2_0.conda sha256: edffc84c001a05b996b5f8607c8164432754e86ec9224e831cd00ebabdec04e7 md5: a2724c93b745fc7861948eb8b9f6679a @@ -7582,6 +8607,38 @@ packages: purls: [] size: 156515 timestamp: 1778673901757 +- conda: https://conda.anaconda.org/conda-forge/win-64/tectonic-0.16.9-h18ecd4e_0.conda + sha256: 006a2aabce48ab137ee0f5740d387cef09b03741999275efadc08dd9247c28d9 + md5: 0c3abdb6cf0bb5ddd03636ebd1771d68 + depends: + - fontconfig + - freetype + - graphite2 + - harfbuzz + - icu + - libpng + - openssl + - zlib + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libzlib >=1.3.2,<2.0a0 + - libexpat >=2.7.5,<3.0a0 + - libpng >=1.6.58,<1.7.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - graphite2 >=1.3.14,<2.0a0 + - harfbuzz >=14.1.0 + - libglib >=2.86.4,<3.0a0 + - openssl >=3.5.6,<4.0a0 + - icu >=78.3,<79.0a0 + - fontconfig >=2.17.1,<3.0a0 + - fonts-conda-ecosystem + license: MIT + license_family: MIT + purls: [] + size: 4652528 + timestamp: 1776736929395 - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda sha256: 0e79810fae28f3b69fe7391b0d43f5474d6bd91d451d5f2bde02f55ae481d5e3 md5: 0481bfd9814bf525bd4b3ee4b51494c4 @@ -7707,6 +8764,19 @@ packages: purls: [] size: 265717 timestamp: 1779124031378 +- conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda + sha256: ef408f85f664a4b9c9dac3cb2e36154d9baa15a88984ea800e11060e0f2394a1 + md5: 5187ecf958be3c39110fe691cbd6873e + depends: + - libzlib 1.3.2 hfd05255_2 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Zlib + license_family: Other + purls: [] + size: 850351 + timestamp: 1774072891049 - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda sha256: 368d8628424966fd8f9c8018326a9c779e06913dd39e646cf331226acc90e5b2 md5: 053b84beec00b71ea8ff7a4f84b55207 @@ -7735,6 +8805,7 @@ packages: - emcee - gemmi - h5py + - kaleido>=1.0 - lmfit - numpy - pandas @@ -7996,6 +9067,11 @@ packages: requires_dist: - numpy requires_python: '>=3.11,<3.15' +- pypi: https://files.pythonhosted.org/packages/0c/b6/156a8de1e1b47694f0e7de6675866936608d45dc68388fd017d36f8693be/simplejson-4.1.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + name: simplejson + version: 4.1.1 + sha256: 45ec18e337fec538b7e902d489505c450b2454653d1290f3f50385e6fd8aa607 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*' - pypi: https://files.pythonhosted.org/packages/0d/12/bbce9472f489cb5c4c23b0d13e5c59c37c1aab11b7ac637dfe6bbdccebe7/copier-9.15.1-py3-none-any.whl name: copier version: 9.15.1 @@ -8059,6 +9135,11 @@ packages: - virtualenv>=20.17 ; python_full_version >= '3.10' and python_full_version < '3.14' and extra == 'virtualenv' - virtualenv>=20.31 ; python_full_version >= '3.14' and extra == 'virtualenv' requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/0e/a4/82b7a2fe5d8a67a59ed831b24d59a3d46ea7d207b66e1602d376541d94a6/orjson-3.11.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: orjson + version: 3.11.9 + sha256: be4fa4f0af7fa18951f7ab3fc2148e223af211bf03f59e1c6034ec3f97f21d61 + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl name: pyparsing version: 3.3.2 @@ -8165,6 +9246,16 @@ packages: requires_dist: - numpy requires_python: '>=3.11,<3.15' +- pypi: https://files.pythonhosted.org/packages/16/6d/11867a3ffa3a3608d84a4de51ef4dd0896d6b5cc9132fbe1daf593e677bc/orjson-3.11.9-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl + name: orjson + version: 3.11.9 + sha256: 9ef6fe90aadef185c7b128859f40beb24720b4ecea95379fc9000931179c3a49 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/18/72/ec1b5cbdcb140c132e6c7bdf99bd73e4f675439e77126c88f472fcffa09c/simplejson-4.1.1-cp314-cp314-win_amd64.whl + name: simplejson + version: 4.1.1 + sha256: cc0442dea71cd9cbf30a0b8b9929ab5aa6c02c0443a3d977351e6ec5bada4388 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*' - pypi: https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl name: pydantic-core version: 2.46.4 @@ -9149,6 +10240,11 @@ packages: - pytest-benchmark ; extra == 'testing' - coverage ; extra == 'testing' requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/54/20/6aa79ba3570bddd1bf7e951c6123f806751e58e8cce736bad77b2cf348d7/logistro-2.0.1-py3-none-any.whl + name: logistro + version: 2.0.1 + sha256: 06ffa127b9fb4ac8b1972ae6b2a9d7fde57598bf5939cd708f43ec5bba2d31eb + requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/56/9e/d13e40f83b8d0a94430e6778ce1d94a43b38cf2efe63278bdd2b4c65abbf/ruff-0.15.14-py3-none-macosx_11_0_arm64.whl name: ruff version: 0.15.14 @@ -9592,6 +10688,11 @@ packages: - xlsxwriter>=3.2.0 ; extra == 'all' - zstandard>=0.23.0 ; extra == 'all' requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/68/31/9a5432c433a7671107182cdc9a20ea78a70f99c4e5334aa54b6d4d0d79ed/simplejson-4.1.1-cp314-cp314-macosx_11_0_arm64.whl + name: simplejson + version: 4.1.1 + sha256: 95407269340c7f22f09776ea7b717a52cf56cfcf119b5e45f66faa4a26445bea + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*' - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl name: arviz-base version: 1.1.0 @@ -9693,6 +10794,11 @@ packages: - sphinx ; extra == 'docs' - furo ; extra == 'docs' requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/76/3e/c0b690253f0b82d86e99949af13533363acfb5432ecb5d53dd5b3bce9c34/orjson-3.11.9-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: orjson + version: 3.11.9 + sha256: aaea64f3f467d22e70eeed68bdccb3bc4f83f650446c4a03c59f2cba28a108db + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl name: mike version: 2.2.0 @@ -9763,6 +10869,11 @@ packages: name: pycifstar version: 0.3.0 sha256: 5892fdf16c83372ee5f32557127d5f36e14b0bbe520883a4e2e70365382f70ed +- pypi: https://files.pythonhosted.org/packages/78/91/3635cdb13318cb0a328abaa69e2b91251caad39d6779aa308098f341f6cb/simplejson-4.1.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + name: simplejson + version: 4.1.1 + sha256: 3851658d642c1184d2023f0e6c9ce44a21eb1629e74e7c84ef956b128841fe12 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*' - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl name: annotated-types version: 0.7.0 @@ -10074,6 +11185,11 @@ packages: sha256: 9717ea9a899ec641b458f53de538e5bae758281f5053be78bc2db0706ae60bcb requires_dist: - ipython ; extra == 'ipython' +- pypi: https://files.pythonhosted.org/packages/8e/eb/5da01e356015aee6ecfa1187ced87aef51364e306f5e695dd52719bf0e78/orjson-3.11.9-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl + name: orjson + version: 3.11.9 + sha256: b6ef1979adc4bc243523f1a2ba91418030a8e29b0a99cbe7e0e2d6807d4dce6e + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl name: uncertainties version: 3.2.3 @@ -10342,6 +11458,16 @@ packages: - pycodestyle>=2.12.0 - tomli ; python_full_version < '3.11' requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/9e/b9/a6d8bb7d228940f01885bd9f327ab7f9d366a9be775c4bf366bf9d9477ae/kaleido-1.3.0-py3-none-any.whl + name: kaleido + version: 1.3.0 + sha256: 52714dfd38e8f2a114831826200c40bb10d0ca0c11d4272f3f48ad499cd8f8ea + requires_dist: + - choreographer>=1.3.0 + - logistro>=1.0.8 + - orjson>=3.10.15 + - packaging + requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/9e/e9/1a19e42cd43cc1365e127db6aae85e1c671da1d9a5d746f4d34a50edb577/h5py-3.16.0-cp312-cp312-manylinux_2_28_x86_64.whl name: h5py version: 3.16.0 @@ -10825,11 +11951,21 @@ packages: - tomli>=1.2.1 ; python_full_version < '3.11' and extra == 'all' - validate-pyproject-schema-store ; extra == 'store' requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/b8/78/fc060d2e3b13c6ec59288574b8efac64075e316b2afba4396a56b2422f78/simplejson-4.1.1-cp312-cp312-macosx_11_0_arm64.whl + name: simplejson + version: 4.1.1 + sha256: 67341c95c0a168ab4a6d1e807e50463f1c8da932c3286d81e201266c427061fa + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*' - pypi: https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl name: frozenlist version: 1.8.0 sha256: 34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746 requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/b9/c4/90de06b2d8737c68c05ff9274113f854dbf6a5f28b7a955212111672cb57/simplejson-4.1.1-cp312-cp312-win_amd64.whl + name: simplejson + version: 4.1.1 + sha256: 63a5451f557d6be48a231bae932458655c620902b868170b2f1c8afed496f6b4 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*' - pypi: https://files.pythonhosted.org/packages/b9/c5/fc1b368f303087d20e8c9bf3d6ceb186263cfac0ade735cd938538bea839/pandas-3.0.3-cp312-cp312-win_amd64.whl name: pandas version: 3.0.3 @@ -10920,6 +12056,20 @@ packages: - xlsxwriter>=3.2.0 ; extra == 'all' - zstandard>=0.23.0 ; extra == 'all' requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/b9/d5/973a43fc9c55e20f2051e9830997649f669be0cb3ca52192087c0143f118/orjson-3.11.9-cp312-cp312-win_amd64.whl + name: orjson + version: 3.11.9 + sha256: 59e403b1cc5a676da8eaf31f6254801b7341b3e29efa85f92b48d272637e77be + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/ba/6c/ff8bf52315064dbeb55cb5067e191120a5b2e58bb648d0d34cf7969dc2c2/choreographer-1.3.0-py3-none-any.whl + name: choreographer + version: 1.3.0 + sha256: cea4cb739e4f61625e4b53888a8d3fa1d3bf73948b56753e460ab44da7d8d44f + requires_dist: + - logistro>=2.0.1 + - platformdirs>=4.3.6 + - simplejson>=3.19.3 + requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl name: pillow version: 12.2.0 @@ -11708,6 +12858,11 @@ packages: requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/f3/c3/0c6798456bade745c75c452342dabacce5798196483e77e643be1f53877d/orjson-3.11.9-cp314-cp314-win_amd64.whl + name: orjson + version: 3.11.9 + sha256: 32ef5f4283a3be81913947d19608eacb7c6608026851123790cd9cc8982af34b + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: numpy version: 2.4.6 diff --git a/pixi.toml b/pixi.toml index c6605061f..feecdfb53 100644 --- a/pixi.toml +++ b/pixi.toml @@ -53,6 +53,7 @@ jupyterlab = '*' # Jupyter notebooks ipython = '*' # Interactive Python shell pixi-kernel = '*' # Pixi Jupyter kernel gsl = '*' # GNU Scientific Library; required for diffpy.pdffit2 +tectonic = '*' # LaTeX engine for PDF report generation [feature.dev.pypi-dependencies] pip = '*' diff --git a/pyproject.toml b/pyproject.toml index de4f24894..c6e087506 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,7 @@ dependencies = [ 'darkdetect', # Detecting dark mode (system-level) 'pandas', # Displaying tables in Jupyter notebooks 'plotly', # Interactive plots + 'kaleido>=1.0', # Static image export for report figures 'py3Dmol', # Visualisation of crystal structures ] diff --git a/src/easydiffraction/report/templates/tex/styles/LICENSES.md b/src/easydiffraction/report/templates/tex/styles/LICENSES.md new file mode 100644 index 000000000..de2e7a527 --- /dev/null +++ b/src/easydiffraction/report/templates/tex/styles/LICENSES.md @@ -0,0 +1,568 @@ +# Vendored LaTeX Style Licenses + +This file records the third-party licensing for the LaTeX style files vendored in this directory. The repository-level index is `THIRD_PARTY_LICENSES.md`. + +## IUCr LaTeX Template Files + +Files: `iucrjournals.cls`, `harvard.sty` + +- Upstream URL: https://www.iucr.org/resources/cif/software/latex +- Local source used for vendoring: `tmp/latex/iucrtemplate/` +- License: CC0 1.0 Universal +- Attribution: International Union of Crystallography LaTeX journal class; `harvard.sty` by Peter Williams, 1994. + +## REVTeX 4.2 Files + +Files: `revtex4-2.cls`, `ltxgrid.sty`, `ltxutil.sty`, `ltxfront.sty`, `ltxdocext.sty`, `revsymb4-2.sty`, `aps4-2.rtx`, `aps10pt4-2.rtx`, `aps11pt4-2.rtx`, `aps12pt4-2.rtx` + +- Upstream URL: https://journals.aps.org/revtex/ +- Local source used for vendoring: `tmp/latex/revtex/tex/latex/revtex/` +- Version: REVTeX 4.2f, 2022-06-05 +- License: LaTeX Project Public License 1.3c +- Attribution: Copyright 2019-2022 American Physical Society. Original version by David Carlisle; modified by Arthur Ogawa, Mark Doyle, and Phelype Oleinik for the American Physical Society. + +## CC0 1.0 Universal + +```text +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. +``` + +## LaTeX Project Public License 1.3c + +```text +The LaTeX Project Public License +=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +LPPL Version 1.3c 2008-05-04 + +Copyright 1999 2002-2008 LaTeX3 Project + Everyone is allowed to distribute verbatim copies of this + license document, but modification of it is not allowed. + + +PREAMBLE +======== + +The LaTeX Project Public License (LPPL) is the primary license under +which the LaTeX kernel and the base LaTeX packages are distributed. + +You may use this license for any work of which you hold the copyright +and which you wish to distribute. This license may be particularly +suitable if your work is TeX-related (such as a LaTeX package), but +it is written in such a way that you can use it even if your work is +unrelated to TeX. + +The section `WHETHER AND HOW TO DISTRIBUTE WORKS UNDER THIS LICENSE', +below, gives instructions, examples, and recommendations for authors +who are considering distributing their works under this license. + +This license gives conditions under which a work may be distributed +and modified, as well as conditions under which modified versions of +that work may be distributed. + +We, the LaTeX3 Project, believe that the conditions below give you +the freedom to make and distribute modified versions of your work +that conform with whatever technical specifications you wish while +maintaining the availability, integrity, and reliability of +that work. If you do not see how to achieve your goal while +meeting these conditions, then read the document `cfgguide.tex' +and `modguide.tex' in the base LaTeX distribution for suggestions. + + +DEFINITIONS +=========== + +In this license document the following terms are used: + + `Work' + Any work being distributed under this License. + + `Derived Work' + Any work that under any applicable law is derived from the Work. + + `Modification' + Any procedure that produces a Derived Work under any applicable + law -- for example, the production of a file containing an + original file associated with the Work or a significant portion of + such a file, either verbatim or with modifications and/or + translated into another language. + + `Modify' + To apply any procedure that produces a Derived Work under any + applicable law. + + `Distribution' + Making copies of the Work available from one person to another, in + whole or in part. Distribution includes (but is not limited to) + making any electronic components of the Work accessible by + file transfer protocols such as FTP or HTTP or by shared file + systems such as Sun's Network File System (NFS). + + `Compiled Work' + A version of the Work that has been processed into a form where it + is directly usable on a computer system. This processing may + include using installation facilities provided by the Work, + transformations of the Work, copying of components of the Work, or + other activities. Note that modification of any installation + facilities provided by the Work constitutes modification of the Work. + + `Current Maintainer' + A person or persons nominated as such within the Work. If there is + no such explicit nomination then it is the `Copyright Holder' under + any applicable law. + + `Base Interpreter' + A program or process that is normally needed for running or + interpreting a part or the whole of the Work. + + A Base Interpreter may depend on external components but these + are not considered part of the Base Interpreter provided that each + external component clearly identifies itself whenever it is used + interactively. Unless explicitly specified when applying the + license to the Work, the only applicable Base Interpreter is a + `LaTeX-Format' or in the case of files belonging to the + `LaTeX-format' a program implementing the `TeX language'. + + + +CONDITIONS ON DISTRIBUTION AND MODIFICATION +=========================================== + +1. Activities other than distribution and/or modification of the Work +are not covered by this license; they are outside its scope. In +particular, the act of running the Work is not restricted and no +requirements are made concerning any offers of support for the Work. + +2. You may distribute a complete, unmodified copy of the Work as you +received it. Distribution of only part of the Work is considered +modification of the Work, and no right to distribute such a Derived +Work may be assumed under the terms of this clause. + +3. You may distribute a Compiled Work that has been generated from a +complete, unmodified copy of the Work as distributed under Clause 2 +above, as long as that Compiled Work is distributed in such a way that +the recipients may install the Compiled Work on their system exactly +as it would have been installed if they generated a Compiled Work +directly from the Work. + +4. If you are the Current Maintainer of the Work, you may, without +restriction, modify the Work, thus creating a Derived Work. You may +also distribute the Derived Work without restriction, including +Compiled Works generated from the Derived Work. Derived Works +distributed in this manner by the Current Maintainer are considered to +be updated versions of the Work. + +5. If you are not the Current Maintainer of the Work, you may modify +your copy of the Work, thus creating a Derived Work based on the Work, +and compile this Derived Work, thus creating a Compiled Work based on +the Derived Work. + +6. If you are not the Current Maintainer of the Work, you may +distribute a Derived Work provided the following conditions are met +for every component of the Work unless that component clearly states +in the copyright notice that it is exempt from that condition. Only +the Current Maintainer is allowed to add such statements of exemption +to a component of the Work. + + a. If a component of this Derived Work can be a direct replacement + for a component of the Work when that component is used with the + Base Interpreter, then, wherever this component of the Work + identifies itself to the user when used interactively with that + Base Interpreter, the replacement component of this Derived Work + clearly and unambiguously identifies itself as a modified version + of this component to the user when used interactively with that + Base Interpreter. + + b. Every component of the Derived Work contains prominent notices + detailing the nature of the changes to that component, or a + prominent reference to another file that is distributed as part + of the Derived Work and that contains a complete and accurate log + of the changes. + + c. No information in the Derived Work implies that any persons, + including (but not limited to) the authors of the original version + of the Work, provide any support, including (but not limited to) + the reporting and handling of errors, to recipients of the + Derived Work unless those persons have stated explicitly that + they do provide such support for the Derived Work. + + d. You distribute at least one of the following with the Derived Work: + + 1. A complete, unmodified copy of the Work; + if your distribution of a modified component is made by + offering access to copy the modified component from a + designated place, then offering equivalent access to copy + the Work from the same or some similar place meets this + condition, even though third parties are not compelled to + copy the Work along with the modified component; + + 2. Information that is sufficient to obtain a complete, + unmodified copy of the Work. + +7. If you are not the Current Maintainer of the Work, you may +distribute a Compiled Work generated from a Derived Work, as long as +the Derived Work is distributed to all recipients of the Compiled +Work, and as long as the conditions of Clause 6, above, are met with +regard to the Derived Work. + +8. The conditions above are not intended to prohibit, and hence do not +apply to, the modification, by any method, of any component so that it +becomes identical to an updated version of that component of the Work as +it is distributed by the Current Maintainer under Clause 4, above. + +9. Distribution of the Work or any Derived Work in an alternative +format, where the Work or that Derived Work (in whole or in part) is +then produced by applying some process to that format, does not relax or +nullify any sections of this license as they pertain to the results of +applying that process. + +10. a. A Derived Work may be distributed under a different license + provided that license itself honors the conditions listed in + Clause 6 above, in regard to the Work, though it does not have + to honor the rest of the conditions in this license. + + b. If a Derived Work is distributed under a different license, that + Derived Work must provide sufficient documentation as part of + itself to allow each recipient of that Derived Work to honor the + restrictions in Clause 6 above, concerning changes from the Work. + +11. This license places no restrictions on works that are unrelated to +the Work, nor does this license place any restrictions on aggregating +such works with the Work by any means. + +12. Nothing in this license is intended to, or may be used to, prevent +complete compliance by all parties with all applicable laws. + + +NO WARRANTY +=========== + +There is no warranty for the Work. Except when otherwise stated in +writing, the Copyright Holder provides the Work `as is', without +warranty of any kind, either expressed or implied, including, but not +limited to, the implied warranties of merchantability and fitness for a +particular purpose. The entire risk as to the quality and performance +of the Work is with you. Should the Work prove defective, you assume +the cost of all necessary servicing, repair, or correction. + +In no event unless required by applicable law or agreed to in writing +will The Copyright Holder, or any author named in the components of the +Work, or any other party who may distribute and/or modify the Work as +permitted above, be liable to you for damages, including any general, +special, incidental or consequential damages arising out of any use of +the Work or out of inability to use the Work (including, but not limited +to, loss of data, data being rendered inaccurate, or losses sustained by +anyone as a result of any failure of the Work to operate with any other +programs), even if the Copyright Holder or said author or said other +party has been advised of the possibility of such damages. + + +MAINTENANCE OF THE WORK +======================= + +The Work has the status `author-maintained' if the Copyright Holder +explicitly and prominently states near the primary copyright notice in +the Work that the Work can only be maintained by the Copyright Holder +or simply that it is `author-maintained'. + +The Work has the status `maintained' if there is a Current Maintainer +who has indicated in the Work that they are willing to receive error +reports for the Work (for example, by supplying a valid e-mail +address). It is not required for the Current Maintainer to acknowledge +or act upon these error reports. + +The Work changes from status `maintained' to `unmaintained' if there +is no Current Maintainer, or the person stated to be Current +Maintainer of the work cannot be reached through the indicated means +of communication for a period of six months, and there are no other +significant signs of active maintenance. + +You can become the Current Maintainer of the Work by agreement with +any existing Current Maintainer to take over this role. + +If the Work is unmaintained, you can become the Current Maintainer of +the Work through the following steps: + + 1. Make a reasonable attempt to trace the Current Maintainer (and + the Copyright Holder, if the two differ) through the means of + an Internet or similar search. + + 2. If this search is successful, then enquire whether the Work + is still maintained. + + a. If it is being maintained, then ask the Current Maintainer + to update their communication data within one month. + + b. If the search is unsuccessful or no action to resume active + maintenance is taken by the Current Maintainer, then announce + within the pertinent community your intention to take over + maintenance. (If the Work is a LaTeX work, this could be + done, for example, by posting to comp.text.tex.) + + 3a. If the Current Maintainer is reachable and agrees to pass + maintenance of the Work to you, then this takes effect + immediately upon announcement. + + b. If the Current Maintainer is not reachable and the Copyright + Holder agrees that maintenance of the Work be passed to you, + then this takes effect immediately upon announcement. + + 4. If you make an `intention announcement' as described in 2b. above + and after three months your intention is challenged neither by + the Current Maintainer nor by the Copyright Holder nor by other + people, then you may arrange for the Work to be changed so as + to name you as the (new) Current Maintainer. + + 5. If the previously unreachable Current Maintainer becomes + reachable once more within three months of a change completed + under the terms of 3b) or 4), then that Current Maintainer must + become or remain the Current Maintainer upon request provided + they then update their communication data within one month. + +A change in the Current Maintainer does not, of itself, alter the fact +that the Work is distributed under the LPPL license. + +If you become the Current Maintainer of the Work, you should +immediately provide, within the Work, a prominent and unambiguous +statement of your status as Current Maintainer. You should also +announce your new status to the same pertinent community as +in 2b) above. + + +WHETHER AND HOW TO DISTRIBUTE WORKS UNDER THIS LICENSE +====================================================== + +This section contains important instructions, examples, and +recommendations for authors who are considering distributing their +works under this license. These authors are addressed as `you' in +this section. + +Choosing This License or Another License +---------------------------------------- + +If for any part of your work you want or need to use *distribution* +conditions that differ significantly from those in this license, then +do not refer to this license anywhere in your work but, instead, +distribute your work under a different license. You may use the text +of this license as a model for your own license, but your license +should not refer to the LPPL or otherwise give the impression that +your work is distributed under the LPPL. + +The document `modguide.tex' in the base LaTeX distribution explains +the motivation behind the conditions of this license. It explains, +for example, why distributing LaTeX under the GNU General Public +License (GPL) was considered inappropriate. Even if your work is +unrelated to LaTeX, the discussion in `modguide.tex' may still be +relevant, and authors intending to distribute their works under any +license are encouraged to read it. + +A Recommendation on Modification Without Distribution +----------------------------------------------------- + +It is wise never to modify a component of the Work, even for your own +personal use, without also meeting the above conditions for +distributing the modified component. While you might intend that such +modifications will never be distributed, often this will happen by +accident -- you may forget that you have modified that component; or +it may not occur to you when allowing others to access the modified +version that you are thus distributing it and violating the conditions +of this license in ways that could have legal implications and, worse, +cause problems for the community. It is therefore usually in your +best interest to keep your copy of the Work identical with the public +one. Many works provide ways to control the behavior of that work +without altering any of its licensed components. + +How to Use This License +----------------------- + +To use this license, place in each of the components of your work both +an explicit copyright notice including your name and the year the work +was authored and/or last substantially modified. Include also a +statement that the distribution and/or modification of that +component is constrained by the conditions in this license. + +Here is an example of such a notice and statement: + + %% pig.dtx + %% Copyright 2008 M. Y. Name + % + % This work may be distributed and/or modified under the + % conditions of the LaTeX Project Public License, either version 1.3 + % of this license or (at your option) any later version. + % The latest version of this license is in + % https://www.latex-project.org/lppl.txt + % and version 1.3c or later is part of all distributions of LaTeX + % version 2008 or later. + % + % This work has the LPPL maintenance status `maintained'. + % + % The Current Maintainer of this work is M. Y. Name. + % + % This work consists of the files pig.dtx and pig.ins + % and the derived file pig.sty. + +Given such a notice and statement in a file, the conditions +given in this license document would apply, with the `Work' referring +to the three files `pig.dtx', `pig.ins', and `pig.sty' (the last being +generated from `pig.dtx' using `pig.ins'), the `Base Interpreter' +referring to any `LaTeX-Format', and both `Copyright Holder' and +`Current Maintainer' referring to the person `M. Y. Name'. + +If you do not want the Maintenance section of LPPL to apply to your +Work, change `maintained' above into `author-maintained'. +However, we recommend that you use `maintained', as the Maintenance +section was added in order to ensure that your Work remains useful to +the community even when you can no longer maintain and support it +yourself. + +Derived Works That Are Not Replacements +--------------------------------------- + +Several clauses of the LPPL specify means to provide reliability and +stability for the user community. They therefore concern themselves +with the case that a Derived Work is intended to be used as a +(compatible or incompatible) replacement of the original Work. If +this is not the case (e.g., if a few lines of code are reused for a +completely different task), then clauses 6b and 6d shall not apply. + + +Important Recommendations +------------------------- + + Defining What Constitutes the Work + + The LPPL requires that distributions of the Work contain all the + files of the Work. It is therefore important that you provide a + way for the licensee to determine which files constitute the Work. + This could, for example, be achieved by explicitly listing all the + files of the Work near the copyright notice of each file or by + using a line such as: + + % This work consists of all files listed in manifest.txt. + + in that place. In the absence of an unequivocal list it might be + impossible for the licensee to determine what is considered by you + to comprise the Work and, in such a case, the licensee would be + entitled to make reasonable conjectures as to which files comprise + the Work. +``` diff --git a/src/easydiffraction/report/templates/tex/styles/aps10pt4-2.rtx b/src/easydiffraction/report/templates/tex/styles/aps10pt4-2.rtx new file mode 100644 index 000000000..6c4d194a6 --- /dev/null +++ b/src/easydiffraction/report/templates/tex/styles/aps10pt4-2.rtx @@ -0,0 +1,186 @@ +%% +%% This is file `aps10pt4-2.rtx', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% revtex4-2.dtx (with options: `10pt') +%% +%% This file is part of the APS files in the REVTeX 4 distribution. +%% For the version number, search on the string +%% Original version by David Carlisle +%% Modified by Arthur Ogawa (mailto:arthur_ogawa at sbcglobal dot net) +%% +%% Version (4.2a, unreleased) +%% Modified by Aptara on behalf of American Physical Society and American Institute of Physics +%% +%% Version (4.2b,4.2c) +%% Modified by Mark Doyle, American Physical Society (mailto:revtex at aps.org) +%% +%% Version (4.2d--4.2f) +%% Modified by Phelype Oleinik for the American Physical Society (mailto:phelype.oleinik at latex-project.org) +%% +%% Copyright (c) 2019--2022 American Physical Society. +%% https://journals.aps.org/revtex/ +%% mailto:revtex@aps.org +%% +%% See the REVTeX 4.2 README-REVTEX file for restrictions and more information. +%% +\ProvidesFile{aps10pt4-2} + [2022/06/05 4.2f (https://journals.aps.org/revtex/ for documentation)]% \fileversion +\ifx\undefined\substyle@ext + \def\@tempa{% + \endinput + \GenericWarning{I must be read in by REVTeX! (Bailing out)}% + }% + \expandafter\else + \def\@tempa{}% + \expandafter\fi\@tempa + \class@info{RevTeX pointsize 10pt selected}% +\def\normalsize{% + \@setfontsize\normalsize\@xpt{11.5}% + \abovedisplayskip 10\p@ \@plus2\p@ \@minus5\p@ + \belowdisplayskip \abovedisplayskip + \abovedisplayshortskip \abovedisplayskip + \belowdisplayshortskip \abovedisplayskip + \let\@listi\@listI +}% +\def\small{% + \@setfontsize\small\@ixpt{10.5}% + \abovedisplayskip 8.5\p@ \@plus3\p@ \@minus4\p@ + \belowdisplayskip \abovedisplayskip + \abovedisplayshortskip \z@ \@plus2\p@ + \belowdisplayshortskip 4\p@ \@plus2\p@ \@minus2\p@ + \def\@listi{% + \leftmargin\leftmargini + \topsep 4\p@ \@plus2\p@ \@minus2\p@ + \parsep 2\p@ \@plus\p@ \@minus\p@ + \itemsep \parsep + }% +}% +\def\footnotesize{% + \@setfontsize\footnotesize\@viiipt{9.5pt}% + \abovedisplayskip 6\p@ \@plus2\p@ \@minus4\p@ + \belowdisplayskip \abovedisplayskip + \abovedisplayshortskip \z@ \@plus\p@ + \belowdisplayshortskip 3\p@ \@plus\p@ \@minus2\p@ + \def\@listi{% + \leftmargin\leftmargini + \topsep 3\p@ \@plus\p@ \@minus\p@ + \parsep 2\p@ \@plus\p@ \@minus\p@ + \itemsep \parsep + }% +}% +\def\scriptsize{% + \@setfontsize\scriptsize\@viipt\@viiipt +}% +\def\tiny{% + \@setfontsize\tiny\@vpt\@vipt +}% +\def\large{% + \@setfontsize\large\@xiipt{14pt}% +}% +\def\Large{% + \@setfontsize\Large\@xivpt{18pt}% +}% +\def\LARGE{% + \@setfontsize\LARGE\@xviipt{22pt}% +}% +\def\huge{% + \@setfontsize\huge\@xxpt{25pt}% +}% +\def\Huge{% + \@setfontsize\Huge\@xxvpt{30pt}% +}% +\appdef\setup@hook{% + \twoside@sw{% + \oddsidemargin -20pt + \evensidemargin -20pt + \marginparwidth 107pt + }{% + \oddsidemargin -.25in + \evensidemargin -.25in + \marginparwidth 30pt + }% +}% +\marginparsep 6pt +\topmargin -61pt +\headheight 25pt +\headsep 16pt +\topskip 10pt +\splittopskip\topskip +\footskip 30pt + \textheight = 56pc +\textwidth42.5pc +\columnsep 1.5pc +\columnseprule 0pt +\footnotesep 1pt +\skip\footins 39pt plus 4pt minus 12pt +\def\footnoterule{% + \dimen@\skip\footins\divide\dimen@\tw@ + \kern-\dimen@\hrule width.5in\kern\dimen@ +}% +\floatsep 12pt plus 2pt minus 2pt +\textfloatsep 20pt plus 2pt minus 4pt +\intextsep 12pt plus 2pt minus 2pt +\dblfloatsep 12pt plus 2pt minus 2pt +\dbltextfloatsep 20pt plus 2pt minus 4pt +\@fptop 0pt plus 1fil +\@fpsep 8pt plus 2fil +\@fpbot 0pt plus 1fil +\@dblfptop 0pt plus 1fil +\@dblfpsep 8pt plus 2fil +\@dblfpbot 0pt plus 1fil +\marginparpush 5pt +\parskip 0pt plus 1pt +\parindent 10pt +\emergencystretch8\p@ +\partopsep 2pt plus 1pt minus 1pt +\leftmargini 25pt +\leftmarginii 22pt +\leftmarginiii 18.7pt +\leftmarginiv 17pt +\leftmarginv 10pt +\leftmarginvi 10pt +\def\@listI{% + \leftmargin\leftmargini + \parsep 4\p@ plus2\p@ minus\p@ + \topsep 8\p@ plus2\p@ minus4\p@ + \itemsep 4\p@ plus2\p@ minus\p@ +}% +\labelsep 4pt +\def\@listii{% + \leftmargin\leftmarginii + \labelwidth\leftmarginii + \advance\labelwidth-\labelsep + \topsep 4\p@ plus2\p@ minus\p@ + \parsep 2\p@ plus\p@ minus\p@ + \itemsep \parsep +}% +\def\@listiii{% + \leftmargin\leftmarginiii + \labelwidth\leftmarginiii + \advance\labelwidth-\labelsep + \topsep 2\p@ plus\p@ minus\p@ + \parsep \z@ + \partopsep \p@ plus\z@ minus\p@ + \itemsep \topsep +}% +\def\@listiv{% + \leftmargin\leftmarginiv + \labelwidth\leftmarginiv + \advance\labelwidth-\labelsep +}% +\def\@listv{% + \leftmargin\leftmarginv + \labelwidth\leftmarginv + \advance\labelwidth-\labelsep +}% +\def\@listvi{% + \leftmargin\leftmarginvi + \labelwidth\leftmarginvi + \advance\labelwidth-\labelsep +}% +\endinput +%% +%% End of file `aps10pt4-2.rtx'. diff --git a/src/easydiffraction/report/templates/tex/styles/aps11pt4-2.rtx b/src/easydiffraction/report/templates/tex/styles/aps11pt4-2.rtx new file mode 100644 index 000000000..144ad1365 --- /dev/null +++ b/src/easydiffraction/report/templates/tex/styles/aps11pt4-2.rtx @@ -0,0 +1,178 @@ +%% +%% This is file `aps11pt4-2.rtx', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% revtex4-2.dtx (with options: `11pt') +%% +%% This file is part of the APS files in the REVTeX 4 distribution. +%% For the version number, search on the string +%% Original version by David Carlisle +%% Modified by Arthur Ogawa (mailto:arthur_ogawa at sbcglobal dot net) +%% +%% Version (4.2a, unreleased) +%% Modified by Aptara on behalf of American Physical Society and American Institute of Physics +%% +%% Version (4.2b,4.2c) +%% Modified by Mark Doyle, American Physical Society (mailto:revtex at aps.org) +%% +%% Version (4.2d--4.2f) +%% Modified by Phelype Oleinik for the American Physical Society (mailto:phelype.oleinik at latex-project.org) +%% +%% Copyright (c) 2019--2022 American Physical Society. +%% https://journals.aps.org/revtex/ +%% mailto:revtex@aps.org +%% +%% See the REVTeX 4.2 README-REVTEX file for restrictions and more information. +%% +\ProvidesFile{aps11pt4-2} + [2022/06/05 4.2f (https://journals.aps.org/revtex/ for documentation)]% \fileversion +\ifx\undefined\substyle@ext + \def\@tempa{% + \endinput + \GenericWarning{I must be read in by REVTeX! (Bailing out)}% + }% + \expandafter\else + \def\@tempa{}% + \expandafter\fi\@tempa + \class@info{RevTeX pointsize 11pt selected}% +\def\normalsize{% + \@setfontsize\normalsize\@xipt{13.6}% + \abovedisplayskip 11\p@ \@plus3\p@ \@minus6\p@ + \belowdisplayskip \abovedisplayskip + \abovedisplayshortskip \abovedisplayskip + \belowdisplayshortskip \abovedisplayskip + \let\@listi\@listI +}% +\def\small{% + \@setfontsize\small\@xpt\@xiipt + \abovedisplayskip 10\p@ \@plus2\p@ \@minus5\p@ + \abovedisplayshortskip \z@ \@plus3\p@ + \belowdisplayshortskip 6\p@ \@plus3\p@ \@minus3\p@ + \def\@listi{\leftmargin\leftmargini + \topsep 6\p@ \@plus2\p@ \@minus2\p@ + \parsep 3\p@ \@plus2\p@ \@minus\p@ + \itemsep \parsep + }% + \belowdisplayskip \abovedisplayskip +}% +\def\footnotesize{% + \@setfontsize\footnotesize\@ixpt{11}% + \abovedisplayskip 8\p@ \@plus2\p@ \@minus4\p@ + \abovedisplayshortskip \z@ \@plus\p@ + \belowdisplayshortskip 4\p@ \@plus2\p@ \@minus2\p@ + \def\@listi{\leftmargin\leftmargini + \topsep 4\p@ \@plus2\p@ \@minus2\p@ + \parsep 2\p@ \@plus\p@ \@minus\p@ + \itemsep \parsep + }% + \belowdisplayskip \abovedisplayskip +}% +\def\scriptsize{% + \@setfontsize\scriptsize\@viiipt{9.5}% +}% +\def\tiny{% + \@setfontsize\tiny\@vipt\@viipt +}% +\def\large{% + \@setfontsize\large\@xiipt{14}% +}% +\def\Large{% + \@setfontsize\Large\@xivpt{18}% +}% +\def\LARGE{% + \@setfontsize\LARGE\@xviipt{22}% +}% +\def\huge{% + \@setfontsize\huge\@xxpt{25pt}% +}% +\def\Huge{% + \@setfontsize\Huge\@xxvpt{30pt}% +}% +\appdef\setup@hook{% + \twoside@sw{% + \oddsidemargin 0pt + \evensidemargin 0pt + \marginparwidth 60pt + }{% + \oddsidemargin 0pt + \evensidemargin 0pt + \marginparwidth 44pt + }% +}% +\marginparsep 10pt +\topmargin -37pt +\headheight 12pt +\headsep 25pt +\topskip 10pt +\splittopskip\topskip +\footskip 30pt +\textheight=665.5\p@ +\appdef\setup@hook{% + \tightenlines@sw{% + \def\baselinestretch{1}% + }{% + \def\baselinestretch{1.5}% + }% +}% +\textwidth 468pt +\columnsep 10pt +\columnseprule 0pt +\footnotesep 1pt +\skip\footins 25.25pt plus 4pt minus 12pt +\def\footnoterule{% + \dimen@\skip\footins\divide\dimen@\f@ur + \kern-\dimen@\hrule width.5in\kern\dimen@ +}% +\floatsep 14pt plus 2pt minus 4pt +\textfloatsep 20pt plus 2pt minus 4pt +\intextsep 14pt plus 4pt minus 4pt +\dblfloatsep 14pt plus 2pt minus 4pt +\dbltextfloatsep 20pt plus 2pt minus 4pt +\@fptop 0pt plus 1fil +\@fpsep 10pt plus 2fil +\@fpbot 0pt plus 1fil +\@dblfptop 0pt plus 1fil +\@dblfpsep 10pt plus 2fil% +\@dblfpbot 0pt plus 1fil +\marginparpush 7pt +\parskip 0pt plus 1pt +\parindent 15pt +\emergencystretch8\p@ +\partopsep 3pt plus 2pt minus 2pt +\leftmargini 30pt +\leftmarginii 26pt +\leftmarginiii 22pt +\leftmarginiv 20pt +\leftmarginv 12pt +\leftmarginvi 12pt +\def\@listI{\leftmargin\leftmargini \parsep 5\p@ plus2.5\p@ minus\p@ + \topsep 10\p@ plus4\p@ minus6\p@ + \itemsep 5\p@ plus2.5\p@ minus\p@ +}% +\labelsep 6pt +\def\@listii{\leftmargin\leftmarginii + \labelwidth\leftmarginii\advance\labelwidth-\labelsep + \topsep 5\p@ plus2.5\p@ minus\p@ + \parsep 2.5\p@ plus\p@ minus\p@ + \itemsep \parsep +}% +\def\@listiii{\leftmargin\leftmarginiii + \labelwidth\leftmarginiii\advance\labelwidth-\labelsep + \topsep 2.5\p@ plus\p@ minus\p@ + \parsep \z@ \partopsep \p@ plus\z@ minus\p@ + \itemsep \topsep +}% +\def\@listiv{\leftmargin\leftmarginiv + \labelwidth\leftmarginiv\advance\labelwidth-\labelsep +}% +\def\@listv{\leftmargin\leftmarginv + \labelwidth\leftmarginv\advance\labelwidth-\labelsep +}% +\def\@listvi{\leftmargin\leftmarginvi + \labelwidth\leftmarginvi\advance\labelwidth-\labelsep +}% +\endinput +%% +%% End of file `aps11pt4-2.rtx'. diff --git a/src/easydiffraction/report/templates/tex/styles/aps12pt4-2.rtx b/src/easydiffraction/report/templates/tex/styles/aps12pt4-2.rtx new file mode 100644 index 000000000..7cd8161b1 --- /dev/null +++ b/src/easydiffraction/report/templates/tex/styles/aps12pt4-2.rtx @@ -0,0 +1,178 @@ +%% +%% This is file `aps12pt4-2.rtx', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% revtex4-2.dtx (with options: `12pt') +%% +%% This file is part of the APS files in the REVTeX 4 distribution. +%% For the version number, search on the string +%% Original version by David Carlisle +%% Modified by Arthur Ogawa (mailto:arthur_ogawa at sbcglobal dot net) +%% +%% Version (4.2a, unreleased) +%% Modified by Aptara on behalf of American Physical Society and American Institute of Physics +%% +%% Version (4.2b,4.2c) +%% Modified by Mark Doyle, American Physical Society (mailto:revtex at aps.org) +%% +%% Version (4.2d--4.2f) +%% Modified by Phelype Oleinik for the American Physical Society (mailto:phelype.oleinik at latex-project.org) +%% +%% Copyright (c) 2019--2022 American Physical Society. +%% https://journals.aps.org/revtex/ +%% mailto:revtex@aps.org +%% +%% See the REVTeX 4.2 README-REVTEX file for restrictions and more information. +%% +\ProvidesFile{aps12pt4-2} + [2022/06/05 4.2f (https://journals.aps.org/revtex/ for documentation)]% \fileversion +\ifx\undefined\substyle@ext + \def\@tempa{% + \endinput + \GenericWarning{I must be read in by REVTeX! (Bailing out)}% + }% + \expandafter\else + \def\@tempa{}% + \expandafter\fi\@tempa + \class@info{RevTeX pointsize 12pt selected}% +\def\normalsize{% + \@setfontsize\normalsize\@xiipt{14pt}% + \abovedisplayskip 12\p@ \@plus3\p@ \@minus7\p@ + \belowdisplayskip \abovedisplayskip + \abovedisplayshortskip \z@ plus3\p@ + \belowdisplayshortskip 6.5\p@ \@plus3.5\p@ \@minus3\p@ + \let\@listi\@listI +}% + \def\small{% + \@setfontsize\small\@xipt{14.5pt}% + \abovedisplayskip 8\p@ \@plus3\p@ \@minus6\p@ + \belowdisplayskip \abovedisplayskip + \abovedisplayshortskip \z@ \@plus3\p@ + \belowdisplayshortskip 6.5\p@ \@plus3.5\p@ \@minus3\p@ + \def\@listi{% + \leftmargin\leftmargini + \topsep 9\p@ \@plus3\p@ \@minus5\p@ + \parsep 4.5\p@ \@plus2\p@ \@minus\p@ + \itemsep \parsep + }% +}% + \def\footnotesize{% + \@setfontsize\footnotesize\@xpt{14.5pt}% + \abovedisplayskip 10\p@ \@plus2\p@ \@minus5\p@ + \belowdisplayskip \abovedisplayskip + \abovedisplayshortskip \z@ \@plus3\p@ + \belowdisplayshortskip 6\p@ \@plus3\p@ \@minus3\p@ + \def\@listi{% + \leftmargin\leftmargini + \topsep 6\p@ \@plus2\p@ \@minus2\p@ + \parsep 3\p@ \@plus2\p@ \@minus\p@ + \itemsep \parsep + }% +}% +\def\scriptsize{% + \@setfontsize\scriptsize\@viiipt{9.5pt}% +}% +\def\tiny{% + \@setfontsize\tiny\@vipt{7pt}% +}% +\def\large{% + \@setfontsize\large\@xivpt{18pt}% +}% +\def\Large{% + \@setfontsize\Large\@xviipt{22pt}% +}% +\def\LARGE{% + \@setfontsize\LARGE\@xxpt{25pt}% +}% +\def\huge{% + \@setfontsize\huge\@xxvpt{30pt}% +}% +\let\Huge=\huge +\appdef\setup@hook{% + \twoside@sw{% + \oddsidemargin 0pt + \evensidemargin 0pt + \marginparwidth 60pt + }{% + \oddsidemargin 0pt + \evensidemargin 0pt + \marginparwidth 44pt + }% +}% +\marginparsep 10pt +\topmargin -37pt +\headheight 12pt +\headsep 25pt +\topskip 10pt +\splittopskip\topskip +\footskip 30pt +\textheight=665.5\p@ +\appdef\setup@hook{% + \tightenlines@sw{% + \def\baselinestretch{1}% + }{% + \def\baselinestretch{1.5}% + }% +}% +\textwidth 468pt +\columnsep 10pt +\columnseprule 0pt +\footnotesep 1pt +\skip\footins 25.25pt plus 4pt minus 12pt +\def\footnoterule{% + \dimen@\skip\footins\divide\dimen@\f@ur + \kern-\dimen@\hrule width.5in\kern\dimen@ +}% +\floatsep 14pt plus 2pt minus 4pt +\textfloatsep 20pt plus 2pt minus 4pt +\intextsep 14pt plus 4pt minus 4pt +\dblfloatsep 14pt plus 2pt minus 4pt +\dbltextfloatsep 20pt plus 2pt minus 4pt +\@fptop 0pt plus 1fil +\@fpsep 10pt plus 2fil +\@fpbot 0pt plus 1fil +\@dblfptop 0pt plus 1fil +\@dblfpsep 10pt plus 2fil% +\@dblfpbot 0pt plus 1fil +\marginparpush 7pt +\parskip 0pt plus 1pt +\parindent 15pt +\emergencystretch8\p@ +\partopsep 3pt plus 2pt minus 2pt +\leftmargini 30pt +\leftmarginii 26pt +\leftmarginiii 22pt +\leftmarginiv 20pt +\leftmarginv 12pt +\leftmarginvi 12pt +\def\@listI{\leftmargin\leftmargini \parsep 5\p@ plus2.5\p@ minus\p@ + \topsep 10\p@ plus4\p@ minus6\p@ + \itemsep 5\p@ plus2.5\p@ minus\p@ +}% +\labelsep 6pt +\def\@listii{\leftmargin\leftmarginii + \labelwidth\leftmarginii\advance\labelwidth-\labelsep + \topsep 5\p@ plus2.5\p@ minus\p@ + \parsep 2.5\p@ plus\p@ minus\p@ + \itemsep \parsep +}% +\def\@listiii{\leftmargin\leftmarginiii + \labelwidth\leftmarginiii\advance\labelwidth-\labelsep + \topsep 2.5\p@ plus\p@ minus\p@ + \parsep \z@ \partopsep \p@ plus\z@ minus\p@ + \itemsep \topsep +}% +\def\@listiv{\leftmargin\leftmarginiv + \labelwidth\leftmarginiv\advance\labelwidth-\labelsep +}% +\def\@listv{\leftmargin\leftmarginv + \labelwidth\leftmarginv\advance\labelwidth-\labelsep +}% +\def\@listvi{\leftmargin\leftmarginvi + \labelwidth\leftmarginvi\advance\labelwidth-\labelsep +}% +\endinput +%% +%% End of file `aps12pt4-2.rtx'. diff --git a/src/easydiffraction/report/templates/tex/styles/aps4-2.rtx b/src/easydiffraction/report/templates/tex/styles/aps4-2.rtx new file mode 100644 index 000000000..7d178abe4 --- /dev/null +++ b/src/easydiffraction/report/templates/tex/styles/aps4-2.rtx @@ -0,0 +1,678 @@ +%% +%% This is file `aps4-2.rtx', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% revtex4-2.dtx (with options: `aps') +%% +%% This file is part of the APS files in the REVTeX 4 distribution. +%% For the version number, search on the string +%% Original version by David Carlisle +%% Modified by Arthur Ogawa (mailto:arthur_ogawa at sbcglobal dot net) +%% +%% Version (4.2a, unreleased) +%% Modified by Aptara on behalf of American Physical Society and American Institute of Physics +%% +%% Version (4.2b,4.2c) +%% Modified by Mark Doyle, American Physical Society (mailto:revtex at aps.org) +%% +%% Version (4.2d--4.2f) +%% Modified by Phelype Oleinik for the American Physical Society (mailto:phelype.oleinik at latex-project.org) +%% +%% Copyright (c) 2019--2022 American Physical Society. +%% https://journals.aps.org/revtex/ +%% mailto:revtex@aps.org +%% +%% See the REVTeX 4.2 README-REVTEX file for restrictions and more information. +%% +\NeedsTeXFormat{LaTeX2e}[1996/12/01]% +\ProvidesFile{aps4-2} + [2022/06/05 4.2f (https://journals.aps.org/revtex/ for documentation)]% \fileversion +\ifx\undefined\substyle@ext + \def\@tempa{% + \endinput + \GenericWarning{I must be read in by REVTeX! (Bailing out)}% + }% + \expandafter\else + \def\@tempa{}% + \expandafter\fi\@tempa + \class@info{RevTeX society APS selected}% +\DeclareOption{pra}{\change@journal{pra}}% +\DeclareOption{prb}{\change@journal{prb}}% +\DeclareOption{prc}{\change@journal{prc}}% +\DeclareOption{prd}{\change@journal{prd}}% +\DeclareOption{pre}{\change@journal{pre}}% +\DeclareOption{prl}{\change@journal{prl}}% +\DeclareOption{prab}{\change@journal{prab}}% +\DeclareOption{prper}{\change@journal{prper}}% +\DeclareOption{rmp}{\change@journal{rmp}}% +\DeclareOption{prx}{\change@journal{prx}}% +\DeclareOption{prapplied}{\change@journal{prapplied}}% +\DeclareOption{prmaterials}{\change@journal{prmaterials}}% +\DeclareOption{prfluids}{\change@journal{prfluids}}% +\DeclareOption{physrev}{\change@journal{physrev}}% +\def\adv{AIP Advances}% +\def\ao{Appl.\ Opt.}% +\def\ap{Appl.\ Phys.}% +\def\apl{Appl.\ Phys.\ Lett.}% +\def\apm{Appl.\ Phys.\ Lett.\ Mater.}% +\def\apj{Astrophys.\ J.}% +\def\bell{Bell Syst.\ Tech.\ J.}% +\def\bmf{Biomicrofluidics}% +\def\cha{Chaos}% +\def\jqe{IEEE J.\ Quantum Electron.}% +\def\assp{IEEE Trans.\ Acoust.\ Speech Signal Process.}% +\def\aprop{IEEE Trans.\ Antennas Propag.}% +\def\mtt{IEEE Trans.\ Microwave Theory Tech.}% +\def\iovs{Invest.\ Ophthalmol.\ Vis.\ Sci.}% +\def\jcp{J.\ Chem.\ Phys.}% +\def\jap{J.\ Appl.\ Phys.}% +\def\jmp{J.\ Math.\ Phys.}% +\def\jmo{J.\ Mod.\ Opt.}% +\def\josa{J.\ Opt.\ Soc.\ Am.}% +\def\josaa{J.\ Opt.\ Soc.\ Am.\ A}% +\def\josab{J.\ Opt.\ Soc.\ Am.\ B}% +\def\jpp{J.\ Phys.\ (Paris)}% +\def\jpr{J.\ Phys.\ Chem.\ Ref.\ Data}% +\def\ltp{Low.\ Temp.\ Phys.}% +\def\nat{Nature (London)}% +\def\oc{Opt.\ Commun.}% +\def\ol{Opt.\ Lett.}% +\def\pl{Phys.\ Lett.}% +\def\pop{Phys.\ Plasmas}% +\def\pof{Phys.\ Fluids}% +\def\pra{Phys.\ Rev.\ A}% +\def\prb{Phys.\ Rev.\ B}% +\def\prc{Phys.\ Rev.\ C}% +\def\prd{Phys.\ Rev.\ D}% +\def\pre{Phys.\ Rev.\ E}% +\def\prl{Phys.\ Rev.\ Lett.}% +\def\rmp{Rev.\ Mod.\ Phys.}% +\def\rsi{Rev.\ Sci.\ Instrum.}% +\def\rse{J.\ Renewable Sustainable Energy}% +\def\pspie{Proc.\ Soc.\ Photo-Opt.\ Instrum.\ Eng.}% +\def\sjqe{Sov.\ J.\ Quantum Electron.}% +\def\vr{Vision Res.}% +\def\sd{Structural\ Dynamics}% +\def\jor{J.\ Rheol.}% +\def\cp{AIP\ Conference\ Proceedings}% +\def\@fnsymbol#1{% + \ensuremath{% + \ifcase#1\or + *\or + \dagger\or + \ddagger\or + \mathsection\or + \mathparagraph\or + **\or + \dagger\dagger\or + \ddagger\ddagger\or + \mathsection\mathsection\or + \mathparagraph\mathparagraph\or + ***\or + \dagger\dagger\dagger\or + \ddagger\ddagger\ddagger\or + \mathsection\mathsection\mathsection\or + \mathparagraph\mathparagraph\mathparagraph + \else + \@ctrerr + \fi + }% +}% +\appdef\document@inithook{% + \@ifxundefined\TextOrMath{% + \DeclareRobustCommand\TextOrMath{\@ifmmode{\false@sw}{\true@sw}}% + }{}% +}% +\let\thefootnote@latex\thefootnote +\clo@groupedaddress +\renewenvironment{titlepage}{% + \let\wastwocol@sw\twocolumn@sw + \onecolumngrid + \newpage + \thispagestyle{titlepage}% + \c@page\z@ +}{% + \wastwocol@sw{\twocolumngrid}{\newpage}% +}% +\def\frontmatter@abstractheading{% + \preprintsty@sw{% + \begingroup + \centering\large + \abstractname + \par + \endgroup + }{}% +}% +\def\frontmatter@abstractwidth{400\p@}% +\def\frontmatter@abstractfont{% + \small + \parindent1em\relax + \adjust@abstractwidth +}% +\def\adjust@abstractwidth{% + \dimen@\textwidth\advance\dimen@-\frontmatter@abstractwidth + \divide\dimen@\tw@ + \galley@sw{% + \advance\rightskip\tw@\dimen@ + }{% + \advance\leftskip\dimen@ + \advance\rightskip\dimen@ + }% + \@totalleftmargin\leftskip +}% +\@booleanfalse\preprintsty@sw +\@booleanfalse\titlepage@sw +\appdef\setup@hook{% + \preprintsty@sw{% + \ps@preprint + \def\frontmatter@abstractwidth{\textwidth}% + \def\frontmatter@affiliationfont{\it}% + \let\section\section@preprintsty + \let\subsection\subsection@preprintsty + \let\subsubsection\subsubsection@preprintsty + }{% + \ps@article + }% +}% +\def\frontmatter@authorformat{% + \skip@\@flushglue + \@flushglue\z@ plus.3\hsize\relax + \centering + \advance\baselineskip\p@ + \parskip11.5\p@\relax + \@flushglue\skip@ +}% +\def\frontmatter@above@affilgroup{% +}% +\def\frontmatter@above@affiliation@script{% + \skip@\@flushglue + \@flushglue\z@ plus.3\hsize\relax + \centering + \@flushglue\skip@ + \addvspace{3.5\p@}% +}% +\def\frontmatter@above@affiliation{% + \preprintsty@sw{}{% + }% +}% +\def\frontmatter@affiliationfont{% + \small\it +}% +\def\frontmatter@collaboration@above{% + \preprintsty@sw{% + }{% + \parskip1.5\p@\relax + }% +}% +\def\frontmatter@setup{% + \normalfont +}% +\def\frontmatter@title@above{\addvspace{6\p@}}% +\def\frontmatter@title@format{\large\bfseries\centering\parskip\z@skip}% +\def\frontmatter@title@below{}% +\def\@author@parskip{3\p@}% +\def\frontmatter@makefnmark{% + \@textsuperscript{% + \normalfont\@thefnmark + }% +}% +\def\frontmatter@authorbelow{% + \addvspace{3\p@}% +}% +\def\frontmatter@RRAP@format{% + \small + \centering + \everypar{\hbox\bgroup(\@gobble@leavemode@uppercase}% + \def\par{\@ifvmode{}{\unskip)\egroup\@@par}}% +}% +\def\punct@RRAP{;\egroup\ \hbox\bgroup}% +\def\@gobble@leavemode@uppercase#1#2{\expandafter\MakeTextUppercase}% +\def\frontmatter@PACS@format{% + \addvspace{11\p@}% + \footnotesize + \adjust@abstractwidth + \parindent\z@ + \parskip\z@skip + \samepage +}% +\def\frontmatter@keys@format{% + \footnotesize + \adjust@abstractwidth + \parindent\z@ + \samepage +}% +\def\ps@titlepage{% + \def\@oddhead{% + \hfill + \preprint@sw{% + \expandafter\produce@preprints\expandafter{\@preprint}% + }{}% + }% + \let\@evenhead\@oddhead + \def\@oddfoot{% + \hb@xt@\z@{\byrevtex\hss}% + \hfil + \preprintsty@sw{\thepage}{}% + \quad\checkindate + \hfil + }% + \let\@evenfoot\@oddfoot +}% +\def\byrevtex{\byrevtex@sw{Typeset by REV\TeX}{}}% +\def\produce@preprints#1{% + \vtop to \z@{% + \def\baselinestretch{1}% + \small + \let\preprint\preprint@count + \count@\z@ + #1% + \@ifnum{\count@>\tw@}{% + \hbox{% + \let\preprint\preprint@hlist + #1% + \setbox\z@\lastbox + }% + }{% + \let\preprint\preprint@cr + \halign{\hfil##\cr#1\crcr}% + \par + \vss + }% + }% +}% +\def\preprint@cr#1{#1\cr}% +\def\preprint@count#1{\advance\count@\@ne}% +\def\preprint@hlist#1{#1\hbox{, }}% +\def\@seccntformat#1{\csname the#1\endcsname.\quad}% +\def\@hang@from#1#2#3{#1#2#3}% +\def\section{% + \@startsection + {section}% + {1}% + {\z@}% + {0.8cm \@plus1ex \@minus .2ex}% + {0.5cm}% + {% + \normalfont\small\bfseries + \centering + }% +}% +\def\@hangfrom@section#1#2#3{\@hangfrom{#1#2}\MakeTextUppercase{#3}}% +\def\@hangfroms@section#1#2{#1\MakeTextUppercase{#2}}% +\def\subsection{% + \@startsection + {subsection}% + {2}% + {\z@}% + {.8cm \@plus1ex \@minus .2ex}% + {.5cm}% + {% + \normalfont\small\bfseries + \centering + }% +}% +\def\subsubsection{% + \@startsection + {subsubsection}% + {3}% + {\z@}% + {.8cm \@plus1ex \@minus .2ex}% + {.5cm}% + {% + \normalfont\small\itshape + \centering + }% +}% +\def\paragraph{% + \@startsection + {paragraph}% + {4}% + {\parindent}% + {\z@}% + {-1em}% + {\normalfont\normalsize\itshape}% +}% +\def\subparagraph{% + \@startsection + {subparagraph}% + {5}% + {\parindent}% + {3.25ex \@plus1ex \@minus .2ex}% + {-1em}% + {\normalfont\normalsize\bfseries}% +}% +\def\section@preprintsty{% + \@startsection + {section}% + {1}% + {\z@}% + {0.8cm \@plus1ex \@minus .2ex}% + {0.5cm}% + {% + \normalfont\small\bfseries + }% +}% +\def\subsection@preprintsty{% + \@startsection + {subsection}% + {2}% + {\z@}% + {.8cm \@plus1ex \@minus .2ex}% + {.5cm}% + {% + \normalfont\small\bfseries + }% +}% +\def\subsubsection@preprintsty{% + \@startsection + {subsubsection}% + {3}% + {\z@}% + {.8cm \@plus1ex \@minus .2ex}% + {.5cm}% + {% + \normalfont\small\itshape + }% +}% +\let\frontmatter@footnote@produce\frontmatter@footnote@produce@footnote +\def\@pnumwidth{1.55em}% +\def\@tocrmarg {2.55em}% +\def\@dotsep{2}% +\def\ltxu@dotsep{4.5pt}% +\setcounter{tocdepth}{3}% +\def\tableofcontents{% + \addtocontents{toc}{\string\tocdepth@munge}% + \print@toc{toc}% + \addtocontents{toc}{\string\tocdepth@restore}% +}% +\def\tocdepth@munge{% + \let\l@section@saved\l@section + \let\l@section\@gobble@tw@ +}% +\def\@gobble@tw@#1#2{}% +\def\tocdepth@restore{% + \let\l@section\l@section@saved +}% +\def\l@part#1#2{\addpenalty{\@secpenalty}% + \begingroup + \set@tocdim@pagenum\@tempboxa{#2}% + \parindent \z@ + \rightskip\tocleft@pagenum plus 1fil\relax + \skip@\parfillskip\parfillskip\z@ + \addvspace{2.25em plus\p@}% + \large \bf % + \leavevmode\ignorespaces#1\unskip\nobreak\hskip\skip@ + \hb@xt@\rightskip{\hfil\unhbox\@tempboxa}\hskip-\rightskip\hskip\z@skip + \par + \nobreak % + \endgroup +}% +\def\tocleft@{\z@}% +\def\tocdim@min{5\p@}% +\def\l@section{% + \l@@sections{}{section}% Implicit #3#4 +}% +\def\l@f@section{% + \addpenalty{\@secpenalty}% + \addvspace{1.0em plus\p@}% + %\bf +}% +\def\l@subsection{% + \l@@sections{section}{subsection}% Implicit #3#4 +}% +\def\l@subsubsection{% + \l@@sections{subsection}{subsubsection}% Implicit #3#4 +}% +\def\l@paragraph#1#2{}% +\def\l@subparagraph#1#2{}% +\let\toc@pre\toc@pre@auto +\let\toc@post\toc@post@auto +\@booleanfalse\raggedcolumn@sw +\def\tableft@skip@float{\z@ plus\hsize}% +\def\tabmid@skip@float{\@flushglue}% +\def\tabright@skip@float{\z@ plus\hsize}% +\def\array@row@pre@float{\hline\hline\noalign{\vskip\doublerulesep}}% +\def\array@row@pst@float{\noalign{\vskip\doublerulesep}\hline\hline}% +\long\def\@makefntext#1{% + \def\baselinestretch{1}% + \leftskip1em% + \parindent1em% + \noindent + \nobreak\hskip-\leftskip + \hb@xt@\leftskip{% + \hss\@makefnmark\ % + }% + #1% + \par +}% +\long\def\frontmatter@makefntext#1{% + \def\baselinestretch{1}% + \leftskip1em% + \parindent1em% + \noindent + \nobreak\hskip-\leftskip + \Hy@raisedlink{\hyper@anchorstart{frontmatter.\expandafter\the\csname c@\@mpfn\endcsname}\hyper@anchorend}% + \hb@xt@\leftskip{% + \hss\@makefnmark\ % + }% + #1% + \par +}% +\prepdef\appendix{% + \par + \let\@hangfrom@section\@hangfrom@appendix + \let\@sectioncntformat\@appendixcntformat +}% +\def\@hangfrom@appendix#1#2#3{% + #1% + \@if@empty{#2}{% + #3% + }{% + #2\@if@empty{#3}{}{:\ #3}% + }% +}% +\def\@hangfroms@appendix#1#2{% + #1#2% +}% +\def\@appendixcntformat#1{\appendixname\ \csname the#1\endcsname}% +\@booleanfalse\authoryear@sw +\appdef\setup@hook{% + \bibpunct{[}{]}{,}{n}{}{,}% +}% + \def\pre@bibdata{\jobname\bibdata@app}% +\appdef\setup@hook{% + \def\bibsection{% + \par + \onecolumngrid@push + \begingroup + \baselineskip26\p@ + \bib@device{\textwidth}{245.5\p@}% + \endgroup + \nobreak\@nobreaktrue + \addvspace{19\p@}% + \par + \onecolumngrid@pop + }% +}% +\def\bib@device#1#2{% + \hb@xt@\z@{% + \hb@xt@#1{% + \hfil + \phantomsection + \addcontentsline {toc}{section}{\protect\numberline{}\refname}% + \hb@xt@#2{% + \skip@\z@\@plus-1fil\relax + \leaders\hrule height.25 \p@ depth.25 \p@ \hskip\z@\@plus1fil + \hskip\skip@ + \hskip\z@\@plus0.125fil\leaders\hrule height.375\p@ depth.375\p@ \hskip\z@\@plus0.75fil \hskip\z@\@plus0.125fil + \hskip\skip@ + \hskip\z@\@plus0.25 fil\leaders\hrule height.5 \p@ depth.5 \p@ \hskip\z@\@plus0.5 fil \hskip\z@\@plus0.25 fil + \hskip\skip@ + \hskip\z@\@plus0.375fil\leaders\hrule height.625\p@ depth.625\p@ \hskip\z@\@plus0.25fil \hskip\z@\@plus0.375fil + % \hskip\skip@ + % \hfil + }% + \hfil + }% + \hss + }% +}% +\appdef\setup@hook{% + \let\bibpreamble\@empty + \bibsep\z@\relax + \def\newblock{\ }% +}% +\appdef\setup@hook{% + \def\bibfont{% + \small + \@clubpenalty\clubpenalty + }% +}% +\newenvironment{theindex}{% + \columnseprule \z@ + \columnsep 35\p@ + \c@secnumdepth-\maxdimen + \onecolumngrid@push + \section{\indexname}% + \thispagestyle{plain}% + \parindent\z@ + \parskip\z@ plus.3\p@\relax + \let\item\@idxitem + \onecolumngrid@pop +}{% +}% +\def\@idxitem{\par\hangindent 40\p@}% +\def\subitem{\par\hangindent 40\p@ \hspace*{20\p@}}% +\def\subsubitem{\par\hangindent 40\p@ \hspace*{30\p@}}% +\def\indexspace{\par \vskip 10\p@ plus5\p@ minus3\p@\relax}% +\def\@journal@default{pra}% +\def\@pointsize@default{10}% +\def\rtx@apspra{% + \class@info{APS journal PRA selected}% +}% +\def\rtx@apsprb{% + \class@info{APS journal PRB selected}% +}% +\def\rtx@apsprc{% + \class@info{APS journal PRC selected}% +}% +\def\rtx@apsprd{% + \class@info{APS journal PRD selected}% +}% +\def\rtx@apspre{% + \class@info{APS journal PRE selected}% +}% +\def\rtx@apsprl{% + \class@info{APS journal PRL selected}% + \let\frontmatter@footnote@produce\frontmatter@footnote@produce@endnote + \@booleanfalse\acknowledgments@sw + \appdef\setup@hook{% + \def\bibsection{% + \par + \begingroup + \baselineskip26\p@ + \bib@device{\hsize}{72\p@}% + \endgroup + \nobreak\@nobreaktrue + \addvspace{19\p@}% + }% + }% +\appdef\setup@hook{% + \lengthcheck@sw{% + \RequirePackage{times}% + }{}% +}% + \c@secnumdepth=-\maxdimen + \appdef\setup@hook{% + \@ifnum{\@pointsize=10\relax}{% + \lengthcheck@sw{% + \def\large{% + \@setfontsize\large{12.5}{14\p@}% + }% + \def\normalsize{% + \@setfontsize\normalsize{10.5}\@xiipt + \abovedisplayskip 6\p@ \@plus6\p@ \@minus5\p@ + \belowdisplayskip \abovedisplayskip + \abovedisplayshortskip \abovedisplayskip + \belowdisplayshortskip \abovedisplayskip + \let\@listi\@listI + }% + \def\small{% + \@setfontsize\small{9.5}\@xipt + \abovedisplayskip 5\p@ \@plus5\p@ \@minus4\p@ + \belowdisplayskip \abovedisplayskip + \abovedisplayshortskip \abovedisplayskip + \belowdisplayshortskip \abovedisplayskip + \let\@listi\@listI + }% + \DeclareMathSizes{12.5}{12.5}{9}{6}% + \DeclareMathSizes{10.5}{10.5}{7.5}{5}% + \DeclareMathSizes{9.5}{9.5}{7.0}{5}% + }{% + \def\normalsize{% + \@setfontsize\normalsize\@xpt\@xiipt + \abovedisplayskip 10\p@ \@plus2\p@ \@minus5\p@ + \belowdisplayskip \abovedisplayskip + \abovedisplayshortskip \abovedisplayskip + \belowdisplayshortskip \abovedisplayskip + \let\@listi\@listI + }% + }% + }{}% + }% + \textheight = 694.0\p@ +}% +\def\rtx@apsprper{% + \class@info{APS journal PRPER selected}% +}% +\def\rtx@apsprab{% + \class@info{APS journal PRAB selected}% +}% +\def\rtx@apsprx{% + \class@info{APS journal PRX selected}% +}% +\def\rtx@apsprapplied{% + \class@info{APS journal PRApplied selected}% +}% +\def\rtx@apsprmaterials{% + \class@info{APS journal PRMaterials selected}% +}% +\def\rtx@apsprfluids{% + \class@info{APS journal PRFluids selected}% + \@booleanfalse\titlepage@sw +}% +\def\rtx@apsphysrev{% + \class@info{APS unified Physical Review journal style selected}% +}% +\@booleantrue\footinbib@sw +\appdef\@bibdataout@rev{\@bibdataout@aps}% +\def\@bibdataout@aps{% + \immediate\write\@bibdataout{% + @CONTROL{% + apsrev42Control% + \longbibliography@sw{% + ,author="08",editor="1",pages="0",title="0",year="1"% + }{% + ,author="08",editor="1",pages="0",title="",year="1"% + }% + }% + }% + \if@filesw + \immediate\write\@auxout{\string\citation{apsrev42Control}}% + \fi +}% +\let\place@bibnumber\place@bibnumber@inl +\def\@bibstyle{apsrev\substyle@post}% +\appdef\setup@hook{% + \@ifx{\place@bibnumber\place@bibnumber@sup}{% + \footinbib@sw{}{% + \class@warn{Citations are superscript numbers: footnotes must be endnotes; changing to that configuration}% + \@booleantrue\footinbib@sw + }% + }{}% +}% +\endinput +%% +%% End of file `aps4-2.rtx'. diff --git a/src/easydiffraction/report/templates/tex/styles/harvard.sty b/src/easydiffraction/report/templates/tex/styles/harvard.sty new file mode 100644 index 000000000..9a4c64623 --- /dev/null +++ b/src/easydiffraction/report/templates/tex/styles/harvard.sty @@ -0,0 +1,270 @@ +%% harvard.sty - harvard bibliography style Version 2.0.5 +%% Author: Peter Williams peterw@archsci.arch.su.edu.au +%% Copyright: Peter Williams 1994 +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{harvard} +\RequirePackage{ifthen} +\IfFileExists{html.sty}{\RequirePackage{html} +\newcommand{\harvardurl}[1]{\htmladdnormallink*{\textbf{URL:} \textit{##1}}{##1}} +}{ +\newcommand{\harvardurl}[1]{\textbf{URL:} \textit{##1}} +} +\DeclareOption{full}{\citationmode{full}} +\DeclareOption{abbr}{\citationmode{abbr}} +\DeclareOption{default}{\citationmode{default}} +\DeclareOption{agsmcite}{\citationstyle{agsm}} +\DeclareOption{dcucite}{\citationstyle{dcu}} +\DeclareOption{round}{\harvardparenthesis{round}\harvardyearparenthesis{round}} +\DeclareOption{curly}{\harvardparenthesis{curly}\harvardyearparenthesis{curly}} +\DeclareOption{angle}{\harvardparenthesis{angle}\harvardyearparenthesis{angle}} +\DeclareOption{square}{\harvardparenthesis{square}\harvardyearparenthesis{square}} +\DeclareOption{none}{\harvardparenthesis{none}\harvardyearparenthesis{none}} +\DeclareOption*{ + \global\edef\HAR@tmp{\CurrentOption} +%% Berwin A. Turlach + \AtEndDocument{\bibliographystyle{\HAR@tmp}} +} + +%% Kristoffer H. Rose 1995/03/01: +%% do not expand macros in citations: put definitions on .aux file instead. +{\catcode`\:=12 \catcode`\-=12 \catcode`\>=12 \catcode`\<=12 % + \gdef\codeof#1{\expandafter\codeof@\meaning#1<-:}% + \gdef\codeof@#1:->#2<-:{#2}} + +\def\harvardpreambletext{\catcode`\#=12 \harvardpreambletext@} +\def\harvardpreambletext@#1{\def\next{#1}\catcode`\#=6 % + \immediate\write\@auxout{\string\harvardpreambledefs{% + \string\AtBeginDocument{\codeof\next}}}} + +\def\harvardpreambledefs#1{#1\gdef\harvardpreambledefs##1{}} + +\newcommand{\harvarditem}[4][\null]{\item[]% +\if@filesw{ \def\protect##1{\string ##1\space}% +\ifthenelse{\equal{#1}{\null}} + {\def\next{{#4}{#2}{#2}{#3}}} + {\def\next{{#4}{#2}{#1}{#3}}} +\immediate\write\@auxout{\string\harvardcite\codeof\next}% +}\fi% +\protect\hspace*{-\labelwidth}\protect\hspace*{-\labelsep}\ignorespaces% +} + +\newcommand{\harvardcite}[4]{ + \global\@namedef{HAR@fn@#1}{#2} + \global\@namedef{HAR@an@#1}{#3} + \global\@namedef{HAR@yr@#1}{#4} + \global\@namedef{HAR@df@#1}{\csname HAR@fn@#1\endcsname} +} + +\newcommand{\citationmode}[1]{ + \renewcommand{\HAR@citemode}{\csname HAR@cite@#1\endcsname} +} + +\newcommand{\HAR@cite@full}{HAR@fn@} +\newcommand{\HAR@cite@abbr}{HAR@an@} +\newcommand{\HAR@cite@default}{HAR@df@} +\newcommand{\HAR@citemode}{\HAR@cite@default} + +\newcommand{\HAR@citetoaux}[1]{% + \if@filesw\immediate\write\@auxout{\string\citation{#1}}\fi% +} + +\newcommand{\HAR@checkdef}[2]{\@ifundefined{HAR@df@#1}% + {\textbf{?}\@warning{Citation '#1' on page \thepage \space undefined}}% + {#2}% +} + +\newcommand{\HAR@dolist}[2]{\def\@citea{\null}\@for\@citeb:=#1\do% +{\@citea\def\@citea{\HAR@hisep\penalty\@m\ }\HAR@checkdef{\@citeb}% +{#2{\@citeb}\HAR@hysep\penalty\@m\ % +\HAR@year{\@citeb}\HAR@setd{\@citeb}}}% +} + +\def\@enamedef#1{\expandafter\def\csname #1\expandafter\endcsname\expandafter} +\newcommand{\HAR@name}[1]{\csname \HAR@citemode#1\endcsname} +\newcommand{\HAR@fname}[1]{\csname HAR@fn@#1\endcsname} +\newcommand{\HAR@aname}[1]{\csname HAR@an@#1\endcsname} +\newcommand{\HAR@year}[1]{\csname HAR@yr@#1\endcsname} +\newcommand{\HAR@setd}[1]{% +\global\@enamedef{HAR@df@#1}{\csname HAR@an@#1\endcsname}% +} + +%% Berwin A. Turlach +\global\@namedef{HAR@df@*}{\csname HAR@fn@*\endcsname} +\renewcommand{\nocite}[1]{\HAR@citetoaux{#1}% +\@for\@citeb:=#1\do% +{\HAR@checkdef{\@citeb}{}}}% + +\renewcommand{\cite}{\@ifstar{\@ifstar{\HAR@acite}{\HAR@fcite}}{\HAR@dcite}} + +\newcommand{\HAR@dcite}[2][\null]{\HAR@citetoaux{#2}% +{\harvardleft\HAR@dolist{#2}{\HAR@name}\ifthenelse{\equal{#1}{\null}}% + {}{, #1}\harvardright}% +} + +\newcommand{\HAR@acite}[2][\null]{\HAR@citetoaux{#2}% +{\harvardleft\HAR@dolist{#2}{\HAR@aname}\ifthenelse{\equal{#1}{\null}}% + {}{, #1}\harvardright}% +} + +\newcommand{\HAR@fcite}[2][\null]{\HAR@citetoaux{#2}% +{\harvardleft\HAR@dolist{#2}{\HAR@fname}\ifthenelse{\equal{#1}{\null}}% + {}{, #1}\harvardright}% +} + +\newcommand{\citeaffixed}{\@ifstar{\@ifstar{\HAR@aciteaff}{\HAR@fciteaff}}% +{\HAR@dciteaff}% +} + +\newcommand{\HAR@fciteaff}[3][\null]{\HAR@citetoaux{#2}% +{\harvardleft#3\ \HAR@dolist{#2}{\HAR@fname}\ifthenelse{\equal{#1}{\null}}% + {}{, #1}\harvardright}% +} + +\newcommand{\HAR@aciteaff}[3][\null]{\HAR@citetoaux{#2}% +{\harvardleft#3\ \HAR@dolist{#2}{\HAR@aname}\ifthenelse{\equal{#1}{\null}}% + {}{, #1}\harvardright}% +} + +\newcommand{\HAR@dciteaff}[3][\null]{\HAR@citetoaux{#2}% +{\harvardleft#3\ \HAR@dolist{#2}{\HAR@name}\ifthenelse{\equal{#1}{\null}}% + {}{, #1}\harvardright}% +} + +\newcommand{\citeasnoun}{\@ifstar{\@ifstar{\HAR@aciteasn}{\HAR@fciteasn}}% +{\HAR@dciteasn}% +} + +\newcommand{\HAR@fciteasn}[2][\null]{\HAR@citetoaux{#2}\HAR@checkdef{#2}{% +{\HAR@fname{#2}\ \harvardyearleft\HAR@year{#2}\ifthenelse{\equal{#1}{\null}} + {}{, #1}\harvardyearright}\HAR@setd{#2}}% +} + +\newcommand{\HAR@aciteasn}[2][\null]{\HAR@citetoaux{#2}\HAR@checkdef{#2}{% +{\HAR@aname{#2}\ \harvardyearleft\HAR@year{#2}\ifthenelse{\equal{#1}{\null}} + {}{, #1}\harvardyearright}\HAR@setd{#2}}% +} + +\newcommand{\HAR@dciteasn}[2][\null]{\HAR@citetoaux{#2}\HAR@checkdef{#2}{% +{\HAR@name{#2}\ \harvardyearleft\HAR@year{#2}\ifthenelse{\equal{#1}{\null}} + {}{, #1}\harvardyearright}\HAR@setd{#2}}% +} + +\newcommand{\possessivecite}{\@ifstar{\@ifstar{\HAR@acitepos}{\HAR@fcitepos}}% +{\HAR@dcitepos}% +} + +\newcommand{\HAR@fcitepos}[2][\null]{\HAR@citetoaux{#2}\HAR@checkdef{#2}{% +{\HAR@fname{#2}'s \harvardyearleft\HAR@year{#2}\ifthenelse{\equal{#1}{\null}} + {}{, #1}\harvardyearright}\HAR@setd{#2}}% +} + +\newcommand{\HAR@acitepos}[2][\null]{\HAR@citetoaux{#2}\HAR@checkdef{#2}{% +{\HAR@aname{#2}'s \harvardyearleft\HAR@year{#2}\ifthenelse{\equal{#1}{\null}} + {}{, #1}\harvardyearright}\HAR@setd{#2}}% +} + +\newcommand{\HAR@dcitepos}[2][\null]{\HAR@citetoaux{#2}\HAR@checkdef{#2}{% +{\HAR@name{#2}'s \harvardyearleft\HAR@year{#2}\ifthenelse{\equal{#1}{\null}} + {}{, #1}\harvardyearright}\HAR@setd{#2}}% +} + +\newcommand{\citename}{\@ifstar{\@ifstar{\HAR@acitenam}\HAR@fcitenam}% +{\HAR@dcitenam}% +} + +\newcommand{\HAR@fcitenam}[2][\null]{\HAR@citetoaux{#2}\HAR@checkdef{#2}{% +{\HAR@fname{#2}\ifthenelse{\equal{#1}{\null}} + {}{\ \harvardleft#1\harvardright}}}% +} + +\newcommand{\HAR@acitenam}[2][\null]{\HAR@citetoaux{#2}\HAR@checkdef{#2}{% +{\HAR@aname{#2}\ifthenelse{\equal{#1}{\null}} + {}{\ \harvardleft#1\harvardright}}}% +} + +\newcommand{\HAR@dcitenam}[2][\null]{\HAR@citetoaux{#2}\HAR@checkdef{#2}{% +{\HAR@name{#2}\ifthenelse{\equal{#1}{\null}} + {}{\ \harvardleft#1\harvardright}}}% +} + +\newcommand{\citeyear}{\@ifstar{\HAR@citeyrnb}{\HAR@citeyr}} + +\newcommand{\HAR@citeyrnb}[2][\null]{\HAR@citetoaux{#2}% +{\def\@citea{\null}\@for\@citeb:=#2\do% +{\@citea\def\@citea{\HAR@hisep\penalty\@m\ }\HAR@checkdef{\@citeb}% +{\HAR@year{\@citeb}}}\ifthenelse{\equal{#1}{\null}}% +{}{, #1}}% +} + +\newcommand{\HAR@citeyr}[2][\null]{\HAR@citetoaux{#2}% +{\harvardleft\def\@citea{\null}\@for\@citeb:=#2\do% +{\@citea\def\@citea{\HAR@hisep\penalty\@m\ }\HAR@checkdef{\@citeb}% +{\HAR@year{\@citeb}}}\ifthenelse{\equal{#1}{\null}}% +{}{, #1}\harvardright}% +} + +\newcommand{\HAR@hysep@apsr}{\null} +\newcommand{\HAR@hisep@apsr}{;} +\newcommand{\HAR@hysep@agsm}{\null} +\newcommand{\HAR@hisep@agsm}{,} +\newcommand{\HAR@hysep@dcu}{,} +\newcommand{\HAR@hisep@dcu}{;} +\newcommand{\HAR@and@agsm}{\&} +\newcommand{\HAR@and@dcu}{and} +\newcommand{\HAR@and@apsr}{and} +\newcommand{\HAR@hysep}{\HAR@hysep@agsm} +\newcommand{\HAR@hisep}{\HAR@hisep@agsm} +\newcommand{\harvardand}{\HAR@and@agsm} +\newcommand{\citationstyle}[1]{% + \renewcommand{\HAR@hysep}{\csname HAR@hysep@#1\endcsname} + \renewcommand{\HAR@hisep}{\csname HAR@hisep@#1\endcsname} + \renewcommand{\harvardand}{\csname HAR@and@#1\endcsname} +} + +\newcommand{\HAR@bl@round}{(} +\newcommand{\HAR@br@round}{)} +\newcommand{\HAR@bl@square}{[} +\newcommand{\HAR@br@square}{]} +\newcommand{\HAR@bl@curly}{\{} +\newcommand{\HAR@br@curly}{\}} +\newcommand{\HAR@bl@angle}{$<$} +\newcommand{\HAR@br@angle}{$>$} +\newcommand{\HAR@bl@none}{} +\newcommand{\HAR@br@none}{} +\newcommand{\harvardleft}{\HAR@bl@round} +\newcommand{\harvardright}{\HAR@br@round} +\newcommand{\harvardparenthesis}[1]{ + \renewcommand{\harvardleft}{\csname HAR@bl@#1\endcsname} + \renewcommand{\harvardright}{\csname HAR@br@#1\endcsname} + \harvardyearparenthesis{#1} +} + +\newcommand{\harvardyearleft}{\HAR@bl@round} +\newcommand{\harvardyearright}{\HAR@br@round} +\newcommand{\harvardyearparenthesis}[1]{ + \renewcommand{\harvardyearleft}{\csname HAR@bl@#1\endcsname} + \renewcommand{\harvardyearright}{\csname HAR@br@#1\endcsname} +} + +\newcommand{\HAR@checkcitations}[4]{ + \def\HAR@tempa{#2}\expandafter + \ifx \csname HAR@fn@#1\endcsname \HAR@tempa + \def\HAR@tempa{#3}\expandafter + \ifx \csname HAR@an@#1\endcsname \HAR@tempa + \def\HAR@tempa{#4}\expandafter + \ifx \csname HAR@yr@#1\endcsname \HAR@tempa + \else + \@tempswatrue + \fi + \else + \@tempswatrue + \fi + \else + \@tempswatrue + \fi +} + +\AtEndDocument{\renewcommand{\harvardcite}{\HAR@checkcitations}} + +\ExecuteOptions{agsm,agsmcite,default,round} +\ProcessOptions* diff --git a/src/easydiffraction/report/templates/tex/styles/iucrjournals.cls b/src/easydiffraction/report/templates/tex/styles/iucrjournals.cls new file mode 100644 index 000000000..db4138764 --- /dev/null +++ b/src/easydiffraction/report/templates/tex/styles/iucrjournals.cls @@ -0,0 +1,385 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% This is the IUCr LaTeX2e class macro file iucrjournals.cls +% This work has been dedicated to the public domain +% License: CC0 1.0 Universal +% https://creativecommons.org/publicdomain/zero/1.0/ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Usage: +% \documentclass{iucrjournals} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\NeedsTeXFormat{LaTeX2e} +\ProvidesClass{iucrjournals} +\date{} + +\LoadClass[11pt]{article} + +\RequirePackage{lineno} +\linenumbers + +\RequirePackage[parfill]{parskip} +\RequirePackage{setspace} +\onehalfspacing + +\RequirePackage[margin=1in]{geometry} +\RequirePackage{float} +\RequirePackage{graphicx} + +\RequirePackage{xcolor} +\RequirePackage{hyperref} +\hypersetup{colorlinks = true, allcolors = blue} + +\RequirePackage{authblk} +\RequirePackage{booktabs} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% bibtex: +\RequirePackage{harvard} +% The following hack addresses a problem with bibtex failing when +% there is whitespace in a comma-separated list of labels passed as +% an argument to \cite - see +% http://tex.stackexchange.com/questions/4517/cite-that-tolerates-whitespace +% +\let\OLDcite\cite +\def\tok@scan#1{% + \ifx#1\relax + \let\tok@next\relax + \else + \edef\my@list{\my@list#1}% + \let\tok@next\tok@scan + \fi + \tok@next +} +\newcommand{\@strip}[2]{% + \def\my@list{}\tok@scan#2\relax\let#1\my@list} +\renewcommand{\cite}[1]{\@strip\@args{#1}\OLDcite\@args} + +% The iucr.bst BibTeX bibliography style requires the harvard.sty package. +% IUCr citations are similar to the "dcu" style within harvard.sty, but +% require the conjunction to be changed to '&'; also abbreviated citations +% ('et al.') are always used. +\citationstyle{dcu} % (Doe, 1990; Soape, 1991) +\renewcommand{\harvardand}{\&} % (Doe & Soape, 1990) +\citationmode{abbr} % (Doe et al., 1990) +\bibliographystyle{iucr} +\renewcommand{\harvardurl}{\relax} % incompatibility with hyperref +% \newblock is output by BibTeX to separate logical sections of a reference +% listing. It serves no useful purpose, and can cause extra spacing to +% intrude +\let\newblock\relax +% A complication of the preferred style of citation of IUCr journals +% is that the volume number for Acta includes the section label, +% which is NOT printed in bold; to accommodate this, \volbf is defined +% and generated by iucr.bst. \volbf needs to test just the first character +% of the volume number +\gdef\@A@{A}% +\gdef\@B@{B}% +\gdef\@C@{C}% +\gdef\@D@{D}% +\gdef\@E@{E}% +\gdef\@F@{F}% +\gdef\@J@{J}% +\gdef\@M@{M}% +\gdef\@S@{S}% +\newif\iffirst\firsttrue +\def\volbf#1{% + {\firsttrue\v@lbf#1\end}% +} +\def\v@lbf#1{% + \ifx#1\end + \let\next=\relax% + \else + \let\next=\v@lbf\iffirst + \def\t@st{#1} + \if\t@st\@A@{\rmfamily{#1}}\else + \if\t@st\@B@{\rmfamily{#1}}\else + \if\t@st\@C@{\rmfamily{#1}}\else + \if\t@st\@D@{\rmfamily{#1}}\else + \if\t@st\@E@{\rmfamily{#1}}\else + \if\t@st\@F@{\rmfamily{#1}}\else + \textbf{#1}% + \fi + \fi + \fi + \fi + \fi + \fi% + \firstfalse% + \else + {\textbf #1}% + \fi% + \fi + \next% +} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% nice orcid links for use in author block: +\RequirePackage{tikz} +\usetikzlibrary{svg.path} + +\IfFileExists{orcidlink.sty}% +{\RequirePackage{orcidlink}}% +{% orcidlink is post 2020, so include full code if sty file not available +\definecolor{orcidlogocol}{HTML}{A6CE39} +\tikzset{ + orcidlogo/.pic={ + \fill[orcidlogocol] svg{M256,128c0,70.7-57.3,128-128,128C57.3,256,0,198.7,0,128C0,57.3,57.3,0,128,0C198.7,0,256,57.3,256,128z}; + \fill[white] svg{M86.3,186.2H70.9V79.1h15.4v48.4V186.2z} + svg{M108.9,79.1h41.6c39.6,0,57,28.3,57,53.6c0,27.5-21.5,53.6-56.8,53.6h-41.8V79.1z M124.3,172.4h24.5c34.9,0,42.9-26.5,42.9-39.7c0-21.5-13.7-39.7-43.7-39.7h-23.7V172.4z} + svg{M88.7,56.8c0,5.5-4.5,10.1-10.1,10.1c-5.6,0-10.1-4.6-10.1-10.1c0-5.6,4.5-10.1,10.1-10.1C84.2,46.7,88.7,51.3,88.7,56.8z}; + } +} +%% Reciprocal of the height of the svg whose source is above. The +%% original generates a 256pt high graphic; this macro holds 1/256. +\newcommand{\@OrigHeightRecip}{0.00390625} +%% We will compute the current X height to make the logo the right height +\newlength{\@curXheight} + +%% Prevent externalization of the ORCiD logo. +\newcommand{\@preventExternalization}{% +\ifcsname tikz@library@external@loaded\endcsname% +\tikzset{external/export next=false}\else\fi% +} + +\newcommand{\orcidlogo}{% +\texorpdfstring{% +\setlength{\@curXheight}{\fontcharht\font`X}% +\XeTeXLinkBox{% +\@preventExternalization% +\begin{tikzpicture}[yscale=-\@OrigHeightRecip*\@curXheight, +xscale=\@OrigHeightRecip*\@curXheight,transform shape] +\pic{orcidlogo}; +\end{tikzpicture}% +}}{}} +\DeclareRobustCommand\orcidlinkX[1]{\href{https://orcid.org/##1}{% +\orcidlogo}} +\newcommand{\orcidlink}[1]{\orcidlinkX{##1}} +} +\newcommand{\IUCrOrcidlink}[1]{\orcidlink{#1}\,} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% nice email links for use in author block (based on orcidlink): +\definecolor{IUCr@emaillogocol}{HTML}{AAAAAA} +\tikzset{ + IUCr@emaillogo/.pic={ + \fill[IUCr@emaillogocol] + svg{M 0 11.755 v 66.489 h 90 V 11.755 H 0 z M 45 50.49 L 7.138 15.755 h 75.724 L 45 50.49 z M 33.099 45 L 4 71.695 V 18.304 L 33.099 45 z M 36.058 47.714 L 45 55.918 l 8.943 -8.204 l 28.919 26.53 H 7.138 L 36.058 47.714 z M 56.901 45 L 86 18.304 v 53.392 L 56.901 45 z}; + } +} +\definecolor{IUCr@cemaillogocol}{HTML}{0000FF} +\tikzset{ + IUCr@cemaillogo/.pic={ + \fill[IUCr@cemaillogocol] + svg{M 0 11.755 v 66.489 h 90 V 11.755 H 0 z M 45 50.49 L 7.138 15.755 h 75.724 L 45 50.49 z M 33.099 45 L 4 71.695 V 18.304 L 33.099 45 z M 36.058 47.714 L 45 55.918 l 8.943 -8.204 l 28.919 26.53 H 7.138 L 36.058 47.714 z M 56.901 45 L 86 18.304 v 53.392 L 56.901 45 z}; + } +} +\newcommand{\IUCr@OrigHeightRecipE}{0.01400625} +\newlength{\IUCr@curXheightE} +\newcommand{\IUCr@preventExternalizationE}{% +\ifcsname tikz@library@external@loaded\endcsname% +\tikzset{external/export next=false}\else\fi% +} +\newcommand{\IUCr@emaillogo}{% +\texorpdfstring{% +\setlength{\IUCr@curXheightE}{\fontcharht\font`X}% +\XeTeXLinkBox{% +\IUCr@preventExternalizationE% +\begin{tikzpicture}[yscale=-\IUCr@OrigHeightRecipE*\IUCr@curXheightE, +xscale=\IUCr@OrigHeightRecipE*\IUCr@curXheightE,transform shape] +\pic{IUCr@emaillogo}; +\end{tikzpicture}% +}}{}} +\newcommand{\IUCr@cemaillogo}{% +\texorpdfstring{% +\setlength{\IUCr@curXheightE}{\fontcharht\font`X}% +\XeTeXLinkBox{% +\IUCr@preventExternalizationE% +\begin{tikzpicture}[yscale=-\IUCr@OrigHeightRecipE*\IUCr@curXheightE, +xscale=\IUCr@OrigHeightRecipE*\IUCr@curXheightE,transform shape] +\pic{IUCr@cemaillogo}; +\end{tikzpicture}% +}}{}} +\DeclareRobustCommand\IUCr@emaillinkX[1]{\href{mailto:#1}{% +\IUCr@emaillogo}} +\DeclareRobustCommand\IUCr@cemaillinkX[1]{\href{mailto:#1}{% +\IUCr@cemaillogo}} +\newcommand{\IUCrEmaillink}[1]{\,\IUCr@emaillinkX{#1}\,} +\newcommand{\IUCrCemaillink}[1]{\,\IUCr@cemaillinkX{#1}\,} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% author footnotes for use in author block +% (alternative to using \thanks or \footnote) + +\newcounter{IUCr@aufnc} +\setcounter{IUCr@aufnc}{0} +\newcommand{\IUCr@storeaufn}[1]{\stepcounter{IUCr@aufnc}\global\expandafter\def\csname aufnX\theIUCr@aufnc\endcsname{#1}} +\newcommand{\IUCr@printaufn}[1]{\footnotesize\IUCr@fnsymbol{#1}\csname aufnX#1\endcsname\\} +\newcommand{\IUCr@printauthornotes}{% +\ifnum\theIUCr@aufnc>0 +\begin{center} +\vskip-22pt +\newcounter{tmpIUCr@aufnc} +\setcounter{tmpIUCr@aufnc}{0} +\loop +\stepcounter{tmpIUCr@aufnc} +%\thetmpIUCr@aufnc +\IUCr@printaufn{\thetmpIUCr@aufnc}% +\addtocounter{IUCr@aufnc}{-1} +\ifnum\theIUCr@aufnc>0 +\repeat +\end{center} +\bigskip +\fi +} +\newcommand{\IUCrAufn}[2][0]{% +% store the note as a new macro for later output using \IUCr@printauthornotes +\if0#1% +\IUCr@storeaufn{#2}% +%$^\theIUCr@aufnc$% +\IUCr@fnsymbol{\theIUCr@aufnc}\,% +\else% allows multiple footnote markers pointing to same text +% if the number is greater than \theIUCr@aufnc count, store as new? +\ifnum#1>\theIUCr@aufnc% +\IUCr@storeaufn{#2}% +\fi% +\IUCr@fnsymbol{\theIUCr@aufnc}\,% +\fi% +} + +\newcommand{\IUCr@fnsymbol}[1]{% +\ifnum#1<6% +$^\IUCr@fnsymbolsingle{#1}$% +\else% +\newcount\@lrepeat +\@lrepeat=#1 +\divide\@lrepeat by 5 +%\the\@lrepeat +%modulo gives symbol number: +\newcount\@modsym +\@modsym=#1 +\divide\@modsym by 5 +\multiply\@modsym by 5 +\multiply\@modsym by -1 +\advance\@modsym by #1\relax +%\the\@modsym +$^{% +\IUCr@fnsymbolsingle{\@modsym}% +\loop +\IUCr@fnsymbolsingle{\@modsym}% +\advance\@lrepeat by -1 +\ifnum\@lrepeat>0 +\repeat +}$% +\fi% +} +\newcommand{\IUCr@fnsymbolsingle}[1]{% + \ensuremath{% + \ifcase#1% 0 + \or % 1 + \dagger + \or % 2 + \ddagger + \or % 3 + \mathsection + \or % 4 + \mathparagraph + \or \| + \else % >= 6 + #1 + \fi + }% +} + +% print author notes after maketitle +\let\IUCr@maketitle=\maketitle +\def\maketitle{% +\IUCr@maketitle +\IUCr@printauthornotes +} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% commands and formatting tweaks: + +\let\origtitle\title +\renewcommand{\title}[1]{\origtitle{\textbf{#1}}} + +\renewenvironment{abstract} + {%\small + \begin{center} + \bfseries \abstractname\vspace{-.5em}\vspace{0pt} + \end{center} + \list{}{ + \setlength{\leftmargin}{1cm}% + \setlength{\rightmargin}{\leftmargin}% + }% + \item\relax} + {\endlist\bigskip} + +\newenvironment{synopsis}% +{%\small + \begin{center} + \bfseries Synopsis\vspace{-.5em}\vspace{0pt} + \end{center} + \list{}{ + \setlength{\leftmargin}{1cm}% + \setlength{\rightmargin}{\leftmargin}% + }% + \item\relax} + {\endlist\bigskip} + +\newcommand{\keywords}[1]{ + \begin{center} + \small + \list{}{ + \setlength{\leftmargin}{1cm}% + \setlength{\rightmargin}{\leftmargin}% + }% + \item\relax\textbf{Keywords:} #1\endlist\end{center}\bigskip} + +\let\origaffil\affil +\def\affil#1#{\origaffilA{#1}} +\def\origaffilA#1#2{\origaffil#1{\footnotesize #2}} + +\newenvironment{acknowledgements}% +{%\small +\bigskip + %\begin{center} + {\bfseries\Large Acknowledgements}\vspace{-.5em}\vspace{0pt} + %\end{center} + \list{}{ + \setlength{\leftmargin}{0cm}% + \setlength{\rightmargin}{\leftmargin}% + }% + \item\relax} + {\endlist\medskip} + +\newenvironment{funding}% +{%\small +\medskip + %\begin{center} + {\bfseries\Large Funding}\vspace{-.5em}\vspace{0pt} + %\end{center} + \list{}{ + \setlength{\leftmargin}{0cm}% + \setlength{\rightmargin}{\leftmargin}% + }% + \item\relax} + {\endlist\medskip} + +\newcommand{\ConflictsOfInterest}[1]{ + \begin{center} + \small + \list{}{ + \setlength{\leftmargin}{0cm}% + \setlength{\rightmargin}{\leftmargin}% + }% + \item\relax\textbf{Conflicts of interest:} #1\endlist\end{center}\medskip} + +\newcommand{\DataAvailability}[1]{ + \begin{center} + \small + \list{}{ + \setlength{\leftmargin}{0cm}% + \setlength{\rightmargin}{\leftmargin}% + }% + \item\relax\textbf{Data availability:} #1\endlist\end{center}\medskip} + diff --git a/src/easydiffraction/report/templates/tex/styles/ltxdocext.sty b/src/easydiffraction/report/templates/tex/styles/ltxdocext.sty new file mode 100644 index 000000000..36b21e7e8 --- /dev/null +++ b/src/easydiffraction/report/templates/tex/styles/ltxdocext.sty @@ -0,0 +1,296 @@ +%% +%% This is file `ltxdocext.sty', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% ltxdocext.dtx (with options: `package,kernel') +%% +%% This is a generated file; +%% altering it directly is inadvisable; +%% instead, modify the original source file. +%% See the URL in the file README. +%% +%% License +%% You may distribute this file under the conditions of the +%% LaTeX Project Public License 1.3c or later +%% (http://www.latex-project.org/lppl.txt). +%% +%% This file is distributed WITHOUT ANY WARRANTY; +%% without even the implied warranty of MERCHANTABILITY +%% or FITNESS FOR A PARTICULAR PURPOSE. +%% +\NeedsTeXFormat{LaTeX2e}[1995/12/01]% +\ProvidesFile{ltxdocext.sty}% + [2018/12/26 1.0a ltxdoc extensions package]% \fileversion +\def\class@name{ltxdocext}% +\expandafter\PackageInfo\expandafter{\class@name}{% + An extension to the \protect\LaTeXe\space ltxdoc class + by A. Ogawa (arthur\_ogawa sbcglobal.net)% +}% +\RequirePackage{verbatim}% +\let\o@verbatim\verbatim +\def\verbatim{% + \ifhmode\unskip\par\fi + \ifx\@currsize\normalsize + \small + \fi + \o@verbatim +}% +\renewcommand \verbatim@font {% + \normalfont \ttfamily + \catcode`\<=\active + \catcode`\>=\active +}% +\RequirePackage{shortvrb} +\AtBeginDocument{% + \MakeShortVerb{\|}% +}% +\begingroup + \catcode`\<=\active + \catcode`\>=\active + \gdef<{\@ifnextchar<\@lt\@meta} + \gdef>{\@ifnextchar>\@gt\@gtr@err} + \gdef\@meta#1>{\marg{#1}} + \gdef\@lt<{\char`\<} + \gdef\@gt>{\char`\>} +\endgroup +\def\@gtr@err{% + \ClassError{ltxguide}{% + Isolated \protect>% + }{% + In this document class, \protect<...\protect> + is used to indicate a parameter.\MessageBreak + I've just found a \protect> on its own. + Perhaps you meant to type \protect>\protect>? + }% +} +\def\verbatim@nolig@list{\do\`\do\,\do\'\do\-} +\def\GetFileInfo#1{% + \def\filename{#1}% + \def\@tempb##1 ##2 ##3\relax##4\relax{% + \def\filedate{##1}% + \def\fileversion{##2}% + \def\fileinfo{##3}}% + \edef\@tempa{\csname ver@#1\endcsname}% + \expandafter\@tempb\@tempa\relax? ? \relax\relax} +\DeclareRobustCommand{\marg}[1]{% + \meta{#1}% + \index{#1=\string\meta{#1} placeholder}\index{placeholder>#1=\string\meta{#1}}% +}% +\DeclareRobustCommand\meta[1]{% + \mbox{\LANGLE\itshape#1\/\RANGLE}% +}% +\def\LANGLE{$\langle$}% +\def\RANGLE{$\rangle$}% +\DeclareRobustCommand{\arg}[1]{% + {\ttfamily\string{}\meta{#1}{\ttfamily\string}}% + \index{#1=\string\ttt{#1}, argument}\index{argument>#1=\string\ttt{#1}}% +}% +\let\oarg\undefined +\DeclareRobustCommand{\oarg}[1]{% + {\ttfamily[%] + }\meta{#1}{\ttfamily%[ + ]}% + \index{#1=\string\ttt{#1}, optional argument}% + \index{argument, optional>#1=\string\ttt{#1}}% +}% +\DeclareRobustCommand\cmd{\begingroup\makeatletter\@cmd}% +\long\def\@cmd#1{% + \endgroup + \cs{\expandafter\cmd@to@cs\string#1}% + \expandafter\cmd@to@index\string#1\@nil +}% +\def\cmd@to@cs#1#2{\char\number`#2\relax}% +\def\cmd@to@index#1#2\@nil{% + \index{#2=\string\cmd#1#2}%\index{command>#2=\string\cmd#1#2}% +}% +\DeclareRobustCommand\cs[1]{{\ttfamily\char`\\#1}}% +\def\scmd#1{% + \cs{\expandafter\cmd@to@cs\string#1}% + \expandafter\scmd@to@index\string#1\@nil +}% +\def\scmd@to@index#1#2\@nil#3{% + \index{\string$#3=\string\cmd#1#2---#3}% +}% +\DeclareRobustCommand\env{\name@idx{environment}}% +\DeclareRobustCommand\envb[1]{% + {\ttfamily\string\begin\string{}\env{#1}{\ttfamily\string}}% +}% +\DeclareRobustCommand\enve[1]{{\ttfamily\string\end\string{}\env{#1}{\ttfamily\string}}}% +\DeclareRobustCommand{\file}{\begingroup\@sanitize\@file}% +\long\def\@file#1{\endgroup + {\ttfamily#1}% + \index{#1=\string\ttt{#1}}\index{file>#1=\string\ttt{#1}}% +}% +\DeclareRobustCommand\substyle{\name@idx{document substyle}}% +\DeclareRobustCommand\classoption{\name@idx{document class option}}% +\DeclareRobustCommand\classname{\name@idx{document class}}% +\def\name@idx#1#2{% + {\ttfamily#2}% + \index{#2\space#1=\string\ttt{#2}\space#1}\index{#1>#2=\string\ttt{#2}}% +}% +\DeclareRobustCommand\url@ltxdocext{\begingroup\catcode`\/\active\catcode`\.\active\catcode`\:\active\@url}% +\AtBeginDocument{% + \ifx\url\undefined\let\url\url@ltxdocext\fi +}% +\def\@url#1{% + \url@break{\ttfamily#1}% + \url@char\edef\@tempa{#1=\string\url{#1}}% + \expandafter\index\expandafter{\@tempa}% + \expandafter\index\expandafter{\expandafter u\expandafter r\expandafter l\expandafter >\@tempa}% + \endgroup +}% +{\catcode`\:\active\aftergroup\def\aftergroup:}{\active@colon}% +\def\colon@break{\colon@char\allowbreak}% +\def\colon@char{:}% +{\catcode`\/\active\aftergroup\def\aftergroup/}{\active@slash}% +\def\slash@break{\slash@char\allowbreak}% +\def\slash@char{/}% +{\catcode`\.\active\aftergroup\def\aftergroup.}{\active@dot}% +\def\dot@break{\dot@char\allowbreak}% +\def\dot@char{.}% +\def\url@break{\let\active@slash\slash@break\let\active@dot\dot@break\let\active@colon\colon@break}% +\def\url@char{\let\active@slash\slash@char\let\active@dot\dot@char\let\active@colon\colon@char}% +\renewenvironment{theindex} + {\if@twocolumn + \@restonecolfalse + \else + \@restonecoltrue + \fi + \columnseprule \z@ + \columnsep 35\p@ +\def\see##1##2{\textit{See} ##1}% +\def\seealso##1##2{\textit{See also} ##1}% +\long\def\cmd##1{\cs{\expandafter\cmd@to@cs\string##1}}% +\def\@url##1{\url@break\ttt{##1}\endgroup}% +\def\ttt{\begingroup\@sanitize\ttfamily\@ttt}% +\def\@ttt##1{##1\endgroup}% +\mathchardef\save@secnumdepth\c@secnumdepth +\c@secnumdepth\m@ne + \twocolumn[\section{\indexname}]% +\c@secnumdepth\save@secnumdepth + \thispagestyle{plain}\parindent\z@ + \parskip\z@ \@plus .3\p@\relax + \let\item\@idxitem} + {\if@restonecol\onecolumn\else\clearpage\fi} +\renewenvironment{quote} + {\list{}{% + \leftmargin1em\relax + \rightmargin\leftmargin + }% + \item\relax} + {\endlist} +\newif\if@mainmatter +\newif\if@openright +\@openrighttrue +\DeclareRobustCommand\frontmatter{% + \cleartorecto + \@mainmatterfalse + \pagenumbering{roman}% +}% +\DeclareRobustCommand\mainmatter{% + \cleartorecto + \@mainmattertrue + \pagenumbering{arabic}% +}% +\DeclareRobustCommand\backmatter{% + \if@openright + \cleartorecto + \else + \clearpage + \fi + \@mainmatterfalse +}% +\ifx\undefined\cleartorecto + \def\cleartorecto{\cleardoublepage}% +\fi +\def\@to{to}% +\newenvironment{unnumtable}{% + \par + \addpenalty\predisplaypenalty + \addvspace\abovedisplayskip + \hbox\@to\hsize\bgroup\hfil\ignorespaces + \let\@Hline\@empty +}{% + \unskip\hfil\egroup + \penalty\postdisplaypenalty + \vskip\belowdisplayskip + \aftergroup\ignorespaces + \@endpetrue +}% +\providecommand\toprule{\hline\hline}% +\providecommand\colrule{\\\hline}% +\providecommand\botrule{\\\hline\hline}% +\DeclareRobustCommand\subsubsubsection{% + \@startsection{subsubsection}{4}% + {\z@}{-15\p@\@plus-5\p@\@minus-2\p@}% + {5\p@}{\normalfont\normalsize\itshape}% +}% +\DoNotIndex{\',\.,\@M,\@@input,\@Alph,\@alph,\@addtoreset,\@arabic} +\DoNotIndex{\@badmath,\@centercr,\@cite} +\DoNotIndex{\@dotsep,\@empty,\@float,\@gobble,\@gobbletwo,\@ignoretrue} +\DoNotIndex{\@input,\@ixpt,\@m,\@minus,\@mkboth} +\DoNotIndex{\@ne,\@nil,\@nomath,\@plus,\roman,\@set@topoint} +\DoNotIndex{\@tempboxa,\@tempcnta,\@tempdima,\@tempdimb} +\DoNotIndex{\@tempswafalse,\@tempswatrue,\@viipt,\@viiipt,\@vipt} +\DoNotIndex{\@vpt,\@warning,\@xiipt,\@xipt,\@xivpt,\@xpt,\@xviipt} +\DoNotIndex{\@xxpt,\@xxvpt,\\,\ ,\addpenalty,\addtolength,\addvspace} +\DoNotIndex{\advance,\ast,\begin,\begingroup,\bfseries,\bgroup,\box} +\DoNotIndex{\bullet} +\DoNotIndex{\cdot,\cite,\CodelineIndex,\cr,\day,\DeclareOption} +\DoNotIndex{\def,\DisableCrossrefs,\divide,\DocInput,\documentclass} +\DoNotIndex{\DoNotIndex,\egroup,\ifdim,\else,\fi,\em,\endtrivlist} +\DoNotIndex{\EnableCrossrefs,\end,\end@dblfloat,\end@float,\endgroup} +\DoNotIndex{\endlist,\everycr,\everypar,\ExecuteOptions,\expandafter} +\DoNotIndex{\fbox} +\DoNotIndex{\filedate,\filename,\fileversion,\fontsize,\framebox,\gdef} +\DoNotIndex{\global,\halign,\hangindent,\hbox,\hfil,\hfill,\hrule} +\DoNotIndex{\hsize,\hskip,\hspace,\hss,\if@tempswa,\ifcase,\or,\fi,\fi} +\DoNotIndex{\ifhmode,\ifvmode,\ifnum,\iftrue,\ifx,\fi,\fi,\fi,\fi,\fi} +\DoNotIndex{\input} +\DoNotIndex{\jobname,\kern,\leavevmode,\let,\leftmark} +\DoNotIndex{\list,\llap,\long,\m@ne,\m@th,\mark,\markboth,\markright} +\DoNotIndex{\month,\newcommand,\newcounter,\newenvironment} +\DoNotIndex{\NeedsTeXFormat,\newdimen} +\DoNotIndex{\newlength,\newpage,\nobreak,\noindent,\null,\number} +\DoNotIndex{\numberline,\OldMakeindex,\OnlyDescription,\p@} +\DoNotIndex{\pagestyle,\par,\paragraph,\paragraphmark,\parfillskip} +\DoNotIndex{\penalty,\PrintChanges,\PrintIndex,\ProcessOptions} +\DoNotIndex{\protect,\ProvidesClass,\raggedbottom,\raggedright} +\DoNotIndex{\refstepcounter,\relax,\renewcommand} +\DoNotIndex{\rightmargin,\rightmark,\rightskip,\rlap,\rmfamily} +\DoNotIndex{\secdef,\selectfont,\setbox,\setcounter,\setlength} +\DoNotIndex{\settowidth,\sfcode,\skip,\sloppy,\slshape,\space} +\DoNotIndex{\symbol,\the,\trivlist,\typeout,\tw@,\undefined,\uppercase} +\DoNotIndex{\usecounter,\usefont,\usepackage,\vfil,\vfill,\viiipt} +\DoNotIndex{\viipt,\vipt,\vskip,\vspace} +\DoNotIndex{\wd,\xiipt,\year,\z@} +\DoNotIndex{\next} +\AtEndDocument{\PrintIndex\PrintChanges}% +\makeatletter +\def\endfilecontents{% + \immediate\write\reserved@c{% + \string\iffalse\space ltxdoc klootch^^J% + \ifx\undefined\fileversion\else + \ifx\undefined\filedate\else + This file has version number \fileversion, last revised \filedate.% + \fi\fi + \string\fi + }% + \immediate\closeout\reserved@c + \def\T##1##2##3{% + \ifx##1\@undefined\else + \@latex@warning@no@line{##2 has been converted to Blank ##3e}% + \fi + }% + \T\L{Form Feed}{Lin}% + \T\I{Tab}{Spac}% + \immediate\write\@unused{}% +}% +\expandafter\let\csname endfilecontents*\endcsname\endfilecontents +\makeatother +\setlength\arraycolsep{0pt}% +\endinput +%% +%% End of file `ltxdocext.sty'. diff --git a/src/easydiffraction/report/templates/tex/styles/ltxfront.sty b/src/easydiffraction/report/templates/tex/styles/ltxfront.sty new file mode 100644 index 000000000..7491dea60 --- /dev/null +++ b/src/easydiffraction/report/templates/tex/styles/ltxfront.sty @@ -0,0 +1,1160 @@ +%% +%% This is file `ltxfront.sty', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% ltxfront.dtx (with options: `package,options,kernel') +%% +%% This is a generated file; +%% altering it directly is inadvisable; +%% instead, modify the original source file. +%% See the URL in the file README-LTXFRONT.tex. +%% +%% License +%% You may distribute this file under the conditions of the +%% LaTeX Project Public License 1.3c or later +%% (http://www.latex-project.org/lppl.txt). +%% +%% This file is distributed WITHOUT ANY WARRANTY; +%% without even the implied warranty of MERCHANTABILITY +%% or FITNESS FOR A PARTICULAR PURPOSE. +%% +%%% @LaTeX-file{ +%%% filename = "ltxfront.dtx", +%%% version = "4.2f", +%%% date = "2022/06/05", +%%% author = "Arthur Ogawa (mailto:arthur_ogawa at sbcglobal.net), +%%% commissioned by the American Physical Society. +%%% ", +%%% copyright = "Copyright (C) 1999, 2009 Arthur Ogawa, +%%% distributed under the terms of the +%%% LaTeX Project Public License 1.3c, see +%%% ftp://ctan.tug.org/macros/latex/base/lppl.txt +%%% ", +%%% address = "Arthur Ogawa, +%%% USA", +%%% telephone = "", +%%% FAX = "", +%%% email = "mailto colon arthur_ogawa at sbcglobal.net", +%%% codetable = "ISO/ASCII", +%%% keywords = "latex, page grid, main vertical list", +%%% supported = "yes", +%%% abstract = "package to change page grid, MVL", +%%% } +\NeedsTeXFormat{LaTeX2e}[1995/12/01]% +\ProvidesFile{% +ltxfront% +.sty% +}% + [2022/06/05 4.2f frontmatter package (AO,DPC,MD)]% \fileversion +\def\package@name{ltxfront}% +\expandafter\PackageInfo\expandafter{\package@name}{% + Title page macros for \protect\LaTeXe, + by A. Ogawa (arthur_ogawa at sbcglobal.net)% +}% +\let\class@name\package@name +\RequirePackage{ltxutil}% +\DeclareOption{frontmatterverbose}{\@booleantrue\frontmatterverbose@sw}% +\@booleanfalse\frontmatterverbose@sw +\DeclareOption{inactive}{\@booleanfalse\frontmatter@syntax@sw}% +\@booleantrue\frontmatter@syntax@sw +\@booleanfalse\runinaddress@sw +\@booleantrue\@affils@sw +\@booleanfalse\groupauthors@sw +\DeclareOption{groupedaddress}{\clo@groupedaddress}% +\def\clo@groupedaddress{% + \@booleantrue\groupauthors@sw + \@booleantrue\@affils@sw + \@booleanfalse\runinaddress@sw +}% +\DeclareOption{unsortedaddress}{\clo@unsortedaddress}% +\def\clo@unsortedaddress{% + \@booleantrue\groupauthors@sw + \@booleanfalse\@affils@sw + \@booleanfalse\runinaddress@sw +}% +\DeclareOption{runinaddress}{\clo@runinaddress}% +\def\clo@runinaddress{% + \@booleantrue\groupauthors@sw + \@booleantrue\@affils@sw + \@booleantrue\runinaddress@sw +}% +\DeclareOption{superscriptaddress}{\clo@superscriptaddress}% +\def\clo@superscriptaddress{% + \@booleanfalse\groupauthors@sw + \@booleantrue\@affils@sw + \@booleanfalse\runinaddress@sw +}% +\DeclareOption*{\OptionNotUsed}% +\ProcessOptions* +\appdef\class@documenthook{\frontmatter@init}% +\let\frontmatter@init\@empty +\newcommand\frontmatter@title[2][]{% + \def\@title{#2}% + \def\@shorttitle{#1}% + \let\@AF@join\@title@join +}% +\appdef\frontmatter@init{% + \def\@title{\class@warn{No title}}% + \let\@shorttitle\@empty + \let\@title@aux\@title@aux@cleared +}% +\def\@title@join{\expandafter\@title@join@\@title@aux}% +\def\@title@join@#1#2{% + \def\@title@aux{{\@join{\@separator}{#1}{#2}}}% +}% +\def\@title@aux@cleared{{}}% +\newcounter{affil}% +\newcounter{collab}% +\appdef\frontmatter@init{% + \c@affil\z@ + \c@collab\z@ +}% +\newcommand\frontmatter@author{% implicit #1 + \@author@def{}% implicit #2 +}% +\def\collaboration{% implicit #1 + \@author@def{\@booleantrue\collaboration@sw}% implicit #2 +}% +\appdef\frontmatter@init{% + \@booleanfalse\collaboration@sw +}% +\def\@author@cleared{{}{}{}}% +\def\@author@gobble#1#2#3{}% +\def\@author@init{% + \let\@author\@author@cleared + \@booleanfalse\collaboration@sw +}% +\def\@authorclear@sw{\@ifx{\@author\@author@cleared}}% +\appdef\frontmatter@init{% + \@author@init +}% +\def\@author@def#1#2{% + \frontmatterverbose@sw{\typeout{\string\author\space\string\collaboration}}{}% + \move@AU\move@AF\move@AUAF + \let\@AF@join\@author@join + #1% + \def\@author{{#2}{}}% +}% +\def\@author@join@#1#2#3{% + \def\@author{{#1}{\@join{\@separator}{#2}{#3}}}% +}% +\def\@author@join{\expandafter\@author@join@\@author}% +\def\move@AU{% + \@authorclear@sw{}{% + \collaboration@sw{% + \advance\c@collab\@ne + \@argswap{\CO@grp\CO@opr}% + }{% + \@argswap{\AU@grp\AU@opr}% + }% + {% + \expandafter\@argswap@val + \expandafter{\@author}% + {\expandafter\@argswap@val\expandafter{\the\c@collab}{\add@AUCO@grp}}% + }% + }% + \@author@init +}% +\def\add@AUCO@grp#1#2#3#4{% + \appdef#3{#4{#1}#2}% + \frontmatterverbose@sw{\say#3}{}% +}% +\def\@author@finish{% + \frontmatterverbose@sw{\typeout{\string\@author@finish}}{}% + \move@AU\move@AF + \@ifx{\AU@grp\@empty}{% + \@ifx{\CO@grp\@empty}% + }{% + \false@sw + }% + {}{% + \@ifx{\AF@grp\@empty}{% + \begingroup + \let\href\@secondoftwo + \let\AU@opr\@secondofthree + \let\CO@opr\@secondofthree + \let\footnote\@gobble + \@ifx{\CO@grp\@empty}{% + \class@warn{Assuming \string\noaffiliation\space for authors}% + \frontmatterverbose@sw{\say\AU@grp}% + }{% + \class@warn{Assuming \string\noaffiliation\space for collaboration}% + \frontmatterverbose@sw{\say\CO@grp}{}% + }% + \endgroup + \@affil@none\move@AF + }{}% + }% + \move@AUAF +}% +\def\@secondofthree#1#2#3{#2}% +\def\@join#1#2#3{% + \@if@empty{#2}{#3}{#2#1#3}% +}% +\def\@separator{;\space}% +\let\surname\@firstofone +\let\firstname\@firstofone +\newcommand\frontmatter@and{\class@err{\protect\and\space is not supported}} +\def\cat@comma@active{\catcode`\,\active}% +{\cat@comma@active\gdef,{\active@comma}}% +\def\active@comma{,\penalty-300\relax}% +\newcommand\affiliation{% + \frontmatterverbose@sw{\typeout{\string\affiliation}}{}% + \move@AU\move@AF + \begingroup + \cat@comma@active + \@affiliation +}% +\def\@affiliation#1{% + \endgroup + \let\@AF@join\@affil@join + \@affil@def{#1}% +}% +\newcommand\frontmatter@noaffiliation{% + \frontmatterverbose@sw{\typeout{\string\noaffiliation}}{}% + \move@AU\move@AF + \@affil@none\move@AF + \move@AUAF +}% +\def\blankaffiliation{{}}% +\def\@affil@cleared{{{}}{}}% +\def\@affil@nil{{\relax}{}}% +\appdef\frontmatter@init{% + \@affil@init +}% +\def\@affil@none{% + \let\@affil\@affil@nil +}% +\def\@affil@init{% + \let\@affil\@affil@cleared +}% +\def\@affilclear@sw{\@ifx{\@affil\@affil@cleared}}% +\def\@affil@def#1{% + \def\@affil{{#1}{}}% +}% +\def\@affil@join@#1#2#3{% + \def\@affil{{#1}{\@join{\@separator}{#2}{#3}}}% +}% +\def\@affil@join{\expandafter\@affil@join@\@affil}% +\def\move@AF{% + \@affilclear@sw{}{% + \@booleanfalse\temp@sw + \let\@tempd\@empty + \@affils@sw{% + \expandafter\@affil@addr@def\expandafter\@tempa\@affil + \def\AFF@opr{\@affil@match\@tempa}% + \@AFF@list + }{}\temp@sw + {% + \expandafter\@affil@aux@def\expandafter\@tempb\@affil + \@ifx{\@tempb\@empty}{}{% + \@ifx{\@tempb\@tempd}{}{% + \class@warn{% + Ancillary information for \@tempa\space must not be different! + Please put all of it on the first instance% + }% + }% + }% + }% + {% + \@ifx{\@affil\@affil@nil}{% + \def\@tempc{0}% + \@argswap@val{0}% + }{% + \advance\c@affil\@ne + \expandafter\def\expandafter\@tempc\expandafter{\the\c@affil}% + \expandafter\@argswap@val\expandafter{\the\c@affil}% + }% + {% + \expandafter\@argswap@val\expandafter{\the\c@collab}{% + \expandafter\@argswap@val\expandafter{\@affil}{% + \add@list@val@val@val\@AFF@list\AFF@opr + }% + }% + }% + }% + \appdef@eval\AF@grp\@tempc + \frontmatterverbose@sw{\say\AF@grp}{}% + \@affil@init + }% +}% +\def\@affil@addr@def#1#2#3{% + \def#1{#2}% +}% +\def\@affil@aux@def#1#2#3{% + \def#1{#3}% +}% +\def\add@list@val@val@val#1#2#3#4#5{% + \appdef#1{#2{#5}{#4}#3}% + \frontmatterverbose@sw{\say#1}{}% +}% +\def\@affil@match#1#2#3#4#5{% + \temp@sw{}{% + \def\@tempifx{#4}% + \@ifx{\@tempifx#1}{% + \groupauthors@sw{% + \@ifnum{#3=\c@collab}{% + \true@sw + }{% + \false@sw + }% + }{% + \true@sw + }% + }{% + \false@sw + }% + {% + \@booleantrue\temp@sw + \def\@tempc{#2}% + \def\@tempd{#5}% + }{% + }% + }% +}% +\def\move@AUAF{% + \frontmatterverbose@sw{\say\AU@grp\say\AF@grp\say\CO@grp}{}% + \@ifx{\AF@grp\@empty}{% + \@ifx{\@empty\CO@grp}{% + }{% + \appdef \@AAC@list{\AF@opr{{0}}}% + \appdef@e \@AAC@list{\CO@grp}% + \appdef@e \@AFG@list{\CO@grp}% + \let\CO@grp\@empty + }% + }{% + \appdef \@AAC@list{\AF@opr}% + \appdef@eval\@AAC@list{\AF@grp}% + \appdef@e \@AAC@list{\AU@grp}% + \@ifx{\@empty\AU@grp}{% + \@ifx{\@empty\CO@grp}% + }{% + \false@sw + }% + {% + }{% + \@booleanfalse\temp@sw + \def\AFG@opr{\x@match\AF@grp}% + \let\CO@opr\@author@gobble + \@AFG@list + \temp@sw{}{% + \appdef \@AFG@list{\AFG@opr}% + \appdef@eval\@AFG@list{\AF@grp}% + }% + \@ifx{\@empty\CO@grp}{}{% + \appdef@e \@AAC@list{\CO@grp}% + \appdef@e \@AFG@list{\CO@grp}% + \let\CO@grp\@empty + }% + }% + \let\CO@grp\@empty + \let\AU@grp\@empty + \let\AF@grp\@empty + }% + \frontmatterverbose@sw{\say\@AAC@list\say\@AFG@list}{}% +}% +\appdef\frontmatter@init{% + \let\AU@grp\@empty + \let\CO@grp\@empty + \let\AF@grp\@empty + \let\@AAC@list\@empty + \let\@AFG@list\@empty + \let\@AFF@list\@empty +}% +\appdef\frontmatter@init{% + \let\@AF@join\@AF@join@error +}% +\def\@AF@join@error#1{% + \class@warn{% + \string\email, \string\homepage, \string\thanks, or \string\altaffiliation\space + appears in wrong context. + }% +}% +\def\sanitize@url{% + \@makeother\%% + \@makeother\~% + \@makeother\_% +}% +\newcommand*\email[1][]{\begingroup\sanitize@url\@email{#1}}% +\def\@email#1#2{% + \endgroup + \@AF@join{#1\href{mailto:#2}{#2}}% +}% +\newcommand*\homepage[1][]{\begingroup\sanitize@url\@homepage{#1}}% +\def\@homepage#1#2{% + \endgroup + \@AF@join{#1\href{#2}{#2}}% +}% +\appdef\class@documenthook{% + \providecommand\href[1]{}% +}% +\def\frontmatter@thanks{% implicit #1 + \@AF@join +}% +\newcommand*\altaffiliation[2][]{% + \@AF@join{#1#2}% +}% +\def\set@listcomma@list#1{% + \expandafter\@reset@ac\expandafter#1#1{0}\@reset@ac{% + \let\@listcomma\relax + }{% + \let\@listcomma\@listcomma@comma + }% +}% +\def\set@listcomma@count#1{% + \@ifnum{#1=\tw@}{% + \let\@listcomma\relax + }{% + \let\@listcomma\@listcomma@comma + }% +}% +\def\@reset@ac#1#2#3\@reset@ac{% + \def#1{#3}% + \@tempcnta#2\relax + \@ifnum{#2=\tw@}% +}% +\def\@listand{\@ifnum{\@tempcnta=\tw@}{\andname\space}{}}% +\def\@listcomma@comma{\@ifnum{\@tempcnta>\@ne}{,}{}}% +\def\@listcomma@comma@UK{\@ifnum{\@tempcnta>\tw@}{,}{}}% +\def\@collaboration@gobble#1#2#3{}% +\def\doauthor#1#2#3{% + \ignorespaces#1\unskip\@listcomma + \begingroup + #3% + \@if@empty{#2}{\endgroup{}{}}{\endgroup{\comma@space}{}\frontmatter@footnote{#2}}% + \space \@listand +}% +\def\x@match#1#2{% + \temp@sw{}{% + \def\@tempifx{#2}% + \@ifx{\@tempifx#1}{% + \@booleantrue\temp@sw + }{% + }% + }% +}% +\def\y@match#1#2#3{% + \temp@sw{}{% + \def\@tempifx{#3}% + \@ifx{\@tempifx#1}{% + \@booleantrue\temp@sw + \def\@tempb{#2}% + }{% + }% + }% +}% +\def\frontmatter@footnote#1{% + \begingroup + \@booleanfalse\temp@sw + \def\@tempa{#1}% + \let\@tempb\@empty + \def\@TBN@opr{\y@match\@tempa}% + \@FMN@list + \temp@sw{% + \expandafter\frontmatter@footnotemark + \expandafter{\@tempb}% + }{% + \stepcounter\@mpfn + \expandafter\expandafter + \expandafter\frontmatter@foot@mark + \expandafter\expandafter + \expandafter{% + \expandafter \the\csname c@\@mpfn\endcsname + }{#1}% + }% + \endgroup +}% +\def\frontmatter@foot@mark#1#2{% + \frontmatter@footnotemark{#1}% + \g@addto@macro\@FMN@list{\@TBN@opr{#1}{#2}}% +}% +\appdef\frontmatter@init{% + \global\let\@FMN@list\@empty +}% +\def\frontmatter@footnotemark#1{% + \leavevmode + \ifhmode\edef\@x@sf{\the\spacefactor}\nobreak\fi + \begingroup + \hyper@linkstart {link}{frontmatter.#1}% + \csname c@\@mpfn\endcsname#1\relax + \def\@thefnmark{\frontmatter@thefootnote}% + \@makefnmark + \hyper@linkend + \endgroup + \ifhmode\spacefactor\@x@sf\fi + \relax +}% +\def\keywords#1{% + \aftermaketitle@chk{\keywords}% + \gdef\@keywords{#1}% +}% +\appdef\frontmatter@init{% + \let\@keywords\@empty +}% +\newcommand*\frontmatter@date[2][\Dated@name]{\def\@date{#1#2}}% +\def\@date{}% +\newcommand*\received[2][\Received@name]{\def\@received{#1#2}}% +\def\@received{}% +\newcommand*\revised[2][\Revised@name]{\def\@revised{#1#2}}% +\def\@revised{}% +\newcommand*\accepted[2][\Accepted@name]{\def\@accepted{#1#2}}% +\def\@accepted{}% +\newcommand*\published[2][\Published@name]{\def\@published{#1#2}}% +\def\@published{}% +\def\pacs#1{% + \aftermaketitle@chk{\pacs}% + \gdef\@pacs{#1}% +}% +\appdef\frontmatter@init{% + \let\@pacs\@empty +}% +\def\preprint#1{\gappdef\@preprint{\preprint{#1}}}% +\appdef\frontmatter@init{% + \let\@preprint\@empty +}% +\newbox\absbox +\def\toclevel@abstract{1}% +\def\addcontents@abstract{% + \phantomsection + \expandafter\def\csname Parent0\endcsname{section*.2}% + \expandafter\@argswap@val\expandafter{\abstractname}{\addcontentsline{toc}{abstract}}% +}% +\newenvironment{frontmatter@abstract}{% + \aftermaketitle@chk{\begin{abstract}}% + \global\setbox\absbox\vbox\bgroup + \color@begingroup + \columnwidth\textwidth + \hsize\columnwidth + \@parboxrestore + \def\@mpfn{mpfootnote}\def\thempfn{\thempfootnote}\c@mpfootnote\z@ + \let\@footnotetext\frontmatter@footnotetext + \minipagefootnote@init + \let\set@listindent\set@listindent@ + \let\@listdepth\@mplistdepth \@mplistdepth\z@ + \let@environment{description}{frontmatter@description}% + \@minipagerestore + \@setminipage + \frontmatter@abstractheading + \frontmatter@abstractfont + \let\footnote\mini@note + \expandafter\everypar\expandafter{\the\everypar\addcontents@abstract\everypar{}}% +}{% + \par + \unskip + \minipagefootnote@here + \@minipagefalse %% added 24 May 89 + \color@endgroup + \egroup +}% +\long\def\frontmatter@footnotetext#1{% + \minipagefootnote@pick + \set@footnotefont + \set@footnotewidth + \@parboxrestore + \protected@edef\@currentlabel{\csname p@\@mpfn\endcsname\@thefnmark}% + \color@begingroup + \frontmatter@makefntext{% + \rule\z@\footnotesep\ignorespaces#1\@finalstrut\strutbox\vadjust{\vskip\z@skip}% + }% + \color@endgroup + \minipagefootnote@drop +}% +\def\ltx@no@footnote{% + \let\ltx@xfootnote\ltx@no@xfootnote\let\ltx@yfootnote\ltx@no@yfootnote + \let\ltx@xfootmark\ltx@no@xfootmark\let\ltx@yfootmark\ltx@no@yfootmark + \let\ltx@xfoottext\ltx@no@xfoottext\let\ltx@yfoottext\ltx@no@yfoottext +}% +\def\ltx@no@xfootnote[#1]#2{\ltx@no@footwarn\footnote}% +\def\ltx@no@yfootnote#1{\ltx@no@footwarn\footnote}% +\def\ltx@no@xfootmark[#1]{\ltx@no@footwarn\footnotemark}% +\def\ltx@no@yfootmark{\ltx@no@footwarn\footnotemark}% +\def\ltx@no@xfoottext[#1]#2{\ltx@no@footwarn\footnotetext}% +\def\ltx@no@yfoottext#1{\ltx@no@footwarn\footnotetext}% +\def\ltx@no@footwarn#1{% + \class@warn{% + The \string#1\space command is not legal on the title page; + using \string\thanks\space instead might suit you: consult the manual for details% + }% +}% +\def\frontmatter@abstractheading{% + \begingroup + \centering\large + \abstractname + \par + \endgroup +}% +\def\frontmatter@abstractfont{}% +\newenvironment{frontmatter@description}{% + \list{}{% + \leftmargin\z@ + \labelwidth\z@ + \itemindent\z@ + \let\makelabel\frontmatter@descriptionlabel + }% +}{% + \endlist +}% +\def\frontmatter@descriptionlabel#1{% + \hspace\labelsep + \normalfont\bfseries + #1:% +}% +\def\frontmatter@abstractwidth{\textwidth} +\def\frontmatter@abstract@produce{% + \par + \preprintsty@sw{% + \do@output@MVL{% + \vskip\frontmatter@preabstractspace + \vskip200\p@\@plus1fil + \penalty-200\relax + \vskip-200\p@\@plus-1fil + }% + }{% + \addvspace{\frontmatter@preabstractspace}% + }% + \begingroup + \dimen@\baselineskip + \setbox\z@\vtop{\unvcopy\absbox}% + \advance\dimen@-\ht\z@\advance\dimen@-\prevdepth + \@ifdim{\dimen@>\z@}{\vskip\dimen@}{}% + \endgroup + \begingroup + \prep@absbox + \unvbox\absbox + \post@absbox + \endgroup + \@ifx{\@empty\mini@notes}{}{\mini@notes\par}% + \addvspace\frontmatter@postabstractspace +}% +\appdef\frontmatter@init{\let\mini@notes\@empty}% +\let\prep@absbox\@empty +\let\post@absbox\@empty +\def\frontmatter@preabstractspace{.5\baselineskip} +\def\frontmatter@postabstractspace{.5\baselineskip} +\newenvironment{frontmatter@titlepage}{% + \twocolumn@sw{\onecolumngrid}{\newpage}% + \thispagestyle{titlepage}% + \setcounter{page}\@ne +}{% + \twocolumn@sw{\twocolumngrid}{\newpage}% + \twoside@sw{}{% + \setcounter{page}\@ne + }% +}% +\def\frontmatter@maketitle{% + \@author@finish + \title@column\titleblock@produce + \suppressfloats[t]% + \let\and\relax + \let\affiliation\@gobble + \let\author\@gobble + \let\@AAC@list\@empty + \let\@AFF@list\@empty + \let\@AFG@list\@empty + \let\@AF@join\@AF@join@error + \let\email\@gobble + \let\@address\@empty + \let\maketitle\relax + \let\thanks\@gobble + \let\abstract\@undefined\let\endabstract\@undefined + \titlepage@sw{% + \vfil + \clearpage + }{}% +}% +\def\maketitle@Hy{% + \let\Hy@saved@footnotemark\@footnotemark + \let\Hy@saved@footnotetext\@footnotetext + \let\@footnotemark\H@@footnotemark + \let\@footnotetext\H@@footnotetext + \@ifnextchar[%] + \Hy@maketitle@optarg + {% + \HyOrg@maketitle + \Hy@maketitle@end + }% +}% +\appdef\class@documenthook{% + \@ifx{\maketitle\maketitle@Hy}{% + \class@info{Taking \string\maketitle\space back from hyperref}% + \let\maketitle\frontmatter@maketitle + }{% + }% +}% +\def\titleblock@produce{% + \begingroup + \ltx@footnote@pop + \def\@mpfn{mpfootnote}% + \def\thempfn{\thempfootnote}% + \c@mpfootnote\z@ + \let\@makefnmark\frontmatter@makefnmark + \frontmatter@setup + \thispagestyle{titlepage}\label{FirstPage}% + \frontmatter@title@produce + \groupauthors@sw{% + \frontmatter@author@produce@group + }{% + \frontmatter@author@produce@script + }% + \frontmatter@RRAPformat{% + \expandafter\produce@RRAP\expandafter{\@date}% + \expandafter\produce@RRAP\expandafter{\@received}% + \expandafter\produce@RRAP\expandafter{\@revised}% + \expandafter\produce@RRAP\expandafter{\@accepted}% + \expandafter\produce@RRAP\expandafter{\@published}% + }% + \frontmatter@abstract@produce + \@ifx@empty\@pacs{}{% + \@pacs@produce\@pacs + }% + \@ifx@empty\@keywords{}{% + \@keywords@produce\@keywords + }% + \par + \frontmatter@finalspace + \endgroup +}% +\def\toclevel@title{0}% +\def\frontmatter@title@produce{% + \begingroup + \frontmatter@title@above + \frontmatter@title@format + \@title + \unskip + \phantomsection\expandafter\@argswap@val\expandafter{\@title}{\addcontentsline{toc}{title}}% + \@ifx{\@title@aux\@title@aux@cleared}{}{% + \expandafter\frontmatter@footnote\expandafter{\@title@aux}% + }% + \par + \frontmatter@title@below + \endgroup +}% +\appdef\let@mark{\let\\\relax}% +\def\frontmatter@title@above{}% +\def\frontmatter@title@format{}% +\def\frontmatter@title@below{\addvspace{\baselineskip}}% +\def\frontmatter@author@produce@script{% + \begingroup + \let\@author@present\@author@present@script + \frontmatterverbose@sw{\typeout{\string\frontmatter@author@produce@script:}\say\@AAC@list\say\@AFF@list\say\@AFG@list}{}% + \let\AU@temp\@empty + \@tempcnta\z@ + \let\AF@opr \@gobble + \def\AU@opr{\@author@count\@tempcnta}% + \def\CO@opr{\@collaboration@count\AU@temp\@tempcnta}% + \@AAC@list + \expandafter\CO@opr\@author@cleared + \begingroup + \frontmatter@authorformat + \let\AF@opr \@affilID@def + \let\AU@opr \@author@present + \def\CO@opr{\@collaboration@present\AU@temp}% + \set@listcomma@list\AU@temp + \@AAC@list + \unskip\unskip + \par + \endgroup + \begingroup + \frontmatter@above@affiliation@script + \let\AFF@opr \@affil@script + \@AFF@list + \frontmatter@footnote@produce + \par + \endgroup + \endgroup +}% +\def\@author@count#1{% + \advance#1\@ne + \@author@gobble +}% +\def\@collaboration@present#1#2#3#4{% + \par + \begingroup + \frontmatter@collaboration@above + \@affilID@def{}% + \@tempcnta\z@ + \@author@present{}{(\ignorespaces#3\unskip)}{#4}% + \par + \endgroup + \set@listcomma@list#1% +}% +\def\frontmatter@collaboration@above{}% +\def\@collaboration@count#1#2{% + \appdef@eval#1{\the#2}#2\z@ + \@author@gobble +}% +\def\@affilID@def{\def\@affilID@temp}% +\let\@affilID@temp\@empty +\def\affil@script#1#2#3{% + \def\@tempifx{#1}\@ifx{\@tempifx\@tempa}{% + \@if@empty{#2}{}{% + \par + \begingroup + \def\@thefnmark{#1}\@makefnmark\ignorespaces + #2% + \@if@empty{#3}{}{\frontmatter@footnote{#3}}% + \par + \endgroup + }% + }{}% +}% +\def\@affil@script#1#2#3#4{% + \@ifnum{#1=\z@}{}{% + \par + \begingroup + \frontmatter@affiliationfont + \@ifnum{\c@affil<\affil@cutoff}{}{% + \def\@thefnmark{#1}\@makefnmark + }% + \ignorespaces#3% + \@if@empty{#4}{}{\frontmatter@footnote{#4}}% + \par + \endgroup + }% +}% +\let\affil@cutoff\@ne +\def\@author@present@script#1#2#3{% + \begingroup + \gdef\comma@space{\textsuperscript{,\,}}% + \doauthor{#2}{#3}{\@affil@present@script}% + \endgroup + \advance\@tempcnta\m@ne +}% +\def\@affilcomma#1#2{% + \@ifx{\z@#1}{% + \@ifx{\relax#2}{}{% + \@affilcomma{#2}% + }% + }{% + #1% + \@ifx{\relax#2}{}{% + \@ifx{\z@#2}{% + \@affilcomma + }{% + ,\,\@affilcomma{#2}% + }% + }% + }% +}% +\def\@affil@present@script{% + \let\@tempa\@empty + \expandafter\@affil@present@script@\@affilID@temp\relax +}% +\def\@affil@present@script@#1{% + \@ifx{\relax#1}{% + \@ifx{\@tempa\@empty}{% + \aftergroup\false@sw + }{% + \textsuperscript{\expandafter\@affilcomma\@tempa\relax\relax}% + \aftergroup\true@sw + }% + }{% + \@ifnum{#1=\z@}{}{\appdef\@tempa{{#1}}}% + \@affil@present@script@ + }% +}% +\@provide\@author@parskip{\z@skip}% +\def\frontmatter@author@produce@group{% + \begingroup + \let\@author@present\@author@present@group + \frontmatter@authorformat + \frontmatterverbose@sw{\typeout{\string\frontmatter@author@produce@group:}\say\@AAC@list\say\@AFF@list\say\@AFG@list}{}% + \let\AU@temp\@empty + \set@listcomma@list\AU@temp + \def\CO@opr{\@collaboration@present\AU@temp}% + \let\AFG@opr \affils@present@group + \let\@listcomma\relax + \@AFG@list + \frontmatter@footnote@produce + \par + \endgroup + \frontmatter@authorbelow +}% +\@provide\frontmatter@authorbelow{}% +\def\affils@present@group#1{% + \begingroup + \def\AF@temp{#1}% + \@tempcnta\z@ + \let\AU@opr \@undefined + \let\CO@opr \@undefined + \def\AF@opr{\@affilID@count\AF@temp\@tempcnta}% + \@AAC@list + \@ifnum{\@tempcnta=\z@}{}{% + \begingroup + \frontmatter@above@affilgroup + \set@listcomma@count\@tempcnta + \let\AU@opr \@undefined + \let\CO@opr \@undefined + \def\AF@opr{\@affilID@match\AF@temp}% + \@AAC@list + \endgroup + \begingroup + \par + \frontmatter@above@affiliation + \frontmatter@affiliationfont + \let\\\frontmatter@addressnewline + \@tempcnta\z@ + \@tfor\AF@temp:=#1\do{% + \expandafter\@ifx\expandafter{\expandafter\z@\AF@temp}{}{% + \advance\@tempcnta\@ne + }% + }% + \@ifnum{\@tempcnta=\tw@}{% + \let\@listcomma\relax + }{}% + \def@after@address + \runinaddress@sw{% + }{% + \tightenlines@sw{}{% + \parskip\z@ + }% + \appdef\after@address\par + }% + \let\AFF@opr \@affil@group + \do@affil@fromgroup\@AFF@list#1\relax + \endgroup + }% + \par + \endgroup +}% +\def\def@after@address{\def\after@address{\@listcomma\ \@listand}}% +\def\def@after@address@empty{\let\after@address\@empty}% +\def\@affilID@count#1#2#3{% + \def\@tempifx{#3}% + \@ifx{\@tempifx#1}{% + \def\AU@opr{\@author@count#2}% + }{% + \let\AU@opr \@author@gobble + }% + \let\CO@opr \@collaboration@gobble +}% +\def\@affilID@match#1#2{% + \def\@tempifx{#2}% + \@ifx{\@tempifx#1}{% + \let\AU@opr \@author@present + }{% + \let\AU@opr \@author@gobble + }% + \let\CO@opr \@collaboration@gobble +}% +\def\do@affil@fromgroup#1#2{% + \@ifx{\relax#2}{}{% + \count@#2\relax + \@ifnum{\z@=\count@}{}{#1}% + \do@affil@fromgroup#1% + }% +}% +\def\@affil@group#1#2#3#4{% + \@ifnum{#1=\count@}{% + \def\@tempa{#3}% + \@ifx{\@tempa\blankaffiliation}{}{% + #3% + \@if@empty{#4}{}{% + \frontmatter@footnote{#4}% + }% + \after@address + }% + \advance\@tempcnta\m@ne + }{}% +}% +\def\@author@present@group#1#2#3{% + \gdef\comma@space{\gdef\comma@space{\textsuperscript{,\,}}}% + \doauthor{#2}{#3}{\@affil@present@group}% + \advance\@tempcnta\m@ne +}% +\def\@affil@present@group{% + \aftergroup\false@sw +}% +\def\@pacs@produce#1{% + \showPACS@sw{% + \begingroup + \frontmatter@PACS@format + \@pacs@name#1\par + \endgroup + }{% + \@if@empty{#1}{}{% + \class@warn{\PACS@warn}% + }% + }% +}% +\def\PACS@warn{If you want your PACS to appear in your output, use document class option showpacs}% +\def\@keywords@produce#1{% + \showKEYS@sw{% + \begingroup + \frontmatter@keys@format + \@keys@name#1\par + \endgroup + }{% + \@if@empty{#1}{}{% + \class@warn{If you want your keywords to appear in your output, use document class option showkeys}% + }% + }% +}% +\def\frontmatter@footnote@produce@footnote{% + \let\@TBN@opr\present@FM@footnote + \@FMN@list + \global\let\@FMN@list\@empty +}% +\def\present@FM@footnote#1#2{% + \begingroup + \csname c@\@mpfn\endcsname#1\relax + \def\@thefnmark{\frontmatter@thefootnote}% + \frontmatter@footnotetext{#2}% + \endgroup +}% +\def\frontmatter@footnote@produce@endnote{% +}% +\appdef\frontmatter@init{% + \@ifxundefined\title@column {\let\title@column\@empty}{}% + \@ifxundefined\preprintsty@sw {\@booleanfalse\preprintsty@sw}{}% + \@ifxundefined\frontmatter@footnote@produce{\let\frontmatter@footnote@produce\frontmatter@footnote@produce@footnote}{}% + \@ifxundefined\do@output@MVL {\let\do@output@MVL\@firstofone}{}% + \@ifxundefined\comma@space {\let\comma@space\@empty}{}% +}% +\def\frontmatter@thefootnote{% + \altaffilletter@sw{\@alph}{\@fnsymbol}{\csname c@\@mpfn\endcsname}% +}% +\@ifx{\altaffilletter@sw\@undefined}{\@booleantrue\altaffilletter@sw}{}% +\def\frontmatter@makefnmark{% + \@textsuperscript{% + \normalfont\@thefnmark + }% +}% +\long\def\frontmatter@makefntext#1{% + \parindent 1em + \noindent + \Hy@raisedlink{\hyper@anchorstart{frontmatter.\expandafter\the\csname c@\@mpfn\endcsname}\hyper@anchorend}% + \@makefnmark + #1% +}% +\def\frontmatter@setup{}% +\def\frontmatter@RRAPformat#1{% + \removelastskip + \begingroup + \frontmatter@RRAP@format + #1\par + \endgroup +}% +\def\punct@RRAP{; }% +\def\produce@RRAP#1{% + \@if@empty{#1}{}{% + \@ifvmode{\leavevmode}{\unskip\punct@RRAP\ignorespaces}% + #1% + }% +}% +\def\frontmatter@authorformat{}% +\def\frontmatter@above@affilgroup{}% +\def\frontmatter@above@affiliation{}% +\def\frontmatter@above@affiliation@script{}% +\def\frontmatter@affiliationfont{\itshape\selectfont}% +\def\frontmatter@RRAP@format{}% +\def\frontmatter@PACS@format{}% +\def\frontmatter@keys@format{}% +\def\frontmatter@finalspace{\addvspace{18\p@}} +\def\frontmatter@addressnewline{% + \@ifhmode{\skip@\lastskip\unskip\unpenalty\break\hskip\skip@}{}% + % was: \vskip-.5ex +}% +\def\frontmatter@preabstractspace{5.5\p@} +\def\frontmatter@postabstractspace{6.5\p@} +\def\aftermaketitle@chk#1{% + \@ifx{\maketitle\relax}{% + \class@err{\protect#1 must be used before \protect\maketitle}% + }{}% +}% +\def\ps@titlepage{\ps@empty}% +\def\volumeyear#1{\gdef\@volumeyear{#1}}% +\def\@volumeyear{}% +\def\volumenumber#1{\gdef\@volumenumber{#1}}% +\def\@volumenumber{}% +\def\issuenumber#1{\gdef\@issuenumber{#1}}% +\def\@issuenumber{}% +\def\eid#1{\gdef\@eid{#1}}% +\def\@eid{}% +\def\startpage#1{\gdef\@startpage{#1}\c@page#1\relax}% +\def\@startpage{\pageref{FirstPage}}% +\def\endpage#1{\gdef\@endpage{#1}}% +\def\@endpage{\pageref{LastPage}}% +\def\print@toc#1{% + \begingroup + \expandafter\section + \expandafter*% + \expandafter{% + \csname#1name\endcsname + }% + \let\appendix\appendix@toc + \@starttoc{#1}% + \endgroup +}% +\def\appendix@toc{}% +\def\Dated@name{Dated }% +\def\Received@name{Received }% +\def\Revised@name{Revised }% +\def\Accepted@name{Accepted }% +\def\Published@name{Published }% +\appdef\robustify@contents{% + \let\thanks\@gobble\let\class@warn\@gobble + \def\begin{\string\begin}\def\end{\string\end}% +}% +\@ifxundefined\frontmatter@syntax@sw{\@booleantrue\frontmatter@syntax@sw}{}% +\frontmatter@syntax@sw{% + \let\title \frontmatter@title + \let\author \frontmatter@author + \let\date \frontmatter@date + \@ifxundefined\@maketitle{% + \let\maketitle \frontmatter@maketitle + \@booleantrue \titlepage@sw + }{% + \let\@maketitle \frontmatter@maketitle + \prepdef\maketitle\@author@finish + }% + \let\noaffiliation \frontmatter@noaffiliation + \let\thanks@latex \thanks + \let\thanks \frontmatter@thanks + \let\and@latex \and + \let\and \frontmatter@and + \let@environment{titlepage}{frontmatter@titlepage}% + \let@environment{abstract}{frontmatter@abstract}% +}{% + \let\noaffiliation\@empty +}% +\def\thanks@latex#1{% + \footnotemark + \expandafter\expandafter + \expandafter\gappdef + \expandafter\expandafter + \expandafter\@thanks + \expandafter\expandafter + \expandafter{% + \expandafter\expandafter + \expandafter\footnotetext + \expandafter\expandafter + \expandafter[% + \expandafter\the\csname c@\@mpfn\endcsname]{#1}}% +}% +\@booleanfalse\altaffilletter@sw +\@if@sw\if@titlepage\fi{\@booleantrue}{\@booleanfalse}\titlepage@sw +\def\frontmatter@title@above{\newpage\null\vskip2em\relax}% +\def\frontmatter@title@format{\centering\LARGE\let\thanks\thanks@latex}% +\def\frontmatter@title@below{\vskip1.5em\relax}% +\def\frontmatter@authorformat{\centering\large\advance\baselineskip\p@\parskip11.5\p@\let\thanks\thanks@latex\let\and\and@space}% +\def\frontmatter@authorbelow{\vskip 1em\relax}% +\def\frontmatter@above@affiliation{}% +\def\frontmatter@above@affiliation@script{}% +\def\frontmatter@affiliationfont{\centering\itshape}% +\def\frontmatter@RRAP@format{\centering\large}% +\def\frontmatter@preabstractspace{1.5em}% +\long\def\frontmatter@footnotetext{% + \expandafter\expandafter + \expandafter\footnotetext + \expandafter\expandafter + \expandafter[% + \expandafter\the\csname c@\@mpfn\endcsname]% +}% +\def\and@space{\\}% +\def\andname{and}% +\endinput +%% +%% End of file `ltxfront.sty'. diff --git a/src/easydiffraction/report/templates/tex/styles/ltxgrid.sty b/src/easydiffraction/report/templates/tex/styles/ltxgrid.sty new file mode 100644 index 000000000..606fd052b --- /dev/null +++ b/src/easydiffraction/report/templates/tex/styles/ltxgrid.sty @@ -0,0 +1,2754 @@ +%% +%% This is file `ltxgrid.sty', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% ltxgrid.dtx (with options: `package,kernel') +%% +%% This is a generated file; +%% altering it directly is inadvisable; +%% instead, modify the original source file. +%% See the URL in the file README-LTXGRID.tex. +%% +%% License +%% You may distribute this file under the conditions of the +%% LaTeX Project Public License 1.3c or later +%% (http://www.latex-project.org/lppl.txt). +%% +%% This file is distributed WITHOUT ANY WARRANTY; +%% without even the implied warranty of MERCHANTABILITY +%% or FITNESS FOR A PARTICULAR PURPOSE. +%% +%%% @LaTeX-file{ +%%% filename = "ltxgrid.dtx", +%%% version = "4.2f", +%%% date = "2022/06/05", +%%% author = "Arthur Ogawa (mailto:arthur_ogawa at sbcglobal.net), +%%% Phelype Oleinik (mailto:phelype.oleinik at latex-project.org), +%%% commissioned by the American Physical Society. +%%% ", +%%% copyright = "Copyright (C) 1999, 2009 Arthur Ogawa, +%%% distributed under the terms of the +%%% LaTeX Project Public License 1.3c, see +%%% ftp://ctan.tug.org/macros/latex/base/lppl.txt +%%% ", +%%% address = "Arthur Ogawa, +%%% USA", +%%% telephone = "", +%%% FAX = "", +%%% email = "mailto colon arthur_ogawa at sbcglobal.net", +%%% codetable = "ISO/ASCII", +%%% keywords = "latex, page grid, main vertical list", +%%% supported = "yes", +%%% abstract = "package to change page grid, MVL", +%%% } +\NeedsTeXFormat{LaTeX2e}[1995/12/01]% +\ProvidesFile{% +ltxgrid% +.sty% +}% + [2022/06/05 4.2f page grid package (portions licensed from W. E. Baxter web at superscript.com)]% \fileversion +\def\package@name{ltxgrid}% +\expandafter\PackageInfo\expandafter{\package@name}{% + Page grid for \protect\LaTeXe, + by A. Ogawa (arthur_ogawa at sbcglobal.net)% +}% +\RequirePackage{ltxutil}% +\newcounter{linecount} +\def\loop@line#1#2{% + \par + \hb@xt@\hsize{% + \global\advance#1\@ne + \edef\@tempa{\@ifnum{100>#1}{0}{}\@ifnum{10>#1}{0}{}\number#1}% + \@tempa\edef\@tempa{\special{line:\@tempa}}\@tempa + \vrule depth2.5\p@#2\leaders\hrule\hfil + }% +}% +\def\lineloop#1{% + \loopwhile{\loop@line\c@linecount{}\@ifnum{#1>\c@linecount}}% +}% +\def\linefoot#1{% + \loop@line\c@linecount{% + \footnote{% + #1\special{foot:#1}\vrule depth2.5\p@\leaders\hrule\hfill + }% + }% +}% +\let\@@mark\mark +\let\@@topmark\topmark +\let\@@firstmark\firstmark +\let\@@botmark\botmark +\let\@@splitfirstmark\splitfirstmark +\let\@@splitbotmark\splitbotmark +\def\@themark{{}{}{}{}}% +\def\nul@mark{{}{}{}{}\@@nul}% +\def\set@mark@netw@#1#2#3#4#5#6#7{\gdef#1{{#6}{#7}{#4}{#5}}\do@mark}% +\def\set@marktw@#1#2#3#4#5#6{\gdef#1{{#2}{#6}{#4}{#5}}\do@mark}% +\def\set@markthr@@#1#2#3#4#5#6{\gdef#1{{#2}{#3}{#6}{#5}}\do@mark}% +\def\get@mark@@ne#1#2#3#4#5\@@nul{#1}% +\def\get@mark@tw@#1#2#3#4#5\@@nul{#2}% +\def\get@mark@thr@@#1#2#3#4#5\@@nul{#3}% +\def\get@mark@f@ur#1#2#3#4#5\@@nul{#4}% +\def\mark@netw@{\expandafter\set@mark@netw@\expandafter\@themark\@themark}% +\def\marktw@{\expandafter\set@marktw@\expandafter\@themark\@themark}% +\def\markthr@@{\expandafter\set@markthr@@\expandafter\@themark\@themark}% +\def\do@mark{\do@@mark\@themark\nobreak@mark}% +\def\do@@mark#1{% + \begingroup + \let@mark + \@@mark{#1}% + \endgroup +}% +\def\let@mark{% + \let\protect\@unexpandable@protect + \let\label\relax + \let\index\relax + \let\glossary\relax +}% +\def\nobreak@mark{% + \@if@sw\if@nobreak\fi{\@ifvmode{\nobreak}{}}{}% +}% +\def\mark@envir{\markthr@@}% +\def\bot@envir{% + \expandafter\expandafter + \expandafter\get@mark@thr@@ + \expandafter\@@botmark + \nul@mark +}% +\def\markboth{\mark@netw@}% +\def\markright{\marktw@}% +\def\leftmark{% + \expandafter\expandafter + \expandafter\get@mark@@ne + \expandafter\saved@@botmark + \nul@mark +}% +\def\rightmark{% + \expandafter\expandafter + \expandafter\get@mark@tw@ + \expandafter\saved@@firstmark + \nul@mark +}% +\let\primitive@output\output +\long\def\@tempa#1\@@nil{#1}% + \toks@ +\expandafter\expandafter +\expandafter{% +\expandafter \@tempa + \the\primitive@output + \@@nil + }% +\newtoks\output@latex +\output@latex\expandafter{\the\toks@}% +\let\output\output@latex +\primitive@output{\dispatch@output}% +\def\dispatch@output{% + \let\par\@@par + \expandafter\let\expandafter\output@procedure\csname output@\the\outputpenalty\endcsname + \@ifnotrelax\output@procedure{}{% + \expandafter\def\expandafter\output@procedure\expandafter{\the\output@latex}% + }% + \expandafter\@ifx\expandafter{\csname output@-\the\execute@message@pen\endcsname\output@procedure}{% + \let\output@procedure\@message@saved + }{}% + \ltxgrid@info@sw{\class@info{\string\dispatch@output}\say\output@procedure\saythe\holdinginserts}{}% + \outputdebug@sw{\output@debug}{}% + \output@procedure +}% +\def\set@output@procedure#1#2{% + \count@\outputpenalty\advance\count@-#2% + \expandafter\let\expandafter#1\csname output@\the\count@\endcsname +}% +\def\output@debug{% + \def\@tempa{\save@message}% + \@ifx{\output@procedure\@tempa}{% + \true@sw + }{% + \@ifnum{\outputpenalty=-\save@column@insert@pen}{% + \@ifnum{\holdinginserts>\z@}% + }{% + \false@sw + }% + }% + {}{\output@debug@}% +}% +\def\output@debug@{% + \saythe\outputpenalty + \saythe\interlinepenalty + \saythe\brokenpenalty + \saythe\clubpenalty + \saythe\widowpenalty + \saythe\displaywidowpenalty + \saythe\predisplaypenalty + \saythe\interdisplaylinepenalty + \saythe\postdisplaypenalty + \saythe\badness + \say\thepagegrid + \saythe\pagegrid@col + \saythe\pagegrid@cur + \saythe\insertpenalties + \say\@@botmark + \saythe\pagegoal + \saythe\pagetotal + \saythe{\badness\@cclv}% + \say\@toplist + \say\@botlist + \say\@dbltoplist + \say\@deferlist + \trace@scroll{% + \showbox\@cclv + \showbox\@cclv@saved + \showbox\pagesofar + \showbox\csname col@1\endcsname + \showbox\footsofar + \showbox\footins + \showbox\footins@saved + \showlists + }% +}% +\@ifxundefined{\outputdebug@sw}{% + \@booleanfalse\outputdebug@sw +}{}% +\def\trace@scroll#1{\begingroup\showboxbreadth\maxdimen\showboxdepth\maxdimen\scrollmode#1\endgroup}% +\def\trace@box#1{\trace@scroll{\showbox#1}}% +\prepdef\@outputpage{\@outputpage@head}% +\let\@outputpage@head\@empty +\appdef\@outputpage{\@outputpage@tail}% +\let\@outputpage@tail\@empty +\def\show@box@size#1#2{% + \show@box@size@sw{% + \begingroup + \setbox\z@\vbox{\unvcopy#2\hrule}% + \class@info{Show box size: #1^^J% + (\the\ht\z@\space X \the\wd\z@) + \the\c@page\space\space\the\pagegrid@cur\space\the\pagegrid@col + }% + \endgroup + }{}% +}% +\def\show@text@box@size{% + \show@box@size{Text column}\@outputbox + \tally@box@size@sw{% + \@ifdim{\wd\@outputbox>\z@}{% + \dimen@\ht\@outputbox\divide\dimen@\@twopowerfourteen + \advance\dimen@-\dp\csname box@size@\the\pagegrid@col\endcsname + \@ifdim{\dimen@>\z@}{% + \advance\dimen@ \ht\csname box@size@\the\pagegrid@col\endcsname + \global\ht\csname box@size@\the\pagegrid@col\endcsname\dimen@ + \show@box@size@sw{% + \class@info{Column: \the\dimen@}% + }{}% + }{}% + }{}% + \global\dp\csname box@size@\the\pagegrid@col\endcsname\z@ + }{}% +}% +\def\show@pagesofar@size{% + \show@box@size{Page so far}\pagesofar + \dimen@\ht\pagesofar\divide\dimen@\@twopowerfourteen + \global\dp\csname box@size@1\endcsname\dimen@ + \show@box@size@sw{% + \class@info{Pagesofar: \the\dimen@}% + }{}% +}% +\@booleanfalse\tally@box@size@sw +\@booleanfalse\show@box@size@sw +\expandafter\newbox\csname box@size@1\endcsname +\expandafter\setbox\csname box@size@1\endcsname\hbox{}% +\expandafter\newbox\csname box@size@2\endcsname +\expandafter\setbox\csname box@size@2\endcsname\hbox{}% +\def\total@text{% + \@tempdima\the\ht\csname box@size@2\endcsname\divide\@tempdima\@twopowertwo\@tempcnta\@tempdima + \@tempdimb\the\ht\csname box@size@1\endcsname\divide\@tempdimb\@twopowertwo\@tempcntb\@tempdimb + \class@info{Total text: Column(\the\@tempcnta pt), Page(\the\@tempcntb pt)}% +}% +\def\natural@output{\toggle@insert{\output@holding}{\output@moving}}% +\output@latex{\natural@output}% +\def\output@holding{% + \csname output@init@\bot@envir\endcsname + \@if@exceed@pagegoal{\unvcopy\@cclv}{% + \setbox\z@\vbox{\unvcopy\@cclv}% + \outputdebug@sw{\trace@box\z@}{}% + \dimen@\ht\@cclv\advance\dimen@-\ht\z@ + \dead@cycle@repair\dimen@ + }{% + \dead@cycle + }% +}% +\def\@if@exceed@pagegoal#1{% + \begingroup + \setbox\z@\vbox{#1}% + \dimen@\ht\z@\advance\dimen@\dp\z@ + \outputdebug@sw{\saythe\dimen@}{}% + \@ifdim{\dimen@>\pagegoal}{% + \setbox\z@\vbox{\@@mark{}\unvbox\z@}% + \splittopskip\topskip + \splitmaxdepth\maxdepth + \vbadness\@M + \vfuzz\maxdimen + \setbox\tw@\vsplit\z@ to\pagegoal + \outputdebug@sw{\trace@scroll{\showbox\tw@\showbox\z@}}{}% + \setbox\tw@\vbox{\unvbox\tw@}% + \@ifdim{\ht\tw@=\z@}{% + \ltxgrid@info{Found overly large chunk while preparing to move insertions. Attempting repairs}% + \aftergroup\true@sw + }{% + \aftergroup\false@sw + }% + }{% + \aftergroup\false@sw + }% + \endgroup +}% +\def\output@moving{% + \set@top@firstmark + \@ifnum{\outputpenalty=\do@newpage@pen}{% + \setbox\@cclv\vbox{% + \unvbox\@cclv + \remove@lastbox + \@ifdim{\ht\z@=\ht\@protection@box}{\box\lastbox}{\unskip}% + }% + }{}% + \@cclv@nontrivial@sw{% + \expandafter\output@do@prep\csname output@prep@\bot@envir \endcsname + \@makecolumn\true@sw + \expandafter\output@column@do\csname output@column@\thepagegrid\endcsname + \protect@penalty\do@startcolumn@pen + \clearpage@sw{% + \protect@penalty\do@endpage@pen + }{}% + \expandafter\let\expandafter\output@post@\csname output@post@\bot@envir \endcsname + \outputdebug@sw{\say\output@post@}{}% + \@ifx{\output@post@\relax}{\output@post@document}{\output@post@}% + }{% + \void@cclv + }% + \set@colht + \global\@mparbottom\z@ + \global\@textfloatsheight\z@ +}% +\def\output@do@prep#1{% + \outputdebug@sw{\class@info{Prep: \string#1}}{}% + \@ifx{#1\relax}{\output@prep@document}{#1}% +}% +\def\output@column@do#1{% + \outputdebug@sw{\class@info{Output column: \string#1}}{}% + \@ifx{#1\relax}{\output@column@one}{#1}% +}% +\def\void@cclv{\begingroup\setbox\z@\box\@cclv\endgroup}% +\def\remove@lastbox{\setbox\z@\lastbox}% +\def\@cclv@nontrivial@sw{% + \@ifx@empty\@toplist{% + \@ifx@empty\@botlist{% + \@ifvoid\footins{% + \@ifvoid\@cclv{% + \false@sw + }{% + \setbox\z@\vbox{\unvcopy\@cclv}% + \@ifdim{\ht\z@=\topskip}{% + \setbox\z@\vbox\bgroup + \unvbox\z@ + \remove@lastbox + \dimen@\lastskip\unskip + \@ifdim{\ht\z@=\ht\@protection@box}{% + \advance\dimen@\ht\z@ + \@ifdim{\dimen@=\topskip}{% + \aftergroup\true@sw + }{% + \aftergroup\false@sw + }% + }{% + \aftergroup\false@sw + }% + \egroup + {% + \false@sw + }{% + \true@sw + }% + }{% + \@ifdim{\ht\z@=\z@}{% + \ltxgrid@info{Found trivial column. Discarding it}% + \outputdebug@sw{\trace@box\@cclv}{}% + \false@sw + }{% + \true@sw + }% + }% + }% + }{% + \true@sw + }% + }{% + \true@sw + }% + }{% + \true@sw + }% +}% +\def\protect@penalty#1{\protection@box\penalty-#1\relax}% +\newbox\@protection@box +\setbox\@protection@box\vbox to1986sp{\vfil}% +\def\protection@box{\nointerlineskip\copy\@protection@box}% +\def\dead@cycle@repair#1{% + \expandafter\do@@mark + \expandafter{% + \@@botmark + }% + \unvbox\@cclv + \nointerlineskip + \vbox to#1{\vss}% + \@ifnum{\outputpenalty<\@M}{\penalty\outputpenalty}{}% +}% +\def\dead@cycle@repair@protected#1{% + \expandafter\do@@mark + \expandafter{% + \@@botmark + }% + \begingroup + \unvbox\@cclv + \remove@lastbox + \nointerlineskip + \advance#1-\ht\@protection@box + \vbox to#1{\vss}% + \protection@box % Reinsert protection box + \@ifnum{\outputpenalty<\@M}{\penalty\outputpenalty}{}% + \endgroup +}% +\def\dead@cycle{% + \expandafter\do@@mark + \expandafter{% + \@@botmark + }% + \unvbox\@cclv + \@ifnum{\outputpenalty<\@M}{\penalty\outputpenalty}{}% +}% +\def\output@init@document{% + \ltxgrid@info@sw{\class@info{\string\output@init@document}}{}% + \global\vsize\vsize +}% +\def\output@prep@document{% + \ltxgrid@foot@info@sw{\class@info{\string\output@prep@document}\trace@scroll{\showbox\footins\showbox\footsofar}}{}% + \@ifvoid\footsofar{% + }{% + \global\setbox\footins\vbox\bgroup + \unvbox\footsofar + \@ifvoid\footins{}{% + \marry@baselines + \unvbox\footins + }% + \egroup + \ltxgrid@foot@info@sw{\trace@box\footins}{}% + }% +}% +\def\output@post@document{}% +\let\@opcol\@undefined +\def\@makecolumn#1{% + \ltxgrid@foot@info@sw{\class@info{\string\@makecolumn\string#1}}{}% + \setbox\@outputbox\vbox\bgroup + \boxmaxdepth\@maxdepth + \@tempdima\dp\@cclv + \unvbox\@cclv + \vskip-\@tempdima + \egroup + \xdef\@freelist{\@freelist\@midlist}\global\let\@midlist\@empty + \show@text@box@size + \@combinefloats + #1{% + \@combineinserts\@outputbox\footins + }{% + \combine@foot@inserts\footsofar\footins + }% + \set@adj@colht\dimen@ + \count@\vbadness + \vbadness\@M + \setbox\@outputbox\vbox to\dimen@\bgroup + \@texttop + \dimen@\dp\@outputbox + \unvbox\@outputbox + \vskip-\dimen@ + \@textbottom + \egroup + \vbadness\count@ + \global\maxdepth\@maxdepth +}% +\let\@makespecialcolbox\@undefined +\def\@combineinserts#1#2{% + \ltxgrid@foot@info@sw{\class@info{\string\@combineinserts\string#1\string#2}\trace@box#2}{}% + \setbox#1\vbox\bgroup + \unvbox#1% + \@ifvoid{#2}{}{% + \dimen@\ht#2\advance\dimen@\dp#2\advance\dimen@\skip#2% + \show@box@size{Combining inserts}#2% + \vskip\skip#2% + \setbox\z@\vbox{\footnoterule}\dimen@i\ht\z@ + \color@begingroup + \normalcolor + \cleaders\box\z@\vskip\dimen@i\kern-\dimen@i + \csname combine@insert@\the\pagegrid@col\endcsname#2% + \color@endgroup + \kern-\dimen@\kern\dimen@ + }% + \egroup + \ltxgrid@foot@info@sw{\trace@box#1}{}% +}% +\def\combine@insert@tw@#1{% + \compose@footnotes@two#1\@ifvbox{#1}{\unvbox}{\box}#1% +}% +\def\combine@insert@@ne#1{% + \compose@footnotes@one#1\@ifvbox{#1}{\unvbox}{\box}#1% +}% +\def\twocolumn@grid@setup{% + \expandafter\let\csname combine@insert@1\endcsname\combine@insert@tw@ + \expandafter\let\csname combine@insert@2\endcsname\combine@insert@@ne +}% +\def\onecolumn@grid@setup{% + \expandafter\let\csname combine@insert@1\endcsname\combine@insert@@ne + \expandafter\let\csname combine@insert@2\endcsname\combine@insert@@ne +}% +\let\columngrid@setup\onecolumn@grid@setup +\columngrid@setup +\appdef\@floatplacement{% + \global\@fpmin\@fpmin +}% +\mathchardef\pagebreak@pen=\@M +\expandafter\let\csname output@-\the\pagebreak@pen\endcsname\relax +\mathchardef\do@startcolumn@pen=10005 +\@namedef{output@-\the\do@startcolumn@pen}{\do@startcolumn}% +\def\do@startcolumn{% + \setbox\@cclv\vbox{\unvbox\@cclv\remove@lastbox\unskip}% + \clearpage@sw{\@clearfloatplacement}{\@floatplacement}% + \set@colht + \@booleanfalse\pfloat@avail@sw + \begingroup + \@colht\@colroom + \@booleanfalse\float@avail@sw + \@tryfcolumn\test@colfloat + \float@avail@sw{\aftergroup\@booleantrue\aftergroup\pfloat@avail@sw}{}% + \endgroup + \fcolmade@sw{% + \setbox\@cclv\vbox{\unvbox\@outputbox\unvbox\@cclv}% + \outputpenalty-\pagebreak@pen + \dead@cycle + }{% + \begingroup + \let\@elt\@scolelt + \let\reserved@b\@deferlist\global\let\@deferlist\@empty\reserved@b + \endgroup + \clearpage@sw{% + \outputpenalty\@M + }{% + \outputpenalty\do@newpage@pen + }% + \dead@cycle + }% + \check@deferlist@stuck\do@startcolumn + \set@vsize +}% +\def\@scolelt#1{\def\@currbox{#1}\@addtonextcol}% +\def\test@colfloat#1{% + \csname @floatselect@sw@\thepagegrid\endcsname#1{}{\@testtrue}% + \@if@sw\if@test\fi{}{\aftergroup\@booleantrue\aftergroup\float@avail@sw}% +}% +\def\@addtonextcol{% + \begingroup + \@insertfalse + \@setfloattypecounts + \csname @floatselect@sw@\thepagegrid\endcsname\@currbox{% + \@ifnum{\@fpstype=8 }{}{% + \@ifnum{\@fpstype=24 }{}{% + \@flsettextmin + \@reqcolroom \ht\@currbox + \advance \@reqcolroom \@textmin + \advance \@reqcolroom \vsize % take into account split insertions + \advance \@reqcolroom -\pagegoal + \@ifdim{\@colroom>\@reqcolroom}{% + \@flsetnum \@colnum + \@ifnum{\@colnum>\z@}{% + \@bitor\@currtype\@deferlist + \@if@sw\if@test\fi{}{% + \@addtotoporbot + }% + }{}% + }{}% + }% + }% + }{}% + \@if@sw\if@insert\fi{}{% + \@cons\@deferlist\@currbox + }% + \endgroup +}% +\mathchardef\do@startpage@pen=10006 +\@namedef{output@-\the\do@startpage@pen}{\do@startpage}% +\def\do@startpage{% + \setbox\@cclv\vbox{\unvbox\@cclv\remove@lastbox\unskip}% + \clearpage@sw{\@clearfloatplacement}{\@dblfloatplacement}% + \set@colht + \@booleanfalse\pfloat@avail@sw + \begingroup + \@booleanfalse\float@avail@sw + \@tryfcolumn\test@dblfloat + \float@avail@sw{\aftergroup\@booleantrue\aftergroup\pfloat@avail@sw}{}% + \endgroup + \fcolmade@sw{% + \global\setbox\pagesofar\vbox{\unvbox\pagesofar\unvbox\@outputbox}% + \@output@combined@page + }{% + \begingroup + \@booleanfalse\float@avail@sw + \let\@elt\@sdblcolelt + \let\reserved@b\@deferlist\global\let\@deferlist\@empty\reserved@b + \endgroup + \@ifdim{\@colht=\textheight}{% No luck... + \pfloat@avail@sw{% ...but a float *was* available! + \forcefloats@sw{% + \ltxgrid@warn{Forced dequeueing of floats stalled}% + }{% + \ltxgrid@warn{Dequeueing of floats stalled}% + }% + }{}% + }{}% + \outputpenalty\@M + \dead@cycle + }% + \check@deferlist@stuck\do@startpage + \set@colht +}% +\def\@output@combined@page{% + \@combinepage\true@sw + \@combinedblfloats + \@outputpage + \global\pagegrid@cur\@ne + \protect@penalty\do@startpage@pen +}% +\def\@sdblcolelt#1{\def\@currbox{#1}\@addtodblcol}% +\def\test@dblfloat#1{% + \@if@notdblfloat{#1}{\@testtrue}{}% + \@if@sw\if@test\fi{}{\aftergroup\@booleantrue\aftergroup\float@avail@sw}% +}% +\def\@if@notdblfloat#1{\@ifdim{\wd#1<\textwidth}}% +\@booleanfalse\forcefloats@sw +\def\@addtodblcol{% + \begingroup + \@if@notdblfloat{\@currbox}{% + \false@sw + }{% + \@setfloattypecounts + \@getfpsbit \tw@ + \@bitor \@currtype \@deferlist + \@if@sw\if@test\fi{% + \false@sw + }{% + \@ifodd\@tempcnta{% + \aftergroup\@booleantrue\aftergroup\float@avail@sw + \@flsetnum \@dbltopnum + \@ifnum{\@dbltopnum>\z@}{% + \@ifdim{\@dbltoproom>\ht\@currbox}{% + \true@sw + }{% + \@ifnum{\@fpstype<\sixt@@n}{% + \begingroup + \advance \@dbltoproom \@textmin + \@ifdim{\@dbltoproom>\ht\@currbox}{% + \endgroup\true@sw + }{% + \endgroup\false@sw + }% + }{% + \false@sw + }% + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }% + }% + {% + \@tempdima -\ht\@currbox + \advance\@tempdima + -\@ifx{\@dbltoplist\@empty}{\dbltextfloatsep}{\dblfloatsep}% + \global \advance \@dbltoproom \@tempdima + \global \advance \@colht \@tempdima + \global \advance \@dbltopnum \m@ne + \@cons \@dbltoplist \@currbox + }{% + \@cons \@deferlist \@currbox + }% + \endgroup +}% +\def\@tryfcolumn#1{% + \global\@booleanfalse\fcolmade@sw + \@ifx@empty\@deferlist{}{% + \global\let\@trylist\@deferlist + \global\let\@failedlist\@empty + \begingroup + \dimen@\vsize\advance\dimen@-\pagegoal\@ifdim{\dimen@>\z@}{% + \advance\@fpmin-\dimen@ + }{}% + \def\@elt{\@xtryfc#1}\@trylist + \endgroup + \fcolmade@sw{% + \global\setbox\@outputbox\vbox{\vskip \@fptop}% + \let \@elt \@wtryfc \@flsucceed + \global\setbox\@outputbox\vbox{\unvbox\@outputbox + \unskip \vskip \@fpbot + }% + \let \@elt \relax + \xdef\@deferlist{\@failedlist\@flfail}% + \xdef\@freelist{\@freelist\@flsucceed}% + }{}% + }% +}% +\def\@wtryfc #1{% + \global\setbox\@outputbox\vbox{\unvbox\@outputbox + \box #1\vskip\@fpsep + }% +}% +\def\@xtryfc#1#2{% + \@next\reserved@a\@trylist{}{}% trim \@trylist. Ugly! + \@currtype \count #2% + \divide\@currtype\@xxxii\multiply\@currtype\@xxxii + \@bitor \@currtype \@failedlist + \@testfp #2% + #1#2% + \@ifdim{\ht #2>\@colht }{\@testtrue}{}% + \@if@sw\if@test\fi{% + \@cons\@failedlist #2% + }{% + \begingroup + \gdef\@flsucceed{\@elt #2}% + \global\let\@flfail\@empty + \@tempdima\ht #2% + \def \@elt {\@ztryfc#1}\@trylist + \@ifdim{\@tempdima >\@fpmin}{% + \global\@booleantrue\fcolmade@sw + }{% + \@cons\@failedlist #2% + }% + \endgroup + \fcolmade@sw{% + \let \@elt \@gobble + }{}% + }% +}% +\def\@ztryfc #1#2{% + \@tempcnta \count#2% + \divide\@tempcnta\@xxxii\multiply\@tempcnta\@xxxii + \@bitor \@tempcnta {\@failedlist \@flfail}% + \@testfp #2% + #1#2% + \@tempdimb\@tempdima + \advance\@tempdimb \ht#2\advance\@tempdimb\@fpsep + \@ifdim{\@tempdimb >\@colht}{% + \@testtrue + }{}% + \@if@sw\if@test\fi{% + \@cons\@flfail #2% + }{% + \@cons\@flsucceed #2% + \@tempdima\@tempdimb + }% +}% +\def\newpage@prep{% + \if@noskipsec + \ifx \@nodocument\relax + \leavevmode + \global \@noskipsecfalse + \fi + \fi + \if@inlabel + \leavevmode + \global \@inlabelfalse + \fi + \if@nobreak \@nobreakfalse \everypar{}\fi + \par +}% +\def \newpage {% + \newpage@prep + \do@output@MVL{% + \vfil + \penalty-\pagebreak@pen + }% +}% +\def\clearpage{% + \newpage@prep + \do@output@MVL{% + \vfil + \penalty-\pagebreak@pen + \global\@booleantrue\clearpage@sw + \protect@penalty\do@startcolumn@pen + \protect@penalty\do@endpage@pen + }% + \do@output@MVL{% + \global\@booleanfalse\clearpage@sw + }% +}% +\def\cleardoublepage{% + \clearpage + \@if@sw\if@twoside\fi{% + \@ifodd\c@page{}{% + \null\clearpage + }% + }{}% +}% +\@booleanfalse\clearpage@sw +\mathchardef\do@endpage@pen=10007 +\@namedef{output@-\the\do@endpage@pen}{\csname end@column@\thepagegrid\endcsname}% +\mathchardef\do@newpage@pen=10001 +\expandafter\let\csname output@-\the\do@newpage@pen\endcsname\relax +\def\@clearfloatplacement{% + \global\@topnum \maxdimen + \global\@toproom \maxdimen + \global\@botnum \maxdimen + \global\@botroom \maxdimen + \global\@colnum \maxdimen + \global\@dbltopnum \maxdimen + \global\@dbltoproom \maxdimen + \global\@textmin \z@ + \global\@fpmin \z@ + \let\@testfp\@gobble + \appdef\@setfloattypecounts{\@fpstype16\advance\@fpstype\m@ne}% +}% +\let\@doclearpage\@undefined +\let\@makefcolumn\@undefined +\let\@makecol\@undefined +\def\clr@top@firstmark{% + \global\let\saved@@topmark\@undefined + \global\let\saved@@firstmark\@empty + \global\let\saved@@botmark\@empty +}% +\clr@top@firstmark +\def\set@top@firstmark{% + \@ifxundefined\saved@@topmark{\expandafter\gdef\expandafter\saved@@topmark\expandafter{\@@topmark}}{}% + \@if@empty\saved@@firstmark{\expandafter\gdef\expandafter\saved@@firstmark\expandafter{\@@firstmark}}{}% + \@if@empty\@@botmark{}{\expandafter\gdef\expandafter\saved@@botmark\expandafter{\@@botmark}}% +}% +\appdef\@outputpage@tail{% + \clr@top@firstmark +}% +\def\@float#1{% + \@ifnextchar[{% + \@yfloat\width@float{#1}% + }{% + \@ifxundefined@cs{fps@#1}{}{\expandafter\let\expandafter\fps@\csname fps@#1\endcsname}% + \expandafter\@argswap\expandafter{\expandafter[\fps@]}{\@yfloat\width@float{#1}}% + }% +}% +\def\@dblfloat#1{% + \@ifnum{\pagegrid@col=\@ne}{% + \@float{#1}% + }{% + \@ifnextchar[{% + \@yfloat\widthd@float{#1}% + }{% + \@ifxundefined@cs{fpsd@#1}{}{\expandafter\let\expandafter\fpsd@\csname fpsd@#1\endcsname}% + \expandafter\@argswap\expandafter{\expandafter[\fpsd@]}{\@yfloat\widthd@float{#1}}% + }% + }% +}% +\def\@yfloat#1#2[#3]{% + \@xfloat{#2}[#3]% + \hsize#1\linewidth\hsize + \let\set@footnotewidth\@empty + \minipagefootnote@init +}% +\def\fps@{tbp}% +\def\fpsd@{tp}% +\def\width@float{\columnwidth}% +\def\widthd@float{\textwidth}% +\def\end@float{% + \end@@float{% + \check@currbox@count + }% +}% +\def\end@dblfloat{% + \@ifnum{\pagegrid@col=\@ne}{% + \end@float + }{% + \end@@float{% + \@iffpsbit\@ne{\global\advance\count\@currbox\m@ne}{}% + \@iffpsbit\f@ur{\global\advance\count\@currbox-4\relax}{}% + \global\wd\@currbox\textwidth % Klootch + \check@currbox@count + }% + }% +}% +\def\end@@float#1{% + \minipagefootnote@here + \@endfloatbox + #1% + \@ifnum{\@floatpenalty <\z@}{% + \@largefloatcheck + \@cons\@currlist\@currbox + \@ifnum{\@floatpenalty <-\@Mii}{% + \do@output@cclv{\@add@float}% + }{% + \vadjust{\do@output@cclv{\@add@float}}% + \@Esphack + }% + }{}% +}% +\newcommand\float@end@float{% + \@endfloatbox + \global\setbox\@currbox\float@makebox\columnwidth + \let\@endfloatbox\relax + \end@float +}% +\newcommand\float@end@ltx{% + \end@@float{% + \global\setbox\@currbox\float@makebox\columnwidth + \check@currbox@count + }% +}% +\newcommand\newfloat@float[3]{% + \@namedef{ext@#1}{#3} %! + \let\float@do=\relax + \xdef\@tempa{\noexpand\float@exts{\the\float@exts \float@do{#3}}}% + \@tempa + \floatplacement{#1}{#2}% + \@ifundefined{fname@#1}{\floatname{#1}{#1}}{} %! + \expandafter\edef\csname ftype@#1\endcsname{\value{float@type}}% + \addtocounter{float@type}{\value{float@type}} %! + \restylefloat{#1}% + \expandafter\edef\csname fnum@#1\endcsname{% + \expandafter\noexpand\csname fname@#1\endcsname{} %! + \expandafter\noexpand\csname the#1\endcsname + } + \@ifnextchar[%] + {% + \float@newx{#1}% + }{% + \@ifundefined{c@#1}{\newcounter{#1}\@namedef{the#1}{\arabic{#1}}}{}% + }% +}% +\newcommand\newfloat@ltx[3]{% + \@namedef{ext@#1}{#3}% + \let\float@do=\relax + \xdef\@tempa{\noexpand\float@exts{\the\float@exts \float@do{#3}}}% + \@tempa + \floatplacement{#1}{#2}% + \@ifundefined{fname@#1}{\floatname{#1}{#1}}{}% + \expandafter\edef\csname ftype@#1\expandafter\endcsname\expandafter{\the\c@float@type}% + \addtocounter{float@type}{\value{float@type}}% + \restylefloat{#1}% + \expandafter\edef\csname fnum@#1\endcsname{% + \expandafter\noexpand\csname fname@#1\endcsname{}% + \expandafter\noexpand\csname the#1\endcsname + } + \@ifnextchar[%] + {% + \float@newx{#1}% + }{% + \@ifundefined{c@#1}{\newcounter{#1}\@namedef{the#1}{\arabic{#1}}}{}% + }% +}% +\appdef\document@inithook{% + \@ifxundefined\newfloat{}{% + \@ifx{\float@end\float@end@float}{% + \@ifx{\newfloat\newfloat@float}{\true@sw}{\false@sw}% + }{\false@sw}% + {% + \class@warn{Repair the float package}% + \let\float@end\float@end@ltx + \let\newfloat\newfloat@ltx + }{% + \class@warn{Failed to patch the float package}% + }% + }% +}% +\def\@iffpsbit#1{% + \begingroup + \@tempcnta\count\@currbox + \divide\@tempcnta#1\relax + \@ifodd\@tempcnta{\aftergroup\true@sw}{\aftergroup\false@sw}% + \endgroup +}% +\def\check@currbox@count{% + \@ifnum{\count\@currbox>\z@}{% + \count@\count\@currbox\divide\count@\sixt@@n\multiply\count@\sixt@@n + \@tempcnta\count\@currbox\advance\@tempcnta-\count@ + \@ifnum{\@tempcnta=\z@}{% + \ltxgrid@warn{Float cannot be placed}% + }{}% + \expandafter\tally@float\expandafter{\@captype}% + }{% + }% +}% +\providecommand\minipagefootnote@init{}% +\providecommand\minipagefootnote@here{}% +\providecommand\tally@float[1]{}% +\let\@specialoutput\@undefined +\def\@add@float{% + \@pageht\ht\@cclv\@pagedp\dp\@cclv + \unvbox\@cclv + \@next\@currbox\@currlist{% + \csname @floatselect@sw@\thepagegrid\endcsname\@currbox{% + \@ifnum{\count\@currbox>\z@}{% + \advance \@pageht \@pagedp + \advance \@pageht \vsize \advance \@pageht -\pagegoal + \@addtocurcol + }{% + \@addmarginpar + }% + }{% + \@resethfps + \@cons\@deferlist\@currbox + }% + }{\@latexbug}% + \@ifnum{\outputpenalty<\z@}{% + \@if@sw\if@nobreak\fi{% + \nobreak + }{% + \addpenalty \interlinepenalty + }% + }{}% + \set@vsize +}% +\let\@reinserts\@undefined +\def \@addtocurcol {% + \@insertfalse + \@setfloattypecounts + \ifnum \@fpstype=8 + \else + \ifnum \@fpstype=24 + \else + \@flsettextmin + \advance \@textmin \@textfloatsheight + \@reqcolroom \@pageht + \ifdim \@textmin>\@reqcolroom + \@reqcolroom \@textmin + \fi + \advance \@reqcolroom \ht\@currbox + \ifdim \@colroom>\@reqcolroom + \@flsetnum \@colnum + \ifnum \@colnum>\z@ + \@bitor\@currtype\@deferlist + \if@test + \else + \@bitor\@currtype\@botlist + \if@test + \@addtobot + \else + \ifodd \count\@currbox + \advance \@reqcolroom \intextsep + \ifdim \@colroom>\@reqcolroom + \global \advance \@colnum \m@ne + \global \advance \@textfloatsheight \ht\@currbox + \global \advance \@textfloatsheight 2\intextsep + \@cons \@midlist \@currbox + \if@nobreak + \nobreak + \@nobreakfalse + \everypar{}% + \else + \addpenalty \interlinepenalty + \fi + \vskip \intextsep + \unvbox\@currbox %AO + \penalty\interlinepenalty + \vskip\intextsep + \ifnum\outputpenalty <-\@Mii \vskip -\parskip\fi + \outputpenalty \z@ + \@inserttrue + \fi + \fi + \if@insert + \else + \@addtotoporbot + \fi + \fi + \fi + \fi + \fi + \fi + \fi + \if@insert + \else + \@resethfps + \@cons\@deferlist\@currbox + \fi +}% +\@twocolumnfalse +\let\@twocolumntrue\@twocolumnfalse +\def\@addmarginpar{% + \@next\@marbox\@currlist{% + \@cons\@freelist\@marbox\@cons\@freelist\@currbox + }\@latexbug + \setbox\@marbox\hb@xt@\columnwidth{% + \csname @addmarginpar@\thepagegrid\endcsname{% + \hskip-\marginparsep\hskip-\marginparwidth + \box\@currbox + }{% + \hskip\columnwidth\hskip\marginparsep + \box\@marbox + }% + \hss + }% + \setbox\z@\box\@currbox + \@tempdima\@mparbottom + \advance\@tempdima -\@pageht + \advance\@tempdima\ht\@marbox + \@ifdim{\@tempdima >\z@}{% + \@latex@warning@no@line {Marginpar on page \thepage\space moved}% + }{% + \@tempdima\z@ + }% + \global\@mparbottom\@pageht + \global\advance\@mparbottom\@tempdima + \global\advance\@mparbottom\dp\@marbox + \global\advance\@mparbottom\marginparpush + \advance\@tempdima -\ht\@marbox + \global\setbox \@marbox + \vbox {\vskip \@tempdima + \box \@marbox}% + \global \ht\@marbox \z@ + \global \dp\@marbox \z@ + \kern -\@pagedp + \nointerlineskip + \box\@marbox + \nointerlineskip + \hbox{\vrule \@height\z@ \@width\z@ \@depth\@pagedp}% +}% +\newenvironment{turnpage}{% + \def\width@float{\textheight}% + \def\widthd@float{\textheight}% + \appdef\@endfloatbox{% + \@ifxundefined\@currbox{% + \ltxgrid@warn{Cannot rotate! Not a float}% + }{% + \setbox\@currbox\vbox to\textwidth{\vfil\unvbox\@currbox\vfil}% + \global\setbox\@currbox\vbox{\rotatebox{90}{\box\@currbox}}% + }% + }% +}{% +}% +\def\rotatebox@dummy#1#2{% + \ltxgrid@warn{You must load the graphics or graphicx package in order to use the turnpage environment}% + #2% +}% +\appdef\document@inithook{% + \@ifxundefined\rotatebox{\let\rotatebox\rotatebox@dummy}{}% +}% +\@namedef{output@-1073741824}{% + \deadcycles\z@ + \void@cclv +}% +\mathchardef\save@column@pen=10016 +\@namedef{output@-\the\save@column@pen}{\save@column}% +\let \@cclv@saved \@holdpg +\let \@holdpg \@undefined +\def\save@column{% + \@ifvoid\@cclv@saved{% + \set@top@firstmark + \global\@topmark@saved\expandafter{\@@topmark}% + }{}% + \global\setbox\@cclv@saved\vbox{% + \@ifvoid\@cclv@saved{}{% + \unvbox\@cclv@saved + \marry@baselines + }% + \unvbox\@cclv + \lose@breaks + \remove@lastbox + }% +}% +\newtoks\@topmark@saved +\def\prep@cclv{% + \void@cclv + \setbox\@cclv\box\@cclv@saved + \vbadness\@M +}% +\mathchardef\save@column@insert@pen=10017 +\@namedef{output@-\the\save@column@insert@pen}{\toggle@insert{\savecolumn@holding}{\savecolumn@moving}}% +\def\savecolumn@holding{% + \@if@exceed@pagegoal{\unvcopy\@cclv\remove@lastbox}{% + \setbox\z@\vbox{\unvcopy\@cclv\remove@lastbox}% + \outputdebug@sw{\trace@box\z@}{}% + \dimen@\ht\@cclv\advance\dimen@-\ht\z@ + \dead@cycle@repair@protected\dimen@ + }{% + \dead@cycle + }% +}% +\def\savecolumn@moving{% + \ltxgrid@info@sw{\class@info{\string\savecolumn@moving}}{}% + \@cclv@nontrivial@sw{% + \save@column + }{% + \void@cclv + }% + \@ifvoid\footins{}{% + \ltxgrid@foot@info@sw{\class@info{\string\savecolumn@moving}\trace@scroll{\showbox\footins@saved\showbox\footins}}{}% + \@ifvoid\footins@saved{% + \global\setbox\footins@saved\box\footins + }{% + \global\setbox\footins@saved\vbox\bgroup + \unvbox\footins@saved + \marry@baselines + \unvbox\footins + \egroup + }% + \ltxgrid@foot@info@sw{\trace@box\footins@saved}{}% + \protect@penalty\save@column@insert@pen + }% +}% +\newbox\footins@saved +\newbox\footins@recovered +\newbox\column@recovered +\mathchardef\save@message@pen=10018 +\@namedef{output@-\the\save@message@pen}{\save@message}% +\def\save@message{% + \void@cclv + \toks@\expandafter{\@@firstmark}% + \expandafter\gdef\expandafter\@message@saved\expandafter{\the\toks@}% + \expandafter\do@@mark\expandafter{\the\@topmark@saved}% +}% +\gdef\@message@saved{}% +\mathchardef\execute@message@pen=10019 +\@namedef{output@-\the\execute@message@pen}{\@message@saved}% +\def\execute@message{% + \@execute@message\save@column@pen +}% +\def\execute@message@insert#1{% + \@execute@message\save@column@insert@pen{% + \setbox \footins \box \footins@saved + \ltxgrid@foot@info@sw{\class@info{\string\execute@message@insert}\trace@box\footins}{}% + #1% + }% +}% +\long\def\@execute@message#1#2{% + \begingroup + \dimen@\prevdepth\@ifdim{\dimen@<\z@}{\dimen@\z@}{}% + \setbox\z@\vbox{% + \protect@penalty#1% + \protection@box + \toks@{\prep@cclv#2}% + \@@mark{\the\toks@}% + \penalty-\save@message@pen + \setbox\z@\null\dp\z@\dimen@\ht\z@-\dimen@ + \nointerlineskip\box\z@ + \penalty-\execute@message@pen + }\unvbox\z@ + \endgroup +}% +\def\do@output@cclv{\execute@message}% +\def\do@output@MVL#1{% + \@ifvmode{% + \begingroup\execute@message{\unvbox\@cclv#1}\endgroup + }{% + \@ifhmode{% + \vadjust{\execute@message{\unvbox\@cclv#1}}% + }{% + \@latexerr{\string\do@output@MVL\space cannot be executed in this mode!}\@eha + }% + }% +}% +\def\lose@breaks{% + \loopwhile{% + \count@\lastpenalty + \@ifnum{\count@=\@M}{% + \unpenalty\true@sw + }{% + \false@sw + }% + }% +}% +\def\removestuff{\do@output@MVL{\unskip\unpenalty}}% +\def\removephantombox{% + \vadjust{% + \execute@message{% + \unvbox\@cclv + \remove@lastbox + \unskip + \unskip + \unpenalty + \penalty\predisplaypenalty + \vskip\abovedisplayskip + }% + }% +}% +\def\addstuff#1#2{\edef\@tempa{\noexpand\do@output@MVL{\noexpand\@addstuff{#1}{#2}}}\@tempa}% +\def\@addstuff#1#2{% + \skip@\lastskip\unskip + \count@\lastpenalty\unpenalty + \@if@empty{#1}{}{\penalty#1\relax}% + \@ifnum{\count@=\z@}{}{\penalty\count@}% + \vskip\skip@ + \@if@empty{#2}{}{\vskip#2\relax}% +}% +\def\replacestuff#1#2{\edef\@tempa{\noexpand\do@output@MVL{\noexpand\@replacestuff{#1}{#2}}}\@tempa}% +\def\@replacestuff#1#2{% + \skip@\lastskip\unskip + \count@\lastpenalty\unpenalty + \@if@empty{#1}{}{% + \@ifnum{\count@>\@M}{}{% + \@ifnum{\count@=\z@}{\count@=#1\relax}{% + \@ifnum{\count@<#1\relax}{}{% + \count@=#1\relax + }% + }% + }% + }% + \@ifnum{\count@=\z@}{}{\penalty\count@}% + \@if@empty{#2}{}{% + \@tempskipa#2\relax + \@ifdim{\z@>\@tempskipa}{% + \advance\skip@-\@tempskipa + }{% + \@ifdim{\skip@>\@tempskipa}{}{% + \skip@\@tempskipa + }% + }% + }% + \vskip\skip@ +}% +\def\move@insertions{\global\holdinginserts\z@}% +\def\hold@insertions{\global\holdinginserts\@ne}% +\hold@insertions +\def\toggle@insert#1#2{% + \@ifnum{\holdinginserts>\z@}{\move@insertions#1}{\hold@insertions#2}% +}% +\def\do@columngrid#1#2{% + \par + \expandafter\let\expandafter\@tempa\csname open@column@#1\endcsname + \@ifx{\relax\@tempa}{% + \ltxgrid@warn{Unknown page grid #1. No action taken}% + }{% + \do@output@MVL{\start@column{#1}{#2}}% + }% +}% +\def\start@column#1#2{% + \def\@tempa{#1}\@ifx{\@tempa\thepagegrid}{% + \ltxgrid@info{Already in page grid \thepagegrid. No action taken}% + }{% + \expandafter\execute@message@insert + \expandafter{% + \csname shut@column@\thepagegrid\expandafter\endcsname + \csname open@column@#1\endcsname{#2}% + \set@vsize + }% + }% +}% +\def\thepagegrid{one}% +\newbox\pagesofar +\newbox\footsofar +\def\combine@foot@inserts#1#2{% + \ltxgrid@info@sw{\class@info{\string\combine@foot@inserts\string#1\string#2}}{}% + \@ifvoid#1{% + \ltxgrid@foot@info@sw{\trace@box#2}{}\global\setbox#1\box#2% + }{% + \global\setbox#1\vbox\bgroup + \ltxgrid@foot@info@sw{\trace@box#1}{}\unvbox#1% + \@ifvoid#2{}{% + \marry@baselines + \ltxgrid@foot@info@sw{\trace@box#2}{}\unvbox#2% + }% + \egroup + }% + \ltxgrid@foot@info@sw{\trace@scroll{\showbox#1\showbox#2}}{}% +}% +\newcommand\onecolumngrid{\do@columngrid{one}{\@ne}}% +\let\onecolumn\@undefined +\def\open@column@one#1{% + \ltxgrid@info@sw{\class@info{\string\open@column@one\string#1}}{}% + \unvbox\pagesofar + \@ifvoid{\footsofar}{}{% + \insert\footins\bgroup\unvbox\footsofar\egroup + \penalty\z@ + }% + \gdef\thepagegrid{one}% + \global\pagegrid@col#1% + \global\pagegrid@cur\@ne + \global\count\footins\@m + \global\divide\count\footins\tw@ + \set@column@hsize\pagegrid@col + \set@colht +}% +\def\shut@column@one{% + \ltxgrid@info@sw{\class@info{\string\shut@column@one}}{}% + \@makecolumn\false@sw + \global\setbox\pagesofar\vbox\bgroup + \recover@column\@outputbox\footsofar\column@recovered\footins@recovered + \egroup + \begingroup\setbox\z@\box\@outputbox\endgroup + \combine@foot@inserts\footsofar\footins + \set@colht +}% +\def\float@column@one{% + \@makecolumn\true@sw + \@outputpage +}% +\def\end@column@one{% + \unvbox\@cclv\remove@lastbox + \protect@penalty\do@newpage@pen +}% +\def\output@column@one{% + \@outputpage +}% +\def\@addmarginpar@one{% + \@if@sw\if@mparswitch\fi{% + \@ifodd\c@page{\false@sw}{\true@sw}% + }{\false@sw}{% + \@if@sw\if@reversemargin\fi{\false@sw}{\true@sw}% + }{% + \@if@sw\if@reversemargin\fi{\true@sw}{\false@sw}% + }% +}% +\def\@floatselect@sw@one#1{\true@sw}% +\def\onecolumngrid@push{% + \do@output@MVL{% + \@ifnum{\pagegrid@col=\@ne}{% + \global\let\restorecolumngrid\@empty + }{% + \xdef\restorecolumngrid{% + \noexpand\start@column{\thepagegrid}{\the\pagegrid@col}% + }% + \start@column{one}{\@ne}% + }% + }% +}% +\def\onecolumngrid@pop{% + \do@output@MVL{\restorecolumngrid}% +}% +\newcommand\twocolumngrid{\do@columngrid{mlt}{\tw@}}% +\let\twocolumn\@undefined +\let\@topnewpage\@undefined +\def\open@column@mlt#1{% + \ltxgrid@info@sw{\class@info{\string\open@column@mlt\string#1}}{}% + \@ifvoid{\footsofar}{}{% + \insert\footins\bgroup\unvbox\footsofar\egroup + }% + \gdef\thepagegrid{mlt}% + \global\pagegrid@col#1% + \global\pagegrid@cur\@ne + \global\count\footins\@m + \set@column@hsize\pagegrid@col + \set@colht +}% +\def\shut@column@mlt{% + \ltxgrid@info@sw{\class@info{\string\shut@column@mlt}}{}% + \@cclv@nontrivial@sw{% + \@makecolumn\false@sw + \@ifnum{\pagegrid@cur<\pagegrid@col}{% + \expandafter\global\expandafter\setbox\csname col@\the\pagegrid@cur\endcsname\box\@outputbox + \global\advance\pagegrid@cur\@ne + }{}% + }{% + \void@cclv + }% + \@ifnum{\pagegrid@cur>\@ne}{% + \csname balance@\the\pagegrid@col\endcsname + \grid@column\@outputbox{}% + \@combinepage\false@sw + \@combinedblfloats + \global\setbox\pagesofar\box\@outputbox + \show@pagesofar@size + }{}% + \set@colht +}% +\def\float@column@mlt{% + \@output@combined@page +}% +\def\end@column@mlt{% + \@ifx@empty\@toplist{% + \@ifx@empty\@botlist{% + \@ifx@empty\@dbltoplist{% + \@ifx@empty\@deferlist{% + \@ifnum{\pagegrid@cur=\@ne}{% + \false@sw + }{% + \true@sw + }% + }{% + \true@sw + }% + }{% + \true@sw + }% + }{% + \true@sw + }% + }{% + \true@sw + }% + % true = kick out a column and try again + {% + \@cclv@nontrivial@sw{% + \unvbox\@cclv\remove@lastbox + }{% + \unvbox\@cclv\remove@lastbox\unskip\null + }% + \protect@penalty\do@newpage@pen + \protect@penalty\do@endpage@pen + }{% + \unvbox\@cclv\remove@lastbox + }% +}% +\def\output@column@mlt{% + \@ifnum{\pagegrid@cur<\pagegrid@col}{% + \expandafter\global\expandafter\setbox\csname col@\the\pagegrid@cur\endcsname\box\@outputbox + \global\advance\pagegrid@cur\@ne + }{% + \set@adj@colht\dimen@ + \grid@column\@outputbox{}% + \@output@combined@page + }% +}% +\let\@outputdblcol\@undefined +\def\@floatselect@sw@mlt#1{\@if@notdblfloat{#1}}% +\def\@addmarginpar@mlt{% emits a boolean + \@ifnum{\pagegrid@cur=\@ne}% +}% +\def\set@footnotewidth@one{% + \hsize\columnwidth + \linewidth\hsize +}% +\def\set@footnotewidth@two{\set@footnotewidth@mlt\tw@}% +\def\set@footnotewidth@mlt#1{% + \hsize\textwidth + \advance\hsize\columnsep + \divide\hsize#1% + \advance\hsize-\columnsep + \linewidth\hsize +}% +\def\compose@footnotes@one#1{% + \ltxgrid@foot@info@sw{\class@info{\string\compose@footnotes@one\string#1}\trace@box#1}{}% +}% +\let\compose@footnotes\compose@footnotes@one +\def\compose@footnotes@two#1{% + \ltxgrid@foot@info@sw{\class@info{\string\compose@footnotes@two\string#1}\trace@box#1}{}% + \setbox\z@\box\@tempboxa + \let\recover@column\recover@column@null + \let\marry@baselines\@empty + \balance@two#1\@tempboxa + \global\setbox#1\hbox to\textwidth{\box#1\hfil\box\@tempboxa}% + \ltxgrid@foot@info@sw{\trace@box#1}{}% +}% +\let\pagegrid@cur\col@number +\let\col@number\@undefined +\newcount\pagegrid@col +\pagegrid@cur\@ne +\expandafter\let\csname col@\the\pagegrid@cur\endcsname\@leftcolumn +\let\@leftcolumn\@undefined +\pagegrid@col\tw@ +\def\pagegrid@init{% + \advance\pagegrid@cur\@ne + \@ifnum{\pagegrid@cur<\pagegrid@col}{% + \csname newbox\expandafter\endcsname\csname col@\the\pagegrid@cur\endcsname + \pagegrid@init + }{% + }% +}% +\appdef\class@documenthook{% + \pagegrid@init +}% +\def\grid@column#1#2{% + \ltxgrid@info@sw{\class@info{\string\grid@column\string#1}}{}% + \global\setbox#1\vbox\bgroup + \hb@xt@\textwidth\bgroup + \vrule\@height\z@\@width\z@\@if@empty{#2}{}{\@depth#2}% + \pagegrid@cur\@ne + \@ifnum{\pagegrid@cur<\pagegrid@col}{\loopwhile{\append@column@\pagegrid@cur\pagegrid@col}}{}% + \box@column#1% + \egroup + \vskip\z@skip + \egroup +}% +\def\append@column@#1#2{% + \expandafter\box@column\csname col@\the#1\endcsname + \hfil\vrule\@width\columnseprule\hfil + \advance#1\@ne + \@ifnum{#1<#2}% +}% +\def\box@column#1{% + \ltxgrid@info@sw{\class@info{\string\box@column\string#1}}{}% + \raise\topskip + \hb@xt@\columnwidth\bgroup + \dimen@\ht#1\@ifdim{\dimen@>\@colht}{\dimen@\@colht}{}% + \count@\vbadness\vbadness\@M + \dimen@ii\vfuzz\vfuzz\maxdimen + \ltxgrid@info@sw{\saythe\@colht\saythe\dimen@}{}% + \vtop to\dimen@\bgroup + \hrule\@height\z@ + \unvbox#1% + \raggedcolumn@skip + \egroup + \vfuzz\dimen@ii + \vbadness\count@ + \hss + \egroup +}% +\def\marry@baselines{% + \begingroup + \setbox\z@\lastbox + \@ifvoid{\z@}{% + \endgroup + }{% + \aftergroup\kern + \aftergroup-% + \expandafter\box\expandafter\z@\expandafter\endgroup\the\dp\z@\relax + }% + \vskip\marry@skip\relax +}% +\gdef\marry@skip{\z@skip}% +\def\set@marry@skip{% + \begingroup + \skip@\baselineskip\advance\skip@-\topskip + \@ifdim{\skip@>\z@}{% + \xdef\marry@skip{\the\skip@}% + }{}% + \endgroup +}% +\appdef\document@inithook{% + \@ifxundefined\raggedcolumn@sw{\@booleanfalse\raggedcolumn@sw}{}% +}% +\def\raggedcolumn@skip{% + \vskip\z@\raggedcolumn@sw{\@plus.0001fil\@minus.0001fil}{}\relax +}% +\def\@combinepage#1{% + \ltxgrid@foot@info@sw{\class@info{\string\@combinepage\string#1}}{}% + \@ifvoid\pagesofar{}{% + \setbox\@outputbox\vbox{% + \unvbox\pagesofar + \marry@baselines + \unvbox\@outputbox + }% + }% + #1{% + \@ifvoid\footsofar{}{% + \show@box@size{Combining page footnotes}\footsofar + \setbox\footins\box\footsofar + \compose@footnotes + \@combineinserts\@outputbox\footins + }% + }{% + }% +}% +\def \@cflt{% + \let \@elt \@comflelt + \setbox\@tempboxa \vbox{}% + \@toplist + \setbox\@outputbox \vbox{% + \boxmaxdepth \maxdepth + \unvbox\@tempboxa\unskip + \topfigrule\vskip \textfloatsep + \unvbox\@outputbox + }% + \let\@elt\relax + \xdef\@freelist{\@freelist\@toplist}% + \global\let\@toplist\@empty +}% +\def \@cflb {% + \let\@elt\@comflelt + \setbox\@tempboxa \vbox{}% + \@botlist + \setbox\@outputbox \vbox{% + \unvbox\@outputbox + \vskip \textfloatsep\botfigrule + \unvbox\@tempboxa\unskip + }% + \let\@elt\relax + \xdef\@freelist{\@freelist\@botlist}% + \global \let \@botlist\@empty +}% +\def\@combinedblfloats{% + \@ifx@empty\@dbltoplist{}{% + \setbox\@tempboxa\vbox{}% + \let\@elt\@comdblflelt\@dbltoplist + \let\@elt\relax\xdef\@freelist{\@freelist\@dbltoplist}% + \global\let\@dbltoplist\@empty + \setbox\@outputbox\vbox{% + %\boxmaxdepth\maxdepth %% probably not needed, CAR + \unvbox\@tempboxa\unskip + \@ifnum{\@dbltopnum>\m@ne}{\dblfigrule}{}%FIXME: how is \@dbltopnum maintained? + \vskip\dbltextfloatsep + \unvbox\@outputbox + }% + }% +}% +\def\set@column@hsize#1{% + \pagegrid@col#1% + \global\columnwidth\textwidth + \global\advance\columnwidth\columnsep + \global\divide\columnwidth\pagegrid@col + \global\advance\columnwidth-\columnsep + \global\hsize\columnwidth + \global\linewidth\columnwidth + \skip@\baselineskip\advance\skip@-\topskip + \@ifnum{\pagegrid@col>\@ne}{\set@marry@skip}{}% +}% +\def\set@colht{% + \set@adj@textheight\@colht + \global\let\enlarge@colroom\@empty + \set@colroom +}% +\def\set@adj@textheight#1{% + \ltxgrid@info@sw{\class@info{\string\set@adj@textheight\string#1}\saythe\textheight}{}% + #1\textheight + \def\@elt{\adj@page#1}% + \@booleantrue\firsttime@sw\@dbltoplist + \let\@elt\relax + \global#1#1\relax + \ltxgrid@info@sw{\saythe#1}{}% +}% +\def\set@colroom{% + \ltxgrid@info@sw{\class@info{\string\set@colroom}}{}% + \set@adj@colht\@colroom + \@if@empty\enlarge@colroom{}{% + \global\advance\@colroom\enlarge@colroom\relax + \ltxgrid@info@sw{\saythe\@colroom}{}% + }% + \@ifdim{\@colroom>\topskip}{}{% + \ltxgrid@info{Not enough room: \string\@colroom=\the\@colroom; increasing to \the\topskip}% + \@colroom\topskip + }% + \global\@colroom\@colroom + \set@vsize +}% +\def\set@vsize{% + \global\vsize\@colroom + \ltxgrid@info@sw{\class@info{\string\set@vsize\string\vsize=\string\colroom}\saythe\vsize}{}% +}% +\def\set@adj@colht#1{% + #1\@colht + \ltxgrid@info@sw{\class@info{\string\set@adj@colht\string#1-\string\pagesofar}\saythe#1}{}% + \@ifvoid\pagesofar{}{% + \advance#1-\ht\pagesofar\advance#1-\dp\pagesofar + \ltxgrid@info@sw{\class@info{\string\pagesofar}\saythe#1}{}% + }% + \def\@elt{\adj@column#1}% + \@booleantrue\firsttime@sw\@toplist + \@booleantrue\firsttime@sw\@botlist + \let\@elt\relax +}% +\def\adj@column#1#2{% + \advance#1-\ht#2% + \advance#1-\firsttime@sw{\textfloatsep\@booleanfalse\firsttime@sw}{\floatsep}% + \ltxgrid@info@sw{\class@info{\string\adj@column\string#1-\string#2}\saythe#1}{}% +}% +\def\adj@page#1#2{% + \advance#1-\ht#2% + \advance#1-\firsttime@sw{\dbltextfloatsep\@booleanfalse\firsttime@sw}{\dblfloatsep}% + \ltxgrid@info@sw{\class@info{\string\adj@page\string#1-\string#2}\saythe#1}{}% +}% +\def\set@adj@box#1#2{% + \@ifvoid#2{}{% + \advance#1-\ht#2\advance#1-\dp#2% + \@booleantrue\temp@sw + \ltxgrid@foot@info@sw{\class@info{\string\set@adj@box\string#2}\saythe#1}{}% + }% +}% +\appdef\@outputpage@tail{% + \set@colht % FIXME: needed? + \@floatplacement % FIXME: needed? + \@dblfloatplacement % FIXME: needed? +}% +\begingroup + \catcode`\1=\cat@letter + \catcode`\2=\cat@letter + \toks@{% + \setbox\footins\box\footsofar + \balance@two\col@1\@outputbox + \global\setbox\col@1\box\col@1 + \global\setbox\@outputbox\box\@outputbox + \combine@foot@inserts\footsofar\footins + }% + \aftergroup\def\aftergroup\balance@2\expandafter +\endgroup\expandafter{\the\toks@}% +\def\balance@two#1#2{% + \ltxgrid@info@sw{\class@info{\string\balance@two\string#1\string#2}}{}% + \outputdebug@sw{\trace@scroll{\showbox#1\showbox#2}}{}% + \setbox\thr@@\copy\footsofar + \setbox\@ne\vbox\bgroup + \@ifvoid{#1}{}{% + \recover@column#1\footsofar\column@recovered\footins@recovered + \@ifvoid{#2}{}{\marry@baselines}% + }% + \@ifvoid{#2}{}{% + \recover@column#2\footsofar\column@recovered\footins@recovered + }% + \egroup + \outputdebug@sw{\trace@scroll{\showbox\@ne}}{}% + \ltxgrid@foot@info@sw{\trace@scroll{\showbox\footsofar}}{}% + \dimen@\ht\@ne\divide\dimen@\tw@ + \dimen@i\dimen@ + \vbadness\@M + \vfuzz\maxdimen + \splittopskip\topskip + \loopwhile{% + \setbox\z@\copy\@ne\setbox\tw@\vsplit\z@ to\dimen@ + \remove@depth\z@\remove@depth\tw@ + \dimen@ii\ht\tw@\advance\dimen@ii-\ht\z@ + \dimen@i=.5\dimen@i + \ltxgrid@info@sw{\saythe\dimen@\saythe\dimen@i\saythe\dimen@ii}{}% + \@ifdim{\dimen@ii<.5\p@}{% + \@ifdim{\dimen@ii>-.5\p@}% + }{% + \false@sw + }% + {% + \true@sw + }{% + \@ifdim{\dimen@i<.5\p@}% + }% + {% + \false@sw + }% + {% + \advance\dimen@\@ifdim{\dimen@ii<\z@}{}{-}\dimen@i + \true@sw + }% + }% + \ltxgrid@info@sw{\saythe\dimen@\saythe\dimen@i\saythe\dimen@ii}{}% + \@ifdim{\ht\z@=\z@}{% + \@ifdim{\ht\tw@=\z@}% + }{% + \true@sw + }% + {% + }{% + \ltxgrid@info{Unsatifactorily balanced columns: giving up}% + \setbox\tw@\box#1% + \setbox\z@ \box#2% + \global\setbox\footsofar\box\thr@@ + }% + \setbox\tw@\vbox{\unvbox\tw@\vskip\z@skip}% + \setbox\z@ \vbox{\unvbox\z@ \vskip\z@skip}% + \set@colht + \dimen@\ht\z@\@ifdim{\dimen@<\ht\tw@}{\dimen@\ht\tw@}{}% + \@ifdim{\dimen@>\@colroom}{\dimen@\@colroom}{}% + \ltxgrid@info@sw{\saythe{\ht\z@}\saythe{\ht\tw@}\saythe\@colroom\saythe\dimen@}{}% + \setbox#1\vbox to\dimen@{\unvbox\tw@\unskip\raggedcolumn@skip}% + \setbox#2\vbox to\dimen@{\unvbox\z@ \unskip\raggedcolumn@skip}% + \outputdebug@sw{\trace@scroll{\showbox#1\showbox#2}}{}% +}% +\def\remove@depth#1{% + \setbox#1\vbox\bgroup + \unvcopy#1% + \setbox\z@\vbox\bgroup + \unvbox#1% + \setbox\z@\lastbox + \aftergroup\kern\aftergroup-\expandafter + \egroup + \the\dp\z@\relax + \egroup +}% +\def\recover@column#1#2#3#4{% + \ltxgrid@info@sw{\class@info{\string\recover@column\string#1\string#2\string#3\string#4}}{}% + \setbox#4\vbox{\unvcopy#1}% + \ltxgrid@foot@info@sw{\trace@scroll{\showbox#4}}{}% + \dimen@\ht#4% + \ltxgrid@foot@info@sw{\saythe\dimen@}{}% + \setbox#4\vbox\bgroup + \unvbox#4\unskip + \dimen@i\lastkern\unkern\advance\dimen@i\lastkern + \@ifdim{\dimen@i=\z@}{% + \dimen@i\lastkern\unkern + \ltxgrid@foot@info@sw{\saythe\dimen@i}{}% + \aftergroup\dimen@i + \expandafter\egroup\the\dimen@i\relax + }{% + \egroup + }% + \@ifdim{\dimen@i<\z@}{% + \advance\dimen@\dimen@i + \ltxgrid@foot@info@sw{\saythe\dimen@i\saythe\dimen@}{}% + \splittopskip\z@skip + \global\setbox#3\vsplit#4 to\dimen@ + \global\setbox#4\vbox{\unvbox#4}% + \ltxgrid@foot@info@sw{\trace@scroll{\showbox#1\showbox#2\showbox#3\showbox#4}}{}% + \global\setbox#2\vbox\bgroup\unvbox#2\vskip\z@skip\unvbox#4\egroup + }{% + \setbox#3\box#4% + \ltxgrid@foot@info@sw{\trace@scroll{\showbox#1\showbox#2\showbox#3\showbox#4}}{}% + }% + \unvbox#3% + \loopwhile{\dimen@\lastskip\@ifdim{\dimen@>\z@}{\unskip\true@sw}{\false@sw}}% +}% +\def\recover@column@null#1#2#3#4{% + \unvcopy#1% +}% +\rvtx@ifformat@geq{2020/10/01}% + {% + \AddToHook{begindocument}{% + \open@column@one\@ne + \set@colht + \@floatplacement + \@dblfloatplacement + }% + }{% + \prepdef\@begindocumenthook{% + \open@column@one\@ne + \set@colht + \@floatplacement + \@dblfloatplacement + }% + } +\def\longtable@longtable{% + \par + \ifx\multicols\@undefined\else\ifnum\col@number>\@ne\@twocolumntrue\fi\fi + \if@twocolumn\LT@err{longtable not in 1-column mode}\@ehc\fi + \begingroup + \@ifnextchar[\LT@array{\LT@array[x]}% +}% +\def\longtable@new{% + \par + \@ifnextchar[\LT@array{\LT@array[x]}% +}% +\def\endlongtable@longtable{% + \crcr + \noalign{% + \let\LT@entry\LT@entry@chop + \xdef\LT@save@row{\LT@save@row}}% + \LT@echunk + \LT@start + \unvbox\z@ + \LT@get@widths + \if@filesw + {\let\LT@entry\LT@entry@write\immediate\write\@auxout{% + \gdef\expandafter\noexpand + \csname LT@\romannumeral\c@LT@tables\endcsname + {\LT@save@row}}}% + \fi + \ifx\LT@save@row\LT@@save@row + \else + \LT@warn{Column \@width s have changed\MessageBreak + in table \thetable}% + \LT@final@warn + \fi + \endgraf\penalty -\LT@end@pen + \endgroup + \global\@mparbottom\z@ + \pagegoal\vsize + \endgraf\penalty\z@\addvspace\LTpost + \ifvoid\footins\else\insert\footins{}\fi +}% +\def\endlongtable@new{% + \crcr + \noalign{% + \let\LT@entry\LT@entry@chop + \xdef\LT@save@row{\LT@save@row}% + }% + \LT@echunk + \LT@start + \unvbox\z@ + \LT@get@widths + \@if@sw\if@filesw\fi{% + {% + \let\LT@entry\LT@entry@write + \immediate\write\@auxout{% + \gdef\expandafter\noexpand\csname LT@\romannumeral\c@LT@tables\endcsname + {\LT@save@row}% + }% + }% + }{}% + \@ifx{\LT@save@row\LT@@save@row}{}{% + \LT@warn{% + Column \@width s have changed\MessageBreak in table \thetable + }\LT@final@warn + }% + \endgraf + \nobreak + \box\@ifvoid\LT@lastfoot{\LT@foot}{\LT@lastfoot}% + \global\@mparbottom\z@ + \endgraf + \LT@post +}% +\def\LT@start@longtable{% + \let\LT@start\endgraf + \endgraf\penalty\z@\vskip\LTpre + \dimen@\pagetotal + \advance\dimen@ \ht\ifvoid\LT@firsthead\LT@head\else\LT@firsthead\fi + \advance\dimen@ \dp\ifvoid\LT@firsthead\LT@head\else\LT@firsthead\fi + \advance\dimen@ \ht\LT@foot + \dimen@ii\vfuzz + \vfuzz\maxdimen + \setbox\tw@\copy\z@ + \setbox\tw@\vsplit\tw@ to \ht\@arstrutbox + \setbox\tw@\vbox{\unvbox\tw@}% + \vfuzz\dimen@ii + \advance\dimen@ \ht + \ifdim\ht\@arstrutbox>\ht\tw@\@arstrutbox\else\tw@\fi + \advance\dimen@\dp + \ifdim\dp\@arstrutbox>\dp\tw@\@arstrutbox\else\tw@\fi + \advance\dimen@ -\pagegoal + \ifdim \dimen@>\z@\vfil\break\fi + \global\@colroom\@colht + \ifvoid\LT@foot\else + \advance\vsize-\ht\LT@foot + \global\advance\@colroom-\ht\LT@foot + \dimen@\pagegoal\advance\dimen@-\ht\LT@foot\pagegoal\dimen@ + \maxdepth\z@ + \fi + \ifvoid\LT@firsthead\copy\LT@head\else\box\LT@firsthead\fi +\nobreak + \output{\LT@output}% +}% +\def\LT@start@new{% + \let\LT@start\endgraf + \endgraf + \markthr@@{}% + \LT@pre + \@ifvoid\LT@firsthead{\LT@top}{\box\LT@firsthead\nobreak}% + \mark@envir{longtable}% +}% +\def\LT@end@hd@ft@longtable#1{% + \LT@echunk + \ifx\LT@start\endgraf + \LT@err{Longtable head or foot not at start of table}{Increase LTchunksize}% + \fi + \setbox#1\box\z@ + \LT@get@widths\LT@bchunk +}% +\def\LT@end@hd@ft@new#1{% + \LT@echunk + \@ifx{\LT@start\endgraf}{% + \LT@err{Longtable head or foot not at start of table}{Increase LTchunksize}% + }% + \global\setbox#1\box\z@ + \LT@get@widths + \LT@bchunk +}% +\def\LT@array@longtable[#1]#2{% + \refstepcounter{table}\stepcounter{LT@tables}% + \if l#1% + \LTleft\z@ \LTright\fill + \else\if r#1% + \LTleft\fill \LTright\z@ + \else\if c#1% + \LTleft\fill \LTright\fill + \fi\fi\fi + \let\LT@mcol\multicolumn + \let\LT@@tabarray\@tabarray + \let\LT@@hl\hline + \def\@tabarray{% + \let\hline\LT@@hl + \LT@@tabarray}% + \let\\\LT@tabularcr\let\tabularnewline\\% + \def\newpage{\noalign{\break}}% + \def\pagebreak{\noalign{\ifnum`}=0\fi\@testopt{\LT@no@pgbk-}4}% + \def\nopagebreak{\noalign{\ifnum`}=0\fi\@testopt\LT@no@pgbk4}% + \let\hline\LT@hline \let\kill\LT@kill\let\caption\LT@caption + \@tempdima\ht\strutbox + \let\@endpbox\LT@endpbox + \ifx\extrarowheight\@undefined + \let\@acol\@tabacol + \let\@classz\@tabclassz \let\@classiv\@tabclassiv + \def\@startpbox{\vtop\LT@startpbox}% + \let\@@startpbox\@startpbox + \let\@@endpbox\@endpbox + \let\LT@LL@FM@cr\@tabularcr + \else + \advance\@tempdima\extrarowheight + \col@sep\tabcolsep + \let\@startpbox\LT@startpbox\let\LT@LL@FM@cr\@arraycr + \fi + \setbox\@arstrutbox\hbox{\vrule + \@height \arraystretch \@tempdima + \@depth \arraystretch \dp \strutbox + \@width \z@}% + \let\@sharp##\let\protect\relax + \begingroup + \@mkpream{#2}% + \xdef\LT@bchunk{% + \global\advance\c@LT@chunks\@ne + \global\LT@rows\z@\setbox\z@\vbox\bgroup + \LT@setprevdepth + \tabskip\LTleft \noexpand\halign to\hsize\bgroup + \tabskip\z@ \@arstrut \@preamble \tabskip\LTright \cr}% + \endgroup + \expandafter\LT@nofcols\LT@bchunk&\LT@nofcols + \LT@make@row + \m@th\let\par\@empty + \everycr{}\lineskip\z@\baselineskip\z@ + \LT@bchunk}% +\def\LT@LR@l{\LTleft\z@ \LTright\fill}% +\def\LT@LR@r{\LTleft\fill \LTright\z@ }% +\def\LT@LR@c{\LTleft\fill \LTright\fill}% +\def\LT@array@new[#1]#2{% + \refstepcounter{table}\stepcounter{LT@tables}% + \table@hook + \LTleft\fill \LTright\fill + \csname LT@LR@#1\endcsname + \let\LT@mcol\multicolumn + \let\LT@@hl\hline + \prepdef\@tabarray{\let\hline\LT@@hl}% + \let\\\LT@tabularcr + \let\tabularnewline\\% + \def\newpage{\noalign{\break}}% + \def\pagebreak{\noalign{\ifnum`}=0\fi\@testopt{\LT@no@pgbk-}4}% + \def\nopagebreak{\noalign{\ifnum`}=0\fi\@testopt\LT@no@pgbk4}% + \let\hline\LT@hline + \let\kill\LT@kill + \let\caption\LT@caption + \@tempdima\ht\strutbox + \let\@endpbox\LT@endpbox + \@ifxundefined\extrarowheight{% + \let\@acol\@tabacol + \let\@classz\@tabclassz + \let\@classiv\@tabclassiv + \def\@startpbox{\vtop\LT@startpbox}% + \let\@@startpbox\@startpbox + \let\@@endpbox\@endpbox + \let\LT@LL@FM@cr\@tabularcr@LaTeX + \let\@xtabularcr\@xtabularcr@LaTeX + }{% + \advance\@tempdima\extrarowheight + \col@sep\tabcolsep + \let\@startpbox\LT@startpbox + \let\LT@LL@FM@cr\@arraycr@array + }% + \let\@acoll\@tabacoll + \let\@acolr\@tabacolr + \let\@acol\@tabacol + \setbox\@arstrutbox\hbox{% + \vrule + \@height \arraystretch \@tempdima + \@depth \arraystretch \dp \strutbox + \@width \z@ + }% + \let\@sharp##% + \let\protect\relax + \begingroup + \@mkpream{#2}% + \@mkpream@relax + \edef\@preamble{\@preamble}% + \prepdef\@preamble{% + \global\advance\c@LT@chunks\@ne + \global\LT@rows\z@ + \setbox\z@\vbox\bgroup + \LT@setprevdepth + \tabskip\LTleft + \halign to\hsize\bgroup + \tabskip\z@ + \@arstrut + }% + \appdef\@preamble{% + \tabskip\LTright + \cr + }% + \global\let\LT@bchunk\@preamble + \endgroup + \expandafter\LT@nofcols\LT@bchunk&\LT@nofcols + \LT@make@row + \m@th + \let\par\@empty + \everycr{}% + \lineskip\z@ + \baselineskip\z@ + \LT@bchunk +}% +\appdef\table@hook{}% +\def\switch@longtable{% + \@ifpackageloaded{longtable}{% + \@ifx{\longtable\longtable@longtable}{% + \@ifx{\endlongtable\endlongtable@longtable}{% + \@ifx{\LT@start\LT@start@longtable}{% + \@ifx{\LT@end@hd@ft\LT@end@hd@ft@longtable}{% + \@ifx{\LT@array\LT@array@longtable}{% + \true@sw + }{\false@sw}% + }{\false@sw}% + }{\false@sw}% + }{\false@sw}% + }{\false@sw}% + {% + \class@info{Patching longtable package}% + }{% + \class@info{Patching unrecognized longtable package. (Proceeding with fingers crossed)}% + }% + \let\longtable\longtable@new + \let\endlongtable\endlongtable@new + \let\LT@start\LT@start@new + \let\LT@end@hd@ft\LT@end@hd@ft@new + \let\LT@array\LT@array@new + \newenvironment{longtable*}{% + \onecolumngrid@push + \longtable + }{% + \endlongtable + \onecolumngrid@pop + }% + }{}% +}% +\def\LT@pre{\penalty\z@\vskip\LTpre}% +\def\LT@bot{\nobreak\copy\LT@foot\vfil}% +\def\LT@top{\copy\LT@head\nobreak}% +\def\LT@post{\penalty\z@\addvspace\LTpost\mark@envir{\curr@envir}}% +\def\LT@adj{% + \setbox\z@\vbox{\null}\dimen@-\ht\z@ + \setbox\z@\vbox{\unvbox\z@\LT@bot}\advance\dimen@\ht\z@ + \global\advance\vsize-\dimen@ +}% +\def\output@init@longtable{\LT@adj}% +\def\output@prep@longtable{\setbox\@cclv\vbox{\unvbox\@cclv\LT@bot}}% +\def\output@post@longtable{\LT@top}% +\let\output@init@theindex\@empty +\let\output@prep@theindex\@empty +\def\output@post@theindex{% + \@ifodd\c@page{}{% + \@ifnum{\pagegrid@cur=\@ne}{% + }% + }% +}% +\def\check@aux{\do@output@MVL{\do@check@aux}}% +\def\check@deferlist@stuck#1{% + \@ifx{\@deferlist@postshipout\@empty}{}{% + \@ifx{\@deferlist@postshipout\@deferlist}{% + \@fltstk + \clearpage@sw{% + \ltxgrid@warn{Deferred float stuck during \string\clearpage\space processing}% + }{% + \force@deferlist@stuck#1% + }% + }{% + }% + \global\let\@deferlist@postshipout\@empty + }% +}% +\def\@fltstk{% + \@latex@warning{A float is stuck (cannot be placed without \string\clearpage)}% +}% +\appdef\@outputpage@tail{% + \global\let\@deferlist@postshipout\@deferlist +}% +\def\@next#1#2{% + \@ifx{#2\@empty}{\false@sw}{% + \expandafter\@xnext#2\@@#1#2% + \true@sw + }% +}% +\def\@xnext\@elt#1#2\@@#3#4{% + \def#3{#1}% + \gdef#4{#2}% + \def\@tempa{#4}\def\@tempb{\@freelist}% + \@ifx{\@tempa\@tempb}{% + \@ifx{#4\@empty}{% + \force@deferlist@empty%{Float register pool exhausted}% + }{}% + }{}% +}% +\def\force@deferlist@stuck#1{% + \force@deferlist@sw{% + \@booleantrue\clearpage@sw + \@booleantrue\forcefloats@sw + #1% + }{% + }% +}% +\def\force@deferlist@empty{% + \force@deferlist@sw{% + \penalty-\pagebreak@pen + \protect@penalty\do@forcecolumn@pen + }{% + }% +}% +\@booleanfalse\force@deferlist@sw +\mathchardef\do@forcecolumn@pen=10009 +\@namedef{output@-\the\do@forcecolumn@pen}{\do@forcecolumn}% +\def\do@forcecolumn{% + \@booleantrue\clearpage@sw + \@booleantrue\forcefloats@sw + \do@startcolumn +}% +\def\enlargethispage{% + \@ifstar{% + \@enlargethispage{}% + }{% + \@enlargethispage{}% + }% +}% +\def\@enlargethispage#1#2{% + \begingroup + \dimen@#2\relax + \edef\@tempa{#1}% + \edef\@tempa{\noexpand\@@enlargethispage{\@tempa}{\the\dimen@}}% + \expandafter\do@output@MVL\expandafter{\@tempa}% + \endgroup +}% +\def\@@enlargethispage#1#2{% + \def\@tempa{one}% + \@ifx{\thepagegrid\@tempa}{% + \true@sw + }{% + \def\@tempa{mlt}% + \@ifx{\thepagegrid\@tempa}{% + \@ifnum{\pagegrid@cur=\@ne}{% + \gdef\enlarge@colroom{#2}% + \true@sw + }{% + \ltxgrid@warn{Too late to enlarge this page; move the command to the first column.}% + \false@sw + }% + }{% + \ltxgrid@warn{Unable to enlarge a page of this kind.}% + \false@sw + }% + }% + {% + \class@info{Enlarging page \thepage\space by #2}% + \global\advance\@colroom#2\relax + \set@vsize + }{% + }% +}% +\let\enlarge@colroom\@empty +\let\@kludgeins\@undefined +\@booleantrue\textheight@sw +\prepdef\@outputpage@head{% + \textheight@sw{% + \count@\vbadness\vbadness\@M + \dimen@\vfuzz\vfuzz\maxdimen + \setbox\@outputbox\vbox to\textheight{\unvbox\@outputbox}% + \vfuzz\dimen@ + \vbadness\count@ + }{}% +}% +\appdef\@outputpage@head{% + \@ifx{\LS@rot\@undefined}{}{\LS@rot}% +}% +\def\ltxgrid@info{% + \ltxgrid@info@sw{\class@info}{\@gobble}% +}% +\@booleanfalse\ltxgrid@info@sw +\def\ltxgrid@warn{% + \ltxgrid@warn@sw{\class@warn}{\@gobble}% +}% +\@booleantrue\ltxgrid@warn@sw +\@booleanfalse\ltxgrid@foot@info@sw +\def\def@next@handler#1#2#3{% + \advance#1\@ne\mathchardef#2\the#1% + \expandafter\def\csname output@-\the#1\endcsname{#3}% +}% +\def\def@line@handler#1#2{% + \begingroup + \@tempcnta\int@parpenalty + \advance\@tempcnta-#1% + \aftergroup\def + \expandafter\aftergroup\csname output@-\the\@tempcnta\endcsname + \endgroup{#2}% +}% +\mathchardef\int@parpenalty11012 +\def@line@handler\z@{\@handle@line@ltx{}{}{}}% +\def@line@handler\@ne{\@handle@line@ltx{}{}{\brokenpenalty@ltx}}% +\def@line@handler\tw@{\@handle@line@ltx{}{\clubpenalty@ltx}{}}% +\def@line@handler\thr@@{\@handle@line@ltx{\clubpenalty@ltx}{}{\brokenpenalty@ltx}}% +\def@line@handler\f@ur{\@handle@line@ltx{\widowpenalty@ltx}{}{}}% +\def@line@handler{5}{\@handle@line@ltx{\widowpenalty@ltx}{}{\brokenpenalty@ltx}}% +\def@line@handler{6}{\@handle@line@ltx{\widowpenalty@ltx}{\clubpenalty@ltx}{}}% +\def@line@handler{7}{\@handle@line@ltx{\widowpenalty@ltx}{\clubpenalty@ltx}{\brokenpenalty@ltx}}% +\def@line@handler{8}{\@handle@line@ltx{\displaywidowpenalty@ltx}{}{}}% +\def@line@handler{9}{\@handle@line@ltx{\displaywidowpenalty@ltx}{}{\brokenpenalty@ltx}}% +\def@line@handler{10}{\@handle@line@ltx{\displaywidowpenalty@ltx}{\clubpenalty@ltx}{}}% +\def@line@handler{11}{\@handle@line@ltx{\displaywidowpenalty@ltx}{\clubpenalty@ltx}{\brokenpenalty@ltx}}% +\def\@handle@line@ltx#1#2#3{% + \@@handle@line@ltx + \@tempcnta\lastpenalty + \@tempcntb\interlinepenalty@ltx\relax + \@if@empty{#1}{}{\advance\@tempcntb#1\relax}% + \@if@empty{#2}{}{\advance\@tempcntb#2\relax}% + \@if@empty{#3}{}{\advance\@tempcntb#3\relax}% + \penalty\@ifnum{\@tempcnta<\@tempcntb}{\@tempcntb}{\@tempcnta}% +}% +\let\@@handle@line@ltx\@empty +\@tempcnta\int@parpenalty +\def@next@handler\@tempcnta\int@postparpenalty{\reset@queues@ltx\handle@par@ltx}% +\def@next@handler\@tempcnta\int@vadjustpenalty{\handle@vadjust@ltx}% +\def@next@handler\@tempcnta\int@whatsitpenalty{\handle@whatsit@ltx}% +\def@next@handler\@tempcnta\int@predisplaypenalty{\reset@queues@ltx\@handle@display@ltx{\predisplaypenalty@ltx}}% +\def@next@handler\@tempcnta\int@interdisplaylinepenalty{\@handle@display@ltx{\interdisplaylinepenalty@ltx}}% +\def@next@handler\@tempcnta\int@postdisplaypenalty{\@handle@display@ltx{\postdisplaypenalty@ltx}}% +\def\@handle@display@ltx#1{% + \@@handle@display@ltx + \@tempcnta\lastpenalty + \@tempcntb#1% + \penalty\@ifnum{\@tempcnta<\@tempcntb}{\@tempcntb}{\@tempcnta}% +}% +\let\@@handle@display@ltx\@empty +\def\handle@par@ltx{}% +\def\set@linepenalties{% + \expandafter\def\expandafter\interlinepenalty@ltx\expandafter{\the\interlinepenalty}% + \interlinepenalty-\int@parpenalty + \expandafter\def\expandafter\brokenpenalty@ltx\expandafter{\the\brokenpenalty}% + \brokenpenalty\@ne + \expandafter\def\expandafter\clubpenalty@ltx\expandafter{\the\clubpenalty}% + \clubpenalty\tw@ + \expandafter\def\expandafter\widowpenalty@ltx\expandafter{\the\widowpenalty}% + \widowpenalty\f@ur + \expandafter\def\expandafter\displaywidowpenalty@ltx\expandafter{\the\displaywidowpenalty}% + \displaywidowpenalty8\relax +}% +\def\restore@linepenalties{% + \interlinepenalty\interlinepenalty@ltx + \brokenpenalty\brokenpenalty@ltx + \clubpenalty\clubpenalty@ltx + \widowpenalty\widowpenalty@ltx + \displaywidowpenalty\displaywidowpenalty@ltx + \relax +}% +\def\set@displaypenalties#1{% + \expandafter\def\expandafter\predisplaypenalty@ltx\expandafter{\the\predisplaypenalty}% + \expandafter\def\expandafter\interdisplaylinepenalty@ltx\expandafter{\the\interdisplaylinepenalty}% + \expandafter\def\expandafter\postdisplaypenalty@ltx\expandafter{\the\postdisplaypenalty}% + \@ifhmode{\predisplaypenalty-\int@predisplaypenalty\relax}{}% + #1{\interdisplaylinepenalty-\int@interdisplaylinepenalty\relax}{}% + #1{\postdisplaypenalty-\int@postdisplaypenalty\relax}{}% +}% +\def\enqueue@whatsit@ltx#1{% + \gappdef\g@whatsit@queue{{#1}}% + \vadjust{\penalty-\int@whatsitpenalty}% +}% +\def\handle@whatsit@ltx{% + \unvbox\@cclv + \g@pop@ltx\g@whatsit@queue\@tempa + \expandafter\do@whatsit\expandafter{\@tempa}% +}% +\def\do@whatsit#1{}% +\def\g@pop@ltx#1#2{% + \expandafter\@g@pop@ltx#1{}{}\@@#1#2% +}% +\def\@g@pop@ltx#1#2\@@#3#4{% + \gdef#3{#2}% + \def#4{#1}% +}% +\let\vspace@ltx\vspace +\let\pagebreak@ltx\pagebreak +\let\nopagebreak@ltx\nopagebreak +\let\endline@ltx\\ +\let\@arrayparboxrestore@ltx\@arrayparboxrestore +\def\@tempa#1{% +\def\@vspace@org ##1{% + \ifvmode + #1% \vskip #1 + \vskip\z@skip + \else + \@bsphack + \vadjust{\@restorepar + #1% \vskip #1 + \vskip\z@skip + }% + \@esphack + \fi +}% +\def\@vspace@ltx##1{% + \@ifvmode{% + #1% \vskip #1 + \vskip\z@skip + }{% + \@bsphack + \ex@vadjust@ltx{% + \@restorepar + \nobreak + #1% \vskip #1 + \vskip\z@skip + }% + \@esphack + }% +}% +\def\@vspacer@org##1{% + \ifvmode + \dimen@\prevdepth + \hrule \@height\z@ + \nobreak + #1%\vskip #1 + \vskip\z@skip + \prevdepth\dimen@ + \else + \@bsphack + \vadjust{\@restorepar + \hrule \@height\z@ + \nobreak + #1%\vskip #1 + \vskip\z@skip}% + \@esphack +\fi +}% +\def\@vspacer@ltx##1{% + \@ifvmode{% + \dimen@\prevdepth + \hrule\@height\z@ + \nobreak + #1%\vskip#1 + \vskip\z@skip + \prevdepth\dimen@ + }{% + \@bsphack + \ex@vadjust@ltx{% + \@restorepar + \hrule\@height\z@ + \nobreak + #1%\vskip#1 + \vskip\z@skip + }% + \@esphack + }% +}% +} +\rvtx@ifformat@geq{2020/10/01}% + {\@tempa{\@vspace@calcify{#1}}}% + {\@tempa{\vskip #1 }}% +\def\@no@pgbk@org #1[#2]{% + \ifvmode + \penalty #1\@getpen{#2}% + \else + \@bsphack + \vadjust{\penalty #1\@getpen{#2}}% + \@esphack + \fi +}% +\def\@no@pgbk@ltx#1[#2]{% + \@ifvmode{% + \penalty#1\@getpen{#2}% + }{% + \@bsphack + \ex@vadjust@ltx{% + \penalty#1\@getpen{#2}% + }% + \@esphack + }% +}% +\rvtx@ifformat@geq{2020/02/02}% +{\protected}{\long}\def\end@line@org{% + \let\reserved@e\relax + \let\reserved@f\relax + \@ifstar{% + \let\reserved@e\vadjust + \let\reserved@f\nobreak + \@xnewline + }% + \@xnewline +}% +\rvtx@ifformat@geq{2020/02/02}% +{\protected}{\long}\def\end@line@ltx{% + \let\reserved@e\relax + \let\reserved@f\relax + \@ifstar{% + \let\reserved@e\ex@vadjust@ltx + \let\reserved@f\nobreak + \@xnewline + }{% + \@xnewline + }% +}% +\def\@tempa#1{% + \def\@newline@org[##1]{% + \let\reserved@e\vadjust + \@gnewline{#1}% \vskip#1 + }% + \def\@newline@ltx[##1]{% + \let\reserved@e\ex@vadjust@ltx + \@gnewline{#1}% \vskip#1 + }% +} +\rvtx@ifformat@geq{2020/10/01}% + {\@tempa{\@vspace@calcify{#1}}}% + {\@tempa{\vskip #1}}% + \@ifx{\@vspace\@vspace@org}{% + \@ifx{\@vspacer\@vspacer@org}{% + \@ifx{\@no@pgbk\@no@pgbk@org}{% + \@ifx{\@newline\@newline@org}{% + \expandafter\@ifx\expandafter{% + \csname\rvtx@ifformat@geq{2020/02/02}% + {\expandafter\@gobble\string\\}% + {\expandafter\@gobble\string\\ }\endcsname + \end@line@org + }{% + \true@sw + }{\false@sw}% + }{\false@sw}% + }{\false@sw}% + }{\false@sw}% + }{\false@sw}% + {% + \class@info{Overriding \string\@vspace, \string\@vspacer, \string\@no@pgbk, \string\@newline, and \string\\ }% + \let\@normalcr\end@line@ltx + \expandafter\let + \csname\rvtx@ifformat@geq{2020/02/02}% + {\expandafter\@gobble\string\\}% + {\expandafter\@gobble\string\\ }\endcsname\@normalcr + \let\@newline\@newline@ltx + \let\@vspace\@vspace@ltx + \let\@vspacer\@vspacer@ltx + \let\@no@pgbk\@no@pgbk@ltx + }{% + \class@warn{% + Failed to recognize \string\@vspace, \string\@vspacer, \string\@no@pgbk, \string\@newline, and \string\\; + no patches applied. Please get a more up-to-date class, + }% + }% +\let\ex@vadjust@ltx\vadjust +\def\enqueue@vadjust@ltx#1{% + \gappdef\g@vadjust@queue{{#1}}% + \vadjust{\penalty-\int@vadjustpenalty}% +}% +\def\handle@vadjust@ltx{% + \unvbox\@cclv + \g@pop@ltx\g@vadjust@queue\@tempa + \expandafter\gappdef\expandafter\g@vadjust@line\expandafter{\@tempa}% +}% +\let\g@vadjust@line\@empty +\def\reset@queues@ltx{% + \global\let\g@whatsit@queue\@empty + \global\let\g@vadjust@queue\@empty +}% +\newcommand\linenomathWithnumbers@LN{% + \ifLineNumbers + \ifnum\interlinepenalty>-\linenopenaltypar + \global\holdinginserts\thr@@ + \advance\interlinepenalty \linenopenalty + \ifhmode + \advance\predisplaypenalty \linenopenalty + \fi + \advance\postdisplaypenalty \linenopenalty + \advance\interdisplaylinepenalty \linenopenalty + \fi + \fi + \ignorespaces +}% +\newcommand\linenomathNonumbers@LN{% + \ifLineNumbers + \ifnum\interlinepenalty>-\linenopenaltypar + \global\holdinginserts\thr@@ + \advance\interlinepenalty \linenopenalty + \ifhmode + \advance\predisplaypenalty \linenopenalty + \fi + \fi + \fi + \ignorespaces +}% +\def\endlinenomath@LN{% + \ifLineNumbers + \global\holdinginserts\@LN@outer@holdins + \fi + \global\@ignoretrue +} +\def\linenumberpar@LN{% + \ifvmode \@@@par \else + \ifinner \@@@par \else + \xdef\@LN@outer@holdins{\the\holdinginserts}% + \advance \interlinepenalty \linenopenalty + \linenoprevgraf \prevgraf + \global \holdinginserts \thr@@ + \@@@par + \ifnum\prevgraf>\linenoprevgraf + \penalty-\linenopenaltypar + \fi + \@LN@parpgbrk + \global\holdinginserts\@LN@outer@holdins + \advance\interlinepenalty -\linenopenalty + \fi + \fi +}% +\appdef\class@documenthook{% + \@ifpackageloaded{lineno}{% + \@ifx{\linenomathWithnumbers\linenomathWithnumbers@LN}{% + \@ifx{\linenomathNonumbers\linenomathNonumbers@LN}{% + \@ifx{\endlinenomath\endlinenomath@LN}{% + \@ifx{\linenumberpar\linenumberpar@LN}{% + \true@sw + }{\false@sw}% + }{\false@sw}% + }{\false@sw}% + }{\false@sw}% + {% + \class@info{Overriding lineo.sty, restoring output routine,}% + \let\linenumberpar\linenumberpar@ltx + \let\endlinenomath\endlinenomath@ltx + \expandafter\let\csname endlinenomath*\endcsname\endlinenomath@ltx + \let\linenomathWithnumbers\linenomathWithnumbers@ltx + \let\linenomathNonumbers\linenomathNonumbers@ltx + \let\ex@vadjust@ltx\ex@vadjust@line + \let\@LN@postlabel\enqueue@whatsit@ltx + \let\do@whatsit\write@linelabel + \let\handle@par@ltx\handle@par@LN + \let\@@handle@line@ltx\Make@LineNo@ltx + \let\@@handle@display@ltx\Make@LineNo@ltx + \output@latex{\natural@output}% + \let\vspace\vspace@ltx + \let\pagebreak\pagebreak@ltx + \let\nopagebreak\nopagebreak@ltx + \let\@arrayparboxrestore\@arrayparboxrestore@ltx + \let\\\endline@ltx + \appdef\set@footnotefont{% + \let\par\@@@par + \let\@@par\@@@par + }% + \@if@sw\ifLineNumbers\fi{% + \class@info{Reinvoke \string\linenumbers}% + \let\@@par\linenumberpar + \@ifx{\@par\linenumberpar@LN}{\let\@par\linenumberpar}{}% + \@ifx{\par\linenumberpar@LN}{\let\par\linenumberpar}{}% + }{% + \class@info{Line numbering not turned on yet}% + }% + }{% + \class@warn{Failed to recognize lineno.sty procedures; no patches applied. Please get a more up-to-date class.}% + }% + }{% + }% +}% +\def\linenumberpar@ltx{\@ifvmode{\@@@par}{\@linenumberpar}}% +\def\@linenumberpar{% + \linenoprevgraf\prevgraf + \set@linepenalties + \@@@par + \@ifnum{\prevgraf>\linenoprevgraf}{ + \penalty-\int@postparpenalty + }{}% + \@LN@parpgbrk + \restore@linepenalties +}% +\newcommand\linenomathWithnumbers@ltx{\@linenomathnumbers@ltx\true@sw}% +\newcommand\linenomathNonumbers@ltx{\@linenomathnumbers@ltx\false@sw}% +\def\@linenomathnumbers@ltx#1{% + \@if@sw\ifLineNumbers\fi{% + \set@linepenalties + \set@displaypenalties#1% + }{}% + \ignorespaces +}% +\def\endlinenomath@ltx{% + \global\@ignoretrue +}% +\def\handle@par@LN{% + \Make@LineNo@ltx + \@tempcnta\lastpenalty + \@ifnum{\@tempcnta=\z@}{}{% + \expandafter\gdef + \expandafter\@LN@parpgbrk + \expandafter{% + \expandafter\penalty + \the\@tempcnta + \global\let\@LN@parpgbrk\@LN@screenoff@pen + }% + }% +}% +\def\Make@LineNo@ltx{% + \@LN@maybe@normalLineNumber + \boxmaxdepth\maxdimen\setbox\z@\vbox{\unvbox\@cclv}% + \@tempdima\dp\z@ + \unvbox\z@ + \sbox\@tempboxa{\hb@xt@\z@{\makeLineNumber}}% + \ht\@tempboxa\z@ + \@LN@depthbox + \stepLineNumber + \g@vadjust@line + \global\let\g@vadjust@line\@empty +}% +\def\write@linelabel#1{% + \protected@write\@auxout{}{% + \string\newlabel{#1}{{\theLineNumber}{\thepage}{}{}{}}% + }% +}% +\def\ex@vadjust@line{% + \@if@sw\ifLineNumbers\fi{\enqueue@vadjust@ltx}{\vadjust}% +}% +\endinput +%% +%% End of file `ltxgrid.sty'. diff --git a/src/easydiffraction/report/templates/tex/styles/ltxutil.sty b/src/easydiffraction/report/templates/tex/styles/ltxutil.sty new file mode 100644 index 000000000..60a23db3a --- /dev/null +++ b/src/easydiffraction/report/templates/tex/styles/ltxutil.sty @@ -0,0 +1,2108 @@ +%% +%% This is file `ltxutil.sty', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% ltxutil.dtx (with options: `package,kernel') +%% +%% This is a generated file; +%% altering it directly is inadvisable; +%% instead, modify the original source file. +%% See the URL in the file README-LTXUTIL.tex. +%% +%% License +%% You may distribute this file under the conditions of the +%% LaTeX Project Public License 1.3c or later +%% (http://www.latex-project.org/lppl.txt). +%% +%% This file is distributed WITHOUT ANY WARRANTY; +%% without even the implied warranty of MERCHANTABILITY +%% or FITNESS FOR A PARTICULAR PURPOSE. +%% +%%% @LaTeX-file{ +%%% filename = "ltxutil.dtx", +%%% version = "4.2f", +%%% date = "2022/06/05", +%%% author = "Arthur Ogawa (mailto:arthur_ogawa at sbcglobal.net), +%%% Phelype Oleinik (mailto:phelype.oleinik at latex-project.org), +%%% commissioned by the American Physical Society. Minor changes by Mark Doyle for version 4.2a-c. +%%% ", +%%% copyright = "Copyright (C) 1999, 2009 Arthur Ogawa, +%%% distributed under the terms of the +%%% LaTeX Project Public License 1.3c, see +%%% ftp://ctan.tug.org/macros/latex/base/lppl.txt +%%% ", +%%% address = "Arthur Ogawa, +%%% USA", +%%% telephone = "", +%%% FAX = "", +%%% email = "mailto colon arthur_ogawa at sbcglobal.net", +%%% codetable = "ISO/ASCII", +%%% keywords = "latex, page grid, main vertical list", +%%% supported = "yes", +%%% abstract = "utilities package", +%%% } +\NeedsTeXFormat{LaTeX2e}[1995/12/01]% +\ProvidesFile{% +ltxutil% +.sty% +}% + [2022/06/05 4.2f utilities package (portions licensed from W. E. Baxter web at superscript.com)]% \fileversion +\def\package@name{ltxutil}% +\expandafter\PackageInfo\expandafter{\package@name}{% + Utility macros for \protect\LaTeXe, + by A. Ogawa (arthur_ogawa at sbcglobal.net)% +}% +\def\class@err#1{\ClassError{\class@name}{#1}\@eha}% +\def\class@warn#1{\ClassWarningNoLine{\class@name}{#1}}% +\def\class@info#1{\ClassInfo{\class@name}{#1}}% +\def\obsolete@command#1{% + \class@warn@end{Command \string#1\space is obsolete.^^JPlease remove from your document}% + \global\let#1\@empty + #1% +}% +\def\replace@command#1#2{% + \class@warn@end{Command \string#1\space is obsolete;^^JUse \string#2\space instead}% + \global\let#1#2% + #1% +}% +\def\replace@environment#1#2{% + \class@warn@end{Environment #1 is obsolete;^^JUse #2 instead}% + \glet@environment{#1}{#2}% + \@nameuse{#1}% +}% +\def\incompatible@package#1{% + \@ifpackageloaded{#1}{% + \def\@tempa{I cannot continue. You must remove the \string\usepackage\ statement that caused that package to be loaded.}% + \ClassError{\class@name}{The #1 package cannot be used with \class@name}% + \@tempa\stop + }{% + \class@info{#1 was not loaded (OK!)}% + }% +}% +\def\class@warn@end#1{% + \gappdef\class@enddocumenthook{\class@warn{#1}}% +}% +\ifx\undefined\class@name + \def\class@name{ltxutil}% + \class@warn{You should define the class name before reading in this package. Using default}% +\fi +\def\t@{to}% +\dimendef\dimen@iii\thr@@ +\def\halignt@{\halign\t@}% +\chardef\f@ur=4\relax +\chardef\cat@letter=11\relax +\chardef\other=12\relax +\def\let@environment#1#2{% + \expandafter\let + \csname#1\expandafter\endcsname\csname#2\endcsname + \expandafter\let + \csname end#1\expandafter\endcsname\csname end#2\endcsname +}% +\def\glet@environment#1#2{% + \global\expandafter\let + \csname#1\expandafter\endcsname\csname#2\endcsname + \global\expandafter\let + \csname end#1\expandafter\endcsname\csname end#2\endcsname +}% +\newcommand\tracingplain{% + \tracingonline\z@\tracingcommands\z@\tracingstats\z@ + \tracingpages\z@\tracingoutput\z@\tracinglostchars\@ne + \tracingmacros\z@\tracingparagraphs\z@\tracingrestores\z@ + \showboxbreadth5\showboxdepth3\relax %\errorstopmode + }% +\newcommand\traceoutput{% + \appdef\@resetactivechars{\showoutput}% +}% +\newcommand\say[1]{\typeout{<\noexpand#1=\meaning#1>}}% +\newcommand\saythe[1]{\typeout{<\noexpand#1=\the#1>}}% +\def\fullinterlineskip{\prevdepth\z@}% +\countdef\count@i\@ne +\countdef\count@ii\tw@ +\long\def\prepdef#1#2{% + \@ifxundefined#1{\toks@{}}{\toks@\expandafter{#1}}% + \toks@ii{#2}% + \edef#1{\the\toks@ii\the\toks@}% +}% +\long\def\appdef#1#2{% + \@ifxundefined#1{\toks@{}}{\toks@\expandafter{#1}}% + \toks@ii{#2}% + \edef#1{\the\toks@\the\toks@ii}% +}% +\long\def\gappdef#1#2{% + \@ifxundefined#1{\toks@{}}{\toks@\expandafter{#1}}% + \toks@ii{#2}% + \global\edef#1{\the\toks@\the\toks@ii}% +}% +\long\def\appdef@val#1#2{% + \appdef#1{{#2}}% +}% +\long\def\appdef@e#1#2{% + \expandafter\appdef + \expandafter#1% + \expandafter{#2}% +}% +\long\def\appdef@eval#1#2{% + \expandafter\appdef@val + \expandafter#1% + \expandafter{#2}% +}% +\toksdef\toks@ii=\tw@ +\long\def\@ifxundefined#1{\@ifx{\undefined#1}}% +\long\def\@ifnotrelax#1#2#3{\@ifx{\relax#1}{#3}{#2}}% +\long\def\@argswap#1#2{#2#1}% +\long\def\@argswap@val#1#2{#2{#1}}% +\def\@ifxundefined@cs#1{\expandafter\@ifx\expandafter{\csname#1\endcsname\relax}}% +\ifx\IfFormatAtLeastTF\undefined + \def\rvtx@ifformat@geq{\@ifl@t@r\fmtversion}% +\else + \let\rvtx@ifformat@geq\IfFormatAtLeastTF +\fi +\def\@boolean#1#2{% + \long\def#1{% + #2% \if + \expandafter\true@sw + \else + \expandafter\false@sw + \fi + }% +}% +\def\@boole@def#1#{\@boolean{#1}}% Implicit #2 +\def\@booleantrue#1{\let#1\true@sw}% +\def\@booleanfalse#1{\let#1\false@sw}% +\@boole@def\@ifx#1{\ifx#1}% +\@boole@def\@ifx@empty#1{\ifx\@empty#1}% +\@boole@def\@if@empty#1{\if!#1!}% +\def\@if@sw#1#2{#1\expandafter\true@sw\else\expandafter\false@sw#2}% +\@boole@def\@ifdim#1{\ifdim#1}% +\@boole@def\@ifeof#1{\ifeof#1}% +\@boole@def\@ifhbox#1{\ifhbox#1}% +\@boole@def\@ifhmode{\ifhmode}% +\@boole@def\@ifinner{\ifinner}% +\@boole@def\@ifmmode{\ifmmode}% +\@boole@def\@ifnum#1{\ifnum#1}% +\@boole@def\@ifodd#1{\ifodd#1}% +\@boole@def\@ifvbox#1{\ifvbox#1}% +\@boole@def\@ifvmode{\ifvmode}% +\@boole@def\@ifvoid#1{\ifvoid#1}% +\long\def\true@sw#1#2{#1}% +\long\def\false@sw#1#2{#2}% +\long\def\loopuntil#1{#1{}{\loopuntil{#1}}}% +\long\def\loopwhile#1{#1{\loopwhile{#1}}{}}% +\def\@provide#1{% + \@ifx{\undefined#1}{\true@sw}{\@ifx{\relax#1}{\true@sw}{\false@sw}}% + {\def#1}{\def\j@nk}% +}% +\rvtx@ifformat@geq{2020/10/01}% + {% + \AddToHook{begindocument/before}{\document@inithook}% + }{% + \prepdef\document{% + \endgroup + \document@inithook + \true@sw{}% + }% + } +\let\document@inithook\@empty +\appdef\document@inithook{% + \AtBeginDocument{\class@documenthook}% +}% +\AtEndDocument{% + \class@enddocumenthook +}% +\let\class@documenthook\@empty +\let\class@enddocumenthook\@empty +\rvtx@ifformat@geq{2020/10/01}{% + % +}{% + % +\def\enddocument{% + \let\AtEndDocument\@firstofone + \@enddocumenthook + \@checkend{document}% + \clear@document + \check@aux + \deadcycles\z@ + \@@end +}% +\def\check@aux{\do@check@aux}% +\def\do@check@aux{% + \@if@sw\if@filesw\fi{% + \immediate\closeout\@mainaux + \let\@setckpt\@gobbletwo + \let\@newl@bel\@testdef + \@tempswafalse + \makeatletter + \input\jobname.aux\relax + }{}% + \@dofilelist + \@ifdim{\font@submax >\fontsubfuzz\relax}{% + \@font@warning{% + Size substitutions with differences\MessageBreak + up to \font@submax\space have occured.\@gobbletwo + }% + }{}% + \@defaultsubs + \@refundefined + \@if@sw\if@filesw\fi{% + \@ifx{\@multiplelabels\relax}{% + \@if@sw\if@tempswa\fi{% + \@latex@warning@no@line{% + Label(s) may have changed. + Rerun to get cross-references right% + }% + }{}% + }{% + \@multiplelabels + }% + }{}% +}% +} +\rvtx@ifformat@geq{2020/10/01}{% + \AddToHook{enddocument}{\rvtx@enddocument@patch{}}% +}{} +\protected\long\def\rvtx@enddocument@patch#1#2\@checkend#3{% + \begingroup + \edef\x{\detokenize{#3}}% + \edef\y{\detokenize{document}}% + \expandafter\endgroup + \ifx\x\y + \expandafter\rvtx@enddocument@patch@end + \else + \expandafter\rvtx@enddocument@patch@more + \fi + {#1#2}{#3}} +\def\rvtx@enddocument@patch@more#1#2{% + \rvtx@enddocument@patch{#1\@checkend{#2}}} +\long\def\rvtx@enddocument@patch@end#1#2\clearpage#3\endgroup{% + \def\do@check@aux{#3\endgroup}% + #1% + \@checkend{#2}% + \clear@document + \check@aux} +\def\check@aux{\do@check@aux}% +\def\clear@document{% + \clearpage + \do@output@cclv{% + \Call@AfterLastShipout + }% +}% +\appdef\class@documenthook{% + \providecommand\Call@AfterLastShipout{}% +}% +\def\class@extension#1#2{% + \IfFileExists{#1.#2}{% + \expandafter\class@extensionfile\csname ver@\@currname.\@currext\endcsname{#1}#2% + }{% + \csname rtx@#1\endcsname + }% +}% +\def\class@extensionfile#1#2#3{% + \@pass@ptions#3\@unusedoptionlist{#2}% + \global\let\@unusedoptionlist\@empty + \expandafter\class@ext@hook\csname#2.#3-h@@k\endcsname#1{#2}#3% +}% +\def\class@ext@hook#1#2#3#4{% + \@pushfilename@ltx + \makeatletter + \let\CurrentOption\@empty + \@reset@ptions + \let#1\@empty + \xdef\@currname{#3}% + \global\let\@currext#4% + \global\let\@clsextension\@currext + \input{#3.#4}% + \@ifl@ter#4{#3}#2{% + \class@info{Class extension later than: #2}% + }{% + \class@info{Class extension earlier: #2}% + \@@end + }% + #1% + \let#1\@undefined + \expandafter\@p@pfilename@ltx\@currnamestack@ltx\@nil + \@reset@ptions +}% +\def\@pushfilename@ltx{% + \xdef\@currnamestack@ltx{% + {\@currname}% + {\@currext}% + {\@clsextension}% + {\the\catcode`\@}% + \@currnamestack@ltx + }% +}% +\def\@p@pfilename@ltx#1#2#3#4#5\@nil{% + \gdef\@currname{#1}% + \gdef\@currext{#2}% + \gdef\@clsextension{#3}% + \catcode`\@#4\relax + \gdef\@currnamestack@ltx{#5}% +}% +\global\let\@currnamestack@ltx\@empty +\def\flushing{% + \let\\\@normalcr + \leftskip\z@skip + \rightskip\z@skip + \@rightskip\z@skip + \parfillskip\@flushglue +}% +\expandafter\DeclareRobustCommand\expandafter\@centercr\expandafter{\@centercr}% +\def\rvtx@tmpa#1{% +\def\eqnarray@LaTeX{% + \stepcounter{equation}% + \def\@currentlabel{\p@equation\theequation}% + #1% \def\@currentcounter{equation} on newer LaTeX + \global\@eqnswtrue + \m@th + \global\@eqcnt\z@ + \tabskip\@centering + \let\\\@eqncr + $$\everycr{}\halign to\displaywidth\bgroup + \hskip\@centering$\displaystyle\tabskip\z@skip{####}$\@eqnsel + &\global\@eqcnt\@ne\hskip \tw@\arraycolsep \hfil${####}$\hfil + &\global\@eqcnt\tw@ \hskip \tw@\arraycolsep + $\displaystyle{####}$\hfil\tabskip\@centering + &\global\@eqcnt\thr@@ \hb@xt@\z@\bgroup\hss####\egroup + \tabskip\z@skip + \cr +}% +\long\def\eqnarray@fleqn@fixed{% + \stepcounter{equation}\def\@currentlabel{\p@equation\theequation}% + #1% \def\@currentcounter{equation} on newer LaTeX + \global\@eqnswtrue\m@th\global\@eqcnt\z@ + \tabskip\ltx@mathindent + \let\\=\@eqncr + \setlength\abovedisplayskip{\topsep}% + \ifvmode\addtolength\abovedisplayskip{\partopsep}\fi + \addtolength\abovedisplayskip{\parskip}% + \setlength\belowdisplayskip{\abovedisplayskip}% + \setlength\belowdisplayshortskip{\abovedisplayskip}% + \setlength\abovedisplayshortskip{\abovedisplayskip}% + $$% + \everycr{}% + \halignt@\linewidth\bgroup + \hskip\@centering$\displaystyle\tabskip\z@skip{####}$\@eqnsel + &\global\@eqcnt\@ne + \hskip\tw@\eqncolsep + \hfil${{}####{}}$\hfil + &\global\@eqcnt\tw@ + \hskip\tw@\eqncolsep + $\displaystyle{####}$\hfil\tabskip\@centering + &\global\@eqcnt\thr@@\hb@xt@\z@\bgroup\hss####\egroup + \tabskip\z@skip + \cr +}% +} +\rvtx@tmpa{}% older LaTeX +\@ifx{\eqnarray\eqnarray@LaTeX}{\@firstofone} + {% + \rvtx@tmpa{\def\@currentcounter{equation}}% newer LaTeX + \@ifx{\eqnarray\eqnarray@LaTeX}{\@firstofone} + {\@gobble} + } +{% + \class@info{Repairing broken LaTeX eqnarray}% + \let\eqnarray\eqnarray@fleqn@fixed + \newlength\eqncolsep + \setlength\eqncolsep\z@ + \let\eqnarray@LaTeX\relax + \let\eqnarray@fleqn@fixed\relax +}% +\def\ltx@mathindent{\@centering}% +\def\set@eqnarray@skips{}% +\def\prep@math{% + \@ifvmode{\everypar{{\setbox\z@\lastbox}}}{}% +}% +\def\prep@math@patch{% + \prepdef\equation{\prep@math}% + \prepdef\eqnarray{\prep@math}% +}% +\def\footnote{\@ifnextchar[\ltx@xfootnote\ltx@yfootnote}% +\def\ltx@xfootnote[#1]{% + \ltx@def@footproc\ltx@footmark[#1]% + \expandafter\ltx@foottext\expandafter{\the\csname c@\@mpfn\endcsname}% +}% +\def\ltx@yfootnote{% + \ltx@stp@footproc\ltx@footmark + \expandafter\ltx@foottext\expandafter{\the\csname c@\@mpfn\endcsname}% +}% +\def\footnotemark{\@ifnextchar[\ltx@xfootmark\ltx@yfootmark}% +\def\ltx@xfootmark{\ltx@def@footproc\ltx@footmark}% +\def\ltx@yfootmark{\ltx@stp@footproc\ltx@footmark}% +\def\ltx@footmark#1{% + \leavevmode + \ifhmode\edef\@x@sf{\the\spacefactor}\nobreak\fi + \begingroup + \expandafter\ltx@make@current@footnote\expandafter{\@mpfn}{#1}% + \expandafter\@argswap@val\expandafter{\Hy@footnote@currentHref}{\hyper@linkstart {link}}% + \@makefnmark + \hyper@linkend + \endgroup + \ifhmode\spacefactor\@x@sf\fi + \relax +}% +\def\footnotetext{\@ifnextchar[\ltx@xfoottext\ltx@yfoottext}% +\def\ltx@xfoottext{\ltx@def@footproc\ltx@foottext}% +\def\ltx@yfoottext{\ltx@stp@footproc\ltx@foottext}% +\long\def\ltx@foottext#1#2{% + \begingroup + \expandafter\ltx@make@current@footnote\expandafter{\@mpfn}{#1}% + \@footnotetext{#2}% + \endgroup +}% +\def\ltx@def@footproc#1[#2]{% + \begingroup + \csname c@\@mpfn\endcsname #2\relax + \unrestored@protected@xdef\@thefnmark{\thempfn}% + \expandafter\endgroup + \expandafter#1% + \expandafter{\the\csname c@\@mpfn\endcsname}% +}% +\def\ltx@stp@footproc#1{% + \expandafter\stepcounter\expandafter{\@mpfn}% + \protected@xdef\@thefnmark{\thempfn}% + \expandafter#1% + \expandafter{\the\csname c@\@mpfn\endcsname}% +}% +\appdef\class@documenthook{% + \let\footnote@latex\footnote + \@ifpackageloaded{hyperref}{}{% + \let\H@@footnotetext\@footnotetext + \def\@footnotetext{\H@@footnotetext}% + \let\H@@mpfootnotetext\@mpfootnotetext + \def\@mpfootnotetext{\H@@mpfootnotetext}% + }% +}% +\def\ltx@make@current@footnote#1#2{% + \csname c@#1\endcsname#2\relax + \protected@edef\Hy@footnote@currentHref{\@currentHref-#1.\csname the#1\endcsname}% +}% +\def\thempfootnote@latex{{\itshape \@alph \c@mpfootnote }}% +\def\ltx@thempfootnote{\@alph\c@mpfootnote}% +\@ifx{\thempfootnote\thempfootnote@latex}{% + \class@info{Repairing hyperref-unfriendly LaTeX definition of \string\mpfootnote}% + \let\thempfootnote\ltx@thempfootnote +}{}% +\def\@makefnmark{% + \hbox{% + \@textsuperscript{% + \normalfont\itshape\@thefnmark + }% + }% +}% +\long\def\@footnotetext{% + \insert\footins\bgroup + \make@footnotetext +}% +\long\def\@mpfootnotetext{% + \minipagefootnote@pick + \make@footnotetext +}% +\long\def\make@footnotetext#1{% + \set@footnotefont + \set@footnotewidth + \@parboxrestore + \protected@edef\@currentlabel{% + \csname p@\@mpfn\endcsname\@thefnmark + }% + \color@begingroup + \@makefntext{% + \rule\z@\footnotesep\ignorespaces#1% + \@finalstrut\strutbox\vadjust{\vskip\z@skip}% + }% + \color@endgroup + \minipagefootnote@drop +}% +\def\set@footnotefont{% + \reset@font\footnotesize + \interlinepenalty\interfootnotelinepenalty + \splittopskip\footnotesep + \splitmaxdepth\dp\strutbox +}% +\def\set@footnotewidth{\set@footnotewidth@one}% +\def\robustify@contents{% + \let \label \@gobble + \let \index \@gobble + \let \glossary \@gobble + \let\footnote \@gobble + \def\({\string\(}% + \def\){\string\)}% + \def\\{\string\\}% +}% +\long\def\addtocontents#1#2{% + \protected@write\@auxout{\robustify@contents}{\string \@writefile {#1}{#2}}% +}% +\def\addcontentsline#1#2#3{% + \addtocontents{#1}{% + \protect\contentsline{#2}{#3}{\thepage}{}% + }% +}% +\def\label#1{% + \@bsphack + \protected@write\@auxout{}{% + \string\newlabel{#1}{{\@currentlabel}{\thepage}{}{}{}}% + }% + \@esphack +}% +\def\ltx@contentsline#1{% + \expandafter\@ifnotrelax\csname l@#1\endcsname{}{% + \expandafter\let\csname l@#1\endcsname\@gobbletwo + }% + \contentsline@latex{#1}% +}% +\appdef\document@inithook{% + \let\contentsline@latex\contentsline + \let\contentsline\ltx@contentsline +}% +\appdef\class@documenthook{% + \prepdef\caption{\minipagefootnote@here}% +}% +\def\minipagefootnote@init{% + \setbox\@mpfootins\box\voidb@x +}% +\def\minipagefootnote@pick{% + \global\setbox\@mpfootins\vbox\bgroup + \unvbox\@mpfootins +}% +\def\minipagefootnote@drop{% + \egroup +}% +\def\minipagefootnote@here{% + \par + \@ifvoid\@mpfootins{}{% + \vskip\skip\@mpfootins + \fullinterlineskip + \@ifinner{% + \vtop{\unvcopy\@mpfootins}% + {\setbox\z@\lastbox}% + }{}% + \unvbox\@mpfootins + }% +}% +\def\minipagefootnote@foot{% + \@ifvoid\@mpfootins{}{% + \insert\footins\bgroup\unvbox\@mpfootins\egroup + }% +}% +\def\endminipage{% + \par + \unskip + \minipagefootnote@here + \@minipagefalse %% added 24 May 89 + \color@endgroup + \egroup + \expandafter\@iiiparbox\@mpargs{\unvbox\@tempboxa}% +}% +\@booleantrue\floats@sw +\let\@xfloat@LaTeX\@xfloat +\def\@xfloat#1[#2]{% + \@xfloat@prep + \@nameuse{fp@proc@#2}% + \floats@sw{\@xfloat@LaTeX{#1}[#2]}{\@xfloat@anchored{#1}[]}% +}% +\def\@xfloat@prep{% + \ltx@footnote@pop + \def\@mpfn{mpfootnote}% + \def\thempfn{\thempfootnote}% + \c@mpfootnote\z@ + \let\H@@footnotetext\H@@mpfootnotetext +}% +\let\ltx@footnote@pop\@empty +\def\@xfloat@anchored#1[#2]{% + \def\@captype{#1}% + \begin@float@pagebreak + \let\end@float\end@float@anchored + \let\end@dblfloat\end@float@anchored + \hsize\columnwidth + \@parboxrestore + \@floatboxreset + \minipagefootnote@init +}% +\def\end@float@anchored{% + \minipagefootnote@here + \par\vskip\z@skip + \par + \end@float@pagebreak +}% +\def\begin@float@pagebreak{\par\addvspace\intextsep}% +\def\end@float@pagebreak{\par\addvspace\intextsep}% +\def\@mpmakefntext#1{% + \parindent=1em + \noindent + \hb@xt@1em{\hss\@makefnmark}% + #1% +}% +\def\do@if@floats#1#2{% + \floats@sw{}{% + \expandafter\newwrite + \csname#1write\endcsname + \expandafter\def + \csname#1@stream\endcsname{\jobname#2}% + \expandafter\immediate + \expandafter\openout + \csname#1write\endcsname + \csname#1@stream\endcsname\relax + \@ifxundefined\@float@LaTeX{% + \let\@float@LaTeX\@float + \let\@dblfloat@LaTeX\@dblfloat + \let\@float\write@float + \let\@dblfloat\write@floats + }{}% + \let@environment{#1@float}{#1}% + \let@environment{#1@floats}{#1*}% + \@ifxundefined@cs{#1@write}{}{% + \let@environment{#1}{#1@write}% + }% + }% +}% +\def\triggerpar{\leavevmode\@@par}% +\def\oneapage{\def\begin@float@pagebreak{\newpage}\def\end@float@pagebreak{\newpage}}% +\def\print@float#1#2{% + \lengthcheck@sw{% + \total@float{#1}% + }{}% + \@ifxundefined@cs{#1write}{}{% + \begingroup + \@booleanfalse\floats@sw + #2% + \raggedbottom + \def\array@default{v}% floats must + \let\@float\@float@LaTeX + \let\@dblfloat\@dblfloat@LaTeX + \let\trigger@float@par\triggerpar + \let@environment{#1}{#1@float}% + \let@environment{#1*}{#1@floats}% + \expandafter\prepdef\csname#1\endcsname{\trigger@float@par}% + \expandafter\prepdef\csname#1*\endcsname{\trigger@float@par}% + \@namedef{fps@#1}{h!}% + \expandafter\immediate + \expandafter\closeout + \csname#1write\endcsname + \everypar{% + \global\let\trigger@float@par\relax + \global\everypar{}\setbox\z@\lastbox + \@ifxundefined@cs{#1sname}{}{% + \begin@float@pagebreak + \expandafter\section + \expandafter*% + \expandafter{% + \csname#1sname\endcsname + }% + }% + }% + \input{\csname#1@stream\endcsname}% + \endgroup + \global\expandafter\let\csname#1write\endcsname\relax + }% +}% +\chardef\@xvi=16\relax +\mathchardef\@twopowerfourteen="4000 +\mathchardef\@twopowertwo="4 +\def\tally@float#1{% + \begingroup + \@tempcnta\count\@currbox + \divide\@tempcnta\@xxxii + \multiply\@tempcnta\@xxxii + \advance\count\@currbox-\@tempcnta + \divide\@tempcnta\@xxxii + \@ifnum{\count\@currbox>\@xvi}{% + \advance\count\@currbox-\@xvi\@booleantrue\@temp@sw + }{% + \@booleanfalse\@temp@sw + }% + \show@box@size@sw{% + \class@info{Float #1 + (\the\@tempcnta)[\@temp@sw{16+}{}\the\count\@currbox]^^J% + (\the\ht\@currbox+\the\dp\@currbox)X\the\wd\@currbox + }% + }{}% + \endgroup + \expandafter\let + \expandafter\@tempa + \csname fbox@\csname ftype@#1\endcsname\endcsname + \@ifnotrelax\@tempa{% + \@ifhbox\@tempa{% + \setbox\@tempboxa\vbox{\unvcopy\@currbox\hrule}% + \dimen@\ht\@tempboxa + \divide\dimen@\@twopowerfourteen + \@ifdim{\wd\@tempboxa<\textwidth}{% + \advance\dimen@\ht\@tempa + \global\ht\@tempa\dimen@ + }{% + \advance\dimen@\dp\@tempa + \global\dp\@tempa\dimen@ + }% + }{}% + }{}% +}% +\def\total@float#1{% + \expandafter\let + \expandafter\@tempa + \csname fbox@\csname ftype@#1\endcsname\endcsname + \@ifnotrelax\@tempa{% + \@ifhbox\@tempa{% + \@tempdima\the\ht\@tempa\divide\@tempdima\@twopowertwo\@tempcnta\@tempdima + \@tempdimb\the\dp\@tempa\divide\@tempdimb\@twopowertwo\@tempcntb\@tempdimb + \class@info{Total #1: Column(\the\@tempcnta pt), Page(\the\@tempcnta pt)}% + }{}% + }{}% +}% +\def\write@float#1{\write@@float{#1}{#1}}% +\def\endwrite@float{\@Esphack}% +\def\write@floats#1{\write@@float{#1*}{#1}}% +\def\endwrite@floats{\@Esphack}% +\def\write@@float#1#2{% + \ifhmode + \@bsphack + \fi + \chardef\@tempc\csname#2write\endcsname + \toks@{\begin{#1}}% + \def\@tempb{#1}% + \expandafter\let\csname end#1\endcsname\endwrite@float + \catcode`\^^M\active + \@makeother\{\@makeother\}\@makeother\% + \write@floatline +}% +\begingroup + \catcode`\[\the\catcode`\{\catcode`\]\the\catcode`\}\@makeother\{\@makeother\}% + \gdef\float@end@tag#1\end{#2}#3\@nul[% + \def\@tempa[#2]% + \@ifx[\@tempa\@tempb][\end[#2]][\write@floatline]% + ]% + \obeylines% + \gdef\write@floatline#1^^M[% + \begingroup% + \newlinechar`\^^M% + \toks@\expandafter[\the\toks@#1]\immediate\write\@tempc[\the\toks@]% + \endgroup% + \toks@[]% + \float@end@tag#1\end{}\@nul% + ]% +\endgroup +\def\@alph#1{\ifcase#1\or a\or b\or c\or d\else\@ialph{#1}\fi} +\def\@ialph#1{\ifcase#1\or \or \or \or \or e\or f\or g\or h\or i\or j\or + k\or l\or m\or n\or o\or p\or q\or r\or s\or t\or u\or v\or w\or x\or + y\or z\or aa\or bb\or cc\or dd\or ee\or ff\or gg\or hh\or ii\or jj\or + kk\or ll\or mm\or nn\or oo\or pp\or qq\or rr\or ss\or tt\or uu\or + vv\or ww\or xx\or yy\or zz\else\@ctrerr\fi} +\def\@startsection#1#2#3#4#5#6{% + \@startsection@hook + \if@noskipsec \leavevmode \fi + \par + \@tempskipa #4\relax + \@afterindenttrue + \ifdim \@tempskipa <\z@ + \@tempskipa -\@tempskipa \@afterindentfalse + \fi + \if@nobreak + \everypar{}% + \else + \addpenalty\@secpenalty\addvspace\@tempskipa + \fi + \@ifstar + {\@dblarg{\@ssect@ltx{#1}{#2}{#3}{#4}{#5}{#6}}}% + {\@dblarg{\@sect@ltx {#1}{#2}{#3}{#4}{#5}{#6}}}% +}% +\def\@startsection@hook{}% +\class@info{Repairing broken LateX \string\@sect}% +\def\@sect@ltx#1#2#3#4#5#6[#7]#8{% + \@ifnum{#2>\c@secnumdepth}{% + \def\H@svsec{\phantomsection}% + \let\@svsec\@empty + }{% + \H@refstepcounter{#1}% + \def\H@svsec{% + \phantomsection + }% + \protected@edef\@svsec{{#1}}% + \@ifundefined{@#1cntformat}{% + \prepdef\@svsec\@seccntformat + }{% + \expandafter\prepdef + \expandafter\@svsec + \csname @#1cntformat\endcsname + }% + }% + \@tempskipa #5\relax + \@ifdim{\@tempskipa>\z@}{% + \begingroup + \interlinepenalty \@M + #6{% + \@ifundefined{@hangfrom@#1}{\@hang@from}{\csname @hangfrom@#1\endcsname}% + {\hskip#3\relax\H@svsec}{\@svsec}{#8}% + }% + \@@par + \endgroup + \@ifundefined{#1mark}{\@gobble}{\csname #1mark\endcsname}{#7}% + \addcontentsline{toc}{#1}{% + \@ifnum{#2>\c@secnumdepth}{% + \protect\numberline{}% + }{% + \protect\numberline{\csname the#1\endcsname}% + }% + #8}% + }{% + \def\@svsechd{% + #6{% + \@ifundefined{@runin@to@#1}{\@runin@to}{\csname @runin@to@#1\endcsname}% + {\hskip#3\relax\H@svsec}{\@svsec}{#8}% + }% + \@ifundefined{#1mark}{\@gobble}{\csname #1mark\endcsname}{#7}% + \addcontentsline{toc}{#1}{% + \@ifnum{#2>\c@secnumdepth}{% + \protect\numberline{}% + }{% + \protect\numberline{\csname the#1\endcsname}% + }% + #8}% + }% + }% + \@xsect{#5}% +}% +\def\@hang@from#1#2#3{\@hangfrom{#1#2}#3}% +\def\@runin@to #1#2#3{#1#2#3}% +\def\@ssect@ltx#1#2#3#4#5#6[#7]#8{% + \def\H@svsec{\phantomsection}% + \@tempskipa #5\relax + \@ifdim{\@tempskipa>\z@}{% + \begingroup + \interlinepenalty \@M + #6{% + \@ifundefined{@hangfroms@#1}{\@hang@froms}{\csname @hangfroms@#1\endcsname}% + {\hskip#3\relax\H@svsec}{#8}% + }% + \@@par + \endgroup + \@ifundefined{#1smark}{\@gobble}{\csname #1smark\endcsname}{#7}% + \addcontentsline{toc}{#1}{\protect\numberline{}#8}% + }{% + \def\@svsechd{% + #6{% + \@ifundefined{@runin@tos@#1}{\@runin@tos}{\csname @runin@tos@#1\endcsname}% + {\hskip#3\relax\H@svsec}{#8}% + }% + \@ifundefined{#1smark}{\@gobble}{\csname #1smark\endcsname}{#7}% + \addcontentsline{toc}{#1}{\protect\numberline{}#8}% + }% + }% + \@xsect{#5}% +}% +\def\@hang@froms#1#2{#1#2}% +\def\@runin@tos #1#2{#1#2}% +\def\init@hyperref{% + \providecommand\phantomsection{}% + \providecommand\hyper@makecurrent[1]{}% + \providecommand\Hy@raisedlink[1]{}% + \providecommand\hyper@anchorstart[1]{}% + \providecommand\hyper@anchorend{}% + \providecommand\hyper@linkstart[2]{}% + \providecommand\hyper@linkend{}% + \providecommand\@currentHref{}% +}% +\let\H@refstepcounter\refstepcounter +\appdef\document@inithook{% + \init@hyperref +}% +\def\sec@upcase#1{\relax{#1}}% +\appdef\document@inithook{% + \@ifpackageloaded{array}{\switch@array}{\switch@tabular}% + \prepdef\endtabular{\endtabular@hook}% + \@provide\endtabular@hook{}% + \prepdef\endarray{\endarray@hook}% + \@provide\endarray@hook{}% + \providecommand\array@hook{}% + \prepdef\@tabular{\tabular@hook}% + \@provide\tabular@hook{}% +}% +\def\switch@tabular{% + \let\@array@sw\@array@sw@array + \@ifx{\@array\@array@LaTeX}{% + \@ifx{\multicolumn\multicolumn@LaTeX}{% + \@ifx{\@tabular\@tabular@LaTeX}{% + \@ifx{\@tabarray\@tabarray@LaTeX}{% + \@ifx{\array\array@LaTeX}{% + \@ifx{\endarray\endarray@LaTeX}{% + \@ifx{\endtabular\endtabular@LaTeX}{% + \@ifx{\@mkpream\@mkpream@LaTeX}{% + \@ifx{\@addamp\@addamp@LaTeX}{% + \@ifx{\@arrayacol\@arrayacol@LaTeX}{% + \@ifx{\@tabacol\@tabacol@LaTeX}{% + \@ifx{\@arrayclassz\@arrayclassz@LaTeX}{% + \@ifx{\@tabclassiv\@tabclassiv@LaTeX}{% + \@ifx{\@arrayclassiv\@arrayclassiv@LaTeX}{% + \@ifx{\@tabclassz\@tabclassz@LaTeX}{% + \@ifx{\@classv\@classv@LaTeX}{% + \@ifx{\hline\hline@LaTeX}{% + \@ifx{\@tabularcr\@tabularcr@LaTeX}{% + \@ifx{\@xtabularcr\@xtabularcr@LaTeX}{% + \@ifx{\@xargarraycr\@xargarraycr@LaTeX}{% + \@ifx{\@yargarraycr\@yargarraycr@LaTeX}{% + \true@sw + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + {% + \class@info{Patching LaTeX tabular.}% + }{% + \class@info{Unrecognized LaTeX tabular. Please update this document class! (Proceeding with fingers crossed.)}% + }% + \let\@array\@array@ltx + \let\multicolumn\multicolumn@ltx + \let\@tabular\@tabular@ltx + \let\@tabarray\@tabarray@ltx + \let\array\array@ltx + \let\endarray\endarray@ltx + \let\endtabular\endtabular@ltx + \let\@mkpream\@mkpream@ltx + \let\@addamp\@addamp@ltx + \let\@arrayacol\@arrayacol@ltx + \let\@tabacol\@tabacol@ltx + \let\@arrayclassz\@arrayclassz@ltx + \let\@tabclassiv\@tabclassiv@ltx + \let\@arrayclassiv\@arrayclassiv@ltx + \let\@tabclassz\@tabclassz@ltx + \let\@classv\@classv@ltx + \let\hline\hline@ltx + \let\@tabularcr\@tabularcr@ltx + \let\@xtabularcr\@xtabularcr@ltx + \let\@xargarraycr\@xargarraycr@ltx + \let\@yargarraycr\@yargarraycr@ltx +}% +\def\switch@array{% + \@ifpackageloaded{colortbl}{\let\switch@array@info\colortbl@message}{\let\switch@array@info\array@message}% + \let\@array@sw\@array@sw@LaTeX + \@ifx{\@array\@array@array}{% + \@ifx{\@tabular\@tabular@array}{% + \@ifx{\@tabarray\@tabarray@array}{% + \@ifx{\array\array@array}{% + \@ifx{\endarray\endarray@array}{% + \@ifx{\endtabular\endtabular@array}{% + \@ifx{\@mkpream\@mkpream@array}{% + \@ifx{\@classx\@classx@array}{% + \@ifx{\insert@column\insert@column@array}{% + \@ifx{\@arraycr\@arraycr@array}{% + \@ifx{\@xarraycr\@xarraycr@array}{% + \@ifx{\@xargarraycr\@xargarraycr@array}{% + \@ifx{\@yargarraycr\@yargarraycr@array}{% + \true@sw + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }{% + \class@info{Patching array package.}% + }{% + \switch@array@info + }% + \let\@array \@array@array@new + \let\@@array \@array % Cosi fan tutti + \let\@tabular \@tabular@array@new + \let\@tabarray \@tabarray@array@new + \let\array \array@array@new + \let\endarray \endarray@array@new + \let\endtabular\endtabular@array@new + \let\@mkpream \@mkpream@array@new + \let\@classx \@classx@array@new + \let\@arrayacol\@arrayacol@ltx + \let\@tabacol \@tabacol@ltx + \let\insert@column\insert@column@array@new + \expandafter\let\csname endtabular*\endcsname\endtabular % Cosi fan tutti + \let\@arraycr \@arraycr@new + \let\@xarraycr \@xarraycr@new + \let\@xargarraycr\@xargarraycr@new + \let\@yargarraycr\@yargarraycr@new +}% +\def\array@message{% + \class@info{Unrecognized array package. Please update this document class! (Proceeding with fingers crossed.)}% +}% +\def\colortbl@message{% + \class@info{colortbl package is loaded. (Proceeding with fingers crossed.)}% +}% +\def\@array@sw@LaTeX{\@ifx{\\\@tabularcr}}% +\def\@array@sw@array{\@ifx{\d@llarbegin\begingroup}}% +\def\@tabular@LaTeX{% + \leavevmode + \hbox\bgroup$% + \let\@acol\@tabacol + \let\@classz\@tabclassz + \let\@classiv\@tabclassiv + \let\\\@tabularcr + \@tabarray +}% +\def\@tabular@ltx{% + \let\@acoll\@tabacoll + \let\@acolr\@tabacolr + \let\@acol\@tabacol + \let\@classz\@tabclassz + \let\@classiv\@tabclassiv + \let\\\@tabularcr + \@tabarray +}% +\def\@tabular@array{% + \leavevmode + \hbox\bgroup$% + \col@sep\tabcolsep + \let\d@llarbegin\begingroup + \let\d@llarend\endgroup + \@tabarray +}% +\def\@tabular@array@new{% + \let\@acoll\@tabacoll + \let\@acolr\@tabacolr + \let\@acol\@tabacol + \let\d@llarbegin\begingroup + \let\d@llarend\endgroup + \@tabarray +}% +\def\@tabarray@LaTeX{% + \m@th\@ifnextchar[\@array{\@array[c]}% +}% +\def\@tabarray@ltx{% + \m@th\@ifnextchar[\@array{\expandafter\@array\expandafter[\array@default]}% +}% +\def\@tabarray@array{% + \@ifnextchar[{\@@array}{\@@array[c]}% +}% +\def\@tabarray@array@new{% + \@ifnextchar[{\@@array}{\expandafter\@@array\expandafter[\array@default]}% +}% +\newcount\intertabularlinepenalty +\intertabularlinepenalty=100 +\newcount\@tbpen +\appdef\samepage{\intertabularlinepenalty\@M}% +\def\@tabularcr@LaTeX{{\ifnum 0=`}\fi \@ifstar \@xtabularcr \@xtabularcr}% +\def\@tabularcr@ltx{{\ifnum 0=`}\fi \@ifstar {\global \@tbpen \@M \@xtabularcr }{\global \@tbpen \intertabularlinepenalty \@xtabularcr }}% +\def\@xtabularcr@LaTeX{\@ifnextchar [\@argtabularcr {\ifnum 0=`{\fi }\cr }}% +\def\@xtabularcr@ltx{\@ifnextchar [\@argtabularcr {\ifnum 0=`{\fi }\cr \noalign {\penalty \@tbpen }}}% +\def\@xargarraycr@LaTeX#1{\@tempdima #1\advance \@tempdima \dp \@arstrutbox \vrule \@height \z@ \@depth \@tempdima \@width \z@ \cr}% +\def\@xargarraycr@ltx#1{\@tempdima #1\advance \@tempdima \dp \@arstrutbox \vrule \@height \z@ \@depth \@tempdima \@width \z@ \cr \noalign {\penalty \@tbpen }}% +\def\@yargarraycr@LaTeX#1{\cr \noalign {\vskip #1}}% +\def\@yargarraycr@ltx#1{\cr \noalign {\penalty \@tbpen \vskip #1}}% +\def\@arraycr@array{% + \relax + \iffalse{\fi\ifnum 0=`}\fi + \@ifstar \@xarraycr \@xarraycr +}% +\def\@arraycr@new{% + \relax + \iffalse{\fi\ifnum 0=`}\fi + \@ifstar {\global \@tbpen \@M \@xarraycr }{\global \@tbpen \intertabularlinepenalty \@xarraycr }% +}% +\def\@xarraycr@array{% + \@ifnextchar [%] + \@argarraycr {\ifnum 0=`{}\fi\cr}% +}% +\def\@xarraycr@new{% + \@ifnextchar [%] + \@argarraycr {\ifnum 0=`{}\fi\cr \noalign {\penalty \@tbpen }}% +}% +\def\@xargarraycr@array#1{% + \unskip + \@tempdima #1\advance\@tempdima \dp\@arstrutbox + \vrule \@depth\@tempdima \@width\z@ + \cr +}% +\def\@xargarraycr@new#1{% + \unskip + \@tempdima #1\advance\@tempdima \dp\@arstrutbox + \vrule \@depth\@tempdima \@width\z@ + \cr + \noalign {\penalty \@tbpen }% +}% +\def\@yargarraycr@array#1{% + \cr + \noalign{\vskip #1}% +}% +\def\@yargarraycr@new#1{% + \cr + \noalign{\penalty \@tbpen \vskip #1}% +}% +\def\array@LaTeX{% + \let\@acol\@arrayacol + \let\@classz\@arrayclassz + \let\@classiv\@arrayclassiv + \let\\\@arraycr + \let\@halignto\@empty + \@tabarray +}% +\def\array@ltx{% + \@ifmmode{}{\@badmath$}% + \let\@acoll\@arrayacol + \let\@acolr\@arrayacol + \let\@acol\@arrayacol + \let\@classz\@arrayclassz + \let\@classiv\@arrayclassiv + \let\\\@arraycr + \let\@halignto\@empty + \@tabarray +}% +\def\array@array{% + \col@sep\arraycolsep + \def\d@llarbegin{$}\let\d@llarend\d@llarbegin\gdef\@halignto{}% + \@tabarray +} +\def\array@array@new{% + \@ifmmode{}{\@badmath$}% + \let\@acoll\@arrayacol + \let\@acolr\@arrayacol + \let\@acol\@arrayacol + \def\d@llarbegin{$}% + \let\d@llarend\d@llarbegin + \gdef\@halignto{}% + \@tabarray +}% +\def\@array@LaTeX[#1]#2{% + \if #1t\vtop \else \if#1b\vbox \else \vcenter \fi\fi + \bgroup + \setbox\@arstrutbox\hbox{% + \vrule \@height\arraystretch\ht\strutbox + \@depth\arraystretch \dp\strutbox + \@width\z@}% + \@mkpream{#2}% + \edef\@preamble{% + \ialign \noexpand\@halignto + \bgroup \@arstrut \@preamble \tabskip\z@skip \cr}% + \let\@startpbox\@@startpbox \let\@endpbox\@@endpbox + \let\tabularnewline\\% + \let\par\@empty + \let\@sharp##% + \set@typeset@protect + \lineskip\z@skip\baselineskip\z@skip + \ifhmode \@preamerr\z@ \@@par\fi + \@preamble +}% +\def\@array@ltx[#1]#2{% + \@nameuse{@array@align@#1}% + \set@arstrutbox + \@mkpream{#2}% + \prepdef\@preamble{% + \tabskip\tabmid@skip + \@arstrut + }% + \appdef\@preamble{% + \tabskip\tabright@skip + \cr + \array@row@pre + }% + \let\tabularnewline\\% + \let\par\@empty + \let\@sharp##% + \set@typeset@protect + \lineskip\z@skip\baselineskip\z@skip + \tabskip\tableft@skip\relax + \ifhmode \@preamerr\z@ \@@par\fi + \everycr{}% + \expandafter\halign\expandafter\@halignto\expandafter\bgroup\@preamble +}% +\def\set@arstrutbox{% + \setbox\@arstrutbox\hbox{% + \vrule \@height\arraystretch\ht\strutbox + \@depth\arraystretch \dp\strutbox + \@width\z@ + }% +}% +\def\@array@array[#1]#2{% + \@tempdima \ht \strutbox + \advance \@tempdima by\extrarowheight + \setbox \@arstrutbox \hbox{\vrule + \@height \arraystretch \@tempdima + \@depth \arraystretch \dp \strutbox + \@width \z@}% + \begingroup + \@mkpream{#2}% + \xdef\@preamble{\noexpand \ialign \@halignto + \bgroup \@arstrut \@preamble + \tabskip \z@ \cr}% + \endgroup + \@arrayleft + \if #1t\vtop \else \if#1b\vbox \else \vcenter \fi \fi + \bgroup + \let \@sharp ##\let \protect \relax + \lineskip \z@ + \baselineskip \z@ + \m@th + \let\\\@arraycr \let\tabularnewline\\\let\par\@empty \@preamble +}% +\def\@array@array@new[#1]#2{% + \@tempdima\ht\strutbox + \advance\@tempdima by\extrarowheight + \setbox\@arstrutbox\hbox{% + \vrule \@height\arraystretch\@tempdima + \@depth \arraystretch\dp\strutbox + \@width \z@ + }% + \begingroup + \@mkpream{#2}% + \xdef\@preamble{\@preamble}% + \endgroup + \prepdef\@preamble{% + \tabskip\tabmid@skip + \@arstrut + }% + \appdef\@preamble{% + \tabskip\tabright@skip + \cr + \array@row@pre + }% + \@arrayleft + \@nameuse{@array@align@#1}% + \m@th + \let\\\@arraycr + \let\tabularnewline\\% + \let\par\@empty + \let\@sharp##% + \set@typeset@protect + \lineskip\z@\baselineskip\z@ + \tabskip\tableft@skip + \everycr{}% + \expandafter\halign\expandafter\@halignto\expandafter\bgroup\@preamble +}% +\def\endarray@LaTeX{% + \crcr\egroup\egroup +}% +\def\endarray@ltx{% + \crcr\array@row@pst\egroup\egroup +}% +\def\endarray@array{% + \crcr \egroup \egroup \@arrayright \gdef\@preamble{}% +}% +\def\endarray@array@new{% + \crcr\array@row@pst\egroup\egroup % Same as \endarray@ltx + \@arrayright + \global\let\@preamble\@empty +}% +\def\endtabular@LaTeX{% + \crcr\egroup\egroup $\egroup +}% +\def\endtabular@ltx{% + \endarray +}% +\def\endtabular@array{% + \endarray $\egroup +}% +\def\endtabular@array@new{% + \endarray +}% +\@namedef{endtabular*}{\endtabular}% +\long\def\multicolumn@LaTeX#1#2#3{% + \multispan{#1}\begingroup + \@mkpream{#2}% + \def\@sharp{#3}\set@typeset@protect + \let\@startpbox\@@startpbox\let\@endpbox\@@endpbox + \@arstrut \@preamble\hbox{}\endgroup\ignorespaces +}% +\long\def\multicolumn@ltx#1#2#3{% + \multispan{#1}% + \begingroup + \@mkpream{#2}% + \def\@sharp{#3}% + \set@typeset@protect + %\let\@startpbox\@@startpbox\let\@endpbox\@@endpbox + \@arstrut + \@preamble + \hbox{}% + \endgroup + \ignorespaces +}% +\def\@array@align@t{\leavevmode\vtop\bgroup}% +\def\@array@align@b{\leavevmode\vbox\bgroup}% +\def\@array@align@c{\leavevmode\@ifmmode{\vcenter\bgroup}{$\vcenter\bgroup\aftergroup$\aftergroup\relax}}% +\def\@array@align@v{% + \@ifmmode{% + \@badmath + \vcenter\bgroup + }{% + \@ifinner{% + $\vcenter\bgroup\aftergroup$ + }{% + \@@par\bgroup + }% + }% +}% +\def\array@default{c}% +\def\array@row@rst{% + \let\@array@align@v\@array@align@c +}% +\def\array@row@pre{}% +\def\array@row@pst{}% +\newcommand\toprule{\tab@rule{\column@font}{\column@fil}{\frstrut}}% +\newcommand\colrule{\unskip\lrstrut\\\tab@rule{\body@font}{}{\frstrut}}% +\newcommand\botrule{\unskip\lrstrut\\\noalign{\hline@rule}{}}% +\def\hline@LaTeX{% + \noalign{\ifnum0=`}\fi\hrule \@height \arrayrulewidth \futurelet + \reserved@a\@xhline +}% +\def\hline@ltx{% + \noalign{% + \ifnum0=`}\fi + \hline@rule + \futurelet\reserved@a\@xhline + % \noalign ended in \@xhline +}% +\def\@xhline@unneeded{% + \say\reserved@a + \ifx\reserved@a\hline + \vskip\doublerulesep + \vskip-\arrayrulewidth + \fi + \ifnum0=`{\fi}% +}% +\def\tab@rule#1#2#3{% + \crcr + \noalign{% + \hline@rule + \gdef\@arstrut@hook{% + \global\let\@arstrut@hook\@empty + #3% + }% + \gdef\cell@font{#1}% + \gdef\cell@fil{#2}% + }% +}% +\def\column@font{}% +\def\column@fil{}% +\def\body@font{}% +\def\cell@font{}% +\def\frstrut{}% +\def\lrstrut{}% +\def\@arstrut@hline{% + \relax + \@ifmmode{\copy}{\unhcopy}\@arstrutbox@hline + \@arstrut@hook +}% +\let\@arstrut@org\@arstrut +\def\@arstrut@hook{% + \global\let\@arstrut\@arstrut@org +}% +\newbox\@arstrutbox@hline +\appdef\set@arstrutbox{% + \setbox\@arstrutbox@hline\hbox{% + \setbox\z@\hbox{$0^{0}_{}$}% + \dimen@\ht\z@\advance\dimen@\@arstrut@hline@clnc + \@ifdim{\dimen@<\arraystretch\ht\strutbox}{\dimen@=\arraystretch\ht\strutbox}{}% + \vrule \@height\dimen@ + \@depth\arraystretch \dp\strutbox + \@width\z@ + }% +}% +\def\hline@rule{% + \hrule \@height \arrayrulewidth + \global\let\@arstrut\@arstrut@hline +}% +\def\@arstrut@hline@clnc{2\p@}% % Klootch: magic number +\def\tableft@skip{\z@skip}% +\def\tabmid@skip{\z@skip}%\@flushglue +\def\tabright@skip{\z@skip}% +\def\tableftsep{\tabcolsep}% +\def\tabmidsep{\tabcolsep}% +\def\tabrightsep{\tabcolsep}% +\def\cell@fil{}% +\def\pbox@hook{}% +\appdef\@arstrut{\@arstrut@hook}% +\let\@arstrut@hook\@empty +\def\@addtopreamble{\appdef\@preamble}% +\def\@mkpream@LaTeX#1{% + \@firstamptrue\@lastchclass6 + \let\@preamble\@empty + \let\protect\@unexpandable@protect + \let\@sharp\relax + \let\@startpbox\relax\let\@endpbox\relax + \@expast{#1}% + \expandafter\@tfor \expandafter + \@nextchar \expandafter:\expandafter=\reserved@a\do + {\@testpach\@nextchar + \ifcase \@chclass \@classz \or \@classi \or \@classii \or \@classiii + \or \@classiv \or\@classv \fi\@lastchclass\@chclass}% + \ifcase \@lastchclass \@acol + \or \or \@preamerr \@ne\or \@preamerr \tw@\or \or \@acol \fi +}% +\def\@mkpream@ltx#1{% + \@firstamptrue + \@lastchclass6 + \let\@preamble\@empty + \let\protect\@unexpandable@protect + \let\@sharp\relax + \@expast{#1}% + \expandafter\@tfor\expandafter\@nextchar\expandafter:\expandafter=\reserved@a + \do{% + \expandafter\@testpach\expandafter{\@nextchar}% + \ifcase\@chclass + \@classz + \or + \@classi + \or + \@classii + \or + \@classiii + \or + \@classiv + \or + \@classv + \fi + \@lastchclass\@chclass + }% + \ifcase\@lastchclass + \@acolr % right-hand column + \or + \or + \@preamerr\@ne + \or + \@preamerr\tw@ + \or + \or + \@acolr % right-hand column + \fi +}% +\def\insert@column@array{% + \the@toks \the \@tempcnta + \ignorespaces \@sharp \unskip + \the@toks \the \count@ \relax +}% +\def\insert@column@array@new{% + \the@toks\the\@tempcnta + \array@row@rst\cell@font + \ignorespaces\@sharp\unskip + \the@toks\the\count@ + \relax +}% +\def\@mkpream@relax{% + \let\tableftsep \relax + \let\tabmidsep \relax + \let\tabrightsep \relax + \let\array@row@rst\relax + \let\cell@font \relax + \let\@startpbox \relax +}% +\def\@mkpream@array#1{% + \gdef\@preamble{}\@lastchclass 4 \@firstamptrue + \let\@sharp\relax \let\@startpbox\relax \let\@endpbox\relax + \@temptokena{#1}\@tempswatrue + \@whilesw\if@tempswa\fi{\@tempswafalse\the\NC@list}% + \count@\m@ne + \let\the@toks\relax + \prepnext@tok + \expandafter \@tfor \expandafter \@nextchar + \expandafter :\expandafter =\the\@temptokena \do + {\@testpach + \ifcase \@chclass \@classz \or \@classi \or \@classii + \or \save@decl \or \or \@classv \or \@classvi + \or \@classvii \or \@classviii + \or \@classx + \or \@classx \fi + \@lastchclass\@chclass}% + \ifcase\@lastchclass + \@acol \or + \or + \@acol \or + \@preamerr \thr@@ \or + \@preamerr \tw@ \@addtopreamble\@sharp \or + \or + \else \@preamerr \@ne \fi + \def\the@toks{\the\toks}% +}% +\def\@mkpream@array@new#1{% + \gdef\@preamble{}% + \@lastchclass\f@ur + \@firstamptrue + \let\@sharp\relax + \@mkpream@relax + \@temptokena{#1}\@tempswatrue + \@whilesw\if@tempswa\fi{\@tempswafalse\the\NC@list}% + \count@\m@ne + \let\the@toks\relax + \prepnext@tok + \expandafter\@tfor\expandafter\@nextchar\expandafter:\expandafter=\the\@temptokena + \do{% + \@testpach + \ifcase\@chclass + \@classz + \or + \@classi + \or + \@classii + \or + \save@decl + \or + \or + \@classv + \or + \@classvi + \or + \@classvii + \or + \@classviii + \or + \@classx + \or + \@classx + \fi + \@lastchclass\@chclass + }% + \ifcase\@lastchclass + \@acolr % right-hand column + \or + \or + \@acolr % right-hand column + \or + \@preamerr\thr@@ + \or + \@preamerr\tw@\@addtopreamble\@sharp + \or + \or + \else + \@preamerr\@ne + \fi + \def\the@toks{\the\toks}% +}% +\appdef\@mkpream@relax{% + \let\CT@setup \relax + \let\CT@color \relax + \let\CT@do@color \relax + \let\color \relax + \let\CT@column@color\relax + \let\CT@row@color \relax + \let\CT@cell@color \relax +}% +\def\@addamp@LaTeX{% + \if@firstamp\@firstampfalse\else\edef\@preamble{\@preamble &}\fi +}% +\def\@addamp@ltx{% + \if@firstamp\@firstampfalse\else\@addtopreamble{&}\fi +}% +\def\@arrayacol@LaTeX{% + \edef\@preamble{\@preamble \hskip \arraycolsep}% +}% +\def\@arrayacol@ltx{% + \@addtopreamble{\hskip\arraycolsep}% +}% +\def\@tabacoll{% + \@addtopreamble{\hskip\tableftsep\relax}% +}% +\def\@tabacol@LaTeX{% + \edef\@preamble{\@preamble \hskip \tabcolsep}% +}% +\def\@tabacol@ltx{% + \@addtopreamble{\hskip\tabmidsep\relax}% +}% +\def\@tabacolr{% + \@addtopreamble{\hskip\tabrightsep\relax}% +}% +\def\@arrayclassz@LaTeX{% + \ifcase \@lastchclass \@acolampacol \or \@ampacol \or + \or \or \@addamp \or + \@acolampacol \or \@firstampfalse \@acol \fi + \edef\@preamble{\@preamble + \ifcase \@chnum + \hfil$\relax\@sharp$\hfil \or $\relax\@sharp$\hfil + \or \hfil$\relax\@sharp$\fi}% +}% +\def\@arrayclassz@ltx{% + \ifcase\@lastchclass + \@acolampacol + \or + \@ampacol + \or + \or + \or + \@addamp + \or + \@acolampacol + \or + \@firstampfalse\@acoll + \fi + \ifcase\@chnum + \@addtopreamble{% + \hfil\array@row@rst$\relax\@sharp$\hfil + }% + \or + \@addtopreamble{% + \array@row@rst$\relax\@sharp$\hfil + }% + \or + \@addtopreamble{% + \hfil\array@row@rst$\relax\@sharp$% + }% + \fi +}% +\def\@tabclassz@LaTeX{% + \ifcase\@lastchclass + \@acolampacol + \or + \@ampacol + \or + \or + \or + \@addamp + \or + \@acolampacol + \or + \@firstampfalse\@acol + \fi + \edef\@preamble{% + \@preamble{% + \ifcase\@chnum + \hfil\ignorespaces\@sharp\unskip\hfil + \or + \hskip1sp\ignorespaces\@sharp\unskip\hfil + \or + \hfil\hskip1sp\ignorespaces\@sharp\unskip + \fi}}% +}% +\def\@tabclassz@ltx{% + \ifcase\@lastchclass + \@acolampacol + \or + \@ampacol + \or + \or + \or + \@addamp + \or + \@acolampacol + \or + \@firstampfalse\@acoll + \fi + \ifcase\@chnum + \@addtopreamble{% + {\hfil\array@row@rst\cell@font\ignorespaces\@sharp\unskip\hfil}% + }% + \or + \@addtopreamble{% + {\cell@fil\hskip1sp\array@row@rst\cell@font\ignorespaces\@sharp\unskip\hfil}% + }% + \or + \@addtopreamble{% + {\hfil\hskip1sp\array@row@rst\cell@font\ignorespaces\@sharp\unskip\cell@fil}% + }% + \fi +}% +\def\@tabclassiv@LaTeX{% + \@addtopreamble\@nextchar +}% +\def\@tabclassiv@ltx{% + \expandafter\@addtopreamble\expandafter{\@nextchar}% +}% +\def\@arrayclassiv@LaTeX{% + \@addtopreamble{$\@nextchar$}% +}% +\def\@arrayclassiv@ltx{% + \expandafter\@addtopreamble\expandafter{\expandafter$\@nextchar$}% +}% +\def\@classv@LaTeX{% + \@addtopreamble{\@startpbox{\@nextchar}\ignorespaces + \@sharp\@endpbox}% +}% +\def\@classv@ltx{% + \expandafter\@addtopreamble + \expandafter{% + \expandafter \@startpbox + \expandafter {\@nextchar}% + \pbox@hook\array@row@rst\cell@font\ignorespaces\@sharp\@endpbox + }% +}% +\def\@classx@array{% + \ifcase \@lastchclass + \@acolampacol \or + \@addamp \@acol \or + \@acolampacol \or + \or + \@acol \@firstampfalse \or + \@addamp + \fi +}% +\def\@classx@array@new{% + \ifcase \@lastchclass + \@acolampacol + \or + \@addamp \@acol + \or + \@acolampacol + \or + \or + \@firstampfalse\@acoll + \or + \@addamp + \fi +}% +\def\@xbitor@LaTeX #1{\@tempcntb \count#1 + \ifnum \@tempcnta =\z@ + \else + \divide\@tempcntb\@tempcnta + \ifodd\@tempcntb \@testtrue\fi + \fi}% +\def\@xbitor@ltx#1{% + \@tempcntb\count#1\relax + \@ifnum{\@tempcnta=\z@}{}{% + \divide\@tempcntb\@tempcnta + \@ifodd\@tempcntb{\@testtrue}{}% + }% +}% +\@ifx{\@xbitor\@xbitor@LaTeX}{% + \class@info{Repairing broken LaTeX \string\@xbitor}% +}{% + \class@info{Unrecognized LaTeX \string\@xbitor. Please update this document class! (Proceeding with fingers crossed.)}% +}% +\let\@xbitor\@xbitor@ltx +\newcommand*\@gobble@opt@one[2][]{}% +\def\@starttoc#1{% + \begingroup + \toc@pre + \makeatletter + \@input{\jobname.#1}% + \if@filesw + \expandafter\newwrite\csname tf@#1\endcsname + \immediate\openout \csname tf@#1\endcsname \jobname.#1\relax + \fi + \@nobreakfalse + \toc@post + \endgroup +}% +\def\toc@pre{}% +\def\toc@post{}% +\def\toc@@font{}% +\def\ltxu@dotsep{\z@}% +\let\tocdim@section \leftmargini +\let\tocdim@subsection \leftmarginii +\let\tocdim@subsubsection \leftmarginiii +\let\tocdim@paragraph \leftmarginiv +\let\tocdim@appendix \leftmarginv +\let\tocdim@pagenum \leftmarginvi +\def\toc@pre@auto{% + \toc@@font + \@tempdima\z@ + \toc@setindent\@tempdima{section}% + \toc@setindent\@tempdima{subsection}% + \toc@setindent\@tempdima{subsubsection}% + \toc@setindent\@tempdima{paragraph}% + \toc@letdimen{appendix}% + \toc@letdimen{pagenum}% +}% +\def\toc@post@auto{% + \if@filesw + \begingroup + \toc@writedimen{section}% + \toc@writedimen{subsection}% + \toc@writedimen{subsubsection}% + \toc@writedimen{paragraph}% + \toc@writedimen{appendix}% + \toc@writedimen{pagenum}% + \endgroup + \fi +}% +\def\toc@setindent#1#2{% + \csname tocdim@#2\endcsname\tocdim@min\relax + \@ifundefined{tocmax@#2}{\@namedef{tocmax@#2}{\z@}}{}% + \advance#1\@nameuse{tocmax@#2}\relax + \expandafter\edef\csname tocleft@#2\endcsname{\the#1}% +}% +\def\toc@letdimen#1{% + \csname tocdim@#1\endcsname\tocdim@min\relax + \@ifundefined{tocmax@#1}{\@namedef{tocmax@#1}{\z@}}{}% + \expandafter\let\csname tocleft@#1\expandafter\endcsname\csname tocmax@#1\endcsname +}% +\def\toc@writedimen#1{% + \immediate\write\@auxout{% + \gdef\expandafter\string\csname tocmax@#1\endcsname{% + \expandafter\the\csname tocdim@#1\endcsname + }% + }% +}% +\def\l@@sections#1#2#3#4{% + \begingroup + \everypar{}% + \set@tocdim@pagenum\@tempboxa{#4}% + \global\@tempdima\csname tocdim@#2\endcsname + \leftskip\csname tocleft@#2\endcsname\relax + \dimen@\csname tocleft@#1\endcsname\relax + \parindent-\leftskip\advance\parindent\dimen@ + \rightskip\tocleft@pagenum plus 1fil\relax + \skip@\parfillskip\parfillskip\z@ + \let\numberline\numberline@@sections + \@nameuse{l@f@#2}% + \ignorespaces#3\unskip\nobreak\hskip\skip@ + \hb@xt@\rightskip{\hfil\unhbox\@tempboxa}\hskip-\rightskip\hskip\z@skip + \expandafter\par + \expandafter\aftergroup\csname tocdim@#2% + \expandafter\endcsname + \expandafter\endgroup + \the\@tempdima\relax +}% +\def\set@tocdim@pagenum#1#2{% + \setbox#1\hbox{\ignorespaces#2}% + \@ifdim{\tocdim@pagenum<\wd#1}{\global\tocdim@pagenum\wd#1}{}% +}% +\def\numberline@@sections#1{% + \leavevmode\hb@xt@-\parindent{% + \hfil + \@if@empty{#1}{}{% + \setbox\z@\hbox{#1.\kern\ltxu@dotsep}% + \@ifdim{\@tempdima<\wd\z@}{\global\@tempdima\wd\z@}{}% + \unhbox\z@ + }% + }% + \ignorespaces +}% +\def\tocdim@min{\z@}% +\def\list#1#2{% + \ifnum \@listdepth >5\relax + \@toodeep + \else + \global\advance\@listdepth\@ne + \fi + \rightmargin\z@ + \listparindent\z@ + \itemindent\z@ + \csname @list\romannumeral\the\@listdepth\endcsname + \def\@itemlabel{#1}% + \let\makelabel\@mklab + \@nmbrlistfalse + #2\relax + \@trivlist + \parskip\parsep + \set@listindent + \ignorespaces +}% +\def\set@listindent@parshape{% + \parindent\listparindent + \advance\@totalleftmargin\leftmargin + \advance\linewidth-\rightmargin + \advance\linewidth-\leftmargin + \parshape\@ne\@totalleftmargin\linewidth +}% +\def\set@listindent@{% + \parindent\listparindent + \advance\@totalleftmargin\leftmargin + \advance\rightskip\rightmargin + \advance\leftskip\@totalleftmargin +}% +\let\set@listindent\set@listindent@parshape +\providecommand\href[0]{\begingroup\@sanitize@url\@href}% +\def\@href#1{\@@startlink{#1}\endgroup\@@href}% +\def\@@href#1{#1\@@endlink}% +\providecommand \url [0]{\begingroup\@sanitize@url \@url }% +\def \@url #1{\endgroup\@href {#1}{\URL@prefix#1}}% +\providecommand \URL@prefix [0]{URL }% +\providecommand\doi[0]{\begingroup\@sanitize@url\@doi}% +\def\@doi#1{\endgroup\@@startlink{\doibase#1}doi:\discretionary {}{}{}#1\@@endlink }% +\providecommand \doibase [0]{https://doi.org/}% +\providecommand \@sanitize@url[0]{\chardef\cat@space\the\catcode`\ \@sanitize\catcode`\ \cat@space}% +\def\@@startlink#1{}% +\def\@@endlink{}% +\@ifxundefined \pdfoutput {\true@sw}{\@ifnum{\z@=\pdfoutput}{\true@sw}{\false@sw}}% +{% + \def\@@startlink@hypertext#1{\leavevmode\special{html:}}% + \def\@@endlink@hypertext{\special{html:}}% +}{% + \def\@@startlink@hypertext#1{% + \leavevmode + \pdfstartlink\pdfstartlink@attr + user{/Subtype/Link/A<>}% + \relax + }% + \def\@@endlink@hypertext{\pdfendlink}% + \def\pdfstartlink@attr{attr{/Border[0 0 1 ]/H/I/C[0 1 1]}}% +}% +\def\hypertext@enable@ltx{% + \let\@@startlink\@@startlink@hypertext + \let\@@endlink\@@endlink@hypertext +}% +\def\href@Hy{\hyper@normalise \href@ }% +\def\href@Hy@ltx{\@ifnextchar\bgroup\Hy@href{\hyper@normalise\href@}}% +\def\Hy@href#{\hyper@normalise\href@}% +\begingroup + \endlinechar=-1 % + \catcode`\^^A=14 % + \catcode`\^^M\active + \catcode`\%\active + \catcode`\#\active + \catcode`\_\active + \catcode`\$\active + \catcode`\&\active + \gdef\hyper@normalise@ltx{^^A + \begingroup + \catcode`\^^M\active + \def^^M{ }^^A + \catcode`\%\active + \let%\@percentchar + \let\%\@percentchar + \catcode`\#\active + \def#{\hyper@hash}^^A + \def\#{\hyper@hash}^^A + \@makeother\&^^A + \edef&{\string&}^^A + \edef\&{\string&}^^A + \edef\textunderscore{\string_}^^A + \let\_\textunderscore + \catcode`\_\active + \let_\textunderscore + \let~\hyper@tilde + \let\~\hyper@tilde + \let\textasciitilde\hyper@tilde + \let\\\@backslashchar + \edef${\string$}^^A + \Hy@safe@activestrue + \hyper@n@rmalise + }^^A + \catcode`\#=6 ^^A + \gdef\Hy@ActiveCarriageReturn@ltx{^^M}^^A + \gdef\hyper@n@rmalise@ltx#1#2{^^A + \def\Hy@tempa{#2}^^A + \ifx\Hy@tempa\Hy@ActiveCarriageReturn + \Hy@ReturnAfterElseFi{^^A + \hyper@@normalise{#1}^^A + }^^A + \else + \Hy@ReturnAfterFi{^^A + \hyper@@normalise{#1}{#2}^^A + }^^A + \fi + }^^A + \gdef\hyper@@normalise@ltx#1#2{^^A + \edef\Hy@tempa{^^A + \endgroup + \noexpand#1{\Hy@RemovePercentCr#2%^^M\@nil}^^A + }^^A + \Hy@tempa + }^^A + \gdef\Hy@RemovePercentCr@ltx#1%^^M#2\@nil{^^A + #1^^A + \ifx\limits#2\limits + \else + \Hy@ReturnAfterFi{^^A + \Hy@RemovePercentCr #2\@nil + }^^A + \fi + }^^A +\endgroup +\def\switch@hyperref@href{% + \expandafter\@ifx\expandafter{\csname href \endcsname\href@Hy}{ + \class@info{Repairing hyperref 6.75r \string\href}% + \let\hyper@normalise\hyper@normalise@ltx + \let\hyper@@normalise\hyper@@normalise@ltx + \let\hyper@n@rmalise\hyper@n@rmalise@ltx + \let\Hy@ActiveCarriageReturn\Hy@ActiveCarriageReturn@ltx + \let\Hy@RemovePercentCr\Hy@RemovePercentCr@ltx + \let\href\href@Hy@ltx + }{}% +}% +\appdef\document@inithook{\switch@hyperref@href}% +\def\typeout@org#1{% + \begingroup + \set@display@protect + \immediate\write\@unused{#1}% + \endgroup +}% +\long\def\typeout@ltx#1{% + \begingroup + \set@display@protect + \immediate\write\@unused{#1}% + \endgroup +}% +\@ifx{\typeout\typeout@org}{% + \let\typeout\typeout@ltx + \true@sw +}{% + \rvtx@ifformat@geq{2020/10/01}% + {\true@sw}{\false@sw}% +}% + {\class@info{Making \string\typeout\space \string\long}}% + {}% +\endinput +%% +%% End of file `ltxutil.sty'. diff --git a/src/easydiffraction/report/templates/tex/styles/revsymb4-2.sty b/src/easydiffraction/report/templates/tex/styles/revsymb4-2.sty new file mode 100644 index 000000000..2abbfefa8 --- /dev/null +++ b/src/easydiffraction/report/templates/tex/styles/revsymb4-2.sty @@ -0,0 +1,167 @@ +%% +%% This is file `revsymb4-2.sty', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% revtex4-2.dtx (with options: `revsymb') +%% +%% This file is part of the APS files in the REVTeX 4 distribution. +%% For the version number, search on the string +%% Original version by David Carlisle +%% Modified by Arthur Ogawa (mailto:arthur_ogawa at sbcglobal dot net) +%% +%% Version (4.2a, unreleased) +%% Modified by Aptara on behalf of American Physical Society and American Institute of Physics +%% +%% Version (4.2b,4.2c) +%% Modified by Mark Doyle, American Physical Society (mailto:revtex at aps.org) +%% +%% Version (4.2d--4.2f) +%% Modified by Phelype Oleinik for the American Physical Society (mailto:phelype.oleinik at latex-project.org) +%% +%% Copyright (c) 2019--2022 American Physical Society. +%% https://journals.aps.org/revtex/ +%% mailto:revtex@aps.org +%% +%% See the REVTeX 4.2 README-REVTEX file for restrictions and more information. +%% +\NeedsTeXFormat{LaTeX2e}[1996/12/01]% +\ProvidesPackage{revsymb4-2} + [2022/06/05 4.2f (https://journals.aps.org/revtex/ for documentation)]% \fileversion +\def\REVSYMB@warn#1{\PackageWarningNoLine{revsymb}{#1}}% +\DeclareRobustCommand\lambdabar{% + \bgroup + \def\@tempa{% + \hbox{% + \raise.73\ht\z@ + \hb@xt@\z@{% + \kern.25\wd\z@ + \vrule \@width.5\wd\z@\@height.1\p@\@depth.1\p@ + \hss + }% + \box\z@ + }% + }% + \mathchoice + {\setbox\z@\hbox{$\displaystyle \lambda$}\@tempa}% + {\setbox\z@\hbox{$\textstyle \lambda$}\@tempa}% + {\setbox\z@\hbox{$\scriptstyle \lambda$}\@tempa}% + {\setbox\z@\hbox{$\scriptscriptstyle\lambda$}\@tempa}% + \egroup +}% +\DeclareRobustCommand\openone{\leavevmode\hbox{\small1\normalsize\kern-.33em1}}% +\DeclareRobustCommand\corresponds{\replace@command\corresponds\triangleq}% +\DeclareRobustCommand\overcirc{\replace@command\overcirc\mathring}% +\DeclareRobustCommand\overdots{\replace@command\overdots\dddot}% +\DeclareRobustCommand\REV@triangleq{% + {\lower.2ex\hbox{=}}{\kern-.75em^\triangle}% +}% +\DeclareRobustCommand\REV@dddot[1]{% + \@ontopof{#1}{\cdots}{1.0}\mathord{\box2}% +}% +\DeclareRobustCommand\altsuccsim{\succ\kern-.9em_\sim\kern.3em}% +\DeclareRobustCommand\altprecsim{\prec\kern-1em_\sim\kern.3em}% +\let\REV@succsim\altsuccsim +\let\REV@precsim\altprecsim +\DeclareRobustCommand\REV@lesssim{\mathrel{\mathpalette\vereq{<}}}% +\DeclareRobustCommand\REV@gtrsim{\mathrel{\mathpalette\vereq{>}}}% +\DeclareRobustCommand\alt{\lesssim} +\DeclareRobustCommand\agt{\gtrsim} +\def\vereq#1#2{% + \lower3\p@\vbox{% + \baselineskip1.5\p@ + \lineskip1.5\p@ + \ialign{$\m@th#1\hfill##\hfil$\crcr#2\crcr\sim\crcr}% + }% +}% +\DeclareRobustCommand\tensor[1]{\@ontopof{#1}{\leftrightarrow}{1.15}\mathord{\box2}} +\DeclareRobustCommand\overstar[1]{\@ontopof{#1}{\ast}{1.15}\mathord{\box2}} +\DeclareRobustCommand\loarrow[1]{\@ontopof{#1}{\leftarrow}{1.15}\mathord{\box2}} +\DeclareRobustCommand\roarrow[1]{\@ontopof{#1}{\rightarrow}{1.15}\mathord{\box2}} +\def\@ontopof#1#2#3{% + {% + \mathchoice + {\@@ontopof{#1}{#2}{#3}\displaystyle \scriptstyle }% + {\@@ontopof{#1}{#2}{#3}\textstyle \scriptstyle }% + {\@@ontopof{#1}{#2}{#3}\scriptstyle \scriptscriptstyle}% + {\@@ontopof{#1}{#2}{#3}\scriptscriptstyle\scriptscriptstyle}% + }% +}% +\def\@@ontopof#1#2#3#4#5{% + \setbox\z@\hbox{$#4#1$}% + \setbox\f@ur\hbox{$#5#2$}% + \setbox\tw@\null\ht\tw@\ht\z@ \dp\tw@\dp\z@ + \@ifdim{\wd\z@>\wd\f@ur}{% + \setbox\f@ur\hb@xt@\wd\z@{\hss\box\f@ur\hss}% + \mathord{\rlap{\raise#3\ht\z@\box\f@ur}\box\z@}% + }{% + \setbox\f@ur\hb@xt@.9\wd\f@ur{\hss\box\f@ur\hss}% + \setbox\z@\hb@xt@\wd\f@ur{\hss$#4\relax#1$\hss}% + \mathord{\rlap{\copy\z@}\raise#3\ht\z@\box\f@ur}% + }% +}% +\DeclareRobustCommand\frak{% + \REVSYMB@warn{% + Command \string\frak\space unsupported:^^J% + please use \string\mathfrak\space instead.% + }% + \global\let\frak\mathfrak + \frak +}% +\DeclareRobustCommand\REV@mathfrak{% + \REVSYMB@warn{% + Command \string\mathfrak\space undefined:^^J% + please specify the amsfonts or amssymb option!% + }% + \global\let\mathfrak\@firstofone + \mathfrak +}% +\DeclareRobustCommand\Bbb{% + \REVSYMB@warn{% + Command \string\Bbb\space unsupported:^^J% + please use \string\mathbb\space instead.% + }% + \global\let\Bbb\mathbb + \Bbb +}% +\DeclareRobustCommand\REV@mathfrak{% + \REVSYMB@warn{% + Command \string\mathbb\space undefined:^^J% + please specify the amsfonts or amssymb option!% + }% + \global\let\mathbb\@firstofone + \mathbb +}% +\def\Bigglb{\REV@boldopen \Bigg}% +\def\Biglb {\REV@boldopen \Big }% +\def\bigglb{\REV@boldopen \bigg}% +\def\biglb {\REV@boldopen \big }% +\def\Biggrb{\REV@boldclose\Bigg}% +\def\Bigrb {\REV@boldclose\Big }% +\def\biggrb{\REV@boldclose\bigg}% +\def\bigrb {\REV@boldclose\big }% +\def\REV@pmb#1{% + \hbox{% + \setbox\z@=\hbox{#1}% + \kern-.02em\copy\z@\kern-\wd\z@ + \kern .04em\copy\z@\kern-\wd\z@ + \kern-.02em + \raise.04em\copy\z@ + }% +}% +\def\REV@boldopen #1#2{\mathopen {\REV@pmb{$#1#2$}}}% +\def\REV@boldclose#1#2{\mathclose{\REV@pmb{$#1#2$}}}% +\def\revsymb@inithook{% + \@ifxundefined\dddot{\let\dddot\REV@dddot}{}% + \@ifxundefined\triangleq{\let\triangleq\REV@triangleq}{}% + \@ifxundefined\succsim{\let\succsim\altsuccsim}{}% + \@ifxundefined\precsim{\let\precsim\altprecsim}{}% + \@ifxundefined\lesssim{\let\lesssim\REV@lesssim}{}% + \@ifxundefined\gtrsim {\let\gtrsim \REV@gtrsim }{}% + \@ifxundefined\mathfrak{\let\mathfrak\REV@mathfrak}{}% + \@ifxundefined\mathbb{\let\mathbb\REV@mathbb}{}% +}% +\endinput +%% +%% End of file `revsymb4-2.sty'. diff --git a/src/easydiffraction/report/templates/tex/styles/revtex4-2.cls b/src/easydiffraction/report/templates/tex/styles/revtex4-2.cls new file mode 100644 index 000000000..2264273bb --- /dev/null +++ b/src/easydiffraction/report/templates/tex/styles/revtex4-2.cls @@ -0,0 +1,7681 @@ +%% +%% This is file `revtex4-2.cls', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% revtex4-2.dtx (with options: `kernel') +%% ltxutil.dtx (with options: `kernel') +%% ltxfront.dtx (with options: `kernel') +%% ltxgrid.dtx (with options: `kernel') +%% revtex4-2.dtx (with options: `options') +%% ltxutil.dtx (with options: `options') +%% ltxfront.dtx (with options: `options') +%% ltxgrid.dtx (with options: `options') +%% revtex4-2.dtx (with options: `package') +%% +%% This file is part of the APS files in the REVTeX 4 distribution. +%% For the version number, search on the string +%% Original version by David Carlisle +%% Modified by Arthur Ogawa (mailto:arthur_ogawa at sbcglobal dot net) +%% +%% Version (4.2a, unreleased) +%% Modified by Aptara on behalf of American Physical Society and American Institute of Physics +%% +%% Version (4.2b,4.2c) +%% Modified by Mark Doyle, American Physical Society (mailto:revtex at aps.org) +%% +%% Version (4.2d--4.2f) +%% Modified by Phelype Oleinik for the American Physical Society (mailto:phelype.oleinik at latex-project.org) +%% +%% Copyright (c) 2019--2022 American Physical Society. +%% https://journals.aps.org/revtex/ +%% mailto:revtex@aps.org +%% +%% See the REVTeX 4.2 README-REVTEX file for restrictions and more information. +%% +\NeedsTeXFormat{LaTeX2e}[1996/12/01]% +\ProvidesClass{revtex4-2} + [2022/06/05 4.2f (https://journals.aps.org/revtex/ for documentation)]% \fileversion +\let\class@name\@gtempa +\GenericInfo{}{\space + Copyright (c) 2019 American Physical Society.^^J + mailto:revtex@aps.org^^J + Licensed under the LPPL:^^Jhttp://www.ctan.org/tex-archive/macros/latex/base/lppl.txt^^J + Arthur Ogawa ^^J + Based on work by David Carlisle ^^J + Version (4.2d--4.2f): Modified by Mark Doyle and Phelype Oleinik^^J + \@gobble +}% +\if@compatibility + \edef\reserved@a{\errhelp{% + Change your \string\documentstyle\space statement to + \string\documentclass\space and rerun. + }}\reserved@a + \errmessage{You cannot run \class@name\space in compatability mode}% + \expandafter\@@end +\fi +\typeout{% +ltxutil% + [2022/06/05 4.2f utilities package (portions licensed from W. E. Baxter web at superscript.com)]% \fileversion +}% +\def\class@err#1{\ClassError{\class@name}{#1}\@eha}% +\def\class@warn#1{\ClassWarningNoLine{\class@name}{#1}}% +\def\class@info#1{\ClassInfo{\class@name}{#1}}% +\def\obsolete@command#1{% + \class@warn@end{Command \string#1\space is obsolete.^^JPlease remove from your document}% + \global\let#1\@empty + #1% +}% +\def\replace@command#1#2{% + \class@warn@end{Command \string#1\space is obsolete;^^JUse \string#2\space instead}% + \global\let#1#2% + #1% +}% +\def\replace@environment#1#2{% + \class@warn@end{Environment #1 is obsolete;^^JUse #2 instead}% + \glet@environment{#1}{#2}% + \@nameuse{#1}% +}% +\def\incompatible@package#1{% + \@ifpackageloaded{#1}{% + \def\@tempa{I cannot continue. You must remove the \string\usepackage\ statement that caused that package to be loaded.}% + \ClassError{\class@name}{The #1 package cannot be used with \class@name}% + \@tempa\stop + }{% + \class@info{#1 was not loaded (OK!)}% + }% +}% +\def\class@warn@end#1{% + \gappdef\class@enddocumenthook{\class@warn{#1}}% +}% +\ifx\undefined\class@name + \def\class@name{ltxutil}% + \class@warn{You should define the class name before reading in this package. Using default}% +\fi +\def\t@{to}% +\dimendef\dimen@iii\thr@@ +\def\halignt@{\halign\t@}% +\chardef\f@ur=4\relax +\chardef\cat@letter=11\relax +\chardef\other=12\relax +\def\let@environment#1#2{% + \expandafter\let + \csname#1\expandafter\endcsname\csname#2\endcsname + \expandafter\let + \csname end#1\expandafter\endcsname\csname end#2\endcsname +}% +\def\glet@environment#1#2{% + \global\expandafter\let + \csname#1\expandafter\endcsname\csname#2\endcsname + \global\expandafter\let + \csname end#1\expandafter\endcsname\csname end#2\endcsname +}% +\newcommand\tracingplain{% + \tracingonline\z@\tracingcommands\z@\tracingstats\z@ + \tracingpages\z@\tracingoutput\z@\tracinglostchars\@ne + \tracingmacros\z@\tracingparagraphs\z@\tracingrestores\z@ + \showboxbreadth5\showboxdepth3\relax %\errorstopmode + }% +\newcommand\traceoutput{% + \appdef\@resetactivechars{\showoutput}% +}% +\newcommand\say[1]{\typeout{<\noexpand#1=\meaning#1>}}% +\newcommand\saythe[1]{\typeout{<\noexpand#1=\the#1>}}% +\def\fullinterlineskip{\prevdepth\z@}% +\countdef\count@i\@ne +\countdef\count@ii\tw@ +\long\def\prepdef#1#2{% + \@ifxundefined#1{\toks@{}}{\toks@\expandafter{#1}}% + \toks@ii{#2}% + \edef#1{\the\toks@ii\the\toks@}% +}% +\long\def\appdef#1#2{% + \@ifxundefined#1{\toks@{}}{\toks@\expandafter{#1}}% + \toks@ii{#2}% + \edef#1{\the\toks@\the\toks@ii}% +}% +\long\def\gappdef#1#2{% + \@ifxundefined#1{\toks@{}}{\toks@\expandafter{#1}}% + \toks@ii{#2}% + \global\edef#1{\the\toks@\the\toks@ii}% +}% +\long\def\appdef@val#1#2{% + \appdef#1{{#2}}% +}% +\long\def\appdef@e#1#2{% + \expandafter\appdef + \expandafter#1% + \expandafter{#2}% +}% +\long\def\appdef@eval#1#2{% + \expandafter\appdef@val + \expandafter#1% + \expandafter{#2}% +}% +\toksdef\toks@ii=\tw@ +\long\def\@ifxundefined#1{\@ifx{\undefined#1}}% +\long\def\@ifnotrelax#1#2#3{\@ifx{\relax#1}{#3}{#2}}% +\long\def\@argswap#1#2{#2#1}% +\long\def\@argswap@val#1#2{#2{#1}}% +\def\@ifxundefined@cs#1{\expandafter\@ifx\expandafter{\csname#1\endcsname\relax}}% +\ifx\IfFormatAtLeastTF\undefined + \def\rvtx@ifformat@geq{\@ifl@t@r\fmtversion}% +\else + \let\rvtx@ifformat@geq\IfFormatAtLeastTF +\fi +\def\@boolean#1#2{% + \long\def#1{% + #2% \if + \expandafter\true@sw + \else + \expandafter\false@sw + \fi + }% +}% +\def\@boole@def#1#{\@boolean{#1}}% Implicit #2 +\def\@booleantrue#1{\let#1\true@sw}% +\def\@booleanfalse#1{\let#1\false@sw}% +\@boole@def\@ifx#1{\ifx#1}% +\@boole@def\@ifx@empty#1{\ifx\@empty#1}% +\@boole@def\@if@empty#1{\if!#1!}% +\def\@if@sw#1#2{#1\expandafter\true@sw\else\expandafter\false@sw#2}% +\@boole@def\@ifdim#1{\ifdim#1}% +\@boole@def\@ifeof#1{\ifeof#1}% +\@boole@def\@ifhbox#1{\ifhbox#1}% +\@boole@def\@ifhmode{\ifhmode}% +\@boole@def\@ifinner{\ifinner}% +\@boole@def\@ifmmode{\ifmmode}% +\@boole@def\@ifnum#1{\ifnum#1}% +\@boole@def\@ifodd#1{\ifodd#1}% +\@boole@def\@ifvbox#1{\ifvbox#1}% +\@boole@def\@ifvmode{\ifvmode}% +\@boole@def\@ifvoid#1{\ifvoid#1}% +\long\def\true@sw#1#2{#1}% +\long\def\false@sw#1#2{#2}% +\long\def\loopuntil#1{#1{}{\loopuntil{#1}}}% +\long\def\loopwhile#1{#1{\loopwhile{#1}}{}}% +\def\@provide#1{% + \@ifx{\undefined#1}{\true@sw}{\@ifx{\relax#1}{\true@sw}{\false@sw}}% + {\def#1}{\def\j@nk}% +}% +\rvtx@ifformat@geq{2020/10/01}% + {% + \AddToHook{begindocument/before}{\document@inithook}% + }{% + \prepdef\document{% + \endgroup + \document@inithook + \true@sw{}% + }% + } +\let\document@inithook\@empty +\appdef\document@inithook{% + \AtBeginDocument{\class@documenthook}% +}% +\AtEndDocument{% + \class@enddocumenthook +}% +\let\class@documenthook\@empty +\let\class@enddocumenthook\@empty +\rvtx@ifformat@geq{2020/10/01}{% + % +}{% + % +\def\enddocument{% + \let\AtEndDocument\@firstofone + \@enddocumenthook + \@checkend{document}% + \clear@document + \check@aux + \deadcycles\z@ + \@@end +}% +\def\check@aux{\do@check@aux}% +\def\do@check@aux{% + \@if@sw\if@filesw\fi{% + \immediate\closeout\@mainaux + \let\@setckpt\@gobbletwo + \let\@newl@bel\@testdef + \@tempswafalse + \makeatletter + \input\jobname.aux\relax + }{}% + \@dofilelist + \@ifdim{\font@submax >\fontsubfuzz\relax}{% + \@font@warning{% + Size substitutions with differences\MessageBreak + up to \font@submax\space have occured.\@gobbletwo + }% + }{}% + \@defaultsubs + \@refundefined + \@if@sw\if@filesw\fi{% + \@ifx{\@multiplelabels\relax}{% + \@if@sw\if@tempswa\fi{% + \@latex@warning@no@line{% + Label(s) may have changed. + Rerun to get cross-references right% + }% + }{}% + }{% + \@multiplelabels + }% + }{}% +}% +} +\rvtx@ifformat@geq{2020/10/01}{% + \AddToHook{enddocument}{\rvtx@enddocument@patch{}}% +}{} +\protected\long\def\rvtx@enddocument@patch#1#2\@checkend#3{% + \begingroup + \edef\x{\detokenize{#3}}% + \edef\y{\detokenize{document}}% + \expandafter\endgroup + \ifx\x\y + \expandafter\rvtx@enddocument@patch@end + \else + \expandafter\rvtx@enddocument@patch@more + \fi + {#1#2}{#3}} +\def\rvtx@enddocument@patch@more#1#2{% + \rvtx@enddocument@patch{#1\@checkend{#2}}} +\long\def\rvtx@enddocument@patch@end#1#2\clearpage#3\endgroup{% + \def\do@check@aux{#3\endgroup}% + #1% + \@checkend{#2}% + \clear@document + \check@aux} +\def\check@aux{\do@check@aux}% +\def\clear@document{% + \clearpage + \do@output@cclv{% + \Call@AfterLastShipout + }% +}% +\appdef\class@documenthook{% + \providecommand\Call@AfterLastShipout{}% +}% +\def\class@extension#1#2{% + \IfFileExists{#1.#2}{% + \expandafter\class@extensionfile\csname ver@\@currname.\@currext\endcsname{#1}#2% + }{% + \csname rtx@#1\endcsname + }% +}% +\def\class@extensionfile#1#2#3{% + \@pass@ptions#3\@unusedoptionlist{#2}% + \global\let\@unusedoptionlist\@empty + \expandafter\class@ext@hook\csname#2.#3-h@@k\endcsname#1{#2}#3% +}% +\def\class@ext@hook#1#2#3#4{% + \@pushfilename@ltx + \makeatletter + \let\CurrentOption\@empty + \@reset@ptions + \let#1\@empty + \xdef\@currname{#3}% + \global\let\@currext#4% + \global\let\@clsextension\@currext + \input{#3.#4}% + \@ifl@ter#4{#3}#2{% + \class@info{Class extension later than: #2}% + }{% + \class@info{Class extension earlier: #2}% + \@@end + }% + #1% + \let#1\@undefined + \expandafter\@p@pfilename@ltx\@currnamestack@ltx\@nil + \@reset@ptions +}% +\def\@pushfilename@ltx{% + \xdef\@currnamestack@ltx{% + {\@currname}% + {\@currext}% + {\@clsextension}% + {\the\catcode`\@}% + \@currnamestack@ltx + }% +}% +\def\@p@pfilename@ltx#1#2#3#4#5\@nil{% + \gdef\@currname{#1}% + \gdef\@currext{#2}% + \gdef\@clsextension{#3}% + \catcode`\@#4\relax + \gdef\@currnamestack@ltx{#5}% +}% +\global\let\@currnamestack@ltx\@empty +\def\flushing{% + \let\\\@normalcr + \leftskip\z@skip + \rightskip\z@skip + \@rightskip\z@skip + \parfillskip\@flushglue +}% +\expandafter\DeclareRobustCommand\expandafter\@centercr\expandafter{\@centercr}% +\def\rvtx@tmpa#1{% +\def\eqnarray@LaTeX{% + \stepcounter{equation}% + \def\@currentlabel{\p@equation\theequation}% + #1% \def\@currentcounter{equation} on newer LaTeX + \global\@eqnswtrue + \m@th + \global\@eqcnt\z@ + \tabskip\@centering + \let\\\@eqncr + $$\everycr{}\halign to\displaywidth\bgroup + \hskip\@centering$\displaystyle\tabskip\z@skip{####}$\@eqnsel + &\global\@eqcnt\@ne\hskip \tw@\arraycolsep \hfil${####}$\hfil + &\global\@eqcnt\tw@ \hskip \tw@\arraycolsep + $\displaystyle{####}$\hfil\tabskip\@centering + &\global\@eqcnt\thr@@ \hb@xt@\z@\bgroup\hss####\egroup + \tabskip\z@skip + \cr +}% +\long\def\eqnarray@fleqn@fixed{% + \stepcounter{equation}\def\@currentlabel{\p@equation\theequation}% + #1% \def\@currentcounter{equation} on newer LaTeX + \global\@eqnswtrue\m@th\global\@eqcnt\z@ + \tabskip\ltx@mathindent + \let\\=\@eqncr + \setlength\abovedisplayskip{\topsep}% + \ifvmode\addtolength\abovedisplayskip{\partopsep}\fi + \addtolength\abovedisplayskip{\parskip}% + \setlength\belowdisplayskip{\abovedisplayskip}% + \setlength\belowdisplayshortskip{\abovedisplayskip}% + \setlength\abovedisplayshortskip{\abovedisplayskip}% + $$% + \everycr{}% + \halignt@\linewidth\bgroup + \hskip\@centering$\displaystyle\tabskip\z@skip{####}$\@eqnsel + &\global\@eqcnt\@ne + \hskip\tw@\eqncolsep + \hfil${{}####{}}$\hfil + &\global\@eqcnt\tw@ + \hskip\tw@\eqncolsep + $\displaystyle{####}$\hfil\tabskip\@centering + &\global\@eqcnt\thr@@\hb@xt@\z@\bgroup\hss####\egroup + \tabskip\z@skip + \cr +}% +} +\rvtx@tmpa{}% older LaTeX +\@ifx{\eqnarray\eqnarray@LaTeX}{\@firstofone} + {% + \rvtx@tmpa{\def\@currentcounter{equation}}% newer LaTeX + \@ifx{\eqnarray\eqnarray@LaTeX}{\@firstofone} + {\@gobble} + } +{% + \class@info{Repairing broken LaTeX eqnarray}% + \let\eqnarray\eqnarray@fleqn@fixed + \newlength\eqncolsep + \setlength\eqncolsep\z@ + \let\eqnarray@LaTeX\relax + \let\eqnarray@fleqn@fixed\relax +}% +\def\ltx@mathindent{\@centering}% +\def\set@eqnarray@skips{}% +\def\prep@math{% + \@ifvmode{\everypar{{\setbox\z@\lastbox}}}{}% +}% +\def\prep@math@patch{% + \prepdef\equation{\prep@math}% + \prepdef\eqnarray{\prep@math}% +}% +\def\footnote{\@ifnextchar[\ltx@xfootnote\ltx@yfootnote}% +\def\ltx@xfootnote[#1]{% + \ltx@def@footproc\ltx@footmark[#1]% + \expandafter\ltx@foottext\expandafter{\the\csname c@\@mpfn\endcsname}% +}% +\def\ltx@yfootnote{% + \ltx@stp@footproc\ltx@footmark + \expandafter\ltx@foottext\expandafter{\the\csname c@\@mpfn\endcsname}% +}% +\def\footnotemark{\@ifnextchar[\ltx@xfootmark\ltx@yfootmark}% +\def\ltx@xfootmark{\ltx@def@footproc\ltx@footmark}% +\def\ltx@yfootmark{\ltx@stp@footproc\ltx@footmark}% +\def\ltx@footmark#1{% + \leavevmode + \ifhmode\edef\@x@sf{\the\spacefactor}\nobreak\fi + \begingroup + \expandafter\ltx@make@current@footnote\expandafter{\@mpfn}{#1}% + \expandafter\@argswap@val\expandafter{\Hy@footnote@currentHref}{\hyper@linkstart {link}}% + \@makefnmark + \hyper@linkend + \endgroup + \ifhmode\spacefactor\@x@sf\fi + \relax +}% +\def\footnotetext{\@ifnextchar[\ltx@xfoottext\ltx@yfoottext}% +\def\ltx@xfoottext{\ltx@def@footproc\ltx@foottext}% +\def\ltx@yfoottext{\ltx@stp@footproc\ltx@foottext}% +\long\def\ltx@foottext#1#2{% + \begingroup + \expandafter\ltx@make@current@footnote\expandafter{\@mpfn}{#1}% + \@footnotetext{#2}% + \endgroup +}% +\def\ltx@def@footproc#1[#2]{% + \begingroup + \csname c@\@mpfn\endcsname #2\relax + \unrestored@protected@xdef\@thefnmark{\thempfn}% + \expandafter\endgroup + \expandafter#1% + \expandafter{\the\csname c@\@mpfn\endcsname}% +}% +\def\ltx@stp@footproc#1{% + \expandafter\stepcounter\expandafter{\@mpfn}% + \protected@xdef\@thefnmark{\thempfn}% + \expandafter#1% + \expandafter{\the\csname c@\@mpfn\endcsname}% +}% +\appdef\class@documenthook{% + \let\footnote@latex\footnote + \@ifpackageloaded{hyperref}{}{% + \let\H@@footnotetext\@footnotetext + \def\@footnotetext{\H@@footnotetext}% + \let\H@@mpfootnotetext\@mpfootnotetext + \def\@mpfootnotetext{\H@@mpfootnotetext}% + }% +}% +\def\ltx@make@current@footnote#1#2{% + \csname c@#1\endcsname#2\relax + \protected@edef\Hy@footnote@currentHref{\@currentHref-#1.\csname the#1\endcsname}% +}% +\def\thempfootnote@latex{{\itshape \@alph \c@mpfootnote }}% +\def\ltx@thempfootnote{\@alph\c@mpfootnote}% +\@ifx{\thempfootnote\thempfootnote@latex}{% + \class@info{Repairing hyperref-unfriendly LaTeX definition of \string\mpfootnote}% + \let\thempfootnote\ltx@thempfootnote +}{}% +\def\@makefnmark{% + \hbox{% + \@textsuperscript{% + \normalfont\itshape\@thefnmark + }% + }% +}% +\long\def\@footnotetext{% + \insert\footins\bgroup + \make@footnotetext +}% +\long\def\@mpfootnotetext{% + \minipagefootnote@pick + \make@footnotetext +}% +\long\def\make@footnotetext#1{% + \set@footnotefont + \set@footnotewidth + \@parboxrestore + \protected@edef\@currentlabel{% + \csname p@\@mpfn\endcsname\@thefnmark + }% + \color@begingroup + \@makefntext{% + \rule\z@\footnotesep\ignorespaces#1% + \@finalstrut\strutbox\vadjust{\vskip\z@skip}% + }% + \color@endgroup + \minipagefootnote@drop +}% +\def\set@footnotefont{% + \reset@font\footnotesize + \interlinepenalty\interfootnotelinepenalty + \splittopskip\footnotesep + \splitmaxdepth\dp\strutbox +}% +\def\set@footnotewidth{\set@footnotewidth@one}% +\def\robustify@contents{% + \let \label \@gobble + \let \index \@gobble + \let \glossary \@gobble + \let\footnote \@gobble + \def\({\string\(}% + \def\){\string\)}% + \def\\{\string\\}% +}% +\long\def\addtocontents#1#2{% + \protected@write\@auxout{\robustify@contents}{\string \@writefile {#1}{#2}}% +}% +\def\addcontentsline#1#2#3{% + \addtocontents{#1}{% + \protect\contentsline{#2}{#3}{\thepage}{}% + }% +}% +\def\label#1{% + \@bsphack + \protected@write\@auxout{}{% + \string\newlabel{#1}{{\@currentlabel}{\thepage}{}{}{}}% + }% + \@esphack +}% +\def\ltx@contentsline#1{% + \expandafter\@ifnotrelax\csname l@#1\endcsname{}{% + \expandafter\let\csname l@#1\endcsname\@gobbletwo + }% + \contentsline@latex{#1}% +}% +\appdef\document@inithook{% + \let\contentsline@latex\contentsline + \let\contentsline\ltx@contentsline +}% +\appdef\class@documenthook{% + \prepdef\caption{\minipagefootnote@here}% +}% +\def\minipagefootnote@init{% + \setbox\@mpfootins\box\voidb@x +}% +\def\minipagefootnote@pick{% + \global\setbox\@mpfootins\vbox\bgroup + \unvbox\@mpfootins +}% +\def\minipagefootnote@drop{% + \egroup +}% +\def\minipagefootnote@here{% + \par + \@ifvoid\@mpfootins{}{% + \vskip\skip\@mpfootins + \fullinterlineskip + \@ifinner{% + \vtop{\unvcopy\@mpfootins}% + {\setbox\z@\lastbox}% + }{}% + \unvbox\@mpfootins + }% +}% +\def\minipagefootnote@foot{% + \@ifvoid\@mpfootins{}{% + \insert\footins\bgroup\unvbox\@mpfootins\egroup + }% +}% +\def\endminipage{% + \par + \unskip + \minipagefootnote@here + \@minipagefalse %% added 24 May 89 + \color@endgroup + \egroup + \expandafter\@iiiparbox\@mpargs{\unvbox\@tempboxa}% +}% +\@booleantrue\floats@sw +\let\@xfloat@LaTeX\@xfloat +\def\@xfloat#1[#2]{% + \@xfloat@prep + \@nameuse{fp@proc@#2}% + \floats@sw{\@xfloat@LaTeX{#1}[#2]}{\@xfloat@anchored{#1}[]}% +}% +\def\@xfloat@prep{% + \ltx@footnote@pop + \def\@mpfn{mpfootnote}% + \def\thempfn{\thempfootnote}% + \c@mpfootnote\z@ + \let\H@@footnotetext\H@@mpfootnotetext +}% +\let\ltx@footnote@pop\@empty +\def\@xfloat@anchored#1[#2]{% + \def\@captype{#1}% + \begin@float@pagebreak + \let\end@float\end@float@anchored + \let\end@dblfloat\end@float@anchored + \hsize\columnwidth + \@parboxrestore + \@floatboxreset + \minipagefootnote@init +}% +\def\end@float@anchored{% + \minipagefootnote@here + \par\vskip\z@skip + \par + \end@float@pagebreak +}% +\def\begin@float@pagebreak{\par\addvspace\intextsep}% +\def\end@float@pagebreak{\par\addvspace\intextsep}% +\def\@mpmakefntext#1{% + \parindent=1em + \noindent + \hb@xt@1em{\hss\@makefnmark}% + #1% +}% +\def\do@if@floats#1#2{% + \floats@sw{}{% + \expandafter\newwrite + \csname#1write\endcsname + \expandafter\def + \csname#1@stream\endcsname{\jobname#2}% + \expandafter\immediate + \expandafter\openout + \csname#1write\endcsname + \csname#1@stream\endcsname\relax + \@ifxundefined\@float@LaTeX{% + \let\@float@LaTeX\@float + \let\@dblfloat@LaTeX\@dblfloat + \let\@float\write@float + \let\@dblfloat\write@floats + }{}% + \let@environment{#1@float}{#1}% + \let@environment{#1@floats}{#1*}% + \@ifxundefined@cs{#1@write}{}{% + \let@environment{#1}{#1@write}% + }% + }% +}% +\def\triggerpar{\leavevmode\@@par}% +\def\oneapage{\def\begin@float@pagebreak{\newpage}\def\end@float@pagebreak{\newpage}}% +\def\print@float#1#2{% + \lengthcheck@sw{% + \total@float{#1}% + }{}% + \@ifxundefined@cs{#1write}{}{% + \begingroup + \@booleanfalse\floats@sw + #2% + \raggedbottom + \def\array@default{v}% floats must + \let\@float\@float@LaTeX + \let\@dblfloat\@dblfloat@LaTeX + \let\trigger@float@par\triggerpar + \let@environment{#1}{#1@float}% + \let@environment{#1*}{#1@floats}% + \expandafter\prepdef\csname#1\endcsname{\trigger@float@par}% + \expandafter\prepdef\csname#1*\endcsname{\trigger@float@par}% + \@namedef{fps@#1}{h!}% + \expandafter\immediate + \expandafter\closeout + \csname#1write\endcsname + \everypar{% + \global\let\trigger@float@par\relax + \global\everypar{}\setbox\z@\lastbox + \@ifxundefined@cs{#1sname}{}{% + \begin@float@pagebreak + \expandafter\section + \expandafter*% + \expandafter{% + \csname#1sname\endcsname + }% + }% + }% + \input{\csname#1@stream\endcsname}% + \endgroup + \global\expandafter\let\csname#1write\endcsname\relax + }% +}% +\chardef\@xvi=16\relax +\mathchardef\@twopowerfourteen="4000 +\mathchardef\@twopowertwo="4 +\def\tally@float#1{% + \begingroup + \@tempcnta\count\@currbox + \divide\@tempcnta\@xxxii + \multiply\@tempcnta\@xxxii + \advance\count\@currbox-\@tempcnta + \divide\@tempcnta\@xxxii + \@ifnum{\count\@currbox>\@xvi}{% + \advance\count\@currbox-\@xvi\@booleantrue\@temp@sw + }{% + \@booleanfalse\@temp@sw + }% + \show@box@size@sw{% + \class@info{Float #1 + (\the\@tempcnta)[\@temp@sw{16+}{}\the\count\@currbox]^^J% + (\the\ht\@currbox+\the\dp\@currbox)X\the\wd\@currbox + }% + }{}% + \endgroup + \expandafter\let + \expandafter\@tempa + \csname fbox@\csname ftype@#1\endcsname\endcsname + \@ifnotrelax\@tempa{% + \@ifhbox\@tempa{% + \setbox\@tempboxa\vbox{\unvcopy\@currbox\hrule}% + \dimen@\ht\@tempboxa + \divide\dimen@\@twopowerfourteen + \@ifdim{\wd\@tempboxa<\textwidth}{% + \advance\dimen@\ht\@tempa + \global\ht\@tempa\dimen@ + }{% + \advance\dimen@\dp\@tempa + \global\dp\@tempa\dimen@ + }% + }{}% + }{}% +}% +\def\total@float#1{% + \expandafter\let + \expandafter\@tempa + \csname fbox@\csname ftype@#1\endcsname\endcsname + \@ifnotrelax\@tempa{% + \@ifhbox\@tempa{% + \@tempdima\the\ht\@tempa\divide\@tempdima\@twopowertwo\@tempcnta\@tempdima + \@tempdimb\the\dp\@tempa\divide\@tempdimb\@twopowertwo\@tempcntb\@tempdimb + \class@info{Total #1: Column(\the\@tempcnta pt), Page(\the\@tempcnta pt)}% + }{}% + }{}% +}% +\def\write@float#1{\write@@float{#1}{#1}}% +\def\endwrite@float{\@Esphack}% +\def\write@floats#1{\write@@float{#1*}{#1}}% +\def\endwrite@floats{\@Esphack}% +\def\write@@float#1#2{% + \ifhmode + \@bsphack + \fi + \chardef\@tempc\csname#2write\endcsname + \toks@{\begin{#1}}% + \def\@tempb{#1}% + \expandafter\let\csname end#1\endcsname\endwrite@float + \catcode`\^^M\active + \@makeother\{\@makeother\}\@makeother\% + \write@floatline +}% +\begingroup + \catcode`\[\the\catcode`\{\catcode`\]\the\catcode`\}\@makeother\{\@makeother\}% + \gdef\float@end@tag#1\end{#2}#3\@nul[% + \def\@tempa[#2]% + \@ifx[\@tempa\@tempb][\end[#2]][\write@floatline]% + ]% + \obeylines% + \gdef\write@floatline#1^^M[% + \begingroup% + \newlinechar`\^^M% + \toks@\expandafter[\the\toks@#1]\immediate\write\@tempc[\the\toks@]% + \endgroup% + \toks@[]% + \float@end@tag#1\end{}\@nul% + ]% +\endgroup +\def\@alph#1{\ifcase#1\or a\or b\or c\or d\else\@ialph{#1}\fi} +\def\@ialph#1{\ifcase#1\or \or \or \or \or e\or f\or g\or h\or i\or j\or + k\or l\or m\or n\or o\or p\or q\or r\or s\or t\or u\or v\or w\or x\or + y\or z\or aa\or bb\or cc\or dd\or ee\or ff\or gg\or hh\or ii\or jj\or + kk\or ll\or mm\or nn\or oo\or pp\or qq\or rr\or ss\or tt\or uu\or + vv\or ww\or xx\or yy\or zz\else\@ctrerr\fi} +\def\@startsection#1#2#3#4#5#6{% + \@startsection@hook + \if@noskipsec \leavevmode \fi + \par + \@tempskipa #4\relax + \@afterindenttrue + \ifdim \@tempskipa <\z@ + \@tempskipa -\@tempskipa \@afterindentfalse + \fi + \if@nobreak + \everypar{}% + \else + \addpenalty\@secpenalty\addvspace\@tempskipa + \fi + \@ifstar + {\@dblarg{\@ssect@ltx{#1}{#2}{#3}{#4}{#5}{#6}}}% + {\@dblarg{\@sect@ltx {#1}{#2}{#3}{#4}{#5}{#6}}}% +}% +\def\@startsection@hook{}% +\class@info{Repairing broken LateX \string\@sect}% +\def\@sect@ltx#1#2#3#4#5#6[#7]#8{% + \@ifnum{#2>\c@secnumdepth}{% + \def\H@svsec{\phantomsection}% + \let\@svsec\@empty + }{% + \H@refstepcounter{#1}% + \def\H@svsec{% + \phantomsection + }% + \protected@edef\@svsec{{#1}}% + \@ifundefined{@#1cntformat}{% + \prepdef\@svsec\@seccntformat + }{% + \expandafter\prepdef + \expandafter\@svsec + \csname @#1cntformat\endcsname + }% + }% + \@tempskipa #5\relax + \@ifdim{\@tempskipa>\z@}{% + \begingroup + \interlinepenalty \@M + #6{% + \@ifundefined{@hangfrom@#1}{\@hang@from}{\csname @hangfrom@#1\endcsname}% + {\hskip#3\relax\H@svsec}{\@svsec}{#8}% + }% + \@@par + \endgroup + \@ifundefined{#1mark}{\@gobble}{\csname #1mark\endcsname}{#7}% + \addcontentsline{toc}{#1}{% + \@ifnum{#2>\c@secnumdepth}{% + \protect\numberline{}% + }{% + \protect\numberline{\csname the#1\endcsname}% + }% + #8}% + }{% + \def\@svsechd{% + #6{% + \@ifundefined{@runin@to@#1}{\@runin@to}{\csname @runin@to@#1\endcsname}% + {\hskip#3\relax\H@svsec}{\@svsec}{#8}% + }% + \@ifundefined{#1mark}{\@gobble}{\csname #1mark\endcsname}{#7}% + \addcontentsline{toc}{#1}{% + \@ifnum{#2>\c@secnumdepth}{% + \protect\numberline{}% + }{% + \protect\numberline{\csname the#1\endcsname}% + }% + #8}% + }% + }% + \@xsect{#5}% +}% +\def\@hang@from#1#2#3{\@hangfrom{#1#2}#3}% +\def\@runin@to #1#2#3{#1#2#3}% +\def\@ssect@ltx#1#2#3#4#5#6[#7]#8{% + \def\H@svsec{\phantomsection}% + \@tempskipa #5\relax + \@ifdim{\@tempskipa>\z@}{% + \begingroup + \interlinepenalty \@M + #6{% + \@ifundefined{@hangfroms@#1}{\@hang@froms}{\csname @hangfroms@#1\endcsname}% + {\hskip#3\relax\H@svsec}{#8}% + }% + \@@par + \endgroup + \@ifundefined{#1smark}{\@gobble}{\csname #1smark\endcsname}{#7}% + \addcontentsline{toc}{#1}{\protect\numberline{}#8}% + }{% + \def\@svsechd{% + #6{% + \@ifundefined{@runin@tos@#1}{\@runin@tos}{\csname @runin@tos@#1\endcsname}% + {\hskip#3\relax\H@svsec}{#8}% + }% + \@ifundefined{#1smark}{\@gobble}{\csname #1smark\endcsname}{#7}% + \addcontentsline{toc}{#1}{\protect\numberline{}#8}% + }% + }% + \@xsect{#5}% +}% +\def\@hang@froms#1#2{#1#2}% +\def\@runin@tos #1#2{#1#2}% +\def\init@hyperref{% + \providecommand\phantomsection{}% + \providecommand\hyper@makecurrent[1]{}% + \providecommand\Hy@raisedlink[1]{}% + \providecommand\hyper@anchorstart[1]{}% + \providecommand\hyper@anchorend{}% + \providecommand\hyper@linkstart[2]{}% + \providecommand\hyper@linkend{}% + \providecommand\@currentHref{}% +}% +\let\H@refstepcounter\refstepcounter +\appdef\document@inithook{% + \init@hyperref +}% +\def\sec@upcase#1{\relax{#1}}% +\appdef\document@inithook{% + \@ifpackageloaded{array}{\switch@array}{\switch@tabular}% + \prepdef\endtabular{\endtabular@hook}% + \@provide\endtabular@hook{}% + \prepdef\endarray{\endarray@hook}% + \@provide\endarray@hook{}% + \providecommand\array@hook{}% + \prepdef\@tabular{\tabular@hook}% + \@provide\tabular@hook{}% +}% +\def\switch@tabular{% + \let\@array@sw\@array@sw@array + \@ifx{\@array\@array@LaTeX}{% + \@ifx{\multicolumn\multicolumn@LaTeX}{% + \@ifx{\@tabular\@tabular@LaTeX}{% + \@ifx{\@tabarray\@tabarray@LaTeX}{% + \@ifx{\array\array@LaTeX}{% + \@ifx{\endarray\endarray@LaTeX}{% + \@ifx{\endtabular\endtabular@LaTeX}{% + \@ifx{\@mkpream\@mkpream@LaTeX}{% + \@ifx{\@addamp\@addamp@LaTeX}{% + \@ifx{\@arrayacol\@arrayacol@LaTeX}{% + \@ifx{\@tabacol\@tabacol@LaTeX}{% + \@ifx{\@arrayclassz\@arrayclassz@LaTeX}{% + \@ifx{\@tabclassiv\@tabclassiv@LaTeX}{% + \@ifx{\@arrayclassiv\@arrayclassiv@LaTeX}{% + \@ifx{\@tabclassz\@tabclassz@LaTeX}{% + \@ifx{\@classv\@classv@LaTeX}{% + \@ifx{\hline\hline@LaTeX}{% + \@ifx{\@tabularcr\@tabularcr@LaTeX}{% + \@ifx{\@xtabularcr\@xtabularcr@LaTeX}{% + \@ifx{\@xargarraycr\@xargarraycr@LaTeX}{% + \@ifx{\@yargarraycr\@yargarraycr@LaTeX}{% + \true@sw + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + {% + \class@info{Patching LaTeX tabular.}% + }{% + \class@info{Unrecognized LaTeX tabular. Please update this document class! (Proceeding with fingers crossed.)}% + }% + \let\@array\@array@ltx + \let\multicolumn\multicolumn@ltx + \let\@tabular\@tabular@ltx + \let\@tabarray\@tabarray@ltx + \let\array\array@ltx + \let\endarray\endarray@ltx + \let\endtabular\endtabular@ltx + \let\@mkpream\@mkpream@ltx + \let\@addamp\@addamp@ltx + \let\@arrayacol\@arrayacol@ltx + \let\@tabacol\@tabacol@ltx + \let\@arrayclassz\@arrayclassz@ltx + \let\@tabclassiv\@tabclassiv@ltx + \let\@arrayclassiv\@arrayclassiv@ltx + \let\@tabclassz\@tabclassz@ltx + \let\@classv\@classv@ltx + \let\hline\hline@ltx + \let\@tabularcr\@tabularcr@ltx + \let\@xtabularcr\@xtabularcr@ltx + \let\@xargarraycr\@xargarraycr@ltx + \let\@yargarraycr\@yargarraycr@ltx +}% +\def\switch@array{% + \@ifpackageloaded{colortbl}{\let\switch@array@info\colortbl@message}{\let\switch@array@info\array@message}% + \let\@array@sw\@array@sw@LaTeX + \@ifx{\@array\@array@array}{% + \@ifx{\@tabular\@tabular@array}{% + \@ifx{\@tabarray\@tabarray@array}{% + \@ifx{\array\array@array}{% + \@ifx{\endarray\endarray@array}{% + \@ifx{\endtabular\endtabular@array}{% + \@ifx{\@mkpream\@mkpream@array}{% + \@ifx{\@classx\@classx@array}{% + \@ifx{\insert@column\insert@column@array}{% + \@ifx{\@arraycr\@arraycr@array}{% + \@ifx{\@xarraycr\@xarraycr@array}{% + \@ifx{\@xargarraycr\@xargarraycr@array}{% + \@ifx{\@yargarraycr\@yargarraycr@array}{% + \true@sw + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }{% + \false@sw + }{% + \class@info{Patching array package.}% + }{% + \switch@array@info + }% + \let\@array \@array@array@new + \let\@@array \@array % Cosi fan tutti + \let\@tabular \@tabular@array@new + \let\@tabarray \@tabarray@array@new + \let\array \array@array@new + \let\endarray \endarray@array@new + \let\endtabular\endtabular@array@new + \let\@mkpream \@mkpream@array@new + \let\@classx \@classx@array@new + \let\@arrayacol\@arrayacol@ltx + \let\@tabacol \@tabacol@ltx + \let\insert@column\insert@column@array@new + \expandafter\let\csname endtabular*\endcsname\endtabular % Cosi fan tutti + \let\@arraycr \@arraycr@new + \let\@xarraycr \@xarraycr@new + \let\@xargarraycr\@xargarraycr@new + \let\@yargarraycr\@yargarraycr@new +}% +\def\array@message{% + \class@info{Unrecognized array package. Please update this document class! (Proceeding with fingers crossed.)}% +}% +\def\colortbl@message{% + \class@info{colortbl package is loaded. (Proceeding with fingers crossed.)}% +}% +\def\@array@sw@LaTeX{\@ifx{\\\@tabularcr}}% +\def\@array@sw@array{\@ifx{\d@llarbegin\begingroup}}% +\def\@tabular@LaTeX{% + \leavevmode + \hbox\bgroup$% + \let\@acol\@tabacol + \let\@classz\@tabclassz + \let\@classiv\@tabclassiv + \let\\\@tabularcr + \@tabarray +}% +\def\@tabular@ltx{% + \let\@acoll\@tabacoll + \let\@acolr\@tabacolr + \let\@acol\@tabacol + \let\@classz\@tabclassz + \let\@classiv\@tabclassiv + \let\\\@tabularcr + \@tabarray +}% +\def\@tabular@array{% + \leavevmode + \hbox\bgroup$% + \col@sep\tabcolsep + \let\d@llarbegin\begingroup + \let\d@llarend\endgroup + \@tabarray +}% +\def\@tabular@array@new{% + \let\@acoll\@tabacoll + \let\@acolr\@tabacolr + \let\@acol\@tabacol + \let\d@llarbegin\begingroup + \let\d@llarend\endgroup + \@tabarray +}% +\def\@tabarray@LaTeX{% + \m@th\@ifnextchar[\@array{\@array[c]}% +}% +\def\@tabarray@ltx{% + \m@th\@ifnextchar[\@array{\expandafter\@array\expandafter[\array@default]}% +}% +\def\@tabarray@array{% + \@ifnextchar[{\@@array}{\@@array[c]}% +}% +\def\@tabarray@array@new{% + \@ifnextchar[{\@@array}{\expandafter\@@array\expandafter[\array@default]}% +}% +\newcount\intertabularlinepenalty +\intertabularlinepenalty=100 +\newcount\@tbpen +\appdef\samepage{\intertabularlinepenalty\@M}% +\def\@tabularcr@LaTeX{{\ifnum 0=`}\fi \@ifstar \@xtabularcr \@xtabularcr}% +\def\@tabularcr@ltx{{\ifnum 0=`}\fi \@ifstar {\global \@tbpen \@M \@xtabularcr }{\global \@tbpen \intertabularlinepenalty \@xtabularcr }}% +\def\@xtabularcr@LaTeX{\@ifnextchar [\@argtabularcr {\ifnum 0=`{\fi }\cr }}% +\def\@xtabularcr@ltx{\@ifnextchar [\@argtabularcr {\ifnum 0=`{\fi }\cr \noalign {\penalty \@tbpen }}}% +\def\@xargarraycr@LaTeX#1{\@tempdima #1\advance \@tempdima \dp \@arstrutbox \vrule \@height \z@ \@depth \@tempdima \@width \z@ \cr}% +\def\@xargarraycr@ltx#1{\@tempdima #1\advance \@tempdima \dp \@arstrutbox \vrule \@height \z@ \@depth \@tempdima \@width \z@ \cr \noalign {\penalty \@tbpen }}% +\def\@yargarraycr@LaTeX#1{\cr \noalign {\vskip #1}}% +\def\@yargarraycr@ltx#1{\cr \noalign {\penalty \@tbpen \vskip #1}}% +\def\@arraycr@array{% + \relax + \iffalse{\fi\ifnum 0=`}\fi + \@ifstar \@xarraycr \@xarraycr +}% +\def\@arraycr@new{% + \relax + \iffalse{\fi\ifnum 0=`}\fi + \@ifstar {\global \@tbpen \@M \@xarraycr }{\global \@tbpen \intertabularlinepenalty \@xarraycr }% +}% +\def\@xarraycr@array{% + \@ifnextchar [%] + \@argarraycr {\ifnum 0=`{}\fi\cr}% +}% +\def\@xarraycr@new{% + \@ifnextchar [%] + \@argarraycr {\ifnum 0=`{}\fi\cr \noalign {\penalty \@tbpen }}% +}% +\def\@xargarraycr@array#1{% + \unskip + \@tempdima #1\advance\@tempdima \dp\@arstrutbox + \vrule \@depth\@tempdima \@width\z@ + \cr +}% +\def\@xargarraycr@new#1{% + \unskip + \@tempdima #1\advance\@tempdima \dp\@arstrutbox + \vrule \@depth\@tempdima \@width\z@ + \cr + \noalign {\penalty \@tbpen }% +}% +\def\@yargarraycr@array#1{% + \cr + \noalign{\vskip #1}% +}% +\def\@yargarraycr@new#1{% + \cr + \noalign{\penalty \@tbpen \vskip #1}% +}% +\def\array@LaTeX{% + \let\@acol\@arrayacol + \let\@classz\@arrayclassz + \let\@classiv\@arrayclassiv + \let\\\@arraycr + \let\@halignto\@empty + \@tabarray +}% +\def\array@ltx{% + \@ifmmode{}{\@badmath$}% + \let\@acoll\@arrayacol + \let\@acolr\@arrayacol + \let\@acol\@arrayacol + \let\@classz\@arrayclassz + \let\@classiv\@arrayclassiv + \let\\\@arraycr + \let\@halignto\@empty + \@tabarray +}% +\def\array@array{% + \col@sep\arraycolsep + \def\d@llarbegin{$}\let\d@llarend\d@llarbegin\gdef\@halignto{}% + \@tabarray +} +\def\array@array@new{% + \@ifmmode{}{\@badmath$}% + \let\@acoll\@arrayacol + \let\@acolr\@arrayacol + \let\@acol\@arrayacol + \def\d@llarbegin{$}% + \let\d@llarend\d@llarbegin + \gdef\@halignto{}% + \@tabarray +}% +\def\@array@LaTeX[#1]#2{% + \if #1t\vtop \else \if#1b\vbox \else \vcenter \fi\fi + \bgroup + \setbox\@arstrutbox\hbox{% + \vrule \@height\arraystretch\ht\strutbox + \@depth\arraystretch \dp\strutbox + \@width\z@}% + \@mkpream{#2}% + \edef\@preamble{% + \ialign \noexpand\@halignto + \bgroup \@arstrut \@preamble \tabskip\z@skip \cr}% + \let\@startpbox\@@startpbox \let\@endpbox\@@endpbox + \let\tabularnewline\\% + \let\par\@empty + \let\@sharp##% + \set@typeset@protect + \lineskip\z@skip\baselineskip\z@skip + \ifhmode \@preamerr\z@ \@@par\fi + \@preamble +}% +\def\@array@ltx[#1]#2{% + \@nameuse{@array@align@#1}% + \set@arstrutbox + \@mkpream{#2}% + \prepdef\@preamble{% + \tabskip\tabmid@skip + \@arstrut + }% + \appdef\@preamble{% + \tabskip\tabright@skip + \cr + \array@row@pre + }% + \let\tabularnewline\\% + \let\par\@empty + \let\@sharp##% + \set@typeset@protect + \lineskip\z@skip\baselineskip\z@skip + \tabskip\tableft@skip\relax + \ifhmode \@preamerr\z@ \@@par\fi + \everycr{}% + \expandafter\halign\expandafter\@halignto\expandafter\bgroup\@preamble +}% +\def\set@arstrutbox{% + \setbox\@arstrutbox\hbox{% + \vrule \@height\arraystretch\ht\strutbox + \@depth\arraystretch \dp\strutbox + \@width\z@ + }% +}% +\def\@array@array[#1]#2{% + \@tempdima \ht \strutbox + \advance \@tempdima by\extrarowheight + \setbox \@arstrutbox \hbox{\vrule + \@height \arraystretch \@tempdima + \@depth \arraystretch \dp \strutbox + \@width \z@}% + \begingroup + \@mkpream{#2}% + \xdef\@preamble{\noexpand \ialign \@halignto + \bgroup \@arstrut \@preamble + \tabskip \z@ \cr}% + \endgroup + \@arrayleft + \if #1t\vtop \else \if#1b\vbox \else \vcenter \fi \fi + \bgroup + \let \@sharp ##\let \protect \relax + \lineskip \z@ + \baselineskip \z@ + \m@th + \let\\\@arraycr \let\tabularnewline\\\let\par\@empty \@preamble +}% +\def\@array@array@new[#1]#2{% + \@tempdima\ht\strutbox + \advance\@tempdima by\extrarowheight + \setbox\@arstrutbox\hbox{% + \vrule \@height\arraystretch\@tempdima + \@depth \arraystretch\dp\strutbox + \@width \z@ + }% + \begingroup + \@mkpream{#2}% + \xdef\@preamble{\@preamble}% + \endgroup + \prepdef\@preamble{% + \tabskip\tabmid@skip + \@arstrut + }% + \appdef\@preamble{% + \tabskip\tabright@skip + \cr + \array@row@pre + }% + \@arrayleft + \@nameuse{@array@align@#1}% + \m@th + \let\\\@arraycr + \let\tabularnewline\\% + \let\par\@empty + \let\@sharp##% + \set@typeset@protect + \lineskip\z@\baselineskip\z@ + \tabskip\tableft@skip + \everycr{}% + \expandafter\halign\expandafter\@halignto\expandafter\bgroup\@preamble +}% +\def\endarray@LaTeX{% + \crcr\egroup\egroup +}% +\def\endarray@ltx{% + \crcr\array@row@pst\egroup\egroup +}% +\def\endarray@array{% + \crcr \egroup \egroup \@arrayright \gdef\@preamble{}% +}% +\def\endarray@array@new{% + \crcr\array@row@pst\egroup\egroup % Same as \endarray@ltx + \@arrayright + \global\let\@preamble\@empty +}% +\def\endtabular@LaTeX{% + \crcr\egroup\egroup $\egroup +}% +\def\endtabular@ltx{% + \endarray +}% +\def\endtabular@array{% + \endarray $\egroup +}% +\def\endtabular@array@new{% + \endarray +}% +\@namedef{endtabular*}{\endtabular}% +\long\def\multicolumn@LaTeX#1#2#3{% + \multispan{#1}\begingroup + \@mkpream{#2}% + \def\@sharp{#3}\set@typeset@protect + \let\@startpbox\@@startpbox\let\@endpbox\@@endpbox + \@arstrut \@preamble\hbox{}\endgroup\ignorespaces +}% +\long\def\multicolumn@ltx#1#2#3{% + \multispan{#1}% + \begingroup + \@mkpream{#2}% + \def\@sharp{#3}% + \set@typeset@protect + %\let\@startpbox\@@startpbox\let\@endpbox\@@endpbox + \@arstrut + \@preamble + \hbox{}% + \endgroup + \ignorespaces +}% +\def\@array@align@t{\leavevmode\vtop\bgroup}% +\def\@array@align@b{\leavevmode\vbox\bgroup}% +\def\@array@align@c{\leavevmode\@ifmmode{\vcenter\bgroup}{$\vcenter\bgroup\aftergroup$\aftergroup\relax}}% +\def\@array@align@v{% + \@ifmmode{% + \@badmath + \vcenter\bgroup + }{% + \@ifinner{% + $\vcenter\bgroup\aftergroup$ + }{% + \@@par\bgroup + }% + }% +}% +\def\array@default{c}% +\def\array@row@rst{% + \let\@array@align@v\@array@align@c +}% +\def\array@row@pre{}% +\def\array@row@pst{}% +\newcommand\toprule{\tab@rule{\column@font}{\column@fil}{\frstrut}}% +\newcommand\colrule{\unskip\lrstrut\\\tab@rule{\body@font}{}{\frstrut}}% +\newcommand\botrule{\unskip\lrstrut\\\noalign{\hline@rule}{}}% +\def\hline@LaTeX{% + \noalign{\ifnum0=`}\fi\hrule \@height \arrayrulewidth \futurelet + \reserved@a\@xhline +}% +\def\hline@ltx{% + \noalign{% + \ifnum0=`}\fi + \hline@rule + \futurelet\reserved@a\@xhline + % \noalign ended in \@xhline +}% +\def\@xhline@unneeded{% + \say\reserved@a + \ifx\reserved@a\hline + \vskip\doublerulesep + \vskip-\arrayrulewidth + \fi + \ifnum0=`{\fi}% +}% +\def\tab@rule#1#2#3{% + \crcr + \noalign{% + \hline@rule + \gdef\@arstrut@hook{% + \global\let\@arstrut@hook\@empty + #3% + }% + \gdef\cell@font{#1}% + \gdef\cell@fil{#2}% + }% +}% +\def\column@font{}% +\def\column@fil{}% +\def\body@font{}% +\def\cell@font{}% +\def\frstrut{}% +\def\lrstrut{}% +\def\@arstrut@hline{% + \relax + \@ifmmode{\copy}{\unhcopy}\@arstrutbox@hline + \@arstrut@hook +}% +\let\@arstrut@org\@arstrut +\def\@arstrut@hook{% + \global\let\@arstrut\@arstrut@org +}% +\newbox\@arstrutbox@hline +\appdef\set@arstrutbox{% + \setbox\@arstrutbox@hline\hbox{% + \setbox\z@\hbox{$0^{0}_{}$}% + \dimen@\ht\z@\advance\dimen@\@arstrut@hline@clnc + \@ifdim{\dimen@<\arraystretch\ht\strutbox}{\dimen@=\arraystretch\ht\strutbox}{}% + \vrule \@height\dimen@ + \@depth\arraystretch \dp\strutbox + \@width\z@ + }% +}% +\def\hline@rule{% + \hrule \@height \arrayrulewidth + \global\let\@arstrut\@arstrut@hline +}% +\def\@arstrut@hline@clnc{2\p@}% % Klootch: magic number +\def\tableft@skip{\z@skip}% +\def\tabmid@skip{\z@skip}%\@flushglue +\def\tabright@skip{\z@skip}% +\def\tableftsep{\tabcolsep}% +\def\tabmidsep{\tabcolsep}% +\def\tabrightsep{\tabcolsep}% +\def\cell@fil{}% +\def\pbox@hook{}% +\appdef\@arstrut{\@arstrut@hook}% +\let\@arstrut@hook\@empty +\def\@addtopreamble{\appdef\@preamble}% +\def\@mkpream@LaTeX#1{% + \@firstamptrue\@lastchclass6 + \let\@preamble\@empty + \let\protect\@unexpandable@protect + \let\@sharp\relax + \let\@startpbox\relax\let\@endpbox\relax + \@expast{#1}% + \expandafter\@tfor \expandafter + \@nextchar \expandafter:\expandafter=\reserved@a\do + {\@testpach\@nextchar + \ifcase \@chclass \@classz \or \@classi \or \@classii \or \@classiii + \or \@classiv \or\@classv \fi\@lastchclass\@chclass}% + \ifcase \@lastchclass \@acol + \or \or \@preamerr \@ne\or \@preamerr \tw@\or \or \@acol \fi +}% +\def\@mkpream@ltx#1{% + \@firstamptrue + \@lastchclass6 + \let\@preamble\@empty + \let\protect\@unexpandable@protect + \let\@sharp\relax + \@expast{#1}% + \expandafter\@tfor\expandafter\@nextchar\expandafter:\expandafter=\reserved@a + \do{% + \expandafter\@testpach\expandafter{\@nextchar}% + \ifcase\@chclass + \@classz + \or + \@classi + \or + \@classii + \or + \@classiii + \or + \@classiv + \or + \@classv + \fi + \@lastchclass\@chclass + }% + \ifcase\@lastchclass + \@acolr % right-hand column + \or + \or + \@preamerr\@ne + \or + \@preamerr\tw@ + \or + \or + \@acolr % right-hand column + \fi +}% +\def\insert@column@array{% + \the@toks \the \@tempcnta + \ignorespaces \@sharp \unskip + \the@toks \the \count@ \relax +}% +\def\insert@column@array@new{% + \the@toks\the\@tempcnta + \array@row@rst\cell@font + \ignorespaces\@sharp\unskip + \the@toks\the\count@ + \relax +}% +\def\@mkpream@relax{% + \let\tableftsep \relax + \let\tabmidsep \relax + \let\tabrightsep \relax + \let\array@row@rst\relax + \let\cell@font \relax + \let\@startpbox \relax +}% +\def\@mkpream@array#1{% + \gdef\@preamble{}\@lastchclass 4 \@firstamptrue + \let\@sharp\relax \let\@startpbox\relax \let\@endpbox\relax + \@temptokena{#1}\@tempswatrue + \@whilesw\if@tempswa\fi{\@tempswafalse\the\NC@list}% + \count@\m@ne + \let\the@toks\relax + \prepnext@tok + \expandafter \@tfor \expandafter \@nextchar + \expandafter :\expandafter =\the\@temptokena \do + {\@testpach + \ifcase \@chclass \@classz \or \@classi \or \@classii + \or \save@decl \or \or \@classv \or \@classvi + \or \@classvii \or \@classviii + \or \@classx + \or \@classx \fi + \@lastchclass\@chclass}% + \ifcase\@lastchclass + \@acol \or + \or + \@acol \or + \@preamerr \thr@@ \or + \@preamerr \tw@ \@addtopreamble\@sharp \or + \or + \else \@preamerr \@ne \fi + \def\the@toks{\the\toks}% +}% +\def\@mkpream@array@new#1{% + \gdef\@preamble{}% + \@lastchclass\f@ur + \@firstamptrue + \let\@sharp\relax + \@mkpream@relax + \@temptokena{#1}\@tempswatrue + \@whilesw\if@tempswa\fi{\@tempswafalse\the\NC@list}% + \count@\m@ne + \let\the@toks\relax + \prepnext@tok + \expandafter\@tfor\expandafter\@nextchar\expandafter:\expandafter=\the\@temptokena + \do{% + \@testpach + \ifcase\@chclass + \@classz + \or + \@classi + \or + \@classii + \or + \save@decl + \or + \or + \@classv + \or + \@classvi + \or + \@classvii + \or + \@classviii + \or + \@classx + \or + \@classx + \fi + \@lastchclass\@chclass + }% + \ifcase\@lastchclass + \@acolr % right-hand column + \or + \or + \@acolr % right-hand column + \or + \@preamerr\thr@@ + \or + \@preamerr\tw@\@addtopreamble\@sharp + \or + \or + \else + \@preamerr\@ne + \fi + \def\the@toks{\the\toks}% +}% +\appdef\@mkpream@relax{% + \let\CT@setup \relax + \let\CT@color \relax + \let\CT@do@color \relax + \let\color \relax + \let\CT@column@color\relax + \let\CT@row@color \relax + \let\CT@cell@color \relax +}% +\def\@addamp@LaTeX{% + \if@firstamp\@firstampfalse\else\edef\@preamble{\@preamble &}\fi +}% +\def\@addamp@ltx{% + \if@firstamp\@firstampfalse\else\@addtopreamble{&}\fi +}% +\def\@arrayacol@LaTeX{% + \edef\@preamble{\@preamble \hskip \arraycolsep}% +}% +\def\@arrayacol@ltx{% + \@addtopreamble{\hskip\arraycolsep}% +}% +\def\@tabacoll{% + \@addtopreamble{\hskip\tableftsep\relax}% +}% +\def\@tabacol@LaTeX{% + \edef\@preamble{\@preamble \hskip \tabcolsep}% +}% +\def\@tabacol@ltx{% + \@addtopreamble{\hskip\tabmidsep\relax}% +}% +\def\@tabacolr{% + \@addtopreamble{\hskip\tabrightsep\relax}% +}% +\def\@arrayclassz@LaTeX{% + \ifcase \@lastchclass \@acolampacol \or \@ampacol \or + \or \or \@addamp \or + \@acolampacol \or \@firstampfalse \@acol \fi + \edef\@preamble{\@preamble + \ifcase \@chnum + \hfil$\relax\@sharp$\hfil \or $\relax\@sharp$\hfil + \or \hfil$\relax\@sharp$\fi}% +}% +\def\@arrayclassz@ltx{% + \ifcase\@lastchclass + \@acolampacol + \or + \@ampacol + \or + \or + \or + \@addamp + \or + \@acolampacol + \or + \@firstampfalse\@acoll + \fi + \ifcase\@chnum + \@addtopreamble{% + \hfil\array@row@rst$\relax\@sharp$\hfil + }% + \or + \@addtopreamble{% + \array@row@rst$\relax\@sharp$\hfil + }% + \or + \@addtopreamble{% + \hfil\array@row@rst$\relax\@sharp$% + }% + \fi +}% +\def\@tabclassz@LaTeX{% + \ifcase\@lastchclass + \@acolampacol + \or + \@ampacol + \or + \or + \or + \@addamp + \or + \@acolampacol + \or + \@firstampfalse\@acol + \fi + \edef\@preamble{% + \@preamble{% + \ifcase\@chnum + \hfil\ignorespaces\@sharp\unskip\hfil + \or + \hskip1sp\ignorespaces\@sharp\unskip\hfil + \or + \hfil\hskip1sp\ignorespaces\@sharp\unskip + \fi}}% +}% +\def\@tabclassz@ltx{% + \ifcase\@lastchclass + \@acolampacol + \or + \@ampacol + \or + \or + \or + \@addamp + \or + \@acolampacol + \or + \@firstampfalse\@acoll + \fi + \ifcase\@chnum + \@addtopreamble{% + {\hfil\array@row@rst\cell@font\ignorespaces\@sharp\unskip\hfil}% + }% + \or + \@addtopreamble{% + {\cell@fil\hskip1sp\array@row@rst\cell@font\ignorespaces\@sharp\unskip\hfil}% + }% + \or + \@addtopreamble{% + {\hfil\hskip1sp\array@row@rst\cell@font\ignorespaces\@sharp\unskip\cell@fil}% + }% + \fi +}% +\def\@tabclassiv@LaTeX{% + \@addtopreamble\@nextchar +}% +\def\@tabclassiv@ltx{% + \expandafter\@addtopreamble\expandafter{\@nextchar}% +}% +\def\@arrayclassiv@LaTeX{% + \@addtopreamble{$\@nextchar$}% +}% +\def\@arrayclassiv@ltx{% + \expandafter\@addtopreamble\expandafter{\expandafter$\@nextchar$}% +}% +\def\@classv@LaTeX{% + \@addtopreamble{\@startpbox{\@nextchar}\ignorespaces + \@sharp\@endpbox}% +}% +\def\@classv@ltx{% + \expandafter\@addtopreamble + \expandafter{% + \expandafter \@startpbox + \expandafter {\@nextchar}% + \pbox@hook\array@row@rst\cell@font\ignorespaces\@sharp\@endpbox + }% +}% +\def\@classx@array{% + \ifcase \@lastchclass + \@acolampacol \or + \@addamp \@acol \or + \@acolampacol \or + \or + \@acol \@firstampfalse \or + \@addamp + \fi +}% +\def\@classx@array@new{% + \ifcase \@lastchclass + \@acolampacol + \or + \@addamp \@acol + \or + \@acolampacol + \or + \or + \@firstampfalse\@acoll + \or + \@addamp + \fi +}% +\def\@xbitor@LaTeX #1{\@tempcntb \count#1 + \ifnum \@tempcnta =\z@ + \else + \divide\@tempcntb\@tempcnta + \ifodd\@tempcntb \@testtrue\fi + \fi}% +\def\@xbitor@ltx#1{% + \@tempcntb\count#1\relax + \@ifnum{\@tempcnta=\z@}{}{% + \divide\@tempcntb\@tempcnta + \@ifodd\@tempcntb{\@testtrue}{}% + }% +}% +\@ifx{\@xbitor\@xbitor@LaTeX}{% + \class@info{Repairing broken LaTeX \string\@xbitor}% +}{% + \class@info{Unrecognized LaTeX \string\@xbitor. Please update this document class! (Proceeding with fingers crossed.)}% +}% +\let\@xbitor\@xbitor@ltx +\newcommand*\@gobble@opt@one[2][]{}% +\def\@starttoc#1{% + \begingroup + \toc@pre + \makeatletter + \@input{\jobname.#1}% + \if@filesw + \expandafter\newwrite\csname tf@#1\endcsname + \immediate\openout \csname tf@#1\endcsname \jobname.#1\relax + \fi + \@nobreakfalse + \toc@post + \endgroup +}% +\def\toc@pre{}% +\def\toc@post{}% +\def\toc@@font{}% +\def\ltxu@dotsep{\z@}% +\let\tocdim@section \leftmargini +\let\tocdim@subsection \leftmarginii +\let\tocdim@subsubsection \leftmarginiii +\let\tocdim@paragraph \leftmarginiv +\let\tocdim@appendix \leftmarginv +\let\tocdim@pagenum \leftmarginvi +\def\toc@pre@auto{% + \toc@@font + \@tempdima\z@ + \toc@setindent\@tempdima{section}% + \toc@setindent\@tempdima{subsection}% + \toc@setindent\@tempdima{subsubsection}% + \toc@setindent\@tempdima{paragraph}% + \toc@letdimen{appendix}% + \toc@letdimen{pagenum}% +}% +\def\toc@post@auto{% + \if@filesw + \begingroup + \toc@writedimen{section}% + \toc@writedimen{subsection}% + \toc@writedimen{subsubsection}% + \toc@writedimen{paragraph}% + \toc@writedimen{appendix}% + \toc@writedimen{pagenum}% + \endgroup + \fi +}% +\def\toc@setindent#1#2{% + \csname tocdim@#2\endcsname\tocdim@min\relax + \@ifundefined{tocmax@#2}{\@namedef{tocmax@#2}{\z@}}{}% + \advance#1\@nameuse{tocmax@#2}\relax + \expandafter\edef\csname tocleft@#2\endcsname{\the#1}% +}% +\def\toc@letdimen#1{% + \csname tocdim@#1\endcsname\tocdim@min\relax + \@ifundefined{tocmax@#1}{\@namedef{tocmax@#1}{\z@}}{}% + \expandafter\let\csname tocleft@#1\expandafter\endcsname\csname tocmax@#1\endcsname +}% +\def\toc@writedimen#1{% + \immediate\write\@auxout{% + \gdef\expandafter\string\csname tocmax@#1\endcsname{% + \expandafter\the\csname tocdim@#1\endcsname + }% + }% +}% +\def\l@@sections#1#2#3#4{% + \begingroup + \everypar{}% + \set@tocdim@pagenum\@tempboxa{#4}% + \global\@tempdima\csname tocdim@#2\endcsname + \leftskip\csname tocleft@#2\endcsname\relax + \dimen@\csname tocleft@#1\endcsname\relax + \parindent-\leftskip\advance\parindent\dimen@ + \rightskip\tocleft@pagenum plus 1fil\relax + \skip@\parfillskip\parfillskip\z@ + \let\numberline\numberline@@sections + \@nameuse{l@f@#2}% + \ignorespaces#3\unskip\nobreak\hskip\skip@ + \hb@xt@\rightskip{\hfil\unhbox\@tempboxa}\hskip-\rightskip\hskip\z@skip + \expandafter\par + \expandafter\aftergroup\csname tocdim@#2% + \expandafter\endcsname + \expandafter\endgroup + \the\@tempdima\relax +}% +\def\set@tocdim@pagenum#1#2{% + \setbox#1\hbox{\ignorespaces#2}% + \@ifdim{\tocdim@pagenum<\wd#1}{\global\tocdim@pagenum\wd#1}{}% +}% +\def\numberline@@sections#1{% + \leavevmode\hb@xt@-\parindent{% + \hfil + \@if@empty{#1}{}{% + \setbox\z@\hbox{#1.\kern\ltxu@dotsep}% + \@ifdim{\@tempdima<\wd\z@}{\global\@tempdima\wd\z@}{}% + \unhbox\z@ + }% + }% + \ignorespaces +}% +\def\tocdim@min{\z@}% +\def\list#1#2{% + \ifnum \@listdepth >5\relax + \@toodeep + \else + \global\advance\@listdepth\@ne + \fi + \rightmargin\z@ + \listparindent\z@ + \itemindent\z@ + \csname @list\romannumeral\the\@listdepth\endcsname + \def\@itemlabel{#1}% + \let\makelabel\@mklab + \@nmbrlistfalse + #2\relax + \@trivlist + \parskip\parsep + \set@listindent + \ignorespaces +}% +\def\set@listindent@parshape{% + \parindent\listparindent + \advance\@totalleftmargin\leftmargin + \advance\linewidth-\rightmargin + \advance\linewidth-\leftmargin + \parshape\@ne\@totalleftmargin\linewidth +}% +\def\set@listindent@{% + \parindent\listparindent + \advance\@totalleftmargin\leftmargin + \advance\rightskip\rightmargin + \advance\leftskip\@totalleftmargin +}% +\let\set@listindent\set@listindent@parshape +\providecommand\href[0]{\begingroup\@sanitize@url\@href}% +\def\@href#1{\@@startlink{#1}\endgroup\@@href}% +\def\@@href#1{#1\@@endlink}% +\providecommand \url [0]{\begingroup\@sanitize@url \@url }% +\def \@url #1{\endgroup\@href {#1}{\URL@prefix#1}}% +\providecommand \URL@prefix [0]{URL }% +\providecommand\doi[0]{\begingroup\@sanitize@url\@doi}% +\def\@doi#1{\endgroup\@@startlink{\doibase#1}doi:\discretionary {}{}{}#1\@@endlink }% +\providecommand \doibase [0]{https://doi.org/}% +\providecommand \@sanitize@url[0]{\chardef\cat@space\the\catcode`\ \@sanitize\catcode`\ \cat@space}% +\def\@@startlink#1{}% +\def\@@endlink{}% +\@ifxundefined \pdfoutput {\true@sw}{\@ifnum{\z@=\pdfoutput}{\true@sw}{\false@sw}}% +{% + \def\@@startlink@hypertext#1{\leavevmode\special{html:}}% + \def\@@endlink@hypertext{\special{html:}}% +}{% + \def\@@startlink@hypertext#1{% + \leavevmode + \pdfstartlink\pdfstartlink@attr + user{/Subtype/Link/A<>}% + \relax + }% + \def\@@endlink@hypertext{\pdfendlink}% + \def\pdfstartlink@attr{attr{/Border[0 0 1 ]/H/I/C[0 1 1]}}% +}% +\def\hypertext@enable@ltx{% + \let\@@startlink\@@startlink@hypertext + \let\@@endlink\@@endlink@hypertext +}% +\def\href@Hy{\hyper@normalise \href@ }% +\def\href@Hy@ltx{\@ifnextchar\bgroup\Hy@href{\hyper@normalise\href@}}% +\def\Hy@href#{\hyper@normalise\href@}% +\begingroup + \endlinechar=-1 % + \catcode`\^^A=14 % + \catcode`\^^M\active + \catcode`\%\active + \catcode`\#\active + \catcode`\_\active + \catcode`\$\active + \catcode`\&\active + \gdef\hyper@normalise@ltx{^^A + \begingroup + \catcode`\^^M\active + \def^^M{ }^^A + \catcode`\%\active + \let%\@percentchar + \let\%\@percentchar + \catcode`\#\active + \def#{\hyper@hash}^^A + \def\#{\hyper@hash}^^A + \@makeother\&^^A + \edef&{\string&}^^A + \edef\&{\string&}^^A + \edef\textunderscore{\string_}^^A + \let\_\textunderscore + \catcode`\_\active + \let_\textunderscore + \let~\hyper@tilde + \let\~\hyper@tilde + \let\textasciitilde\hyper@tilde + \let\\\@backslashchar + \edef${\string$}^^A + \Hy@safe@activestrue + \hyper@n@rmalise + }^^A + \catcode`\#=6 ^^A + \gdef\Hy@ActiveCarriageReturn@ltx{^^M}^^A + \gdef\hyper@n@rmalise@ltx#1#2{^^A + \def\Hy@tempa{#2}^^A + \ifx\Hy@tempa\Hy@ActiveCarriageReturn + \Hy@ReturnAfterElseFi{^^A + \hyper@@normalise{#1}^^A + }^^A + \else + \Hy@ReturnAfterFi{^^A + \hyper@@normalise{#1}{#2}^^A + }^^A + \fi + }^^A + \gdef\hyper@@normalise@ltx#1#2{^^A + \edef\Hy@tempa{^^A + \endgroup + \noexpand#1{\Hy@RemovePercentCr#2%^^M\@nil}^^A + }^^A + \Hy@tempa + }^^A + \gdef\Hy@RemovePercentCr@ltx#1%^^M#2\@nil{^^A + #1^^A + \ifx\limits#2\limits + \else + \Hy@ReturnAfterFi{^^A + \Hy@RemovePercentCr #2\@nil + }^^A + \fi + }^^A +\endgroup +\def\switch@hyperref@href{% + \expandafter\@ifx\expandafter{\csname href \endcsname\href@Hy}{ + \class@info{Repairing hyperref 6.75r \string\href}% + \let\hyper@normalise\hyper@normalise@ltx + \let\hyper@@normalise\hyper@@normalise@ltx + \let\hyper@n@rmalise\hyper@n@rmalise@ltx + \let\Hy@ActiveCarriageReturn\Hy@ActiveCarriageReturn@ltx + \let\Hy@RemovePercentCr\Hy@RemovePercentCr@ltx + \let\href\href@Hy@ltx + }{}% +}% +\appdef\document@inithook{\switch@hyperref@href}% +\def\typeout@org#1{% + \begingroup + \set@display@protect + \immediate\write\@unused{#1}% + \endgroup +}% +\long\def\typeout@ltx#1{% + \begingroup + \set@display@protect + \immediate\write\@unused{#1}% + \endgroup +}% +\@ifx{\typeout\typeout@org}{% + \let\typeout\typeout@ltx + \true@sw +}{% + \rvtx@ifformat@geq{2020/10/01}% + {\true@sw}{\false@sw}% +}% + {\class@info{Making \string\typeout\space \string\long}}% + {}% +\typeout{% +ltxfront% + [2022/06/05 4.2f frontmatter package (AO,DPC,MD)]% \fileversion +}% +\appdef\class@documenthook{\frontmatter@init}% +\let\frontmatter@init\@empty +\newcommand\frontmatter@title[2][]{% + \def\@title{#2}% + \def\@shorttitle{#1}% + \let\@AF@join\@title@join +}% +\appdef\frontmatter@init{% + \def\@title{\class@warn{No title}}% + \let\@shorttitle\@empty + \let\@title@aux\@title@aux@cleared +}% +\def\@title@join{\expandafter\@title@join@\@title@aux}% +\def\@title@join@#1#2{% + \def\@title@aux{{\@join{\@separator}{#1}{#2}}}% +}% +\def\@title@aux@cleared{{}}% +\newcounter{affil}% +\newcounter{collab}% +\appdef\frontmatter@init{% + \c@affil\z@ + \c@collab\z@ +}% +\newcommand\frontmatter@author{% implicit #1 + \@author@def{}% implicit #2 +}% +\def\collaboration{% implicit #1 + \@author@def{\@booleantrue\collaboration@sw}% implicit #2 +}% +\appdef\frontmatter@init{% + \@booleanfalse\collaboration@sw +}% +\def\@author@cleared{{}{}{}}% +\def\@author@gobble#1#2#3{}% +\def\@author@init{% + \let\@author\@author@cleared + \@booleanfalse\collaboration@sw +}% +\def\@authorclear@sw{\@ifx{\@author\@author@cleared}}% +\appdef\frontmatter@init{% + \@author@init +}% +\def\@author@def#1#2{% + \frontmatterverbose@sw{\typeout{\string\author\space\string\collaboration}}{}% + \move@AU\move@AF\move@AUAF + \let\@AF@join\@author@join + #1% + \def\@author{{#2}{}}% +}% +\def\@author@join@#1#2#3{% + \def\@author{{#1}{\@join{\@separator}{#2}{#3}}}% +}% +\def\@author@join{\expandafter\@author@join@\@author}% +\def\move@AU{% + \@authorclear@sw{}{% + \collaboration@sw{% + \advance\c@collab\@ne + \@argswap{\CO@grp\CO@opr}% + }{% + \@argswap{\AU@grp\AU@opr}% + }% + {% + \expandafter\@argswap@val + \expandafter{\@author}% + {\expandafter\@argswap@val\expandafter{\the\c@collab}{\add@AUCO@grp}}% + }% + }% + \@author@init +}% +\def\add@AUCO@grp#1#2#3#4{% + \appdef#3{#4{#1}#2}% + \frontmatterverbose@sw{\say#3}{}% +}% +\def\@author@finish{% + \frontmatterverbose@sw{\typeout{\string\@author@finish}}{}% + \move@AU\move@AF + \@ifx{\AU@grp\@empty}{% + \@ifx{\CO@grp\@empty}% + }{% + \false@sw + }% + {}{% + \@ifx{\AF@grp\@empty}{% + \begingroup + \let\href\@secondoftwo + \let\AU@opr\@secondofthree + \let\CO@opr\@secondofthree + \let\footnote\@gobble + \@ifx{\CO@grp\@empty}{% + \class@warn{Assuming \string\noaffiliation\space for authors}% + \frontmatterverbose@sw{\say\AU@grp}% + }{% + \class@warn{Assuming \string\noaffiliation\space for collaboration}% + \frontmatterverbose@sw{\say\CO@grp}{}% + }% + \endgroup + \@affil@none\move@AF + }{}% + }% + \move@AUAF +}% +\def\@secondofthree#1#2#3{#2}% +\def\@join#1#2#3{% + \@if@empty{#2}{#3}{#2#1#3}% +}% +\def\@separator{;\space}% +\let\surname\@firstofone +\let\firstname\@firstofone +\newcommand\frontmatter@and{\class@err{\protect\and\space is not supported}} +\def\cat@comma@active{\catcode`\,\active}% +{\cat@comma@active\gdef,{\active@comma}}% +\def\active@comma{,\penalty-300\relax}% +\newcommand\affiliation{% + \frontmatterverbose@sw{\typeout{\string\affiliation}}{}% + \move@AU\move@AF + \begingroup + \cat@comma@active + \@affiliation +}% +\def\@affiliation#1{% + \endgroup + \let\@AF@join\@affil@join + \@affil@def{#1}% +}% +\newcommand\frontmatter@noaffiliation{% + \frontmatterverbose@sw{\typeout{\string\noaffiliation}}{}% + \move@AU\move@AF + \@affil@none\move@AF + \move@AUAF +}% +\def\blankaffiliation{{}}% +\def\@affil@cleared{{{}}{}}% +\def\@affil@nil{{\relax}{}}% +\appdef\frontmatter@init{% + \@affil@init +}% +\def\@affil@none{% + \let\@affil\@affil@nil +}% +\def\@affil@init{% + \let\@affil\@affil@cleared +}% +\def\@affilclear@sw{\@ifx{\@affil\@affil@cleared}}% +\def\@affil@def#1{% + \def\@affil{{#1}{}}% +}% +\def\@affil@join@#1#2#3{% + \def\@affil{{#1}{\@join{\@separator}{#2}{#3}}}% +}% +\def\@affil@join{\expandafter\@affil@join@\@affil}% +\def\move@AF{% + \@affilclear@sw{}{% + \@booleanfalse\temp@sw + \let\@tempd\@empty + \@affils@sw{% + \expandafter\@affil@addr@def\expandafter\@tempa\@affil + \def\AFF@opr{\@affil@match\@tempa}% + \@AFF@list + }{}\temp@sw + {% + \expandafter\@affil@aux@def\expandafter\@tempb\@affil + \@ifx{\@tempb\@empty}{}{% + \@ifx{\@tempb\@tempd}{}{% + \class@warn{% + Ancillary information for \@tempa\space must not be different! + Please put all of it on the first instance% + }% + }% + }% + }% + {% + \@ifx{\@affil\@affil@nil}{% + \def\@tempc{0}% + \@argswap@val{0}% + }{% + \advance\c@affil\@ne + \expandafter\def\expandafter\@tempc\expandafter{\the\c@affil}% + \expandafter\@argswap@val\expandafter{\the\c@affil}% + }% + {% + \expandafter\@argswap@val\expandafter{\the\c@collab}{% + \expandafter\@argswap@val\expandafter{\@affil}{% + \add@list@val@val@val\@AFF@list\AFF@opr + }% + }% + }% + }% + \appdef@eval\AF@grp\@tempc + \frontmatterverbose@sw{\say\AF@grp}{}% + \@affil@init + }% +}% +\def\@affil@addr@def#1#2#3{% + \def#1{#2}% +}% +\def\@affil@aux@def#1#2#3{% + \def#1{#3}% +}% +\def\add@list@val@val@val#1#2#3#4#5{% + \appdef#1{#2{#5}{#4}#3}% + \frontmatterverbose@sw{\say#1}{}% +}% +\def\@affil@match#1#2#3#4#5{% + \temp@sw{}{% + \def\@tempifx{#4}% + \@ifx{\@tempifx#1}{% + \groupauthors@sw{% + \@ifnum{#3=\c@collab}{% + \true@sw + }{% + \false@sw + }% + }{% + \true@sw + }% + }{% + \false@sw + }% + {% + \@booleantrue\temp@sw + \def\@tempc{#2}% + \def\@tempd{#5}% + }{% + }% + }% +}% +\def\move@AUAF{% + \frontmatterverbose@sw{\say\AU@grp\say\AF@grp\say\CO@grp}{}% + \@ifx{\AF@grp\@empty}{% + \@ifx{\@empty\CO@grp}{% + }{% + \appdef \@AAC@list{\AF@opr{{0}}}% + \appdef@e \@AAC@list{\CO@grp}% + \appdef@e \@AFG@list{\CO@grp}% + \let\CO@grp\@empty + }% + }{% + \appdef \@AAC@list{\AF@opr}% + \appdef@eval\@AAC@list{\AF@grp}% + \appdef@e \@AAC@list{\AU@grp}% + \@ifx{\@empty\AU@grp}{% + \@ifx{\@empty\CO@grp}% + }{% + \false@sw + }% + {% + }{% + \@booleanfalse\temp@sw + \def\AFG@opr{\x@match\AF@grp}% + \let\CO@opr\@author@gobble + \@AFG@list + \temp@sw{}{% + \appdef \@AFG@list{\AFG@opr}% + \appdef@eval\@AFG@list{\AF@grp}% + }% + \@ifx{\@empty\CO@grp}{}{% + \appdef@e \@AAC@list{\CO@grp}% + \appdef@e \@AFG@list{\CO@grp}% + \let\CO@grp\@empty + }% + }% + \let\CO@grp\@empty + \let\AU@grp\@empty + \let\AF@grp\@empty + }% + \frontmatterverbose@sw{\say\@AAC@list\say\@AFG@list}{}% +}% +\appdef\frontmatter@init{% + \let\AU@grp\@empty + \let\CO@grp\@empty + \let\AF@grp\@empty + \let\@AAC@list\@empty + \let\@AFG@list\@empty + \let\@AFF@list\@empty +}% +\appdef\frontmatter@init{% + \let\@AF@join\@AF@join@error +}% +\def\@AF@join@error#1{% + \class@warn{% + \string\email, \string\homepage, \string\thanks, or \string\altaffiliation\space + appears in wrong context. + }% +}% +\def\sanitize@url{% + \@makeother\%% + \@makeother\~% + \@makeother\_% +}% +\newcommand*\email[1][]{\begingroup\sanitize@url\@email{#1}}% +\def\@email#1#2{% + \endgroup + \@AF@join{#1\href{mailto:#2}{#2}}% +}% +\newcommand*\homepage[1][]{\begingroup\sanitize@url\@homepage{#1}}% +\def\@homepage#1#2{% + \endgroup + \@AF@join{#1\href{#2}{#2}}% +}% +\appdef\class@documenthook{% + \providecommand\href[1]{}% +}% +\def\frontmatter@thanks{% implicit #1 + \@AF@join +}% +\newcommand*\altaffiliation[2][]{% + \@AF@join{#1#2}% +}% +\def\set@listcomma@list#1{% + \expandafter\@reset@ac\expandafter#1#1{0}\@reset@ac{% + \let\@listcomma\relax + }{% + \let\@listcomma\@listcomma@comma + }% +}% +\def\set@listcomma@count#1{% + \@ifnum{#1=\tw@}{% + \let\@listcomma\relax + }{% + \let\@listcomma\@listcomma@comma + }% +}% +\def\@reset@ac#1#2#3\@reset@ac{% + \def#1{#3}% + \@tempcnta#2\relax + \@ifnum{#2=\tw@}% +}% +\def\@listand{\@ifnum{\@tempcnta=\tw@}{\andname\space}{}}% +\def\@listcomma@comma{\@ifnum{\@tempcnta>\@ne}{,}{}}% +\def\@listcomma@comma@UK{\@ifnum{\@tempcnta>\tw@}{,}{}}% +\def\@collaboration@gobble#1#2#3{}% +\def\doauthor#1#2#3{% + \ignorespaces#1\unskip\@listcomma + \begingroup + #3% + \@if@empty{#2}{\endgroup{}{}}{\endgroup{\comma@space}{}\frontmatter@footnote{#2}}% + \space \@listand +}% +\def\x@match#1#2{% + \temp@sw{}{% + \def\@tempifx{#2}% + \@ifx{\@tempifx#1}{% + \@booleantrue\temp@sw + }{% + }% + }% +}% +\def\y@match#1#2#3{% + \temp@sw{}{% + \def\@tempifx{#3}% + \@ifx{\@tempifx#1}{% + \@booleantrue\temp@sw + \def\@tempb{#2}% + }{% + }% + }% +}% +\def\frontmatter@footnote#1{% + \begingroup + \@booleanfalse\temp@sw + \def\@tempa{#1}% + \let\@tempb\@empty + \def\@TBN@opr{\y@match\@tempa}% + \@FMN@list + \temp@sw{% + \expandafter\frontmatter@footnotemark + \expandafter{\@tempb}% + }{% + \stepcounter\@mpfn + \expandafter\expandafter + \expandafter\frontmatter@foot@mark + \expandafter\expandafter + \expandafter{% + \expandafter \the\csname c@\@mpfn\endcsname + }{#1}% + }% + \endgroup +}% +\def\frontmatter@foot@mark#1#2{% + \frontmatter@footnotemark{#1}% + \g@addto@macro\@FMN@list{\@TBN@opr{#1}{#2}}% +}% +\appdef\frontmatter@init{% + \global\let\@FMN@list\@empty +}% +\def\frontmatter@footnotemark#1{% + \leavevmode + \ifhmode\edef\@x@sf{\the\spacefactor}\nobreak\fi + \begingroup + \hyper@linkstart {link}{frontmatter.#1}% + \csname c@\@mpfn\endcsname#1\relax + \def\@thefnmark{\frontmatter@thefootnote}% + \@makefnmark + \hyper@linkend + \endgroup + \ifhmode\spacefactor\@x@sf\fi + \relax +}% +\def\keywords#1{% + \aftermaketitle@chk{\keywords}% + \gdef\@keywords{#1}% +}% +\appdef\frontmatter@init{% + \let\@keywords\@empty +}% +\newcommand*\frontmatter@date[2][\Dated@name]{\def\@date{#1#2}}% +\def\@date{}% +\newcommand*\received[2][\Received@name]{\def\@received{#1#2}}% +\def\@received{}% +\newcommand*\revised[2][\Revised@name]{\def\@revised{#1#2}}% +\def\@revised{}% +\newcommand*\accepted[2][\Accepted@name]{\def\@accepted{#1#2}}% +\def\@accepted{}% +\newcommand*\published[2][\Published@name]{\def\@published{#1#2}}% +\def\@published{}% +\def\pacs#1{% + \aftermaketitle@chk{\pacs}% + \gdef\@pacs{#1}% +}% +\appdef\frontmatter@init{% + \let\@pacs\@empty +}% +\def\preprint#1{\gappdef\@preprint{\preprint{#1}}}% +\appdef\frontmatter@init{% + \let\@preprint\@empty +}% +\newbox\absbox +\def\toclevel@abstract{1}% +\def\addcontents@abstract{% + \phantomsection + \expandafter\def\csname Parent0\endcsname{section*.2}% + \expandafter\@argswap@val\expandafter{\abstractname}{\addcontentsline{toc}{abstract}}% +}% +\newenvironment{frontmatter@abstract}{% + \aftermaketitle@chk{\begin{abstract}}% + \global\setbox\absbox\vbox\bgroup + \color@begingroup + \columnwidth\textwidth + \hsize\columnwidth + \@parboxrestore + \def\@mpfn{mpfootnote}\def\thempfn{\thempfootnote}\c@mpfootnote\z@ + \let\@footnotetext\frontmatter@footnotetext + \minipagefootnote@init + \let\set@listindent\set@listindent@ + \let\@listdepth\@mplistdepth \@mplistdepth\z@ + \let@environment{description}{frontmatter@description}% + \@minipagerestore + \@setminipage + \frontmatter@abstractheading + \frontmatter@abstractfont + \let\footnote\mini@note + \expandafter\everypar\expandafter{\the\everypar\addcontents@abstract\everypar{}}% +}{% + \par + \unskip + \minipagefootnote@here + \@minipagefalse %% added 24 May 89 + \color@endgroup + \egroup +}% +\long\def\frontmatter@footnotetext#1{% + \minipagefootnote@pick + \set@footnotefont + \set@footnotewidth + \@parboxrestore + \protected@edef\@currentlabel{\csname p@\@mpfn\endcsname\@thefnmark}% + \color@begingroup + \frontmatter@makefntext{% + \rule\z@\footnotesep\ignorespaces#1\@finalstrut\strutbox\vadjust{\vskip\z@skip}% + }% + \color@endgroup + \minipagefootnote@drop +}% +\def\ltx@no@footnote{% + \let\ltx@xfootnote\ltx@no@xfootnote\let\ltx@yfootnote\ltx@no@yfootnote + \let\ltx@xfootmark\ltx@no@xfootmark\let\ltx@yfootmark\ltx@no@yfootmark + \let\ltx@xfoottext\ltx@no@xfoottext\let\ltx@yfoottext\ltx@no@yfoottext +}% +\def\ltx@no@xfootnote[#1]#2{\ltx@no@footwarn\footnote}% +\def\ltx@no@yfootnote#1{\ltx@no@footwarn\footnote}% +\def\ltx@no@xfootmark[#1]{\ltx@no@footwarn\footnotemark}% +\def\ltx@no@yfootmark{\ltx@no@footwarn\footnotemark}% +\def\ltx@no@xfoottext[#1]#2{\ltx@no@footwarn\footnotetext}% +\def\ltx@no@yfoottext#1{\ltx@no@footwarn\footnotetext}% +\def\ltx@no@footwarn#1{% + \class@warn{% + The \string#1\space command is not legal on the title page; + using \string\thanks\space instead might suit you: consult the manual for details% + }% +}% +\def\frontmatter@abstractheading{% + \begingroup + \centering\large + \abstractname + \par + \endgroup +}% +\def\frontmatter@abstractfont{}% +\newenvironment{frontmatter@description}{% + \list{}{% + \leftmargin\z@ + \labelwidth\z@ + \itemindent\z@ + \let\makelabel\frontmatter@descriptionlabel + }% +}{% + \endlist +}% +\def\frontmatter@descriptionlabel#1{% + \hspace\labelsep + \normalfont\bfseries + #1:% +}% +\def\frontmatter@abstractwidth{\textwidth} +\def\frontmatter@abstract@produce{% + \par + \preprintsty@sw{% + \do@output@MVL{% + \vskip\frontmatter@preabstractspace + \vskip200\p@\@plus1fil + \penalty-200\relax + \vskip-200\p@\@plus-1fil + }% + }{% + \addvspace{\frontmatter@preabstractspace}% + }% + \begingroup + \dimen@\baselineskip + \setbox\z@\vtop{\unvcopy\absbox}% + \advance\dimen@-\ht\z@\advance\dimen@-\prevdepth + \@ifdim{\dimen@>\z@}{\vskip\dimen@}{}% + \endgroup + \begingroup + \prep@absbox + \unvbox\absbox + \post@absbox + \endgroup + \@ifx{\@empty\mini@notes}{}{\mini@notes\par}% + \addvspace\frontmatter@postabstractspace +}% +\appdef\frontmatter@init{\let\mini@notes\@empty}% +\let\prep@absbox\@empty +\let\post@absbox\@empty +\def\frontmatter@preabstractspace{.5\baselineskip} +\def\frontmatter@postabstractspace{.5\baselineskip} +\newenvironment{frontmatter@titlepage}{% + \twocolumn@sw{\onecolumngrid}{\newpage}% + \thispagestyle{titlepage}% + \setcounter{page}\@ne +}{% + \twocolumn@sw{\twocolumngrid}{\newpage}% + \twoside@sw{}{% + \setcounter{page}\@ne + }% +}% +\def\frontmatter@maketitle{% + \@author@finish + \title@column\titleblock@produce + \suppressfloats[t]% + \let\and\relax + \let\affiliation\@gobble + \let\author\@gobble + \let\@AAC@list\@empty + \let\@AFF@list\@empty + \let\@AFG@list\@empty + \let\@AF@join\@AF@join@error + \let\email\@gobble + \let\@address\@empty + \let\maketitle\relax + \let\thanks\@gobble + \let\abstract\@undefined\let\endabstract\@undefined + \titlepage@sw{% + \vfil + \clearpage + }{}% +}% +\def\maketitle@Hy{% + \let\Hy@saved@footnotemark\@footnotemark + \let\Hy@saved@footnotetext\@footnotetext + \let\@footnotemark\H@@footnotemark + \let\@footnotetext\H@@footnotetext + \@ifnextchar[%] + \Hy@maketitle@optarg + {% + \HyOrg@maketitle + \Hy@maketitle@end + }% +}% +\appdef\class@documenthook{% + \@ifx{\maketitle\maketitle@Hy}{% + \class@info{Taking \string\maketitle\space back from hyperref}% + \let\maketitle\frontmatter@maketitle + }{% + }% +}% +\def\titleblock@produce{% + \begingroup + \ltx@footnote@pop + \def\@mpfn{mpfootnote}% + \def\thempfn{\thempfootnote}% + \c@mpfootnote\z@ + \let\@makefnmark\frontmatter@makefnmark + \frontmatter@setup + \thispagestyle{titlepage}\label{FirstPage}% + \frontmatter@title@produce + \groupauthors@sw{% + \frontmatter@author@produce@group + }{% + \frontmatter@author@produce@script + }% + \frontmatter@RRAPformat{% + \expandafter\produce@RRAP\expandafter{\@date}% + \expandafter\produce@RRAP\expandafter{\@received}% + \expandafter\produce@RRAP\expandafter{\@revised}% + \expandafter\produce@RRAP\expandafter{\@accepted}% + \expandafter\produce@RRAP\expandafter{\@published}% + }% + \frontmatter@abstract@produce + \@ifx@empty\@pacs{}{% + \@pacs@produce\@pacs + }% + \@ifx@empty\@keywords{}{% + \@keywords@produce\@keywords + }% + \par + \frontmatter@finalspace + \endgroup +}% +\def\toclevel@title{0}% +\def\frontmatter@title@produce{% + \begingroup + \frontmatter@title@above + \frontmatter@title@format + \@title + \unskip + \phantomsection\expandafter\@argswap@val\expandafter{\@title}{\addcontentsline{toc}{title}}% + \@ifx{\@title@aux\@title@aux@cleared}{}{% + \expandafter\frontmatter@footnote\expandafter{\@title@aux}% + }% + \par + \frontmatter@title@below + \endgroup +}% +\appdef\let@mark{\let\\\relax}% +\def\frontmatter@title@above{}% +\def\frontmatter@title@format{}% +\def\frontmatter@title@below{\addvspace{\baselineskip}}% +\def\frontmatter@author@produce@script{% + \begingroup + \let\@author@present\@author@present@script + \frontmatterverbose@sw{\typeout{\string\frontmatter@author@produce@script:}\say\@AAC@list\say\@AFF@list\say\@AFG@list}{}% + \let\AU@temp\@empty + \@tempcnta\z@ + \let\AF@opr \@gobble + \def\AU@opr{\@author@count\@tempcnta}% + \def\CO@opr{\@collaboration@count\AU@temp\@tempcnta}% + \@AAC@list + \expandafter\CO@opr\@author@cleared + \begingroup + \frontmatter@authorformat + \let\AF@opr \@affilID@def + \let\AU@opr \@author@present + \def\CO@opr{\@collaboration@present\AU@temp}% + \set@listcomma@list\AU@temp + \@AAC@list + \unskip\unskip + \par + \endgroup + \begingroup + \frontmatter@above@affiliation@script + \let\AFF@opr \@affil@script + \@AFF@list + \frontmatter@footnote@produce + \par + \endgroup + \endgroup +}% +\def\@author@count#1{% + \advance#1\@ne + \@author@gobble +}% +\def\@collaboration@present#1#2#3#4{% + \par + \begingroup + \frontmatter@collaboration@above + \@affilID@def{}% + \@tempcnta\z@ + \@author@present{}{(\ignorespaces#3\unskip)}{#4}% + \par + \endgroup + \set@listcomma@list#1% +}% +\def\frontmatter@collaboration@above{}% +\def\@collaboration@count#1#2{% + \appdef@eval#1{\the#2}#2\z@ + \@author@gobble +}% +\def\@affilID@def{\def\@affilID@temp}% +\let\@affilID@temp\@empty +\def\affil@script#1#2#3{% + \def\@tempifx{#1}\@ifx{\@tempifx\@tempa}{% + \@if@empty{#2}{}{% + \par + \begingroup + \def\@thefnmark{#1}\@makefnmark\ignorespaces + #2% + \@if@empty{#3}{}{\frontmatter@footnote{#3}}% + \par + \endgroup + }% + }{}% +}% +\def\@affil@script#1#2#3#4{% + \@ifnum{#1=\z@}{}{% + \par + \begingroup + \frontmatter@affiliationfont + \@ifnum{\c@affil<\affil@cutoff}{}{% + \def\@thefnmark{#1}\@makefnmark + }% + \ignorespaces#3% + \@if@empty{#4}{}{\frontmatter@footnote{#4}}% + \par + \endgroup + }% +}% +\let\affil@cutoff\@ne +\def\@author@present@script#1#2#3{% + \begingroup + \gdef\comma@space{\textsuperscript{,\,}}% + \doauthor{#2}{#3}{\@affil@present@script}% + \endgroup + \advance\@tempcnta\m@ne +}% +\def\@affilcomma#1#2{% + \@ifx{\z@#1}{% + \@ifx{\relax#2}{}{% + \@affilcomma{#2}% + }% + }{% + #1% + \@ifx{\relax#2}{}{% + \@ifx{\z@#2}{% + \@affilcomma + }{% + ,\,\@affilcomma{#2}% + }% + }% + }% +}% +\def\@affil@present@script{% + \let\@tempa\@empty + \expandafter\@affil@present@script@\@affilID@temp\relax +}% +\def\@affil@present@script@#1{% + \@ifx{\relax#1}{% + \@ifx{\@tempa\@empty}{% + \aftergroup\false@sw + }{% + \textsuperscript{\expandafter\@affilcomma\@tempa\relax\relax}% + \aftergroup\true@sw + }% + }{% + \@ifnum{#1=\z@}{}{\appdef\@tempa{{#1}}}% + \@affil@present@script@ + }% +}% +\@provide\@author@parskip{\z@skip}% +\def\frontmatter@author@produce@group{% + \begingroup + \let\@author@present\@author@present@group + \frontmatter@authorformat + \frontmatterverbose@sw{\typeout{\string\frontmatter@author@produce@group:}\say\@AAC@list\say\@AFF@list\say\@AFG@list}{}% + \let\AU@temp\@empty + \set@listcomma@list\AU@temp + \def\CO@opr{\@collaboration@present\AU@temp}% + \let\AFG@opr \affils@present@group + \let\@listcomma\relax + \@AFG@list + \frontmatter@footnote@produce + \par + \endgroup + \frontmatter@authorbelow +}% +\@provide\frontmatter@authorbelow{}% +\def\affils@present@group#1{% + \begingroup + \def\AF@temp{#1}% + \@tempcnta\z@ + \let\AU@opr \@undefined + \let\CO@opr \@undefined + \def\AF@opr{\@affilID@count\AF@temp\@tempcnta}% + \@AAC@list + \@ifnum{\@tempcnta=\z@}{}{% + \begingroup + \frontmatter@above@affilgroup + \set@listcomma@count\@tempcnta + \let\AU@opr \@undefined + \let\CO@opr \@undefined + \def\AF@opr{\@affilID@match\AF@temp}% + \@AAC@list + \endgroup + \begingroup + \par + \frontmatter@above@affiliation + \frontmatter@affiliationfont + \let\\\frontmatter@addressnewline + \@tempcnta\z@ + \@tfor\AF@temp:=#1\do{% + \expandafter\@ifx\expandafter{\expandafter\z@\AF@temp}{}{% + \advance\@tempcnta\@ne + }% + }% + \@ifnum{\@tempcnta=\tw@}{% + \let\@listcomma\relax + }{}% + \def@after@address + \runinaddress@sw{% + }{% + \tightenlines@sw{}{% + \parskip\z@ + }% + \appdef\after@address\par + }% + \let\AFF@opr \@affil@group + \do@affil@fromgroup\@AFF@list#1\relax + \endgroup + }% + \par + \endgroup +}% +\def\def@after@address{\def\after@address{\@listcomma\ \@listand}}% +\def\def@after@address@empty{\let\after@address\@empty}% +\def\@affilID@count#1#2#3{% + \def\@tempifx{#3}% + \@ifx{\@tempifx#1}{% + \def\AU@opr{\@author@count#2}% + }{% + \let\AU@opr \@author@gobble + }% + \let\CO@opr \@collaboration@gobble +}% +\def\@affilID@match#1#2{% + \def\@tempifx{#2}% + \@ifx{\@tempifx#1}{% + \let\AU@opr \@author@present + }{% + \let\AU@opr \@author@gobble + }% + \let\CO@opr \@collaboration@gobble +}% +\def\do@affil@fromgroup#1#2{% + \@ifx{\relax#2}{}{% + \count@#2\relax + \@ifnum{\z@=\count@}{}{#1}% + \do@affil@fromgroup#1% + }% +}% +\def\@affil@group#1#2#3#4{% + \@ifnum{#1=\count@}{% + \def\@tempa{#3}% + \@ifx{\@tempa\blankaffiliation}{}{% + #3% + \@if@empty{#4}{}{% + \frontmatter@footnote{#4}% + }% + \after@address + }% + \advance\@tempcnta\m@ne + }{}% +}% +\def\@author@present@group#1#2#3{% + \gdef\comma@space{\gdef\comma@space{\textsuperscript{,\,}}}% + \doauthor{#2}{#3}{\@affil@present@group}% + \advance\@tempcnta\m@ne +}% +\def\@affil@present@group{% + \aftergroup\false@sw +}% +\def\@pacs@produce#1{% + \showPACS@sw{% + \begingroup + \frontmatter@PACS@format + \@pacs@name#1\par + \endgroup + }{% + \@if@empty{#1}{}{% + \class@warn{\PACS@warn}% + }% + }% +}% +\def\PACS@warn{If you want your PACS to appear in your output, use document class option showpacs}% +\def\@keywords@produce#1{% + \showKEYS@sw{% + \begingroup + \frontmatter@keys@format + \@keys@name#1\par + \endgroup + }{% + \@if@empty{#1}{}{% + \class@warn{If you want your keywords to appear in your output, use document class option showkeys}% + }% + }% +}% +\def\frontmatter@footnote@produce@footnote{% + \let\@TBN@opr\present@FM@footnote + \@FMN@list + \global\let\@FMN@list\@empty +}% +\def\present@FM@footnote#1#2{% + \begingroup + \csname c@\@mpfn\endcsname#1\relax + \def\@thefnmark{\frontmatter@thefootnote}% + \frontmatter@footnotetext{#2}% + \endgroup +}% +\def\frontmatter@footnote@produce@endnote{% +}% +\appdef\frontmatter@init{% + \@ifxundefined\title@column {\let\title@column\@empty}{}% + \@ifxundefined\preprintsty@sw {\@booleanfalse\preprintsty@sw}{}% + \@ifxundefined\frontmatter@footnote@produce{\let\frontmatter@footnote@produce\frontmatter@footnote@produce@footnote}{}% + \@ifxundefined\do@output@MVL {\let\do@output@MVL\@firstofone}{}% + \@ifxundefined\comma@space {\let\comma@space\@empty}{}% +}% +\def\frontmatter@thefootnote{% + \altaffilletter@sw{\@alph}{\@fnsymbol}{\csname c@\@mpfn\endcsname}% +}% +\@ifx{\altaffilletter@sw\@undefined}{\@booleantrue\altaffilletter@sw}{}% +\def\frontmatter@makefnmark{% + \@textsuperscript{% + \normalfont\@thefnmark + }% +}% +\long\def\frontmatter@makefntext#1{% + \parindent 1em + \noindent + \Hy@raisedlink{\hyper@anchorstart{frontmatter.\expandafter\the\csname c@\@mpfn\endcsname}\hyper@anchorend}% + \@makefnmark + #1% +}% +\def\frontmatter@setup{}% +\def\frontmatter@RRAPformat#1{% + \removelastskip + \begingroup + \frontmatter@RRAP@format + #1\par + \endgroup +}% +\def\punct@RRAP{; }% +\def\produce@RRAP#1{% + \@if@empty{#1}{}{% + \@ifvmode{\leavevmode}{\unskip\punct@RRAP\ignorespaces}% + #1% + }% +}% +\def\frontmatter@authorformat{}% +\def\frontmatter@above@affilgroup{}% +\def\frontmatter@above@affiliation{}% +\def\frontmatter@above@affiliation@script{}% +\def\frontmatter@affiliationfont{\itshape\selectfont}% +\def\frontmatter@RRAP@format{}% +\def\frontmatter@PACS@format{}% +\def\frontmatter@keys@format{}% +\def\frontmatter@finalspace{\addvspace{18\p@}} +\def\frontmatter@addressnewline{% + \@ifhmode{\skip@\lastskip\unskip\unpenalty\break\hskip\skip@}{}% + % was: \vskip-.5ex +}% +\def\frontmatter@preabstractspace{5.5\p@} +\def\frontmatter@postabstractspace{6.5\p@} +\def\aftermaketitle@chk#1{% + \@ifx{\maketitle\relax}{% + \class@err{\protect#1 must be used before \protect\maketitle}% + }{}% +}% +\def\ps@titlepage{\ps@empty}% +\def\volumeyear#1{\gdef\@volumeyear{#1}}% +\def\@volumeyear{}% +\def\volumenumber#1{\gdef\@volumenumber{#1}}% +\def\@volumenumber{}% +\def\issuenumber#1{\gdef\@issuenumber{#1}}% +\def\@issuenumber{}% +\def\eid#1{\gdef\@eid{#1}}% +\def\@eid{}% +\def\startpage#1{\gdef\@startpage{#1}\c@page#1\relax}% +\def\@startpage{\pageref{FirstPage}}% +\def\endpage#1{\gdef\@endpage{#1}}% +\def\@endpage{\pageref{LastPage}}% +\def\print@toc#1{% + \begingroup + \expandafter\section + \expandafter*% + \expandafter{% + \csname#1name\endcsname + }% + \let\appendix\appendix@toc + \@starttoc{#1}% + \endgroup +}% +\def\appendix@toc{}% +\def\Dated@name{Dated }% +\def\Received@name{Received }% +\def\Revised@name{Revised }% +\def\Accepted@name{Accepted }% +\def\Published@name{Published }% +\appdef\robustify@contents{% + \let\thanks\@gobble\let\class@warn\@gobble + \def\begin{\string\begin}\def\end{\string\end}% +}% +\@ifxundefined\frontmatter@syntax@sw{\@booleantrue\frontmatter@syntax@sw}{}% +\frontmatter@syntax@sw{% + \let\title \frontmatter@title + \let\author \frontmatter@author + \let\date \frontmatter@date + \@ifxundefined\@maketitle{% + \let\maketitle \frontmatter@maketitle + \@booleantrue \titlepage@sw + }{% + \let\@maketitle \frontmatter@maketitle + \prepdef\maketitle\@author@finish + }% + \let\noaffiliation \frontmatter@noaffiliation + \let\thanks@latex \thanks + \let\thanks \frontmatter@thanks + \let\and@latex \and + \let\and \frontmatter@and + \let@environment{titlepage}{frontmatter@titlepage}% + \let@environment{abstract}{frontmatter@abstract}% +}{% + \let\noaffiliation\@empty +}% +\typeout{% +ltxgrid% + [2022/06/05 4.2f page grid package (portions licensed from W. E. Baxter web at superscript.com)]% \fileversion +}% +\newcounter{linecount} +\def\loop@line#1#2{% + \par + \hb@xt@\hsize{% + \global\advance#1\@ne + \edef\@tempa{\@ifnum{100>#1}{0}{}\@ifnum{10>#1}{0}{}\number#1}% + \@tempa\edef\@tempa{\special{line:\@tempa}}\@tempa + \vrule depth2.5\p@#2\leaders\hrule\hfil + }% +}% +\def\lineloop#1{% + \loopwhile{\loop@line\c@linecount{}\@ifnum{#1>\c@linecount}}% +}% +\def\linefoot#1{% + \loop@line\c@linecount{% + \footnote{% + #1\special{foot:#1}\vrule depth2.5\p@\leaders\hrule\hfill + }% + }% +}% +\let\@@mark\mark +\let\@@topmark\topmark +\let\@@firstmark\firstmark +\let\@@botmark\botmark +\let\@@splitfirstmark\splitfirstmark +\let\@@splitbotmark\splitbotmark +\def\@themark{{}{}{}{}}% +\def\nul@mark{{}{}{}{}\@@nul}% +\def\set@mark@netw@#1#2#3#4#5#6#7{\gdef#1{{#6}{#7}{#4}{#5}}\do@mark}% +\def\set@marktw@#1#2#3#4#5#6{\gdef#1{{#2}{#6}{#4}{#5}}\do@mark}% +\def\set@markthr@@#1#2#3#4#5#6{\gdef#1{{#2}{#3}{#6}{#5}}\do@mark}% +\def\get@mark@@ne#1#2#3#4#5\@@nul{#1}% +\def\get@mark@tw@#1#2#3#4#5\@@nul{#2}% +\def\get@mark@thr@@#1#2#3#4#5\@@nul{#3}% +\def\get@mark@f@ur#1#2#3#4#5\@@nul{#4}% +\def\mark@netw@{\expandafter\set@mark@netw@\expandafter\@themark\@themark}% +\def\marktw@{\expandafter\set@marktw@\expandafter\@themark\@themark}% +\def\markthr@@{\expandafter\set@markthr@@\expandafter\@themark\@themark}% +\def\do@mark{\do@@mark\@themark\nobreak@mark}% +\def\do@@mark#1{% + \begingroup + \let@mark + \@@mark{#1}% + \endgroup +}% +\def\let@mark{% + \let\protect\@unexpandable@protect + \let\label\relax + \let\index\relax + \let\glossary\relax +}% +\def\nobreak@mark{% + \@if@sw\if@nobreak\fi{\@ifvmode{\nobreak}{}}{}% +}% +\def\mark@envir{\markthr@@}% +\def\bot@envir{% + \expandafter\expandafter + \expandafter\get@mark@thr@@ + \expandafter\@@botmark + \nul@mark +}% +\def\markboth{\mark@netw@}% +\def\markright{\marktw@}% +\def\leftmark{% + \expandafter\expandafter + \expandafter\get@mark@@ne + \expandafter\saved@@botmark + \nul@mark +}% +\def\rightmark{% + \expandafter\expandafter + \expandafter\get@mark@tw@ + \expandafter\saved@@firstmark + \nul@mark +}% +\let\primitive@output\output +\long\def\@tempa#1\@@nil{#1}% + \toks@ +\expandafter\expandafter +\expandafter{% +\expandafter \@tempa + \the\primitive@output + \@@nil + }% +\newtoks\output@latex +\output@latex\expandafter{\the\toks@}% +\let\output\output@latex +\primitive@output{\dispatch@output}% +\def\dispatch@output{% + \let\par\@@par + \expandafter\let\expandafter\output@procedure\csname output@\the\outputpenalty\endcsname + \@ifnotrelax\output@procedure{}{% + \expandafter\def\expandafter\output@procedure\expandafter{\the\output@latex}% + }% + \expandafter\@ifx\expandafter{\csname output@-\the\execute@message@pen\endcsname\output@procedure}{% + \let\output@procedure\@message@saved + }{}% + \ltxgrid@info@sw{\class@info{\string\dispatch@output}\say\output@procedure\saythe\holdinginserts}{}% + \outputdebug@sw{\output@debug}{}% + \output@procedure +}% +\def\set@output@procedure#1#2{% + \count@\outputpenalty\advance\count@-#2% + \expandafter\let\expandafter#1\csname output@\the\count@\endcsname +}% +\def\output@debug{% + \def\@tempa{\save@message}% + \@ifx{\output@procedure\@tempa}{% + \true@sw + }{% + \@ifnum{\outputpenalty=-\save@column@insert@pen}{% + \@ifnum{\holdinginserts>\z@}% + }{% + \false@sw + }% + }% + {}{\output@debug@}% +}% +\def\output@debug@{% + \saythe\outputpenalty + \saythe\interlinepenalty + \saythe\brokenpenalty + \saythe\clubpenalty + \saythe\widowpenalty + \saythe\displaywidowpenalty + \saythe\predisplaypenalty + \saythe\interdisplaylinepenalty + \saythe\postdisplaypenalty + \saythe\badness + \say\thepagegrid + \saythe\pagegrid@col + \saythe\pagegrid@cur + \saythe\insertpenalties + \say\@@botmark + \saythe\pagegoal + \saythe\pagetotal + \saythe{\badness\@cclv}% + \say\@toplist + \say\@botlist + \say\@dbltoplist + \say\@deferlist + \trace@scroll{% + \showbox\@cclv + \showbox\@cclv@saved + \showbox\pagesofar + \showbox\csname col@1\endcsname + \showbox\footsofar + \showbox\footins + \showbox\footins@saved + \showlists + }% +}% +\@ifxundefined{\outputdebug@sw}{% + \@booleanfalse\outputdebug@sw +}{}% +\def\trace@scroll#1{\begingroup\showboxbreadth\maxdimen\showboxdepth\maxdimen\scrollmode#1\endgroup}% +\def\trace@box#1{\trace@scroll{\showbox#1}}% +\prepdef\@outputpage{\@outputpage@head}% +\let\@outputpage@head\@empty +\appdef\@outputpage{\@outputpage@tail}% +\let\@outputpage@tail\@empty +\def\show@box@size#1#2{% + \show@box@size@sw{% + \begingroup + \setbox\z@\vbox{\unvcopy#2\hrule}% + \class@info{Show box size: #1^^J% + (\the\ht\z@\space X \the\wd\z@) + \the\c@page\space\space\the\pagegrid@cur\space\the\pagegrid@col + }% + \endgroup + }{}% +}% +\def\show@text@box@size{% + \show@box@size{Text column}\@outputbox + \tally@box@size@sw{% + \@ifdim{\wd\@outputbox>\z@}{% + \dimen@\ht\@outputbox\divide\dimen@\@twopowerfourteen + \advance\dimen@-\dp\csname box@size@\the\pagegrid@col\endcsname + \@ifdim{\dimen@>\z@}{% + \advance\dimen@ \ht\csname box@size@\the\pagegrid@col\endcsname + \global\ht\csname box@size@\the\pagegrid@col\endcsname\dimen@ + \show@box@size@sw{% + \class@info{Column: \the\dimen@}% + }{}% + }{}% + }{}% + \global\dp\csname box@size@\the\pagegrid@col\endcsname\z@ + }{}% +}% +\def\show@pagesofar@size{% + \show@box@size{Page so far}\pagesofar + \dimen@\ht\pagesofar\divide\dimen@\@twopowerfourteen + \global\dp\csname box@size@1\endcsname\dimen@ + \show@box@size@sw{% + \class@info{Pagesofar: \the\dimen@}% + }{}% +}% +\@booleanfalse\tally@box@size@sw +\@booleanfalse\show@box@size@sw +\expandafter\newbox\csname box@size@1\endcsname +\expandafter\setbox\csname box@size@1\endcsname\hbox{}% +\expandafter\newbox\csname box@size@2\endcsname +\expandafter\setbox\csname box@size@2\endcsname\hbox{}% +\def\total@text{% + \@tempdima\the\ht\csname box@size@2\endcsname\divide\@tempdima\@twopowertwo\@tempcnta\@tempdima + \@tempdimb\the\ht\csname box@size@1\endcsname\divide\@tempdimb\@twopowertwo\@tempcntb\@tempdimb + \class@info{Total text: Column(\the\@tempcnta pt), Page(\the\@tempcntb pt)}% +}% +\def\natural@output{\toggle@insert{\output@holding}{\output@moving}}% +\output@latex{\natural@output}% +\def\output@holding{% + \csname output@init@\bot@envir\endcsname + \@if@exceed@pagegoal{\unvcopy\@cclv}{% + \setbox\z@\vbox{\unvcopy\@cclv}% + \outputdebug@sw{\trace@box\z@}{}% + \dimen@\ht\@cclv\advance\dimen@-\ht\z@ + \dead@cycle@repair\dimen@ + }{% + \dead@cycle + }% +}% +\def\@if@exceed@pagegoal#1{% + \begingroup + \setbox\z@\vbox{#1}% + \dimen@\ht\z@\advance\dimen@\dp\z@ + \outputdebug@sw{\saythe\dimen@}{}% + \@ifdim{\dimen@>\pagegoal}{% + \setbox\z@\vbox{\@@mark{}\unvbox\z@}% + \splittopskip\topskip + \splitmaxdepth\maxdepth + \vbadness\@M + \vfuzz\maxdimen + \setbox\tw@\vsplit\z@ to\pagegoal + \outputdebug@sw{\trace@scroll{\showbox\tw@\showbox\z@}}{}% + \setbox\tw@\vbox{\unvbox\tw@}% + \@ifdim{\ht\tw@=\z@}{% + \ltxgrid@info{Found overly large chunk while preparing to move insertions. Attempting repairs}% + \aftergroup\true@sw + }{% + \aftergroup\false@sw + }% + }{% + \aftergroup\false@sw + }% + \endgroup +}% +\def\output@moving{% + \set@top@firstmark + \@ifnum{\outputpenalty=\do@newpage@pen}{% + \setbox\@cclv\vbox{% + \unvbox\@cclv + \remove@lastbox + \@ifdim{\ht\z@=\ht\@protection@box}{\box\lastbox}{\unskip}% + }% + }{}% + \@cclv@nontrivial@sw{% + \expandafter\output@do@prep\csname output@prep@\bot@envir \endcsname + \@makecolumn\true@sw + \expandafter\output@column@do\csname output@column@\thepagegrid\endcsname + \protect@penalty\do@startcolumn@pen + \clearpage@sw{% + \protect@penalty\do@endpage@pen + }{}% + \expandafter\let\expandafter\output@post@\csname output@post@\bot@envir \endcsname + \outputdebug@sw{\say\output@post@}{}% + \@ifx{\output@post@\relax}{\output@post@document}{\output@post@}% + }{% + \void@cclv + }% + \set@colht + \global\@mparbottom\z@ + \global\@textfloatsheight\z@ +}% +\def\output@do@prep#1{% + \outputdebug@sw{\class@info{Prep: \string#1}}{}% + \@ifx{#1\relax}{\output@prep@document}{#1}% +}% +\def\output@column@do#1{% + \outputdebug@sw{\class@info{Output column: \string#1}}{}% + \@ifx{#1\relax}{\output@column@one}{#1}% +}% +\def\void@cclv{\begingroup\setbox\z@\box\@cclv\endgroup}% +\def\remove@lastbox{\setbox\z@\lastbox}% +\def\@cclv@nontrivial@sw{% + \@ifx@empty\@toplist{% + \@ifx@empty\@botlist{% + \@ifvoid\footins{% + \@ifvoid\@cclv{% + \false@sw + }{% + \setbox\z@\vbox{\unvcopy\@cclv}% + \@ifdim{\ht\z@=\topskip}{% + \setbox\z@\vbox\bgroup + \unvbox\z@ + \remove@lastbox + \dimen@\lastskip\unskip + \@ifdim{\ht\z@=\ht\@protection@box}{% + \advance\dimen@\ht\z@ + \@ifdim{\dimen@=\topskip}{% + \aftergroup\true@sw + }{% + \aftergroup\false@sw + }% + }{% + \aftergroup\false@sw + }% + \egroup + {% + \false@sw + }{% + \true@sw + }% + }{% + \@ifdim{\ht\z@=\z@}{% + \ltxgrid@info{Found trivial column. Discarding it}% + \outputdebug@sw{\trace@box\@cclv}{}% + \false@sw + }{% + \true@sw + }% + }% + }% + }{% + \true@sw + }% + }{% + \true@sw + }% + }{% + \true@sw + }% +}% +\def\protect@penalty#1{\protection@box\penalty-#1\relax}% +\newbox\@protection@box +\setbox\@protection@box\vbox to1986sp{\vfil}% +\def\protection@box{\nointerlineskip\copy\@protection@box}% +\def\dead@cycle@repair#1{% + \expandafter\do@@mark + \expandafter{% + \@@botmark + }% + \unvbox\@cclv + \nointerlineskip + \vbox to#1{\vss}% + \@ifnum{\outputpenalty<\@M}{\penalty\outputpenalty}{}% +}% +\def\dead@cycle@repair@protected#1{% + \expandafter\do@@mark + \expandafter{% + \@@botmark + }% + \begingroup + \unvbox\@cclv + \remove@lastbox + \nointerlineskip + \advance#1-\ht\@protection@box + \vbox to#1{\vss}% + \protection@box % Reinsert protection box + \@ifnum{\outputpenalty<\@M}{\penalty\outputpenalty}{}% + \endgroup +}% +\def\dead@cycle{% + \expandafter\do@@mark + \expandafter{% + \@@botmark + }% + \unvbox\@cclv + \@ifnum{\outputpenalty<\@M}{\penalty\outputpenalty}{}% +}% +\def\output@init@document{% + \ltxgrid@info@sw{\class@info{\string\output@init@document}}{}% + \global\vsize\vsize +}% +\def\output@prep@document{% + \ltxgrid@foot@info@sw{\class@info{\string\output@prep@document}\trace@scroll{\showbox\footins\showbox\footsofar}}{}% + \@ifvoid\footsofar{% + }{% + \global\setbox\footins\vbox\bgroup + \unvbox\footsofar + \@ifvoid\footins{}{% + \marry@baselines + \unvbox\footins + }% + \egroup + \ltxgrid@foot@info@sw{\trace@box\footins}{}% + }% +}% +\def\output@post@document{}% +\let\@opcol\@undefined +\def\@makecolumn#1{% + \ltxgrid@foot@info@sw{\class@info{\string\@makecolumn\string#1}}{}% + \setbox\@outputbox\vbox\bgroup + \boxmaxdepth\@maxdepth + \@tempdima\dp\@cclv + \unvbox\@cclv + \vskip-\@tempdima + \egroup + \xdef\@freelist{\@freelist\@midlist}\global\let\@midlist\@empty + \show@text@box@size + \@combinefloats + #1{% + \@combineinserts\@outputbox\footins + }{% + \combine@foot@inserts\footsofar\footins + }% + \set@adj@colht\dimen@ + \count@\vbadness + \vbadness\@M + \setbox\@outputbox\vbox to\dimen@\bgroup + \@texttop + \dimen@\dp\@outputbox + \unvbox\@outputbox + \vskip-\dimen@ + \@textbottom + \egroup + \vbadness\count@ + \global\maxdepth\@maxdepth +}% +\let\@makespecialcolbox\@undefined +\def\@combineinserts#1#2{% + \ltxgrid@foot@info@sw{\class@info{\string\@combineinserts\string#1\string#2}\trace@box#2}{}% + \setbox#1\vbox\bgroup + \unvbox#1% + \@ifvoid{#2}{}{% + \dimen@\ht#2\advance\dimen@\dp#2\advance\dimen@\skip#2% + \show@box@size{Combining inserts}#2% + \vskip\skip#2% + \setbox\z@\vbox{\footnoterule}\dimen@i\ht\z@ + \color@begingroup + \normalcolor + \cleaders\box\z@\vskip\dimen@i\kern-\dimen@i + \csname combine@insert@\the\pagegrid@col\endcsname#2% + \color@endgroup + \kern-\dimen@\kern\dimen@ + }% + \egroup + \ltxgrid@foot@info@sw{\trace@box#1}{}% +}% +\def\combine@insert@tw@#1{% + \compose@footnotes@two#1\@ifvbox{#1}{\unvbox}{\box}#1% +}% +\def\combine@insert@@ne#1{% + \compose@footnotes@one#1\@ifvbox{#1}{\unvbox}{\box}#1% +}% +\def\twocolumn@grid@setup{% + \expandafter\let\csname combine@insert@1\endcsname\combine@insert@tw@ + \expandafter\let\csname combine@insert@2\endcsname\combine@insert@@ne +}% +\def\onecolumn@grid@setup{% + \expandafter\let\csname combine@insert@1\endcsname\combine@insert@@ne + \expandafter\let\csname combine@insert@2\endcsname\combine@insert@@ne +}% +\let\columngrid@setup\onecolumn@grid@setup +\columngrid@setup +\appdef\@floatplacement{% + \global\@fpmin\@fpmin +}% +\mathchardef\pagebreak@pen=\@M +\expandafter\let\csname output@-\the\pagebreak@pen\endcsname\relax +\mathchardef\do@startcolumn@pen=10005 +\@namedef{output@-\the\do@startcolumn@pen}{\do@startcolumn}% +\def\do@startcolumn{% + \setbox\@cclv\vbox{\unvbox\@cclv\remove@lastbox\unskip}% + \clearpage@sw{\@clearfloatplacement}{\@floatplacement}% + \set@colht + \@booleanfalse\pfloat@avail@sw + \begingroup + \@colht\@colroom + \@booleanfalse\float@avail@sw + \@tryfcolumn\test@colfloat + \float@avail@sw{\aftergroup\@booleantrue\aftergroup\pfloat@avail@sw}{}% + \endgroup + \fcolmade@sw{% + \setbox\@cclv\vbox{\unvbox\@outputbox\unvbox\@cclv}% + \outputpenalty-\pagebreak@pen + \dead@cycle + }{% + \begingroup + \let\@elt\@scolelt + \let\reserved@b\@deferlist\global\let\@deferlist\@empty\reserved@b + \endgroup + \clearpage@sw{% + \outputpenalty\@M + }{% + \outputpenalty\do@newpage@pen + }% + \dead@cycle + }% + \check@deferlist@stuck\do@startcolumn + \set@vsize +}% +\def\@scolelt#1{\def\@currbox{#1}\@addtonextcol}% +\def\test@colfloat#1{% + \csname @floatselect@sw@\thepagegrid\endcsname#1{}{\@testtrue}% + \@if@sw\if@test\fi{}{\aftergroup\@booleantrue\aftergroup\float@avail@sw}% +}% +\def\@addtonextcol{% + \begingroup + \@insertfalse + \@setfloattypecounts + \csname @floatselect@sw@\thepagegrid\endcsname\@currbox{% + \@ifnum{\@fpstype=8 }{}{% + \@ifnum{\@fpstype=24 }{}{% + \@flsettextmin + \@reqcolroom \ht\@currbox + \advance \@reqcolroom \@textmin + \advance \@reqcolroom \vsize % take into account split insertions + \advance \@reqcolroom -\pagegoal + \@ifdim{\@colroom>\@reqcolroom}{% + \@flsetnum \@colnum + \@ifnum{\@colnum>\z@}{% + \@bitor\@currtype\@deferlist + \@if@sw\if@test\fi{}{% + \@addtotoporbot + }% + }{}% + }{}% + }% + }% + }{}% + \@if@sw\if@insert\fi{}{% + \@cons\@deferlist\@currbox + }% + \endgroup +}% +\mathchardef\do@startpage@pen=10006 +\@namedef{output@-\the\do@startpage@pen}{\do@startpage}% +\def\do@startpage{% + \setbox\@cclv\vbox{\unvbox\@cclv\remove@lastbox\unskip}% + \clearpage@sw{\@clearfloatplacement}{\@dblfloatplacement}% + \set@colht + \@booleanfalse\pfloat@avail@sw + \begingroup + \@booleanfalse\float@avail@sw + \@tryfcolumn\test@dblfloat + \float@avail@sw{\aftergroup\@booleantrue\aftergroup\pfloat@avail@sw}{}% + \endgroup + \fcolmade@sw{% + \global\setbox\pagesofar\vbox{\unvbox\pagesofar\unvbox\@outputbox}% + \@output@combined@page + }{% + \begingroup + \@booleanfalse\float@avail@sw + \let\@elt\@sdblcolelt + \let\reserved@b\@deferlist\global\let\@deferlist\@empty\reserved@b + \endgroup + \@ifdim{\@colht=\textheight}{% No luck... + \pfloat@avail@sw{% ...but a float *was* available! + \forcefloats@sw{% + \ltxgrid@warn{Forced dequeueing of floats stalled}% + }{% + \ltxgrid@warn{Dequeueing of floats stalled}% + }% + }{}% + }{}% + \outputpenalty\@M + \dead@cycle + }% + \check@deferlist@stuck\do@startpage + \set@colht +}% +\def\@output@combined@page{% + \@combinepage\true@sw + \@combinedblfloats + \@outputpage + \global\pagegrid@cur\@ne + \protect@penalty\do@startpage@pen +}% +\def\@sdblcolelt#1{\def\@currbox{#1}\@addtodblcol}% +\def\test@dblfloat#1{% + \@if@notdblfloat{#1}{\@testtrue}{}% + \@if@sw\if@test\fi{}{\aftergroup\@booleantrue\aftergroup\float@avail@sw}% +}% +\def\@if@notdblfloat#1{\@ifdim{\wd#1<\textwidth}}% +\@booleanfalse\forcefloats@sw +\def\@addtodblcol{% + \begingroup + \@if@notdblfloat{\@currbox}{% + \false@sw + }{% + \@setfloattypecounts + \@getfpsbit \tw@ + \@bitor \@currtype \@deferlist + \@if@sw\if@test\fi{% + \false@sw + }{% + \@ifodd\@tempcnta{% + \aftergroup\@booleantrue\aftergroup\float@avail@sw + \@flsetnum \@dbltopnum + \@ifnum{\@dbltopnum>\z@}{% + \@ifdim{\@dbltoproom>\ht\@currbox}{% + \true@sw + }{% + \@ifnum{\@fpstype<\sixt@@n}{% + \begingroup + \advance \@dbltoproom \@textmin + \@ifdim{\@dbltoproom>\ht\@currbox}{% + \endgroup\true@sw + }{% + \endgroup\false@sw + }% + }{% + \false@sw + }% + }% + }{% + \false@sw + }% + }{% + \false@sw + }% + }% + }% + {% + \@tempdima -\ht\@currbox + \advance\@tempdima + -\@ifx{\@dbltoplist\@empty}{\dbltextfloatsep}{\dblfloatsep}% + \global \advance \@dbltoproom \@tempdima + \global \advance \@colht \@tempdima + \global \advance \@dbltopnum \m@ne + \@cons \@dbltoplist \@currbox + }{% + \@cons \@deferlist \@currbox + }% + \endgroup +}% +\def\@tryfcolumn#1{% + \global\@booleanfalse\fcolmade@sw + \@ifx@empty\@deferlist{}{% + \global\let\@trylist\@deferlist + \global\let\@failedlist\@empty + \begingroup + \dimen@\vsize\advance\dimen@-\pagegoal\@ifdim{\dimen@>\z@}{% + \advance\@fpmin-\dimen@ + }{}% + \def\@elt{\@xtryfc#1}\@trylist + \endgroup + \fcolmade@sw{% + \global\setbox\@outputbox\vbox{\vskip \@fptop}% + \let \@elt \@wtryfc \@flsucceed + \global\setbox\@outputbox\vbox{\unvbox\@outputbox + \unskip \vskip \@fpbot + }% + \let \@elt \relax + \xdef\@deferlist{\@failedlist\@flfail}% + \xdef\@freelist{\@freelist\@flsucceed}% + }{}% + }% +}% +\def\@wtryfc #1{% + \global\setbox\@outputbox\vbox{\unvbox\@outputbox + \box #1\vskip\@fpsep + }% +}% +\def\@xtryfc#1#2{% + \@next\reserved@a\@trylist{}{}% trim \@trylist. Ugly! + \@currtype \count #2% + \divide\@currtype\@xxxii\multiply\@currtype\@xxxii + \@bitor \@currtype \@failedlist + \@testfp #2% + #1#2% + \@ifdim{\ht #2>\@colht }{\@testtrue}{}% + \@if@sw\if@test\fi{% + \@cons\@failedlist #2% + }{% + \begingroup + \gdef\@flsucceed{\@elt #2}% + \global\let\@flfail\@empty + \@tempdima\ht #2% + \def \@elt {\@ztryfc#1}\@trylist + \@ifdim{\@tempdima >\@fpmin}{% + \global\@booleantrue\fcolmade@sw + }{% + \@cons\@failedlist #2% + }% + \endgroup + \fcolmade@sw{% + \let \@elt \@gobble + }{}% + }% +}% +\def\@ztryfc #1#2{% + \@tempcnta \count#2% + \divide\@tempcnta\@xxxii\multiply\@tempcnta\@xxxii + \@bitor \@tempcnta {\@failedlist \@flfail}% + \@testfp #2% + #1#2% + \@tempdimb\@tempdima + \advance\@tempdimb \ht#2\advance\@tempdimb\@fpsep + \@ifdim{\@tempdimb >\@colht}{% + \@testtrue + }{}% + \@if@sw\if@test\fi{% + \@cons\@flfail #2% + }{% + \@cons\@flsucceed #2% + \@tempdima\@tempdimb + }% +}% +\def\newpage@prep{% + \if@noskipsec + \ifx \@nodocument\relax + \leavevmode + \global \@noskipsecfalse + \fi + \fi + \if@inlabel + \leavevmode + \global \@inlabelfalse + \fi + \if@nobreak \@nobreakfalse \everypar{}\fi + \par +}% +\def \newpage {% + \newpage@prep + \do@output@MVL{% + \vfil + \penalty-\pagebreak@pen + }% +}% +\def\clearpage{% + \newpage@prep + \do@output@MVL{% + \vfil + \penalty-\pagebreak@pen + \global\@booleantrue\clearpage@sw + \protect@penalty\do@startcolumn@pen + \protect@penalty\do@endpage@pen + }% + \do@output@MVL{% + \global\@booleanfalse\clearpage@sw + }% +}% +\def\cleardoublepage{% + \clearpage + \@if@sw\if@twoside\fi{% + \@ifodd\c@page{}{% + \null\clearpage + }% + }{}% +}% +\@booleanfalse\clearpage@sw +\mathchardef\do@endpage@pen=10007 +\@namedef{output@-\the\do@endpage@pen}{\csname end@column@\thepagegrid\endcsname}% +\mathchardef\do@newpage@pen=10001 +\expandafter\let\csname output@-\the\do@newpage@pen\endcsname\relax +\def\@clearfloatplacement{% + \global\@topnum \maxdimen + \global\@toproom \maxdimen + \global\@botnum \maxdimen + \global\@botroom \maxdimen + \global\@colnum \maxdimen + \global\@dbltopnum \maxdimen + \global\@dbltoproom \maxdimen + \global\@textmin \z@ + \global\@fpmin \z@ + \let\@testfp\@gobble + \appdef\@setfloattypecounts{\@fpstype16\advance\@fpstype\m@ne}% +}% +\let\@doclearpage\@undefined +\let\@makefcolumn\@undefined +\let\@makecol\@undefined +\def\clr@top@firstmark{% + \global\let\saved@@topmark\@undefined + \global\let\saved@@firstmark\@empty + \global\let\saved@@botmark\@empty +}% +\clr@top@firstmark +\def\set@top@firstmark{% + \@ifxundefined\saved@@topmark{\expandafter\gdef\expandafter\saved@@topmark\expandafter{\@@topmark}}{}% + \@if@empty\saved@@firstmark{\expandafter\gdef\expandafter\saved@@firstmark\expandafter{\@@firstmark}}{}% + \@if@empty\@@botmark{}{\expandafter\gdef\expandafter\saved@@botmark\expandafter{\@@botmark}}% +}% +\appdef\@outputpage@tail{% + \clr@top@firstmark +}% +\def\@float#1{% + \@ifnextchar[{% + \@yfloat\width@float{#1}% + }{% + \@ifxundefined@cs{fps@#1}{}{\expandafter\let\expandafter\fps@\csname fps@#1\endcsname}% + \expandafter\@argswap\expandafter{\expandafter[\fps@]}{\@yfloat\width@float{#1}}% + }% +}% +\def\@dblfloat#1{% + \@ifnum{\pagegrid@col=\@ne}{% + \@float{#1}% + }{% + \@ifnextchar[{% + \@yfloat\widthd@float{#1}% + }{% + \@ifxundefined@cs{fpsd@#1}{}{\expandafter\let\expandafter\fpsd@\csname fpsd@#1\endcsname}% + \expandafter\@argswap\expandafter{\expandafter[\fpsd@]}{\@yfloat\widthd@float{#1}}% + }% + }% +}% +\def\@yfloat#1#2[#3]{% + \@xfloat{#2}[#3]% + \hsize#1\linewidth\hsize + \let\set@footnotewidth\@empty + \minipagefootnote@init +}% +\def\fps@{tbp}% +\def\fpsd@{tp}% +\def\width@float{\columnwidth}% +\def\widthd@float{\textwidth}% +\def\end@float{% + \end@@float{% + \check@currbox@count + }% +}% +\def\end@dblfloat{% + \@ifnum{\pagegrid@col=\@ne}{% + \end@float + }{% + \end@@float{% + \@iffpsbit\@ne{\global\advance\count\@currbox\m@ne}{}% + \@iffpsbit\f@ur{\global\advance\count\@currbox-4\relax}{}% + \global\wd\@currbox\textwidth % Klootch + \check@currbox@count + }% + }% +}% +\def\end@@float#1{% + \minipagefootnote@here + \@endfloatbox + #1% + \@ifnum{\@floatpenalty <\z@}{% + \@largefloatcheck + \@cons\@currlist\@currbox + \@ifnum{\@floatpenalty <-\@Mii}{% + \do@output@cclv{\@add@float}% + }{% + \vadjust{\do@output@cclv{\@add@float}}% + \@Esphack + }% + }{}% +}% +\newcommand\float@end@float{% + \@endfloatbox + \global\setbox\@currbox\float@makebox\columnwidth + \let\@endfloatbox\relax + \end@float +}% +\newcommand\float@end@ltx{% + \end@@float{% + \global\setbox\@currbox\float@makebox\columnwidth + \check@currbox@count + }% +}% +\newcommand\newfloat@float[3]{% + \@namedef{ext@#1}{#3} %! + \let\float@do=\relax + \xdef\@tempa{\noexpand\float@exts{\the\float@exts \float@do{#3}}}% + \@tempa + \floatplacement{#1}{#2}% + \@ifundefined{fname@#1}{\floatname{#1}{#1}}{} %! + \expandafter\edef\csname ftype@#1\endcsname{\value{float@type}}% + \addtocounter{float@type}{\value{float@type}} %! + \restylefloat{#1}% + \expandafter\edef\csname fnum@#1\endcsname{% + \expandafter\noexpand\csname fname@#1\endcsname{} %! + \expandafter\noexpand\csname the#1\endcsname + } + \@ifnextchar[%] + {% + \float@newx{#1}% + }{% + \@ifundefined{c@#1}{\newcounter{#1}\@namedef{the#1}{\arabic{#1}}}{}% + }% +}% +\newcommand\newfloat@ltx[3]{% + \@namedef{ext@#1}{#3}% + \let\float@do=\relax + \xdef\@tempa{\noexpand\float@exts{\the\float@exts \float@do{#3}}}% + \@tempa + \floatplacement{#1}{#2}% + \@ifundefined{fname@#1}{\floatname{#1}{#1}}{}% + \expandafter\edef\csname ftype@#1\expandafter\endcsname\expandafter{\the\c@float@type}% + \addtocounter{float@type}{\value{float@type}}% + \restylefloat{#1}% + \expandafter\edef\csname fnum@#1\endcsname{% + \expandafter\noexpand\csname fname@#1\endcsname{}% + \expandafter\noexpand\csname the#1\endcsname + } + \@ifnextchar[%] + {% + \float@newx{#1}% + }{% + \@ifundefined{c@#1}{\newcounter{#1}\@namedef{the#1}{\arabic{#1}}}{}% + }% +}% +\appdef\document@inithook{% + \@ifxundefined\newfloat{}{% + \@ifx{\float@end\float@end@float}{% + \@ifx{\newfloat\newfloat@float}{\true@sw}{\false@sw}% + }{\false@sw}% + {% + \class@warn{Repair the float package}% + \let\float@end\float@end@ltx + \let\newfloat\newfloat@ltx + }{% + \class@warn{Failed to patch the float package}% + }% + }% +}% +\def\@iffpsbit#1{% + \begingroup + \@tempcnta\count\@currbox + \divide\@tempcnta#1\relax + \@ifodd\@tempcnta{\aftergroup\true@sw}{\aftergroup\false@sw}% + \endgroup +}% +\def\check@currbox@count{% + \@ifnum{\count\@currbox>\z@}{% + \count@\count\@currbox\divide\count@\sixt@@n\multiply\count@\sixt@@n + \@tempcnta\count\@currbox\advance\@tempcnta-\count@ + \@ifnum{\@tempcnta=\z@}{% + \ltxgrid@warn{Float cannot be placed}% + }{}% + \expandafter\tally@float\expandafter{\@captype}% + }{% + }% +}% +\providecommand\minipagefootnote@init{}% +\providecommand\minipagefootnote@here{}% +\providecommand\tally@float[1]{}% +\let\@specialoutput\@undefined +\def\@add@float{% + \@pageht\ht\@cclv\@pagedp\dp\@cclv + \unvbox\@cclv + \@next\@currbox\@currlist{% + \csname @floatselect@sw@\thepagegrid\endcsname\@currbox{% + \@ifnum{\count\@currbox>\z@}{% + \advance \@pageht \@pagedp + \advance \@pageht \vsize \advance \@pageht -\pagegoal + \@addtocurcol + }{% + \@addmarginpar + }% + }{% + \@resethfps + \@cons\@deferlist\@currbox + }% + }{\@latexbug}% + \@ifnum{\outputpenalty<\z@}{% + \@if@sw\if@nobreak\fi{% + \nobreak + }{% + \addpenalty \interlinepenalty + }% + }{}% + \set@vsize +}% +\let\@reinserts\@undefined +\def \@addtocurcol {% + \@insertfalse + \@setfloattypecounts + \ifnum \@fpstype=8 + \else + \ifnum \@fpstype=24 + \else + \@flsettextmin + \advance \@textmin \@textfloatsheight + \@reqcolroom \@pageht + \ifdim \@textmin>\@reqcolroom + \@reqcolroom \@textmin + \fi + \advance \@reqcolroom \ht\@currbox + \ifdim \@colroom>\@reqcolroom + \@flsetnum \@colnum + \ifnum \@colnum>\z@ + \@bitor\@currtype\@deferlist + \if@test + \else + \@bitor\@currtype\@botlist + \if@test + \@addtobot + \else + \ifodd \count\@currbox + \advance \@reqcolroom \intextsep + \ifdim \@colroom>\@reqcolroom + \global \advance \@colnum \m@ne + \global \advance \@textfloatsheight \ht\@currbox + \global \advance \@textfloatsheight 2\intextsep + \@cons \@midlist \@currbox + \if@nobreak + \nobreak + \@nobreakfalse + \everypar{}% + \else + \addpenalty \interlinepenalty + \fi + \vskip \intextsep + \unvbox\@currbox %AO + \penalty\interlinepenalty + \vskip\intextsep + \ifnum\outputpenalty <-\@Mii \vskip -\parskip\fi + \outputpenalty \z@ + \@inserttrue + \fi + \fi + \if@insert + \else + \@addtotoporbot + \fi + \fi + \fi + \fi + \fi + \fi + \fi + \if@insert + \else + \@resethfps + \@cons\@deferlist\@currbox + \fi +}% +\@twocolumnfalse +\let\@twocolumntrue\@twocolumnfalse +\def\@addmarginpar{% + \@next\@marbox\@currlist{% + \@cons\@freelist\@marbox\@cons\@freelist\@currbox + }\@latexbug + \setbox\@marbox\hb@xt@\columnwidth{% + \csname @addmarginpar@\thepagegrid\endcsname{% + \hskip-\marginparsep\hskip-\marginparwidth + \box\@currbox + }{% + \hskip\columnwidth\hskip\marginparsep + \box\@marbox + }% + \hss + }% + \setbox\z@\box\@currbox + \@tempdima\@mparbottom + \advance\@tempdima -\@pageht + \advance\@tempdima\ht\@marbox + \@ifdim{\@tempdima >\z@}{% + \@latex@warning@no@line {Marginpar on page \thepage\space moved}% + }{% + \@tempdima\z@ + }% + \global\@mparbottom\@pageht + \global\advance\@mparbottom\@tempdima + \global\advance\@mparbottom\dp\@marbox + \global\advance\@mparbottom\marginparpush + \advance\@tempdima -\ht\@marbox + \global\setbox \@marbox + \vbox {\vskip \@tempdima + \box \@marbox}% + \global \ht\@marbox \z@ + \global \dp\@marbox \z@ + \kern -\@pagedp + \nointerlineskip + \box\@marbox + \nointerlineskip + \hbox{\vrule \@height\z@ \@width\z@ \@depth\@pagedp}% +}% +\newenvironment{turnpage}{% + \def\width@float{\textheight}% + \def\widthd@float{\textheight}% + \appdef\@endfloatbox{% + \@ifxundefined\@currbox{% + \ltxgrid@warn{Cannot rotate! Not a float}% + }{% + \setbox\@currbox\vbox to\textwidth{\vfil\unvbox\@currbox\vfil}% + \global\setbox\@currbox\vbox{\rotatebox{90}{\box\@currbox}}% + }% + }% +}{% +}% +\def\rotatebox@dummy#1#2{% + \ltxgrid@warn{You must load the graphics or graphicx package in order to use the turnpage environment}% + #2% +}% +\appdef\document@inithook{% + \@ifxundefined\rotatebox{\let\rotatebox\rotatebox@dummy}{}% +}% +\@namedef{output@-1073741824}{% + \deadcycles\z@ + \void@cclv +}% +\mathchardef\save@column@pen=10016 +\@namedef{output@-\the\save@column@pen}{\save@column}% +\let \@cclv@saved \@holdpg +\let \@holdpg \@undefined +\def\save@column{% + \@ifvoid\@cclv@saved{% + \set@top@firstmark + \global\@topmark@saved\expandafter{\@@topmark}% + }{}% + \global\setbox\@cclv@saved\vbox{% + \@ifvoid\@cclv@saved{}{% + \unvbox\@cclv@saved + \marry@baselines + }% + \unvbox\@cclv + \lose@breaks + \remove@lastbox + }% +}% +\newtoks\@topmark@saved +\def\prep@cclv{% + \void@cclv + \setbox\@cclv\box\@cclv@saved + \vbadness\@M +}% +\mathchardef\save@column@insert@pen=10017 +\@namedef{output@-\the\save@column@insert@pen}{\toggle@insert{\savecolumn@holding}{\savecolumn@moving}}% +\def\savecolumn@holding{% + \@if@exceed@pagegoal{\unvcopy\@cclv\remove@lastbox}{% + \setbox\z@\vbox{\unvcopy\@cclv\remove@lastbox}% + \outputdebug@sw{\trace@box\z@}{}% + \dimen@\ht\@cclv\advance\dimen@-\ht\z@ + \dead@cycle@repair@protected\dimen@ + }{% + \dead@cycle + }% +}% +\def\savecolumn@moving{% + \ltxgrid@info@sw{\class@info{\string\savecolumn@moving}}{}% + \@cclv@nontrivial@sw{% + \save@column + }{% + \void@cclv + }% + \@ifvoid\footins{}{% + \ltxgrid@foot@info@sw{\class@info{\string\savecolumn@moving}\trace@scroll{\showbox\footins@saved\showbox\footins}}{}% + \@ifvoid\footins@saved{% + \global\setbox\footins@saved\box\footins + }{% + \global\setbox\footins@saved\vbox\bgroup + \unvbox\footins@saved + \marry@baselines + \unvbox\footins + \egroup + }% + \ltxgrid@foot@info@sw{\trace@box\footins@saved}{}% + \protect@penalty\save@column@insert@pen + }% +}% +\newbox\footins@saved +\newbox\footins@recovered +\newbox\column@recovered +\mathchardef\save@message@pen=10018 +\@namedef{output@-\the\save@message@pen}{\save@message}% +\def\save@message{% + \void@cclv + \toks@\expandafter{\@@firstmark}% + \expandafter\gdef\expandafter\@message@saved\expandafter{\the\toks@}% + \expandafter\do@@mark\expandafter{\the\@topmark@saved}% +}% +\gdef\@message@saved{}% +\mathchardef\execute@message@pen=10019 +\@namedef{output@-\the\execute@message@pen}{\@message@saved}% +\def\execute@message{% + \@execute@message\save@column@pen +}% +\def\execute@message@insert#1{% + \@execute@message\save@column@insert@pen{% + \setbox \footins \box \footins@saved + \ltxgrid@foot@info@sw{\class@info{\string\execute@message@insert}\trace@box\footins}{}% + #1% + }% +}% +\long\def\@execute@message#1#2{% + \begingroup + \dimen@\prevdepth\@ifdim{\dimen@<\z@}{\dimen@\z@}{}% + \setbox\z@\vbox{% + \protect@penalty#1% + \protection@box + \toks@{\prep@cclv#2}% + \@@mark{\the\toks@}% + \penalty-\save@message@pen + \setbox\z@\null\dp\z@\dimen@\ht\z@-\dimen@ + \nointerlineskip\box\z@ + \penalty-\execute@message@pen + }\unvbox\z@ + \endgroup +}% +\def\do@output@cclv{\execute@message}% +\def\do@output@MVL#1{% + \@ifvmode{% + \begingroup\execute@message{\unvbox\@cclv#1}\endgroup + }{% + \@ifhmode{% + \vadjust{\execute@message{\unvbox\@cclv#1}}% + }{% + \@latexerr{\string\do@output@MVL\space cannot be executed in this mode!}\@eha + }% + }% +}% +\def\lose@breaks{% + \loopwhile{% + \count@\lastpenalty + \@ifnum{\count@=\@M}{% + \unpenalty\true@sw + }{% + \false@sw + }% + }% +}% +\def\removestuff{\do@output@MVL{\unskip\unpenalty}}% +\def\removephantombox{% + \vadjust{% + \execute@message{% + \unvbox\@cclv + \remove@lastbox + \unskip + \unskip + \unpenalty + \penalty\predisplaypenalty + \vskip\abovedisplayskip + }% + }% +}% +\def\addstuff#1#2{\edef\@tempa{\noexpand\do@output@MVL{\noexpand\@addstuff{#1}{#2}}}\@tempa}% +\def\@addstuff#1#2{% + \skip@\lastskip\unskip + \count@\lastpenalty\unpenalty + \@if@empty{#1}{}{\penalty#1\relax}% + \@ifnum{\count@=\z@}{}{\penalty\count@}% + \vskip\skip@ + \@if@empty{#2}{}{\vskip#2\relax}% +}% +\def\replacestuff#1#2{\edef\@tempa{\noexpand\do@output@MVL{\noexpand\@replacestuff{#1}{#2}}}\@tempa}% +\def\@replacestuff#1#2{% + \skip@\lastskip\unskip + \count@\lastpenalty\unpenalty + \@if@empty{#1}{}{% + \@ifnum{\count@>\@M}{}{% + \@ifnum{\count@=\z@}{\count@=#1\relax}{% + \@ifnum{\count@<#1\relax}{}{% + \count@=#1\relax + }% + }% + }% + }% + \@ifnum{\count@=\z@}{}{\penalty\count@}% + \@if@empty{#2}{}{% + \@tempskipa#2\relax + \@ifdim{\z@>\@tempskipa}{% + \advance\skip@-\@tempskipa + }{% + \@ifdim{\skip@>\@tempskipa}{}{% + \skip@\@tempskipa + }% + }% + }% + \vskip\skip@ +}% +\def\move@insertions{\global\holdinginserts\z@}% +\def\hold@insertions{\global\holdinginserts\@ne}% +\hold@insertions +\def\toggle@insert#1#2{% + \@ifnum{\holdinginserts>\z@}{\move@insertions#1}{\hold@insertions#2}% +}% +\def\do@columngrid#1#2{% + \par + \expandafter\let\expandafter\@tempa\csname open@column@#1\endcsname + \@ifx{\relax\@tempa}{% + \ltxgrid@warn{Unknown page grid #1. No action taken}% + }{% + \do@output@MVL{\start@column{#1}{#2}}% + }% +}% +\def\start@column#1#2{% + \def\@tempa{#1}\@ifx{\@tempa\thepagegrid}{% + \ltxgrid@info{Already in page grid \thepagegrid. No action taken}% + }{% + \expandafter\execute@message@insert + \expandafter{% + \csname shut@column@\thepagegrid\expandafter\endcsname + \csname open@column@#1\endcsname{#2}% + \set@vsize + }% + }% +}% +\def\thepagegrid{one}% +\newbox\pagesofar +\newbox\footsofar +\def\combine@foot@inserts#1#2{% + \ltxgrid@info@sw{\class@info{\string\combine@foot@inserts\string#1\string#2}}{}% + \@ifvoid#1{% + \ltxgrid@foot@info@sw{\trace@box#2}{}\global\setbox#1\box#2% + }{% + \global\setbox#1\vbox\bgroup + \ltxgrid@foot@info@sw{\trace@box#1}{}\unvbox#1% + \@ifvoid#2{}{% + \marry@baselines + \ltxgrid@foot@info@sw{\trace@box#2}{}\unvbox#2% + }% + \egroup + }% + \ltxgrid@foot@info@sw{\trace@scroll{\showbox#1\showbox#2}}{}% +}% +\newcommand\onecolumngrid{\do@columngrid{one}{\@ne}}% +\let\onecolumn\@undefined +\def\open@column@one#1{% + \ltxgrid@info@sw{\class@info{\string\open@column@one\string#1}}{}% + \unvbox\pagesofar + \@ifvoid{\footsofar}{}{% + \insert\footins\bgroup\unvbox\footsofar\egroup + \penalty\z@ + }% + \gdef\thepagegrid{one}% + \global\pagegrid@col#1% + \global\pagegrid@cur\@ne + \global\count\footins\@m + \global\divide\count\footins\tw@ + \set@column@hsize\pagegrid@col + \set@colht +}% +\def\shut@column@one{% + \ltxgrid@info@sw{\class@info{\string\shut@column@one}}{}% + \@makecolumn\false@sw + \global\setbox\pagesofar\vbox\bgroup + \recover@column\@outputbox\footsofar\column@recovered\footins@recovered + \egroup + \begingroup\setbox\z@\box\@outputbox\endgroup + \combine@foot@inserts\footsofar\footins + \set@colht +}% +\def\float@column@one{% + \@makecolumn\true@sw + \@outputpage +}% +\def\end@column@one{% + \unvbox\@cclv\remove@lastbox + \protect@penalty\do@newpage@pen +}% +\def\output@column@one{% + \@outputpage +}% +\def\@addmarginpar@one{% + \@if@sw\if@mparswitch\fi{% + \@ifodd\c@page{\false@sw}{\true@sw}% + }{\false@sw}{% + \@if@sw\if@reversemargin\fi{\false@sw}{\true@sw}% + }{% + \@if@sw\if@reversemargin\fi{\true@sw}{\false@sw}% + }% +}% +\def\@floatselect@sw@one#1{\true@sw}% +\def\onecolumngrid@push{% + \do@output@MVL{% + \@ifnum{\pagegrid@col=\@ne}{% + \global\let\restorecolumngrid\@empty + }{% + \xdef\restorecolumngrid{% + \noexpand\start@column{\thepagegrid}{\the\pagegrid@col}% + }% + \start@column{one}{\@ne}% + }% + }% +}% +\def\onecolumngrid@pop{% + \do@output@MVL{\restorecolumngrid}% +}% +\newcommand\twocolumngrid{\do@columngrid{mlt}{\tw@}}% +\let\twocolumn\@undefined +\let\@topnewpage\@undefined +\def\open@column@mlt#1{% + \ltxgrid@info@sw{\class@info{\string\open@column@mlt\string#1}}{}% + \@ifvoid{\footsofar}{}{% + \insert\footins\bgroup\unvbox\footsofar\egroup + }% + \gdef\thepagegrid{mlt}% + \global\pagegrid@col#1% + \global\pagegrid@cur\@ne + \global\count\footins\@m + \set@column@hsize\pagegrid@col + \set@colht +}% +\def\shut@column@mlt{% + \ltxgrid@info@sw{\class@info{\string\shut@column@mlt}}{}% + \@cclv@nontrivial@sw{% + \@makecolumn\false@sw + \@ifnum{\pagegrid@cur<\pagegrid@col}{% + \expandafter\global\expandafter\setbox\csname col@\the\pagegrid@cur\endcsname\box\@outputbox + \global\advance\pagegrid@cur\@ne + }{}% + }{% + \void@cclv + }% + \@ifnum{\pagegrid@cur>\@ne}{% + \csname balance@\the\pagegrid@col\endcsname + \grid@column\@outputbox{}% + \@combinepage\false@sw + \@combinedblfloats + \global\setbox\pagesofar\box\@outputbox + \show@pagesofar@size + }{}% + \set@colht +}% +\def\float@column@mlt{% + \@output@combined@page +}% +\def\end@column@mlt{% + \@ifx@empty\@toplist{% + \@ifx@empty\@botlist{% + \@ifx@empty\@dbltoplist{% + \@ifx@empty\@deferlist{% + \@ifnum{\pagegrid@cur=\@ne}{% + \false@sw + }{% + \true@sw + }% + }{% + \true@sw + }% + }{% + \true@sw + }% + }{% + \true@sw + }% + }{% + \true@sw + }% + % true = kick out a column and try again + {% + \@cclv@nontrivial@sw{% + \unvbox\@cclv\remove@lastbox + }{% + \unvbox\@cclv\remove@lastbox\unskip\null + }% + \protect@penalty\do@newpage@pen + \protect@penalty\do@endpage@pen + }{% + \unvbox\@cclv\remove@lastbox + }% +}% +\def\output@column@mlt{% + \@ifnum{\pagegrid@cur<\pagegrid@col}{% + \expandafter\global\expandafter\setbox\csname col@\the\pagegrid@cur\endcsname\box\@outputbox + \global\advance\pagegrid@cur\@ne + }{% + \set@adj@colht\dimen@ + \grid@column\@outputbox{}% + \@output@combined@page + }% +}% +\let\@outputdblcol\@undefined +\def\@floatselect@sw@mlt#1{\@if@notdblfloat{#1}}% +\def\@addmarginpar@mlt{% emits a boolean + \@ifnum{\pagegrid@cur=\@ne}% +}% +\def\set@footnotewidth@one{% + \hsize\columnwidth + \linewidth\hsize +}% +\def\set@footnotewidth@two{\set@footnotewidth@mlt\tw@}% +\def\set@footnotewidth@mlt#1{% + \hsize\textwidth + \advance\hsize\columnsep + \divide\hsize#1% + \advance\hsize-\columnsep + \linewidth\hsize +}% +\def\compose@footnotes@one#1{% + \ltxgrid@foot@info@sw{\class@info{\string\compose@footnotes@one\string#1}\trace@box#1}{}% +}% +\let\compose@footnotes\compose@footnotes@one +\def\compose@footnotes@two#1{% + \ltxgrid@foot@info@sw{\class@info{\string\compose@footnotes@two\string#1}\trace@box#1}{}% + \setbox\z@\box\@tempboxa + \let\recover@column\recover@column@null + \let\marry@baselines\@empty + \balance@two#1\@tempboxa + \global\setbox#1\hbox to\textwidth{\box#1\hfil\box\@tempboxa}% + \ltxgrid@foot@info@sw{\trace@box#1}{}% +}% +\let\pagegrid@cur\col@number +\let\col@number\@undefined +\newcount\pagegrid@col +\pagegrid@cur\@ne +\expandafter\let\csname col@\the\pagegrid@cur\endcsname\@leftcolumn +\let\@leftcolumn\@undefined +\pagegrid@col\tw@ +\def\pagegrid@init{% + \advance\pagegrid@cur\@ne + \@ifnum{\pagegrid@cur<\pagegrid@col}{% + \csname newbox\expandafter\endcsname\csname col@\the\pagegrid@cur\endcsname + \pagegrid@init + }{% + }% +}% +\appdef\class@documenthook{% + \pagegrid@init +}% +\def\grid@column#1#2{% + \ltxgrid@info@sw{\class@info{\string\grid@column\string#1}}{}% + \global\setbox#1\vbox\bgroup + \hb@xt@\textwidth\bgroup + \vrule\@height\z@\@width\z@\@if@empty{#2}{}{\@depth#2}% + \pagegrid@cur\@ne + \@ifnum{\pagegrid@cur<\pagegrid@col}{\loopwhile{\append@column@\pagegrid@cur\pagegrid@col}}{}% + \box@column#1% + \egroup + \vskip\z@skip + \egroup +}% +\def\append@column@#1#2{% + \expandafter\box@column\csname col@\the#1\endcsname + \hfil\vrule\@width\columnseprule\hfil + \advance#1\@ne + \@ifnum{#1<#2}% +}% +\def\box@column#1{% + \ltxgrid@info@sw{\class@info{\string\box@column\string#1}}{}% + \raise\topskip + \hb@xt@\columnwidth\bgroup + \dimen@\ht#1\@ifdim{\dimen@>\@colht}{\dimen@\@colht}{}% + \count@\vbadness\vbadness\@M + \dimen@ii\vfuzz\vfuzz\maxdimen + \ltxgrid@info@sw{\saythe\@colht\saythe\dimen@}{}% + \vtop to\dimen@\bgroup + \hrule\@height\z@ + \unvbox#1% + \raggedcolumn@skip + \egroup + \vfuzz\dimen@ii + \vbadness\count@ + \hss + \egroup +}% +\def\marry@baselines{% + \begingroup + \setbox\z@\lastbox + \@ifvoid{\z@}{% + \endgroup + }{% + \aftergroup\kern + \aftergroup-% + \expandafter\box\expandafter\z@\expandafter\endgroup\the\dp\z@\relax + }% + \vskip\marry@skip\relax +}% +\gdef\marry@skip{\z@skip}% +\def\set@marry@skip{% + \begingroup + \skip@\baselineskip\advance\skip@-\topskip + \@ifdim{\skip@>\z@}{% + \xdef\marry@skip{\the\skip@}% + }{}% + \endgroup +}% +\appdef\document@inithook{% + \@ifxundefined\raggedcolumn@sw{\@booleanfalse\raggedcolumn@sw}{}% +}% +\def\raggedcolumn@skip{% + \vskip\z@\raggedcolumn@sw{\@plus.0001fil\@minus.0001fil}{}\relax +}% +\def\@combinepage#1{% + \ltxgrid@foot@info@sw{\class@info{\string\@combinepage\string#1}}{}% + \@ifvoid\pagesofar{}{% + \setbox\@outputbox\vbox{% + \unvbox\pagesofar + \marry@baselines + \unvbox\@outputbox + }% + }% + #1{% + \@ifvoid\footsofar{}{% + \show@box@size{Combining page footnotes}\footsofar + \setbox\footins\box\footsofar + \compose@footnotes + \@combineinserts\@outputbox\footins + }% + }{% + }% +}% +\def \@cflt{% + \let \@elt \@comflelt + \setbox\@tempboxa \vbox{}% + \@toplist + \setbox\@outputbox \vbox{% + \boxmaxdepth \maxdepth + \unvbox\@tempboxa\unskip + \topfigrule\vskip \textfloatsep + \unvbox\@outputbox + }% + \let\@elt\relax + \xdef\@freelist{\@freelist\@toplist}% + \global\let\@toplist\@empty +}% +\def \@cflb {% + \let\@elt\@comflelt + \setbox\@tempboxa \vbox{}% + \@botlist + \setbox\@outputbox \vbox{% + \unvbox\@outputbox + \vskip \textfloatsep\botfigrule + \unvbox\@tempboxa\unskip + }% + \let\@elt\relax + \xdef\@freelist{\@freelist\@botlist}% + \global \let \@botlist\@empty +}% +\def\@combinedblfloats{% + \@ifx@empty\@dbltoplist{}{% + \setbox\@tempboxa\vbox{}% + \let\@elt\@comdblflelt\@dbltoplist + \let\@elt\relax\xdef\@freelist{\@freelist\@dbltoplist}% + \global\let\@dbltoplist\@empty + \setbox\@outputbox\vbox{% + %\boxmaxdepth\maxdepth %% probably not needed, CAR + \unvbox\@tempboxa\unskip + \@ifnum{\@dbltopnum>\m@ne}{\dblfigrule}{}%FIXME: how is \@dbltopnum maintained? + \vskip\dbltextfloatsep + \unvbox\@outputbox + }% + }% +}% +\def\set@column@hsize#1{% + \pagegrid@col#1% + \global\columnwidth\textwidth + \global\advance\columnwidth\columnsep + \global\divide\columnwidth\pagegrid@col + \global\advance\columnwidth-\columnsep + \global\hsize\columnwidth + \global\linewidth\columnwidth + \skip@\baselineskip\advance\skip@-\topskip + \@ifnum{\pagegrid@col>\@ne}{\set@marry@skip}{}% +}% +\def\set@colht{% + \set@adj@textheight\@colht + \global\let\enlarge@colroom\@empty + \set@colroom +}% +\def\set@adj@textheight#1{% + \ltxgrid@info@sw{\class@info{\string\set@adj@textheight\string#1}\saythe\textheight}{}% + #1\textheight + \def\@elt{\adj@page#1}% + \@booleantrue\firsttime@sw\@dbltoplist + \let\@elt\relax + \global#1#1\relax + \ltxgrid@info@sw{\saythe#1}{}% +}% +\def\set@colroom{% + \ltxgrid@info@sw{\class@info{\string\set@colroom}}{}% + \set@adj@colht\@colroom + \@if@empty\enlarge@colroom{}{% + \global\advance\@colroom\enlarge@colroom\relax + \ltxgrid@info@sw{\saythe\@colroom}{}% + }% + \@ifdim{\@colroom>\topskip}{}{% + \ltxgrid@info{Not enough room: \string\@colroom=\the\@colroom; increasing to \the\topskip}% + \@colroom\topskip + }% + \global\@colroom\@colroom + \set@vsize +}% +\def\set@vsize{% + \global\vsize\@colroom + \ltxgrid@info@sw{\class@info{\string\set@vsize\string\vsize=\string\colroom}\saythe\vsize}{}% +}% +\def\set@adj@colht#1{% + #1\@colht + \ltxgrid@info@sw{\class@info{\string\set@adj@colht\string#1-\string\pagesofar}\saythe#1}{}% + \@ifvoid\pagesofar{}{% + \advance#1-\ht\pagesofar\advance#1-\dp\pagesofar + \ltxgrid@info@sw{\class@info{\string\pagesofar}\saythe#1}{}% + }% + \def\@elt{\adj@column#1}% + \@booleantrue\firsttime@sw\@toplist + \@booleantrue\firsttime@sw\@botlist + \let\@elt\relax +}% +\def\adj@column#1#2{% + \advance#1-\ht#2% + \advance#1-\firsttime@sw{\textfloatsep\@booleanfalse\firsttime@sw}{\floatsep}% + \ltxgrid@info@sw{\class@info{\string\adj@column\string#1-\string#2}\saythe#1}{}% +}% +\def\adj@page#1#2{% + \advance#1-\ht#2% + \advance#1-\firsttime@sw{\dbltextfloatsep\@booleanfalse\firsttime@sw}{\dblfloatsep}% + \ltxgrid@info@sw{\class@info{\string\adj@page\string#1-\string#2}\saythe#1}{}% +}% +\def\set@adj@box#1#2{% + \@ifvoid#2{}{% + \advance#1-\ht#2\advance#1-\dp#2% + \@booleantrue\temp@sw + \ltxgrid@foot@info@sw{\class@info{\string\set@adj@box\string#2}\saythe#1}{}% + }% +}% +\appdef\@outputpage@tail{% + \set@colht % FIXME: needed? + \@floatplacement % FIXME: needed? + \@dblfloatplacement % FIXME: needed? +}% +\begingroup + \catcode`\1=\cat@letter + \catcode`\2=\cat@letter + \toks@{% + \setbox\footins\box\footsofar + \balance@two\col@1\@outputbox + \global\setbox\col@1\box\col@1 + \global\setbox\@outputbox\box\@outputbox + \combine@foot@inserts\footsofar\footins + }% + \aftergroup\def\aftergroup\balance@2\expandafter +\endgroup\expandafter{\the\toks@}% +\def\balance@two#1#2{% + \ltxgrid@info@sw{\class@info{\string\balance@two\string#1\string#2}}{}% + \outputdebug@sw{\trace@scroll{\showbox#1\showbox#2}}{}% + \setbox\thr@@\copy\footsofar + \setbox\@ne\vbox\bgroup + \@ifvoid{#1}{}{% + \recover@column#1\footsofar\column@recovered\footins@recovered + \@ifvoid{#2}{}{\marry@baselines}% + }% + \@ifvoid{#2}{}{% + \recover@column#2\footsofar\column@recovered\footins@recovered + }% + \egroup + \outputdebug@sw{\trace@scroll{\showbox\@ne}}{}% + \ltxgrid@foot@info@sw{\trace@scroll{\showbox\footsofar}}{}% + \dimen@\ht\@ne\divide\dimen@\tw@ + \dimen@i\dimen@ + \vbadness\@M + \vfuzz\maxdimen + \splittopskip\topskip + \loopwhile{% + \setbox\z@\copy\@ne\setbox\tw@\vsplit\z@ to\dimen@ + \remove@depth\z@\remove@depth\tw@ + \dimen@ii\ht\tw@\advance\dimen@ii-\ht\z@ + \dimen@i=.5\dimen@i + \ltxgrid@info@sw{\saythe\dimen@\saythe\dimen@i\saythe\dimen@ii}{}% + \@ifdim{\dimen@ii<.5\p@}{% + \@ifdim{\dimen@ii>-.5\p@}% + }{% + \false@sw + }% + {% + \true@sw + }{% + \@ifdim{\dimen@i<.5\p@}% + }% + {% + \false@sw + }% + {% + \advance\dimen@\@ifdim{\dimen@ii<\z@}{}{-}\dimen@i + \true@sw + }% + }% + \ltxgrid@info@sw{\saythe\dimen@\saythe\dimen@i\saythe\dimen@ii}{}% + \@ifdim{\ht\z@=\z@}{% + \@ifdim{\ht\tw@=\z@}% + }{% + \true@sw + }% + {% + }{% + \ltxgrid@info{Unsatifactorily balanced columns: giving up}% + \setbox\tw@\box#1% + \setbox\z@ \box#2% + \global\setbox\footsofar\box\thr@@ + }% + \setbox\tw@\vbox{\unvbox\tw@\vskip\z@skip}% + \setbox\z@ \vbox{\unvbox\z@ \vskip\z@skip}% + \set@colht + \dimen@\ht\z@\@ifdim{\dimen@<\ht\tw@}{\dimen@\ht\tw@}{}% + \@ifdim{\dimen@>\@colroom}{\dimen@\@colroom}{}% + \ltxgrid@info@sw{\saythe{\ht\z@}\saythe{\ht\tw@}\saythe\@colroom\saythe\dimen@}{}% + \setbox#1\vbox to\dimen@{\unvbox\tw@\unskip\raggedcolumn@skip}% + \setbox#2\vbox to\dimen@{\unvbox\z@ \unskip\raggedcolumn@skip}% + \outputdebug@sw{\trace@scroll{\showbox#1\showbox#2}}{}% +}% +\def\remove@depth#1{% + \setbox#1\vbox\bgroup + \unvcopy#1% + \setbox\z@\vbox\bgroup + \unvbox#1% + \setbox\z@\lastbox + \aftergroup\kern\aftergroup-\expandafter + \egroup + \the\dp\z@\relax + \egroup +}% +\def\recover@column#1#2#3#4{% + \ltxgrid@info@sw{\class@info{\string\recover@column\string#1\string#2\string#3\string#4}}{}% + \setbox#4\vbox{\unvcopy#1}% + \ltxgrid@foot@info@sw{\trace@scroll{\showbox#4}}{}% + \dimen@\ht#4% + \ltxgrid@foot@info@sw{\saythe\dimen@}{}% + \setbox#4\vbox\bgroup + \unvbox#4\unskip + \dimen@i\lastkern\unkern\advance\dimen@i\lastkern + \@ifdim{\dimen@i=\z@}{% + \dimen@i\lastkern\unkern + \ltxgrid@foot@info@sw{\saythe\dimen@i}{}% + \aftergroup\dimen@i + \expandafter\egroup\the\dimen@i\relax + }{% + \egroup + }% + \@ifdim{\dimen@i<\z@}{% + \advance\dimen@\dimen@i + \ltxgrid@foot@info@sw{\saythe\dimen@i\saythe\dimen@}{}% + \splittopskip\z@skip + \global\setbox#3\vsplit#4 to\dimen@ + \global\setbox#4\vbox{\unvbox#4}% + \ltxgrid@foot@info@sw{\trace@scroll{\showbox#1\showbox#2\showbox#3\showbox#4}}{}% + \global\setbox#2\vbox\bgroup\unvbox#2\vskip\z@skip\unvbox#4\egroup + }{% + \setbox#3\box#4% + \ltxgrid@foot@info@sw{\trace@scroll{\showbox#1\showbox#2\showbox#3\showbox#4}}{}% + }% + \unvbox#3% + \loopwhile{\dimen@\lastskip\@ifdim{\dimen@>\z@}{\unskip\true@sw}{\false@sw}}% +}% +\def\recover@column@null#1#2#3#4{% + \unvcopy#1% +}% +\rvtx@ifformat@geq{2020/10/01}% + {% + \AddToHook{begindocument}{% + \open@column@one\@ne + \set@colht + \@floatplacement + \@dblfloatplacement + }% + }{% + \prepdef\@begindocumenthook{% + \open@column@one\@ne + \set@colht + \@floatplacement + \@dblfloatplacement + }% + } +\def\longtable@longtable{% + \par + \ifx\multicols\@undefined\else\ifnum\col@number>\@ne\@twocolumntrue\fi\fi + \if@twocolumn\LT@err{longtable not in 1-column mode}\@ehc\fi + \begingroup + \@ifnextchar[\LT@array{\LT@array[x]}% +}% +\def\longtable@new{% + \par + \@ifnextchar[\LT@array{\LT@array[x]}% +}% +\def\endlongtable@longtable{% + \crcr + \noalign{% + \let\LT@entry\LT@entry@chop + \xdef\LT@save@row{\LT@save@row}}% + \LT@echunk + \LT@start + \unvbox\z@ + \LT@get@widths + \if@filesw + {\let\LT@entry\LT@entry@write\immediate\write\@auxout{% + \gdef\expandafter\noexpand + \csname LT@\romannumeral\c@LT@tables\endcsname + {\LT@save@row}}}% + \fi + \ifx\LT@save@row\LT@@save@row + \else + \LT@warn{Column \@width s have changed\MessageBreak + in table \thetable}% + \LT@final@warn + \fi + \endgraf\penalty -\LT@end@pen + \endgroup + \global\@mparbottom\z@ + \pagegoal\vsize + \endgraf\penalty\z@\addvspace\LTpost + \ifvoid\footins\else\insert\footins{}\fi +}% +\def\endlongtable@new{% + \crcr + \noalign{% + \let\LT@entry\LT@entry@chop + \xdef\LT@save@row{\LT@save@row}% + }% + \LT@echunk + \LT@start + \unvbox\z@ + \LT@get@widths + \@if@sw\if@filesw\fi{% + {% + \let\LT@entry\LT@entry@write + \immediate\write\@auxout{% + \gdef\expandafter\noexpand\csname LT@\romannumeral\c@LT@tables\endcsname + {\LT@save@row}% + }% + }% + }{}% + \@ifx{\LT@save@row\LT@@save@row}{}{% + \LT@warn{% + Column \@width s have changed\MessageBreak in table \thetable + }\LT@final@warn + }% + \endgraf + \nobreak + \box\@ifvoid\LT@lastfoot{\LT@foot}{\LT@lastfoot}% + \global\@mparbottom\z@ + \endgraf + \LT@post +}% +\def\LT@start@longtable{% + \let\LT@start\endgraf + \endgraf\penalty\z@\vskip\LTpre + \dimen@\pagetotal + \advance\dimen@ \ht\ifvoid\LT@firsthead\LT@head\else\LT@firsthead\fi + \advance\dimen@ \dp\ifvoid\LT@firsthead\LT@head\else\LT@firsthead\fi + \advance\dimen@ \ht\LT@foot + \dimen@ii\vfuzz + \vfuzz\maxdimen + \setbox\tw@\copy\z@ + \setbox\tw@\vsplit\tw@ to \ht\@arstrutbox + \setbox\tw@\vbox{\unvbox\tw@}% + \vfuzz\dimen@ii + \advance\dimen@ \ht + \ifdim\ht\@arstrutbox>\ht\tw@\@arstrutbox\else\tw@\fi + \advance\dimen@\dp + \ifdim\dp\@arstrutbox>\dp\tw@\@arstrutbox\else\tw@\fi + \advance\dimen@ -\pagegoal + \ifdim \dimen@>\z@\vfil\break\fi + \global\@colroom\@colht + \ifvoid\LT@foot\else + \advance\vsize-\ht\LT@foot + \global\advance\@colroom-\ht\LT@foot + \dimen@\pagegoal\advance\dimen@-\ht\LT@foot\pagegoal\dimen@ + \maxdepth\z@ + \fi + \ifvoid\LT@firsthead\copy\LT@head\else\box\LT@firsthead\fi +\nobreak + \output{\LT@output}% +}% +\def\LT@start@new{% + \let\LT@start\endgraf + \endgraf + \markthr@@{}% + \LT@pre + \@ifvoid\LT@firsthead{\LT@top}{\box\LT@firsthead\nobreak}% + \mark@envir{longtable}% +}% +\def\LT@end@hd@ft@longtable#1{% + \LT@echunk + \ifx\LT@start\endgraf + \LT@err{Longtable head or foot not at start of table}{Increase LTchunksize}% + \fi + \setbox#1\box\z@ + \LT@get@widths\LT@bchunk +}% +\def\LT@end@hd@ft@new#1{% + \LT@echunk + \@ifx{\LT@start\endgraf}{% + \LT@err{Longtable head or foot not at start of table}{Increase LTchunksize}% + }% + \global\setbox#1\box\z@ + \LT@get@widths + \LT@bchunk +}% +\def\LT@array@longtable[#1]#2{% + \refstepcounter{table}\stepcounter{LT@tables}% + \if l#1% + \LTleft\z@ \LTright\fill + \else\if r#1% + \LTleft\fill \LTright\z@ + \else\if c#1% + \LTleft\fill \LTright\fill + \fi\fi\fi + \let\LT@mcol\multicolumn + \let\LT@@tabarray\@tabarray + \let\LT@@hl\hline + \def\@tabarray{% + \let\hline\LT@@hl + \LT@@tabarray}% + \let\\\LT@tabularcr\let\tabularnewline\\% + \def\newpage{\noalign{\break}}% + \def\pagebreak{\noalign{\ifnum`}=0\fi\@testopt{\LT@no@pgbk-}4}% + \def\nopagebreak{\noalign{\ifnum`}=0\fi\@testopt\LT@no@pgbk4}% + \let\hline\LT@hline \let\kill\LT@kill\let\caption\LT@caption + \@tempdima\ht\strutbox + \let\@endpbox\LT@endpbox + \ifx\extrarowheight\@undefined + \let\@acol\@tabacol + \let\@classz\@tabclassz \let\@classiv\@tabclassiv + \def\@startpbox{\vtop\LT@startpbox}% + \let\@@startpbox\@startpbox + \let\@@endpbox\@endpbox + \let\LT@LL@FM@cr\@tabularcr + \else + \advance\@tempdima\extrarowheight + \col@sep\tabcolsep + \let\@startpbox\LT@startpbox\let\LT@LL@FM@cr\@arraycr + \fi + \setbox\@arstrutbox\hbox{\vrule + \@height \arraystretch \@tempdima + \@depth \arraystretch \dp \strutbox + \@width \z@}% + \let\@sharp##\let\protect\relax + \begingroup + \@mkpream{#2}% + \xdef\LT@bchunk{% + \global\advance\c@LT@chunks\@ne + \global\LT@rows\z@\setbox\z@\vbox\bgroup + \LT@setprevdepth + \tabskip\LTleft \noexpand\halign to\hsize\bgroup + \tabskip\z@ \@arstrut \@preamble \tabskip\LTright \cr}% + \endgroup + \expandafter\LT@nofcols\LT@bchunk&\LT@nofcols + \LT@make@row + \m@th\let\par\@empty + \everycr{}\lineskip\z@\baselineskip\z@ + \LT@bchunk}% +\def\LT@LR@l{\LTleft\z@ \LTright\fill}% +\def\LT@LR@r{\LTleft\fill \LTright\z@ }% +\def\LT@LR@c{\LTleft\fill \LTright\fill}% +\def\LT@array@new[#1]#2{% + \refstepcounter{table}\stepcounter{LT@tables}% + \table@hook + \LTleft\fill \LTright\fill + \csname LT@LR@#1\endcsname + \let\LT@mcol\multicolumn + \let\LT@@hl\hline + \prepdef\@tabarray{\let\hline\LT@@hl}% + \let\\\LT@tabularcr + \let\tabularnewline\\% + \def\newpage{\noalign{\break}}% + \def\pagebreak{\noalign{\ifnum`}=0\fi\@testopt{\LT@no@pgbk-}4}% + \def\nopagebreak{\noalign{\ifnum`}=0\fi\@testopt\LT@no@pgbk4}% + \let\hline\LT@hline + \let\kill\LT@kill + \let\caption\LT@caption + \@tempdima\ht\strutbox + \let\@endpbox\LT@endpbox + \@ifxundefined\extrarowheight{% + \let\@acol\@tabacol + \let\@classz\@tabclassz + \let\@classiv\@tabclassiv + \def\@startpbox{\vtop\LT@startpbox}% + \let\@@startpbox\@startpbox + \let\@@endpbox\@endpbox + \let\LT@LL@FM@cr\@tabularcr@LaTeX + \let\@xtabularcr\@xtabularcr@LaTeX + }{% + \advance\@tempdima\extrarowheight + \col@sep\tabcolsep + \let\@startpbox\LT@startpbox + \let\LT@LL@FM@cr\@arraycr@array + }% + \let\@acoll\@tabacoll + \let\@acolr\@tabacolr + \let\@acol\@tabacol + \setbox\@arstrutbox\hbox{% + \vrule + \@height \arraystretch \@tempdima + \@depth \arraystretch \dp \strutbox + \@width \z@ + }% + \let\@sharp##% + \let\protect\relax + \begingroup + \@mkpream{#2}% + \@mkpream@relax + \edef\@preamble{\@preamble}% + \prepdef\@preamble{% + \global\advance\c@LT@chunks\@ne + \global\LT@rows\z@ + \setbox\z@\vbox\bgroup + \LT@setprevdepth + \tabskip\LTleft + \halign to\hsize\bgroup + \tabskip\z@ + \@arstrut + }% + \appdef\@preamble{% + \tabskip\LTright + \cr + }% + \global\let\LT@bchunk\@preamble + \endgroup + \expandafter\LT@nofcols\LT@bchunk&\LT@nofcols + \LT@make@row + \m@th + \let\par\@empty + \everycr{}% + \lineskip\z@ + \baselineskip\z@ + \LT@bchunk +}% +\appdef\table@hook{}% +\def\switch@longtable{% + \@ifpackageloaded{longtable}{% + \@ifx{\longtable\longtable@longtable}{% + \@ifx{\endlongtable\endlongtable@longtable}{% + \@ifx{\LT@start\LT@start@longtable}{% + \@ifx{\LT@end@hd@ft\LT@end@hd@ft@longtable}{% + \@ifx{\LT@array\LT@array@longtable}{% + \true@sw + }{\false@sw}% + }{\false@sw}% + }{\false@sw}% + }{\false@sw}% + }{\false@sw}% + {% + \class@info{Patching longtable package}% + }{% + \class@info{Patching unrecognized longtable package. (Proceeding with fingers crossed)}% + }% + \let\longtable\longtable@new + \let\endlongtable\endlongtable@new + \let\LT@start\LT@start@new + \let\LT@end@hd@ft\LT@end@hd@ft@new + \let\LT@array\LT@array@new + \newenvironment{longtable*}{% + \onecolumngrid@push + \longtable + }{% + \endlongtable + \onecolumngrid@pop + }% + }{}% +}% +\def\LT@pre{\penalty\z@\vskip\LTpre}% +\def\LT@bot{\nobreak\copy\LT@foot\vfil}% +\def\LT@top{\copy\LT@head\nobreak}% +\def\LT@post{\penalty\z@\addvspace\LTpost\mark@envir{\curr@envir}}% +\def\LT@adj{% + \setbox\z@\vbox{\null}\dimen@-\ht\z@ + \setbox\z@\vbox{\unvbox\z@\LT@bot}\advance\dimen@\ht\z@ + \global\advance\vsize-\dimen@ +}% +\def\output@init@longtable{\LT@adj}% +\def\output@prep@longtable{\setbox\@cclv\vbox{\unvbox\@cclv\LT@bot}}% +\def\output@post@longtable{\LT@top}% +\let\output@init@theindex\@empty +\let\output@prep@theindex\@empty +\def\output@post@theindex{% + \@ifodd\c@page{}{% + \@ifnum{\pagegrid@cur=\@ne}{% + }% + }% +}% +\def\check@aux{\do@output@MVL{\do@check@aux}}% +\def\check@deferlist@stuck#1{% + \@ifx{\@deferlist@postshipout\@empty}{}{% + \@ifx{\@deferlist@postshipout\@deferlist}{% + \@fltstk + \clearpage@sw{% + \ltxgrid@warn{Deferred float stuck during \string\clearpage\space processing}% + }{% + \force@deferlist@stuck#1% + }% + }{% + }% + \global\let\@deferlist@postshipout\@empty + }% +}% +\def\@fltstk{% + \@latex@warning{A float is stuck (cannot be placed without \string\clearpage)}% +}% +\appdef\@outputpage@tail{% + \global\let\@deferlist@postshipout\@deferlist +}% +\def\@next#1#2{% + \@ifx{#2\@empty}{\false@sw}{% + \expandafter\@xnext#2\@@#1#2% + \true@sw + }% +}% +\def\@xnext\@elt#1#2\@@#3#4{% + \def#3{#1}% + \gdef#4{#2}% + \def\@tempa{#4}\def\@tempb{\@freelist}% + \@ifx{\@tempa\@tempb}{% + \@ifx{#4\@empty}{% + \force@deferlist@empty%{Float register pool exhausted}% + }{}% + }{}% +}% +\def\force@deferlist@stuck#1{% + \force@deferlist@sw{% + \@booleantrue\clearpage@sw + \@booleantrue\forcefloats@sw + #1% + }{% + }% +}% +\def\force@deferlist@empty{% + \force@deferlist@sw{% + \penalty-\pagebreak@pen + \protect@penalty\do@forcecolumn@pen + }{% + }% +}% +\@booleanfalse\force@deferlist@sw +\mathchardef\do@forcecolumn@pen=10009 +\@namedef{output@-\the\do@forcecolumn@pen}{\do@forcecolumn}% +\def\do@forcecolumn{% + \@booleantrue\clearpage@sw + \@booleantrue\forcefloats@sw + \do@startcolumn +}% +\def\enlargethispage{% + \@ifstar{% + \@enlargethispage{}% + }{% + \@enlargethispage{}% + }% +}% +\def\@enlargethispage#1#2{% + \begingroup + \dimen@#2\relax + \edef\@tempa{#1}% + \edef\@tempa{\noexpand\@@enlargethispage{\@tempa}{\the\dimen@}}% + \expandafter\do@output@MVL\expandafter{\@tempa}% + \endgroup +}% +\def\@@enlargethispage#1#2{% + \def\@tempa{one}% + \@ifx{\thepagegrid\@tempa}{% + \true@sw + }{% + \def\@tempa{mlt}% + \@ifx{\thepagegrid\@tempa}{% + \@ifnum{\pagegrid@cur=\@ne}{% + \gdef\enlarge@colroom{#2}% + \true@sw + }{% + \ltxgrid@warn{Too late to enlarge this page; move the command to the first column.}% + \false@sw + }% + }{% + \ltxgrid@warn{Unable to enlarge a page of this kind.}% + \false@sw + }% + }% + {% + \class@info{Enlarging page \thepage\space by #2}% + \global\advance\@colroom#2\relax + \set@vsize + }{% + }% +}% +\let\enlarge@colroom\@empty +\let\@kludgeins\@undefined +\@booleantrue\textheight@sw +\prepdef\@outputpage@head{% + \textheight@sw{% + \count@\vbadness\vbadness\@M + \dimen@\vfuzz\vfuzz\maxdimen + \setbox\@outputbox\vbox to\textheight{\unvbox\@outputbox}% + \vfuzz\dimen@ + \vbadness\count@ + }{}% +}% +\appdef\@outputpage@head{% + \@ifx{\LS@rot\@undefined}{}{\LS@rot}% +}% +\def\ltxgrid@info{% + \ltxgrid@info@sw{\class@info}{\@gobble}% +}% +\@booleanfalse\ltxgrid@info@sw +\def\ltxgrid@warn{% + \ltxgrid@warn@sw{\class@warn}{\@gobble}% +}% +\@booleantrue\ltxgrid@warn@sw +\@booleanfalse\ltxgrid@foot@info@sw +\def\def@next@handler#1#2#3{% + \advance#1\@ne\mathchardef#2\the#1% + \expandafter\def\csname output@-\the#1\endcsname{#3}% +}% +\def\def@line@handler#1#2{% + \begingroup + \@tempcnta\int@parpenalty + \advance\@tempcnta-#1% + \aftergroup\def + \expandafter\aftergroup\csname output@-\the\@tempcnta\endcsname + \endgroup{#2}% +}% +\mathchardef\int@parpenalty11012 +\def@line@handler\z@{\@handle@line@ltx{}{}{}}% +\def@line@handler\@ne{\@handle@line@ltx{}{}{\brokenpenalty@ltx}}% +\def@line@handler\tw@{\@handle@line@ltx{}{\clubpenalty@ltx}{}}% +\def@line@handler\thr@@{\@handle@line@ltx{\clubpenalty@ltx}{}{\brokenpenalty@ltx}}% +\def@line@handler\f@ur{\@handle@line@ltx{\widowpenalty@ltx}{}{}}% +\def@line@handler{5}{\@handle@line@ltx{\widowpenalty@ltx}{}{\brokenpenalty@ltx}}% +\def@line@handler{6}{\@handle@line@ltx{\widowpenalty@ltx}{\clubpenalty@ltx}{}}% +\def@line@handler{7}{\@handle@line@ltx{\widowpenalty@ltx}{\clubpenalty@ltx}{\brokenpenalty@ltx}}% +\def@line@handler{8}{\@handle@line@ltx{\displaywidowpenalty@ltx}{}{}}% +\def@line@handler{9}{\@handle@line@ltx{\displaywidowpenalty@ltx}{}{\brokenpenalty@ltx}}% +\def@line@handler{10}{\@handle@line@ltx{\displaywidowpenalty@ltx}{\clubpenalty@ltx}{}}% +\def@line@handler{11}{\@handle@line@ltx{\displaywidowpenalty@ltx}{\clubpenalty@ltx}{\brokenpenalty@ltx}}% +\def\@handle@line@ltx#1#2#3{% + \@@handle@line@ltx + \@tempcnta\lastpenalty + \@tempcntb\interlinepenalty@ltx\relax + \@if@empty{#1}{}{\advance\@tempcntb#1\relax}% + \@if@empty{#2}{}{\advance\@tempcntb#2\relax}% + \@if@empty{#3}{}{\advance\@tempcntb#3\relax}% + \penalty\@ifnum{\@tempcnta<\@tempcntb}{\@tempcntb}{\@tempcnta}% +}% +\let\@@handle@line@ltx\@empty +\@tempcnta\int@parpenalty +\def@next@handler\@tempcnta\int@postparpenalty{\reset@queues@ltx\handle@par@ltx}% +\def@next@handler\@tempcnta\int@vadjustpenalty{\handle@vadjust@ltx}% +\def@next@handler\@tempcnta\int@whatsitpenalty{\handle@whatsit@ltx}% +\def@next@handler\@tempcnta\int@predisplaypenalty{\reset@queues@ltx\@handle@display@ltx{\predisplaypenalty@ltx}}% +\def@next@handler\@tempcnta\int@interdisplaylinepenalty{\@handle@display@ltx{\interdisplaylinepenalty@ltx}}% +\def@next@handler\@tempcnta\int@postdisplaypenalty{\@handle@display@ltx{\postdisplaypenalty@ltx}}% +\def\@handle@display@ltx#1{% + \@@handle@display@ltx + \@tempcnta\lastpenalty + \@tempcntb#1% + \penalty\@ifnum{\@tempcnta<\@tempcntb}{\@tempcntb}{\@tempcnta}% +}% +\let\@@handle@display@ltx\@empty +\def\handle@par@ltx{}% +\def\set@linepenalties{% + \expandafter\def\expandafter\interlinepenalty@ltx\expandafter{\the\interlinepenalty}% + \interlinepenalty-\int@parpenalty + \expandafter\def\expandafter\brokenpenalty@ltx\expandafter{\the\brokenpenalty}% + \brokenpenalty\@ne + \expandafter\def\expandafter\clubpenalty@ltx\expandafter{\the\clubpenalty}% + \clubpenalty\tw@ + \expandafter\def\expandafter\widowpenalty@ltx\expandafter{\the\widowpenalty}% + \widowpenalty\f@ur + \expandafter\def\expandafter\displaywidowpenalty@ltx\expandafter{\the\displaywidowpenalty}% + \displaywidowpenalty8\relax +}% +\def\restore@linepenalties{% + \interlinepenalty\interlinepenalty@ltx + \brokenpenalty\brokenpenalty@ltx + \clubpenalty\clubpenalty@ltx + \widowpenalty\widowpenalty@ltx + \displaywidowpenalty\displaywidowpenalty@ltx + \relax +}% +\def\set@displaypenalties#1{% + \expandafter\def\expandafter\predisplaypenalty@ltx\expandafter{\the\predisplaypenalty}% + \expandafter\def\expandafter\interdisplaylinepenalty@ltx\expandafter{\the\interdisplaylinepenalty}% + \expandafter\def\expandafter\postdisplaypenalty@ltx\expandafter{\the\postdisplaypenalty}% + \@ifhmode{\predisplaypenalty-\int@predisplaypenalty\relax}{}% + #1{\interdisplaylinepenalty-\int@interdisplaylinepenalty\relax}{}% + #1{\postdisplaypenalty-\int@postdisplaypenalty\relax}{}% +}% +\def\enqueue@whatsit@ltx#1{% + \gappdef\g@whatsit@queue{{#1}}% + \vadjust{\penalty-\int@whatsitpenalty}% +}% +\def\handle@whatsit@ltx{% + \unvbox\@cclv + \g@pop@ltx\g@whatsit@queue\@tempa + \expandafter\do@whatsit\expandafter{\@tempa}% +}% +\def\do@whatsit#1{}% +\def\g@pop@ltx#1#2{% + \expandafter\@g@pop@ltx#1{}{}\@@#1#2% +}% +\def\@g@pop@ltx#1#2\@@#3#4{% + \gdef#3{#2}% + \def#4{#1}% +}% +\let\vspace@ltx\vspace +\let\pagebreak@ltx\pagebreak +\let\nopagebreak@ltx\nopagebreak +\let\endline@ltx\\ +\let\@arrayparboxrestore@ltx\@arrayparboxrestore +\def\@tempa#1{% +\def\@vspace@org ##1{% + \ifvmode + #1% \vskip #1 + \vskip\z@skip + \else + \@bsphack + \vadjust{\@restorepar + #1% \vskip #1 + \vskip\z@skip + }% + \@esphack + \fi +}% +\def\@vspace@ltx##1{% + \@ifvmode{% + #1% \vskip #1 + \vskip\z@skip + }{% + \@bsphack + \ex@vadjust@ltx{% + \@restorepar + \nobreak + #1% \vskip #1 + \vskip\z@skip + }% + \@esphack + }% +}% +\def\@vspacer@org##1{% + \ifvmode + \dimen@\prevdepth + \hrule \@height\z@ + \nobreak + #1%\vskip #1 + \vskip\z@skip + \prevdepth\dimen@ + \else + \@bsphack + \vadjust{\@restorepar + \hrule \@height\z@ + \nobreak + #1%\vskip #1 + \vskip\z@skip}% + \@esphack +\fi +}% +\def\@vspacer@ltx##1{% + \@ifvmode{% + \dimen@\prevdepth + \hrule\@height\z@ + \nobreak + #1%\vskip#1 + \vskip\z@skip + \prevdepth\dimen@ + }{% + \@bsphack + \ex@vadjust@ltx{% + \@restorepar + \hrule\@height\z@ + \nobreak + #1%\vskip#1 + \vskip\z@skip + }% + \@esphack + }% +}% +} +\rvtx@ifformat@geq{2020/10/01}% + {\@tempa{\@vspace@calcify{#1}}}% + {\@tempa{\vskip #1 }}% +\def\@no@pgbk@org #1[#2]{% + \ifvmode + \penalty #1\@getpen{#2}% + \else + \@bsphack + \vadjust{\penalty #1\@getpen{#2}}% + \@esphack + \fi +}% +\def\@no@pgbk@ltx#1[#2]{% + \@ifvmode{% + \penalty#1\@getpen{#2}% + }{% + \@bsphack + \ex@vadjust@ltx{% + \penalty#1\@getpen{#2}% + }% + \@esphack + }% +}% +\rvtx@ifformat@geq{2020/02/02}% +{\protected}{\long}\def\end@line@org{% + \let\reserved@e\relax + \let\reserved@f\relax + \@ifstar{% + \let\reserved@e\vadjust + \let\reserved@f\nobreak + \@xnewline + }% + \@xnewline +}% +\rvtx@ifformat@geq{2020/02/02}% +{\protected}{\long}\def\end@line@ltx{% + \let\reserved@e\relax + \let\reserved@f\relax + \@ifstar{% + \let\reserved@e\ex@vadjust@ltx + \let\reserved@f\nobreak + \@xnewline + }{% + \@xnewline + }% +}% +\def\@tempa#1{% + \def\@newline@org[##1]{% + \let\reserved@e\vadjust + \@gnewline{#1}% \vskip#1 + }% + \def\@newline@ltx[##1]{% + \let\reserved@e\ex@vadjust@ltx + \@gnewline{#1}% \vskip#1 + }% +} +\rvtx@ifformat@geq{2020/10/01}% + {\@tempa{\@vspace@calcify{#1}}}% + {\@tempa{\vskip #1}}% + \@ifx{\@vspace\@vspace@org}{% + \@ifx{\@vspacer\@vspacer@org}{% + \@ifx{\@no@pgbk\@no@pgbk@org}{% + \@ifx{\@newline\@newline@org}{% + \expandafter\@ifx\expandafter{% + \csname\rvtx@ifformat@geq{2020/02/02}% + {\expandafter\@gobble\string\\}% + {\expandafter\@gobble\string\\ }\endcsname + \end@line@org + }{% + \true@sw + }{\false@sw}% + }{\false@sw}% + }{\false@sw}% + }{\false@sw}% + }{\false@sw}% + {% + \class@info{Overriding \string\@vspace, \string\@vspacer, \string\@no@pgbk, \string\@newline, and \string\\ }% + \let\@normalcr\end@line@ltx + \expandafter\let + \csname\rvtx@ifformat@geq{2020/02/02}% + {\expandafter\@gobble\string\\}% + {\expandafter\@gobble\string\\ }\endcsname\@normalcr + \let\@newline\@newline@ltx + \let\@vspace\@vspace@ltx + \let\@vspacer\@vspacer@ltx + \let\@no@pgbk\@no@pgbk@ltx + }{% + \class@warn{% + Failed to recognize \string\@vspace, \string\@vspacer, \string\@no@pgbk, \string\@newline, and \string\\; + no patches applied. Please get a more up-to-date class, + }% + }% +\let\ex@vadjust@ltx\vadjust +\def\enqueue@vadjust@ltx#1{% + \gappdef\g@vadjust@queue{{#1}}% + \vadjust{\penalty-\int@vadjustpenalty}% +}% +\def\handle@vadjust@ltx{% + \unvbox\@cclv + \g@pop@ltx\g@vadjust@queue\@tempa + \expandafter\gappdef\expandafter\g@vadjust@line\expandafter{\@tempa}% +}% +\let\g@vadjust@line\@empty +\def\reset@queues@ltx{% + \global\let\g@whatsit@queue\@empty + \global\let\g@vadjust@queue\@empty +}% +\newcommand\linenomathWithnumbers@LN{% + \ifLineNumbers + \ifnum\interlinepenalty>-\linenopenaltypar + \global\holdinginserts\thr@@ + \advance\interlinepenalty \linenopenalty + \ifhmode + \advance\predisplaypenalty \linenopenalty + \fi + \advance\postdisplaypenalty \linenopenalty + \advance\interdisplaylinepenalty \linenopenalty + \fi + \fi + \ignorespaces +}% +\newcommand\linenomathNonumbers@LN{% + \ifLineNumbers + \ifnum\interlinepenalty>-\linenopenaltypar + \global\holdinginserts\thr@@ + \advance\interlinepenalty \linenopenalty + \ifhmode + \advance\predisplaypenalty \linenopenalty + \fi + \fi + \fi + \ignorespaces +}% +\def\endlinenomath@LN{% + \ifLineNumbers + \global\holdinginserts\@LN@outer@holdins + \fi + \global\@ignoretrue +} +\def\linenumberpar@LN{% + \ifvmode \@@@par \else + \ifinner \@@@par \else + \xdef\@LN@outer@holdins{\the\holdinginserts}% + \advance \interlinepenalty \linenopenalty + \linenoprevgraf \prevgraf + \global \holdinginserts \thr@@ + \@@@par + \ifnum\prevgraf>\linenoprevgraf + \penalty-\linenopenaltypar + \fi + \@LN@parpgbrk + \global\holdinginserts\@LN@outer@holdins + \advance\interlinepenalty -\linenopenalty + \fi + \fi +}% +\appdef\class@documenthook{% + \@ifpackageloaded{lineno}{% + \@ifx{\linenomathWithnumbers\linenomathWithnumbers@LN}{% + \@ifx{\linenomathNonumbers\linenomathNonumbers@LN}{% + \@ifx{\endlinenomath\endlinenomath@LN}{% + \@ifx{\linenumberpar\linenumberpar@LN}{% + \true@sw + }{\false@sw}% + }{\false@sw}% + }{\false@sw}% + }{\false@sw}% + {% + \class@info{Overriding lineo.sty, restoring output routine,}% + \let\linenumberpar\linenumberpar@ltx + \let\endlinenomath\endlinenomath@ltx + \expandafter\let\csname endlinenomath*\endcsname\endlinenomath@ltx + \let\linenomathWithnumbers\linenomathWithnumbers@ltx + \let\linenomathNonumbers\linenomathNonumbers@ltx + \let\ex@vadjust@ltx\ex@vadjust@line + \let\@LN@postlabel\enqueue@whatsit@ltx + \let\do@whatsit\write@linelabel + \let\handle@par@ltx\handle@par@LN + \let\@@handle@line@ltx\Make@LineNo@ltx + \let\@@handle@display@ltx\Make@LineNo@ltx + \output@latex{\natural@output}% + \let\vspace\vspace@ltx + \let\pagebreak\pagebreak@ltx + \let\nopagebreak\nopagebreak@ltx + \let\@arrayparboxrestore\@arrayparboxrestore@ltx + \let\\\endline@ltx + \appdef\set@footnotefont{% + \let\par\@@@par + \let\@@par\@@@par + }% + \@if@sw\ifLineNumbers\fi{% + \class@info{Reinvoke \string\linenumbers}% + \let\@@par\linenumberpar + \@ifx{\@par\linenumberpar@LN}{\let\@par\linenumberpar}{}% + \@ifx{\par\linenumberpar@LN}{\let\par\linenumberpar}{}% + }{% + \class@info{Line numbering not turned on yet}% + }% + }{% + \class@warn{Failed to recognize lineno.sty procedures; no patches applied. Please get a more up-to-date class.}% + }% + }{% + }% +}% +\def\linenumberpar@ltx{\@ifvmode{\@@@par}{\@linenumberpar}}% +\def\@linenumberpar{% + \linenoprevgraf\prevgraf + \set@linepenalties + \@@@par + \@ifnum{\prevgraf>\linenoprevgraf}{ + \penalty-\int@postparpenalty + }{}% + \@LN@parpgbrk + \restore@linepenalties +}% +\newcommand\linenomathWithnumbers@ltx{\@linenomathnumbers@ltx\true@sw}% +\newcommand\linenomathNonumbers@ltx{\@linenomathnumbers@ltx\false@sw}% +\def\@linenomathnumbers@ltx#1{% + \@if@sw\ifLineNumbers\fi{% + \set@linepenalties + \set@displaypenalties#1% + }{}% + \ignorespaces +}% +\def\endlinenomath@ltx{% + \global\@ignoretrue +}% +\def\handle@par@LN{% + \Make@LineNo@ltx + \@tempcnta\lastpenalty + \@ifnum{\@tempcnta=\z@}{}{% + \expandafter\gdef + \expandafter\@LN@parpgbrk + \expandafter{% + \expandafter\penalty + \the\@tempcnta + \global\let\@LN@parpgbrk\@LN@screenoff@pen + }% + }% +}% +\def\Make@LineNo@ltx{% + \@LN@maybe@normalLineNumber + \boxmaxdepth\maxdimen\setbox\z@\vbox{\unvbox\@cclv}% + \@tempdima\dp\z@ + \unvbox\z@ + \sbox\@tempboxa{\hb@xt@\z@{\makeLineNumber}}% + \ht\@tempboxa\z@ + \@LN@depthbox + \stepLineNumber + \g@vadjust@line + \global\let\g@vadjust@line\@empty +}% +\def\write@linelabel#1{% + \protected@write\@auxout{}{% + \string\newlabel{#1}{{\theLineNumber}{\thepage}{}{}{}}% + }% +}% +\def\ex@vadjust@line{% + \@if@sw\ifLineNumbers\fi{\enqueue@vadjust@ltx}{\vadjust}% +}% +\let\setup@hook\@empty +\appdef\document@inithook{% + \switch@longtable + \let\LT@makecaption\LT@makecaption@rtx +}% +\def\LT@makecaption@rtx#1#2#3{% + \LT@mcol\LT@cols c{% + \hbox to\z@{% + \hss + \parbox[t]\LTcapwidth{% + \sbox\@tempboxa{#1{#2: }#3\unskip\nobreak\vrule\@width\z@\@height\z@\@depth .5\baselineskip}% + \ifdim\wd\@tempboxa>\hsize + #1{#2: }#3\unskip\nobreak\vrule\@width\z@\@height\z@\@depth .5\baselineskip + \else + \hbox to\hsize{\hfil\box\@tempboxa\hfil}% + \fi + \endgraf + }% + \hss + }% + }% +}% +\def\protectdef@boldmath{% + \expandafter\@ifnotrelax\csname boldmath \endcsname{}{% + \class@info{Robustifying \string\LaTeX's \string\boldmath\space command}% + \expandafter\let\csname boldmath \endcsname\boldmath + \expandafter\def\expandafter\boldmath\expandafter{\expandafter\protect\csname boldmath \endcsname}% + }% +}% +\appdef\document@inithook{% + \protectdef@boldmath +}% +\DeclareOption{checkin}{% + \@booleantrue\dateinRH@sw + \@booleantrue\preprintsty@sw + \def\@pointsize{12}% + \@booleantrue\showPACS@sw + \@booleantrue\showKEYS@sw + \def\fp@proc@h{\allow@breaking@tables}% + \def\fp@proc@H{\allow@breaking@tables}% +}% +\@booleanfalse\dateinRH@sw +\def\checkindate{\dateinRH@sw{{\tiny(\today)}}{}}% +\def\allow@breaking@tables{% + \def\array@default{v}% tabular can break over pages + \@booleanfalse\floats@sw % table can break over pages +}% +\DeclareOption{preprint}{% + \@booleantrue\preprintsty@sw + \ExecuteOptions{12pt}% +}% +\DeclareOption{reprint}{% +\@booleanfalse\preprintsty@sw +\@booleantrue\twocolumn@sw + \ExecuteOptions{10pt}% +}% +\DeclareOption{manuscript}{% + \class@warn{Document class option manuscript is obsolete; use preprint instead}% + \ExecuteOptions{preprint}% +}% +\@booleanfalse\preprintsty@sw +\DeclareOption{showpacs}{% + \@booleantrue\showPACS@sw +}% +\DeclareOption{noshowpacs}{% + \@booleanfalse\showPACS@sw +}% +\DeclareOption{showkeys}{% + \@booleantrue\showKEYS@sw +}% +\DeclareOption{noshowkeys}{% + \@booleanfalse\showKEYS@sw +}% +\@booleanfalse\showPACS@sw +\@booleanfalse\showKEYS@sw +\DeclareOption{balancelastpage}{% + \@booleantrue\balancelastpage@sw +}% +\DeclareOption{nobalancelastpage}{% + \@booleanfalse\balancelastpage@sw +}% +\@booleantrue\balancelastpage@sw +\DeclareOption{nopreprintnumbers}{\@booleanfalse\preprint@sw}% +\DeclareOption{preprintnumbers}{\@booleantrue\preprint@sw}% +\appdef\setup@hook{% + \@ifxundefined\preprint@sw{\let\preprint@sw\preprintsty@sw}{}% +}% +\DeclareOption{hyperref}{% + \class@warn{Class option "hyperref" is no longer supported.^^JEmploy \string\usepackage{hyperref} instead}% +}% +\DeclareOption{10pt}{\def\@pointsize{10}}% +\DeclareOption{11pt}{\def\@pointsize{11}}% +\DeclareOption{12pt}{\def\@pointsize{12}}% +\let\@pointsize\@undefined +\DeclareOption{a4paper}{% + \setlength\paperheight {297mm}% + \setlength\paperwidth {210mm}% +}% +\DeclareOption{a5paper}{% + \setlength\paperheight {210mm}% + \setlength\paperwidth {148mm}% +}% +\DeclareOption{b5paper}{% + \setlength\paperheight {250mm}% + \setlength\paperwidth {176mm}% +}% +\DeclareOption{letterpaper}{% + \setlength\paperheight {11in}% + \setlength\paperwidth {8.5in}% +}% +\DeclareOption{legalpaper}{% + \setlength\paperheight {14in}% + \setlength\paperwidth {8.5in}% +}% +\DeclareOption{executivepaper}{% + \setlength\paperheight {10.5in}% + \setlength\paperwidth {7.25in}% +}% +\DeclareOption{landscape}{% + \setlength\@tempdima {\paperheight}% + \setlength\paperheight {\paperwidth}% + \setlength\paperwidth {\@tempdima}% +}% +\ds@letterpaper +\DeclareOption{bibnotes}{\let\frontmatter@footnote@produce\frontmatter@footnote@produce@endnote}% +\DeclareOption{nobibnotes}{\let\frontmatter@footnote@produce\frontmatter@footnote@produce@footnote}% +\let\frontmatter@footnote@produce\frontmatter@footnote@produce@footnote +\appdef\class@enddocumenthook{\auto@bib}% +\DeclareOption{footinbib}{\@booleantrue\footinbib@sw} +\DeclareOption{nofootinbib}{\@booleanfalse\footinbib@sw} +\@booleanfalse\footinbib@sw +\DeclareOption{altaffilletter}{\@booleantrue\altaffilletter@sw}% +\DeclareOption{altaffilsymbol}{\@booleanfalse\altaffilletter@sw}% +\@booleanfalse\altaffilletter@sw +\DeclareOption{superbib}{% + \let\place@bibnumber\place@bibnumber@sup +}% +\def\place@bibnumber{\NATx@bibnumfmt}% +\def\place@bibnumber@sup#1{\textsuperscript{#1}}% +\def\place@bibnumber@inl#1{[#1]}% +\DeclareOption{citeautoscript}{\@booleantrue\citeautoscript@sw}% +\@booleanfalse\citeautoscript@sw +\DeclareOption{longbibliography}{\@booleantrue\longbibliography@sw}% +\DeclareOption{nolongbibliography}{\@booleanfalse\longbibliography@sw}% +\@booleantrue\longbibliography@sw +\DeclareOption{eprint}{\@booleantrue\eprint@enable@sw}% +\DeclareOption{noeprint}{\@booleanfalse\eprint@enable@sw}% +\@booleantrue\eprint@enable@sw +\@booleanfalse\twoside@sw +\appdef\document@inithook{% + \twoside@sw{\@twosidetrue}{\@twosidefalse}% +}% +\DeclareOption{twoside}{\@booleantrue \twoside@sw\@mparswitchfalse}% +\DeclareOption{oneside}{\@booleanfalse\twoside@sw\@mparswitchtrue}% +\DeclareOption{onecolumn}{\@booleanfalse\twocolumn@sw}% +\DeclareOption{twocolumn}{\@booleantrue\twocolumn@sw}% +\@booleanfalse\twocolumn@sw +\def\select@column@grid{% + \twocolumn@sw{% + \twocolumn@grid@setup + \open@twocolumn + }{% + \onecolumn@grid@setup + }% +}% +\appdef\class@documenthook{% + \select@column@grid +}% +\appdef\setup@hook{% + \let\clearpage@ltx\clearpage + \prepdef\clear@document{\let\clearpage\clearpage@ltx\let\clear@document\@empty\close@column}% + \appdef\class@documenthook{% + \appdef\class@enddocumenthook{% + \let\clearpage\clear@document + }% + }% +}% +\DeclareOption{author-year}{\@booleantrue\authoryear@sw}% +\DeclareOption{numerical}{\@booleanfalse\authoryear@sw}% +\@booleanfalse\authoryear@sw +\DeclareOption{galley}{% + \ExecuteOptions{onecolumn}% + \@booleantrue\galley@sw + \@booleanfalse\preprintsty@sw + \appdef\setup@hook{% + \advance\textwidth-\columnsep + \textwidth.5\textwidth + }% +}% +\@booleanfalse\galley@sw +\DeclareOption{raggedbottom}{\@booleantrue\raggedcolumn@sw} +\DeclareOption{flushbottom}{\@booleanfalse\raggedcolumn@sw} +\@booleanfalse\raggedcolumn@sw +\appdef\setup@hook{% + \raggedcolumn@sw{\raggedbottom}{\flushbottom}% +}% +\DeclareOption{tightenlines}{\@booleantrue\tightenlines@sw} +\@booleanfalse\tightenlines@sw +\@booleanfalse\lengthcheck@sw +\DeclareOption{lengthcheck}{% + \@booleantrue\lengthcheck@sw + \ExecuteOptions{reprint}% +}% +\appdef\setup@hook{% + \lengthcheck@sw{\@booleantrue\tally@box@size@sw}{}% +}% +\appdef\setup@hook{% + \draft@sw{\overfullrule 5\p@}{\overfullrule\z@}% +}% +\DeclareOption{draft}{\@booleantrue\draft@sw}% +\DeclareOption{final}{\@booleanfalse\draft@sw}% +\@booleanfalse\draft@sw +\appdef\setup@hook{% + \eqsecnum@sw{% + \@addtoreset{equation}{section}% + \def\theequation@prefix{\arabic{section}.}% + }{}% +}% +\DeclareOption{eqsecnum}{\@booleantrue\eqsecnum@sw}% +\@booleanfalse\eqsecnum@sw +\appdef\setup@hook{% + \setup@secnums +}% +\DeclareOption{secnumarabic}{% + \def\setup@secnums{\secnums@arabic}% +}% +\def\setup@secnums{\secnums@rtx}% +\DeclareOption{fleqn}{% + \input{fleqn.clo}% +}% +\DeclareOption{floats}{\@booleantrue\floats@sw\@booleanfalse\floatp@sw} +\DeclareOption{endfloats}{\@booleanfalse\floats@sw\@booleanfalse\floatp@sw} +\DeclareOption{endfloats*}{\@booleanfalse\floats@sw\@booleantrue\floatp@sw} +\@booleantrue\floats@sw +\@booleantrue\floatp@sw +\DeclareOption{titlepage}{\@booleantrue\titlepage@sw} +\DeclareOption{notitlepage}{\@booleanfalse\titlepage@sw} +\@booleanfalse\titlepage@sw +\def\change@society#1{% + \def\@tempa{#1}% + \@ifxundefined\@society{% + \class@info{Selecting society \@tempa}% + \let\@society\@tempa + }{% + \@ifx{\@tempa\@society}{}{% + \class@warn{Conflicting society \@tempa<>\@society; not selected}% + }% + }% +}% +\def\change@journal#1{% + \def\@tempa{#1}% + \@ifxundefined\@journal{% + \class@info{Selecting journal \@tempa}% + \let\@journal\@tempa + }{% + \@ifx{\@tempa\@journal}{}{% + \class@warn{Conflicting journal \@tempa<>\@journal; not selected}% + }% + }% +}% +\DeclareOption{osa}{\change@society{osa}\let\@journal\@undefined}% +\DeclareOption{osameet}{\change@society{osa}\def\@journal{osameet}}% +\DeclareOption{opex}{\change@society{osa}\def\@journal{opex}}% +\DeclareOption{tops}{\change@society{osa}\def\@journal{tops}}% +\DeclareOption{josa}{\change@society{osa}\def\@journal{josa}}% +\let\rtx@require@packages\@empty +\appdef\rtx@require@packages{% + \RequirePackage[overload]{textcase}% +}% +\DeclareOption{amsfonts}{% + \def\class@amsfonts{\RequirePackage{amsfonts}}% +}% +\DeclareOption{noamsfonts}{% + \let\class@amsfonts\@empty +}% +\appdef\rtx@require@packages{% + \@ifxundefined\class@amsfonts{}{\class@amsfonts}% +}% +\DeclareOption{amssymb}{% + \def\class@amssymb{\RequirePackage{amssymb}}% +}% +\DeclareOption{noamssymb}{% + \let\class@amssymb\@empty +}% +\appdef\rtx@require@packages{% + \@ifxundefined\class@amssymb{}{\class@amssymb}% +}% +\DeclareOption{amsmath}{% + \def\class@amsmath{\RequirePackage{amsmath}[\ver@amsmath@prefer]}% +}% +\DeclareOption{noamsmath}{% + \let\class@amsmath\@empty +}% +\appdef\rtx@require@packages{% + \preserve@LaTeX + \@ifxundefined\class@amsmath{}{\class@amsmath}% + \appdef\class@enddocumenthook{\test@amsmath@ver}% +}% +\appdef\preserve@LaTeX{% + \let\@ifl@aded@LaTeX\@ifl@aded + \let\@ifpackageloaded@LaTeX\@ifpackageloaded + \let\@pkgextension@LaTeX\@pkgextension + \let\@ifpackagelater@LaTeX\@ifpackagelater + \let\@ifl@ter@LaTeX\@ifl@ter + \let\@ifl@t@r@LaTeX\@ifl@t@r + \let\@parse@version@LaTeX\@parse@version +}% +\appdef\restore@LaTeX{% + \let\@ifl@aded\@ifl@aded@LaTeX + \let\@ifpackageloaded\@ifpackageloaded@LaTeX + \let\@pkgextension\@pkgextension@LaTeX + \let\@ifpackagelater\@ifpackagelater@LaTeX + \let\@ifl@ter\@ifl@ter@LaTeX + \let\@ifl@t@r\@ifl@t@r@LaTeX + \let\@parse@version\@parse@version@LaTeX +}% +\def\test@amsmath@ver{% + \begingroup + \restore@LaTeX + \@ifpackageloaded{amsmath}{% + \@ifpackagelater{amsmath}{\ver@amsmath@prefer}{}{% + \class@warn{% + You have loaded amsmath, version "\csname ver@amsmath.sty\endcsname",\MessageBreak + but this class requires version "\ver@amsmath@prefer", or later.\MessageBreak + Please update your LaTeX installation. + }% + }% + }{% + }% + \endgroup +}% +\def\ver@amsmath@prefer{2000/01/15 v2.05 AMS math features}% +\DeclareOption{byrevtex}{\@booleantrue\byrevtex@sw}% +\@booleanfalse\byrevtex@sw +\DeclareOption{floatfix}{\@booleantrue\force@deferlist@sw}% +\DeclareOption{nofloatfix}{\@booleanfalse\force@deferlist@sw}% +\@booleanfalse\force@deferlist@sw +\gdef\@fltovf{% + \@latex@error{% + Too many unprocessed floats% + \force@deferlist@sw{}{; try class option [floatfix]}% + }\@ehb +}% +\def\@fltstk{% + \@latex@warning{% + A float is stuck (cannot be placed)% + \force@deferlist@sw{}{; try class option [floatfix]}% + }% +}% +\DeclareOption{ltxgridinfo}{% + \@booleantrue\ltxgrid@info@sw +}% +\DeclareOption{outputdebug}{% + \@booleantrue\outputdebug@sw + \@booleantrue\ltxgrid@info@sw + \@booleantrue\ltxgrid@foot@info@sw + \traceoutput +}% +\DeclareOption{raggedfooter}{\@booleanfalse\textheight@sw}% +\DeclareOption{noraggedfooter}{\@booleantrue\textheight@sw}% +\DeclareOption{frontmatterverbose}{\@booleantrue\frontmatterverbose@sw}% +\@booleanfalse\frontmatterverbose@sw +\DeclareOption{linenumbers}{% + \appdef + \class@documenthook{% + \RequirePackage{lineno}[2005/11/02 v4.41]% + \linenumbersep4pt\relax + \linenumbers\relax + }% +}% +\DeclareOption{nomerge}{% + \appdef\setup@hook{% + \@ifnum{\NAT@merge>\z@}{\let\NAT@merge\z@}{}% + }% +}% +\def\@parse@class@options@society{% + \edef\@tempa{\@ptionlist{\@currname.\@currext}}% + \expandafter\@for\expandafter\CurrentOption\expandafter:\expandafter=\@tempa\do{% + \expandafter\@ifnotrelax\csname ds@\CurrentOption\endcsname{}{% + \IfFileExists{\CurrentOption\substyle@post.\substyle@ext}{% + \expandafter\change@society\expandafter{\CurrentOption}% + \expandafter\let\csname ds@\CurrentOption\endcsname\@empty + }{}% + }% + }% +}% +\def\@parse@class@options@#1{% + \edef\@tempa{\@ptionlist{\@currname.\@currext}}% + \expandafter\@for\expandafter\CurrentOption\expandafter:\expandafter=\@tempa\do{% + \expandafter\@ifnotrelax\csname ds@\CurrentOption\endcsname{% + \begingroup\csname ds@\CurrentOption\endcsname + \@ifxundefined#1{% + \endgroup + }{% + \expandafter\endgroup\expandafter\def\expandafter#1\expandafter{#1}% + }% + }{}% + }% +}% +\def\@parse@class@options@journal{% + \edef\@tempa{\@ptionlist{\@currname.\@currext}}% + \expandafter\@for\expandafter\CurrentOption\expandafter:\expandafter=\@tempa\do{% + \expandafter\@ifnotrelax\csname ds@\CurrentOption\endcsname{% + \begingroup + \csname ds@\CurrentOption\endcsname + \@ifxundefined\@journal{% + \endgroup + }{% + \expandafter\endgroup\expandafter\def\expandafter\@journal\expandafter{\@journal}% + }% + }{}% + }% +}% +\def\@parse@class@options{% + \edef\@tempa{\@ptionlist{\@currname.\@currext}}% + \expandafter\@for\expandafter\CurrentOption\expandafter:\expandafter=\@tempa\do{% + \expandafter\@ifnotrelax\csname ds@\CurrentOption\endcsname{% + \begingroup + \csname ds@\CurrentOption\endcsname + \@ifxundefined\@pointsize{% + \endgroup + }{% + \expandafter\endgroup\expandafter\def\expandafter\@pointsize\expandafter{\@pointsize}% + }% + }{% + \IfFileExists{\CurrentOption\substyle@post.\substyle@ext}{% + \expandafter\change@society\expandafter{\CurrentOption}% + \expandafter\let\csname ds@\CurrentOption\endcsname\@empty + }{}% + }% + }% +}% +\DeclareOption{hypertext}{\hypertext@enable@ltx}% +\appdef\document@inithook{\@ifpackageloaded{hyperref}{\hypertext@enable@ltx}{}}% +\DeclareOption{frontmatterverbose}{\@booleantrue\frontmatterverbose@sw}% +\@booleanfalse\frontmatterverbose@sw +\DeclareOption{inactive}{\@booleanfalse\frontmatter@syntax@sw}% +\@booleantrue\frontmatter@syntax@sw +\@booleanfalse\runinaddress@sw +\@booleantrue\@affils@sw +\@booleanfalse\groupauthors@sw +\DeclareOption{groupedaddress}{\clo@groupedaddress}% +\def\clo@groupedaddress{% + \@booleantrue\groupauthors@sw + \@booleantrue\@affils@sw + \@booleanfalse\runinaddress@sw +}% +\DeclareOption{unsortedaddress}{\clo@unsortedaddress}% +\def\clo@unsortedaddress{% + \@booleantrue\groupauthors@sw + \@booleanfalse\@affils@sw + \@booleanfalse\runinaddress@sw +}% +\DeclareOption{runinaddress}{\clo@runinaddress}% +\def\clo@runinaddress{% + \@booleantrue\groupauthors@sw + \@booleantrue\@affils@sw + \@booleantrue\runinaddress@sw +}% +\DeclareOption{superscriptaddress}{\clo@superscriptaddress}% +\def\clo@superscriptaddress{% + \@booleanfalse\groupauthors@sw + \@booleantrue\@affils@sw + \@booleanfalse\runinaddress@sw +}% +%%% @LaTeX-file{ +%%% filename = "revtex4-2.dtx", +%%% version = "4.2f", +%%% date = "2022/06/05", +%%% author = "Mark Doyle (mailto: revtex at aps.org), Arthur Ogawa (mailto:arthur_ogawa at sbcglobal.net), +%%% commissioned by the American Physical Society. +%%% ", +%%% copyright = "Copyright (C) 1999, 2009 Arthur Ogawa, Version 4.2 Copyright (C) 2019 American Physical Society +%%% distributed under the terms of the +%%% LaTeX Project Public License 1.3c, see +%%% ftp://ctan.tug.org/macros/latex/base/lppl.txt +%%% ", +%%% address = "Mark Doyle, +%%% USA", +%%% telephone = "", +%%% FAX = "", +%%% email = "mailto colon revtex at aps.org", +%%% codetable = "ISO/ASCII", +%%% keywords = "", +%%% supported = "yes", +%%% abstract = "", +%%% } +\def\substyle@post{4-2}% +\def\substyle@ext{rtx}% +\DeclareOption*{\OptionNotUsed}% +\def\@process@society#1{% + \@ifxundefined\@society{% + \class@warn{No Society specified, using default society #1}% + \def\@society{#1}\let\@journal\@undefined + }{}% + \expandafter\input\expandafter{\@society\substyle@post.\substyle@ext}% +}% +\def\@process@journal#1{% + \@ifxundefined\@journal{% + \class@warn{No journal specified, using default #1}% + \def\@journal{#1}% + }{}% + \expandafter\expandafter + \expandafter\rtx@do@substyle + \expandafter\expandafter + \expandafter{\expandafter\@society\@journal}% +}% +\def\rtx@do@substyle#1{% + \InputIfFileExists{#1\substyle@post.\substyle@ext}{}{\csname rtx@#1\endcsname}% +}% +\def\@process@pointsize#1{% + \@ifxundefined\@pointsize{% + \def\@pointsize{#1}% + \class@warn{No type size specified, using default \@pointsize}% + }{}% + \expandafter\expandafter + \expandafter\rtx@do@substyle + \expandafter\expandafter + \expandafter{\expandafter\@society\@pointsize pt}% +}% + \def\ps@headings{% + \let\@oddfoot\@empty\let\@evenfoot\@empty + \def\@evenhead{\thepage\hfil\slshape\leftmark}% + \def\@oddhead{{\slshape\rightmark}\hfil\thepage}% + \let\@mkboth\markboth + \def\sectionmark##1{% + \markboth {\MakeUppercase{% + \ifnum \c@secnumdepth >\z@ + \thesection\quad + \fi + ##1}}{}}% + \def\subsectionmark##1{% + \markright {% + \ifnum \c@secnumdepth >\@ne + \thesubsection\quad + \fi + ##1}}}% +\def\ps@myheadings{% + \let\@oddfoot\@empty\let\@evenfoot\@empty + \def\@evenhead{\thepage\hfil\slshape\leftmark}% + \def\@oddhead{{\slshape\rightmark}\hfil\thepage}% + \let\@mkboth\@gobbletwo + \let\sectionmark\@gobble + \let\subsectionmark\@gobble + }% +\def\ps@article{% + \def\@evenhead{\let\\\heading@cr\thepage\quad\checkindate\hfil{\leftmark}}% + \def\@oddhead{\let\\\heading@cr{\rightmark}\hfil\checkindate\quad\thepage}% + \def\@oddfoot{}% + \def\@evenfoot{}% + \let\@mkboth\markboth + \let\sectionmark\@gobble + \let\subsectionmark\@gobble +}% +\def\ps@article@final{% + \def\@evenhead{\let\\\heading@cr\thepage\quad\checkindate\hfil{\leftmark}}% + \def\@oddhead{\let\\\heading@cr{\rightmark}\hfil\checkindate\quad\thepage}% + \def\@oddfoot{}% + \def\@evenfoot{}% + \let\@mkboth\markboth + \def\sectionmark##1{% + \markboth{% + \MakeTextUppercase{% + \@ifnum{\c@secnumdepth >\z@}{\thesection\hskip 1em\relax}{}% + ##1% + }% + }{}% + }% + \def\subsectionmark##1{% + \markright {% + \@ifnum{\c@secnumdepth >\@ne}{\thesubsection\hskip 1em\relax}{}% + ##1% + }% + }% +}% +\def\heading@cr{\unskip\space\ignorespaces}% +\def\ps@preprint{% + \def\@oddfoot{\hfil\thepage\quad\checkindate\hfil}% + \def\@evenfoot{\hfil\thepage\quad\checkindate\hfil}% + \def\@oddhead{}% + \def\@evenhead{}% + \let\@mkboth\@gobbletwo + \let\sectionmark\@gobble + \let\subsectionmark\@gobble +}% +\let\@oddhead\@empty +\let\@evenhead\@empty +\let\@oddfoot\@empty +\let\@evenfoot\@empty +\def\lastpage@putlabel{% + \if@filesw + \begingroup + \advance\c@page\m@ne + \immediate\write\@auxout{\string\newlabel{LastPage}{{}{\thepage}{}{}{}}}% + \endgroup + \fi +}% +\appdef\clear@document{% + \do@output@cclv{% + \lastpage@putlabel + \tally@box@size@sw{\total@text}{}% + }% +}% +\providecommand\write@column@totals{}% +\appdef\rtx@require@packages{% + \RequirePackage{url}% +}% +\appdef\document@inithook{% + \incompatible@package{cite}% + \incompatible@package{mcite}% + \incompatible@package{multicol}% +}% +\def\labelenumi{\theenumi.} +\def\theenumi{\arabic{enumi}} +\def\labelenumii{(\theenumii)} +\def\theenumii{\alph{enumii}} +\def\p@enumii{\theenumi} +\def\labelenumiii{\theenumiii.} +\def\theenumiii{\roman{enumiii}} +\def\p@enumiii{\theenumi(\theenumii)} +\def\labelenumiv{\theenumiv.} +\def\theenumiv{\Alph{enumiv}} +\def\p@enumiv{\p@enumiii\theenumiii} +\def\labelitemi{\textbullet} +\def\labelitemii{\normalfont\bfseries\textendash} +\def\labelitemiii{\textasteriskcentered} +\def\labelitemiv{\textperiodcentered} +\pagenumbering{arabic} +\setcounter{topnumber}{2} +\def\topfraction{.9} +\setcounter{bottomnumber}{1} +\def\bottomfraction{.9} +\setcounter{totalnumber}{3} +\def\textfraction{.1} +\def\floatpagefraction{.9} +\setcounter{dbltopnumber}{2} +\def\dbltopfraction{.9} +\def\dblfloatpagefraction{.9} +\newenvironment{verse}{% + \let\\=\@centercr + \list{}{% + \itemsep\z@ \itemindent -1.5em\listparindent \itemindent + \rightmargin\leftmargin\advance\leftmargin 1.5em}\item[]% +}{% + \endlist +}% +\newenvironment{quotation}{% + \list{}{% + \listparindent 1.5em + \itemindent\listparindent + \rightmargin\leftmargin \parsep \z@ \@plus\p@}\item[]% +}{% + \endlist +}% +\newenvironment{quote}{% + \list{}{% + \rightmargin\leftmargin}\item[]% +}{% + \endlist +}% +\def\descriptionlabel#1{% + \hspace\labelsep \normalfont\bfseries #1\unskip:% +}% +\newenvironment{description}{% + \list{}{% + \labelwidth\z@ \itemindent-\leftmargin + \let\makelabel\descriptionlabel + }% +}{% + \endlist +}% +\newcounter{part}% +\let\thepart\@undefined +\newcounter{section}% +\let\thesection\@undefined +\newcounter{subsection}[section]% +\let\thesubsection\@undefined +\newcounter{subsubsection}[subsection]% +\let\thesubsubsection\@undefined +\newcounter{paragraph}[subsubsection]% +\let\theparagraph\@undefined +\newcounter{subparagraph}[paragraph]% +\let\thesubparagraph\@undefined +\def\secnums@rtx{% + \@ifxundefined\thepart{% + \def\thepart{\Roman{part}}% + }{}% + \@ifxundefined\thesection{% + \def\thesection {\Roman{section}}% + \def\p@section {}% + }{}% + \@ifxundefined\thesubsection{% + \def\thesubsection {\Alph{subsection}}% + \def\p@subsection {\thesection\,}% + }{}% + \@ifxundefined\thesubsubsection{% + \def\thesubsubsection {\arabic{subsubsection}}% + \def\p@subsubsection {\thesection\,\thesubsection\,}% + }{}% + \@ifxundefined\theparagraph{% + \def\theparagraph {\alph{paragraph}}% + \def\p@paragraph {\thesection\,\thesubsection\,\thesubsubsection\,}% + }{}% + \@ifxundefined\thesubparagraph{% + \def\thesubparagraph {\arabic{subparagraph}}% + \def\p@subparagraph {\thesection\,\thesubsection\,\thesubsubsection\,\theparagraph\,}% + }{}% +}% +\def\secnums@arabic{% + \@ifxundefined\thepart{% + \def\thepart {\Roman{part}}% + }{}% + \@ifxundefined\thesection{% + \def\thesection {\Roman{section}}% + \def\p@section {}% + }{}% + \@ifxundefined\thesubsection{% + \def\thesubsection {\thesection.\arabic{subsection}}% + \def\p@subsection {}% + }{}% + \@ifxundefined\thesubsubsection{% + \def\thesubsubsection {\thesubsection.\arabic{subsubsection}}% + \def\p@subsubsection {}% + }{}% + \@ifxundefined\theparagraph{% + \def\theparagraph {\thesubsubsection.\arabic{paragraph}}% + \def\p@paragraph {}% + }{}% + \@ifxundefined\thesubparagraph{% + \def\thesubparagraph {\theparagraph.\arabic{subparagraph}}% + \def\p@subparagraph {}% + }{}% +}% +\newenvironment{acknowledgments}{% + \acknowledgments@sw{% + \expandafter\section\expandafter*\expandafter{\acknowledgmentsname}% + }{% + \par + \phantomsection + \addcontentsline{toc}{section}{\protect\numberline{}\acknowledgmentsname}% + }% +}{% + \par +}% +\@booleantrue\acknowledgments@sw +\newenvironment{acknowledgements}{% + \replace@environment{acknowledgements}{acknowledgments}% +}{% + \endacknowledgments +}% +\def\part{\par + \addvspace{4ex}% + \@afterindentfalse + \secdef\@part\@spart}% +\def\@part[#1]#2{% + \@ifnum{\c@secnumdepth >\m@ne}{% + \refstepcounter{part}% + \addcontentsline{toc}{part}{\thepart\hspace{1em}#1}% + }{% + \addcontentsline{toc}{part}{#1}% + }% + \begingroup + \parindent \z@ \raggedright + \interlinepenalty\@M + \@ifnum{\c@secnumdepth >\m@ne}{% + \Large \bf \partname~\thepart% + \par\nobreak + }{}% + \huge \bf + #2% + \markboth{}{}\par + \endgroup + \nobreak + \vskip 3ex + \@afterheading +}% +\def\@spart#1{{\parindent \z@ \raggedright + \interlinepenalty\@M + \huge \bf + #1\par} + \nobreak + \vskip 3ex + \@afterheading} +\def\section{% + \@startsection + {section}% + {1}% + {\z@}% + {0.8cm \@plus1ex \@minus .2ex}% + {0.5cm}% + {\normalfont\small\bfseries}% +}% +\def\subsection{% + \@startsection + {subsection}% + {2}% + {\z@}% + {.8cm \@plus1ex \@minus .2ex}% + {.5cm}% + {\normalfont\small\bfseries}% +}% +\def\subsubsection{% + \@startsection + {subsubsection}% + {3}% + {\z@}% + {.8cm \@plus1ex \@minus .2ex}% + {.5cm}% + {\normalfont\small\itshape}% +}% +\def\paragraph{% + \@startsection + {paragraph}% + {4}% + {\parindent}% + {\z@}% + {-1em}% + {\normalfont\normalsize\itshape}% +}% +\def\subparagraph{% + \@startsection + {subparagraph}% + {5}% + {\parindent}% + {3.25ex \@plus1ex \@minus .2ex}% + {-1em}% + {\normalfont\normalsize\bfseries}% +}% +\def\theequation{% + \theequation@prefix\arabic{equation}% +}% +\def\theequation@prefix{}% +\setcounter{secnumdepth}{4} +\lineskip 1pt +\normallineskip 1pt +\def\baselinestretch{1}% +\@lowpenalty 51 +\@medpenalty 151 +\@highpenalty 301 +\@beginparpenalty -\@lowpenalty +\@endparpenalty -\@lowpenalty +\@itempenalty -\@lowpenalty +\arraycolsep 3pt +\tabcolsep 2pt +\arrayrulewidth .4pt +\doublerulesep 2pt +\skip\@mpfootins = 0pt +\fboxsep = 3.0pt +\fboxrule = 0.4pt +\newenvironment{figure} + {\@float{figure}} + {\end@float} +\newenvironment{figure*} + {\@dblfloat{figure}} + {\end@dblfloat} +\def\listoffigures{\print@toc{lof}}% +\def\l@figure{\@dottedtocline{1}{1.5em}{2.3em}}% +\newlength\abovecaptionskip +\newlength\belowcaptionskip +\setlength\abovecaptionskip{10\p@} +\setlength\belowcaptionskip{2\p@} +\long\def\@makecaption#1#2{% + \par + \vskip\abovecaptionskip + \begingroup + \small\rmfamily + \sbox\@tempboxa{% + \let\\\heading@cr + \@make@capt@title{#1}{#2}% + }% + \@ifdim{\wd\@tempboxa >\hsize}{% + \begingroup + \samepage + \flushing + \let\footnote\@footnotemark@gobble + \@make@capt@title{#1}{#2}\par + \endgroup + }{% + \global \@minipagefalse + \hb@xt@\hsize{\hfil\unhbox\@tempboxa\hfil}% + }% + \endgroup + \vskip\belowcaptionskip +}% +\def\@make@capt@title#1#2{% + \@ifx@empty\float@link{\@firstofone}{\expandafter\href\expandafter{\float@link}}% + {#1}\@caption@fignum@sep#2% +}% +\def\@footnotemark@gobble{% + \@footnotemark + \@ifnextchar[{\@gobble@opt@i}{\@gobble}% +}% +\def\@gobble@opt@i[#1]#2{}% +\def\@mpmakefntext#1{% + \flushing + \parindent=1em + \noindent + \hb@xt@1em{\hss\@makefnmark}% + #1% +}% +\def\@caption@fignum@sep{. }% +\def\setfloatlink{\def\float@link}% +\let\float@link\@empty +\newcounter{figure} +\renewcommand \thefigure {\@arabic\c@figure} +\def\fps@figure{tbp} +\def\ftype@figure{1} +\def\ext@figure{lof} +\def\fnum@figure{\figurename~\thefigure} +\expandafter\newbox\csname fbox@\ftype@figure\endcsname +\expandafter\setbox\csname fbox@\ftype@figure\endcsname\hbox{}% +\appdef\class@documenthook{% + \do@if@floats{figure}{.fgx}% +}% +\appdef\class@enddocumenthook{% + \printfigures\relax +}% +\newcommand\printfigures{% + \@ifstar{\true@sw}{\floatp@sw{\true@sw}{\false@sw}}% + {% + \print@float{figure}{\oneapage}% + }{% + \print@float{figure}{}% + }% +}% +\appdef\@xfloat@prep{% + \appdef\@parboxrestore{\centering}% +}% +\newenvironment{table} + {\@float{table}} + {\end@float} +\newenvironment{table*} + {\@dblfloat{table}} + {\end@dblfloat} +\newcounter{table} +\renewcommand\thetable{\@Roman\c@table} +\def\fps@table{tbp} +\def\ftype@table{2} +\def\ext@table{lot} +\def\fnum@table{\tablename~\thetable} +\expandafter\newbox\csname fbox@\ftype@table\endcsname +\expandafter\setbox\csname fbox@\ftype@table\endcsname\hbox{}% +\def\listoftables{\print@toc{lot}}% +\let\l@table\l@figure +\def\table@hook{\small}% +\def\squeezetable{\def\table@hook{\scriptsize}}% +\appdef\@floatboxreset{\table@hook}% +\def\set@table@environments{% + \floats@sw{}{% + \let@environment{longtable@float}{longtable}% + \let@environment{longtable}{longtable@write}% + \let@environment{longtable*@float}{longtable*}% + \let@environment{longtable*}{longtable*@write}% + \let@environment{turnpage@float}{turnpage}% + \let@environment{turnpage}{turnpage@write}% + }% + \do@if@floats{table}{.tbx}% +}% +\appdef\document@inithook{% + \set@table@environments +}% +\appdef\class@enddocumenthook{% + \printtables\relax +}% +\newenvironment{longtable@write}{% + \write@@float{longtable}{table}% +}{% + \endwrite@float +}% +\newenvironment{longtable*@write}{% + \write@@float{longtable*}{table}% +}{% + \endwrite@float +}% +\newenvironment{turnpage@write}{% + \immediate\write\tablewrite{\string\begin{turnpage}}% +}{% + \immediate\write\tablewrite{\string\end{turnpage}}% +}% +\newcommand\printtables{% + \begingroup + \let@environment{longtable}{longtable@float}% + \let@environment{longtable*}{longtable*@float}% + \let@environment{turnpage}{turnpage@anchored}% + \prepdef\longtable{\trigger@float@par}% + \expandafter\prepdef\csname longtable*\endcsname{\trigger@float@par}% + \expandafter\prepdef\csname table@floats\endcsname{% + \onecolumngrid@push + }% + \expandafter\appdef\csname endtable@floats\endcsname{% + \onecolumngrid@pop + }% + \@ifstar{\true@sw}{\floatp@sw{\true@sw}{\false@sw}}% + {% + \print@float{table}{\oneapage}% + }{% + \print@float{table}{}% + }% + \endgroup +}% +\newenvironment{turnpage@anchored}{% + \onecolumngrid@push + \setbox\z@\vbox to\textwidth\bgroup + \columnwidth\textheight +}{% + \vfil + \egroup + \rotatebox{90}{\box\z@}% + \onecolumngrid@pop +}% +\newenvironment{video} + {\@float{video}} + {\end@float}% +\newenvironment{video*} + {\@dblfloat{video}} + {\end@dblfloat}% +\newcounter{video} +\renewcommand \thevideo {\@arabic\c@video} +\def\ext@video{lov}% +\def\fname@video{Video}% +\def\lovname{List of Videos}% +\def\fps@video{tbp}% +\def\ftype@video{4}% +\def\fnum@video{\fname@video~\thevideo}% +\appdef\document@inithook{% + \@ifxundefined\c@float@type{}{% + \global\setcounter{float@type}{8}% + }% +}% +\expandafter\newbox\csname fbox@\ftype@video\endcsname +\expandafter\setbox\csname fbox@\ftype@video\endcsname\hbox{}% +\let\theHvideo\thevideo +\def\listofvideos{\print@toc{lov}}% +\let\l@video\l@figure +\appdef\class@documenthook{% + \do@if@floats{video}{.vdx}% +}% +\appdef\class@enddocumenthook{% + \printvideos\relax +}% +\newcommand\printvideos{% + \@ifstar{\true@sw}{\floatp@sw{\true@sw}{\false@sw}}% + {% + \print@float{video}{\oneapage}% + }{% + \print@float{video}{}% + }% +}% +\def\endtabular@hook{}% +\appdef\document@inithook{% + \@ifpackageloaded{dcolumn}{% + \expandafter\@ifnotrelax\csname NC@find@d\endcsname{}{% + \newcolumntype{d}{D{.}{.}{-1}}% + }% + }{}% +}% +\def\toprule{\hline\hline}% +\def\colrule{\hline}% +\def\botrule{\hline\hline}% +\newenvironment{ruledtabular}{% + \def\array@default{v}% + \appdef\tabular@hook{\def\@halignto{to\hsize}}% + \let\tableft@skip@default\tableft@skip + \let\tableft@skip\tableft@skip@float + \let\tabmid@skip@default\tabmid@skip + \let\tabmid@skip\tabmid@skip@float + \let\tabright@skip@default\tabright@skip + \let\tabright@skip\tabright@skip@float + \let\array@row@pre@default\array@row@pre + \let\array@row@pre\array@row@pre@float + \let\array@row@pst@default\array@row@pst + \let\array@row@pst\array@row@pst@float + \appdef\array@row@rst{% + \let\array@row@pre\array@row@pre@default + \let\array@row@pst\array@row@pst@default + \let\tableft@skip\tableft@skip@default + \let\tabmid@skip\tabmid@skip@default + \let\tabright@skip\tabright@skip@default + \appdef\tabular@hook{\let\@halignto\@empty}% + }% +}{% +}% +\def\@makefntext#1{% + \def\baselinestretch{1}% + \parindent1em% + \noindent + \hb@xt@1.8em{% + \hss\@makefnmark + }% + #1% + \par +}% +\def\@makefnmark{% + \hbox{% + \@textsuperscript{% + \normalfont\@thefnmark + }% + }% +}% +\expandafter\DeclareRobustCommand +\expandafter\rev@citet +\expandafter{% + \expandafter\begingroup + \expandafter\rtx@swap@citea + \expandafter\g@bblefirsttoken + \csname citet \endcsname +}% +\expandafter\DeclareRobustCommand +\expandafter\rev@citealp +\expandafter{% + \expandafter\begingroup + \expandafter\rtx@swap@citea + \expandafter\g@bblefirsttoken + \csname citealp \endcsname +}% +\expandafter\DeclareRobustCommand +\expandafter\rev@citealpnum +\expandafter{% + \expandafter\begingroup + \expandafter\rtx@swap@citenum + \expandafter\g@bblefirsttoken + \csname citealp \endcsname +}% +\def\rtx@swap@citenum{% + \rtx@swap@citea + \let\@cite\NAT@citenum + \let\NAT@mbox\mbox + \let\citeyear\NAT@citeyear + \let\NAT@space\NAT@spacechar +}% +\def\g@bblefirsttoken{% + \expandafter\true@sw + \expandafter\@empty +}% +\newcommand\rtx@citesuper[3]{% + \ifNAT@swa + \leavevmode + \unskip + \textsuperscript{\normalfont#1}% + \if*#3*\else\ (#3)\fi + \else + #1% + \fi + \endgroup +}% +\def\@makefnmark@cite{\begingroup\NAT@swatrue\@cite{{\@thefnmark}}{}{}}% +\def\rtx@bibsection{% + \@ifx@empty\refname{% + \par + }{% + \expandafter\section\expandafter*\expandafter{\refname}% + \@nobreaktrue + }% +}% +\def\rtx@swap@citea{% + \let\NAT@def@citea\rtx@def@citea + \let\NAT@def@citea@close\rtx@def@citea@close + \let\NAT@def@citea@box\rtx@def@citea@box +}% +\def\rtx@def@citea{% + \def\@citea{\NAT@separator\NAT@space}% + \advance\c@NAT@ctr\@ne + \@ifnum{\count@>\tw@}{% + \@ifnum{\c@NAT@ctr=\count@}{\appdef\@citea{\NAT@conj\NAT@space}}{}% + }{% + \def\@citea{\NAT@space\NAT@conj\NAT@space}% + }% +}% +\def\rtx@def@citea@close{% + \rtx@def@citea + \prepdef\@citea{\NAT@@close}% +}% +\def\rtx@def@citea@box{% + \rtx@def@citea@close + \expandafter\def\expandafter\@citea\expandafter{\expandafter\NAT@mbox\expandafter{\@citea}}% +}% +\def\NAT@conj{and}% +\def\NAT@BibitemShut#1{% + \def\@bibstop{#1}% + \let\bibitem@Stop\bibitemStop + \let\bibitem@NoStop\bibitemNoStop + \@ifx{\bibitemShut\relax}{\let\@bibitemShut\@empty}{% + \expandafter\def\expandafter\@bibitemShut\expandafter{\bibitemShut}% + }% +}% +\def\BibitemShut@ltx#1{% + \unskip + \def\@bibstop{#1}% + \let\bibitem@Stop\bibitemStop + \let\bibitem@NoStop\bibitemNoStop + \@ifx{\bibitemShut\relax}{\let\@bibitemShut\@empty}{% + \expandafter\def\expandafter\@bibitemShut\expandafter{\bibitemShut}% + }% +}% +\newenvironment{thebibliography}{}{}% +\let\@listi\@empty +\appdef\rtx@require@packages{% + \RequirePackage[sort&compress]{natbib}[2009/11/07 8.31a (PWD, AO)]% + \let@environment{NAT@thebibliography}{thebibliography}% + \let@environment{thebibliography}{rtx@thebibliography}% + \let\bibliographystyle@latex\bibliographystyle + \let\NAT@citesuper\rtx@citesuper +\let\bibsection\rtx@bibsection +\let\NATx@bibsetnum\NAT@bibsetnum +\def\NAT@bibsetnum#1{% + \setlength{\topsep}{\z@}% + \NATx@bibsetnum{\ref{LastBibItem}}% +}% +\let\NATx@bibsetup\NAT@bibsetup +\def\NAT@bibsetup{% + \setlength{\labelwidth}{\z@}% + \setlength{\labelsep}{\z@}% + \setlength{\itemindent}{\z@}% + \setlength{\listparindent}{\z@}% + \setlength{\topsep}{\z@}% + \setlength{\parsep}{\z@}% + \NATx@bibsetup +}% +\let\bibpreamble\@empty +\def\newblock{\ }% +\let\NATx@bibnumfmt\bibnumfmt +\def\bibnumfmt{\place@bibnumber}% +\let\NAT@merge\thr@@ +\let\NAT@citeyear\citeyear +\let\onlinecite\rev@citealp +\let\textcite\rev@citet +\@ifx{\BibitemShut\NAT@BibitemShut}{% + \class@info{Repairing natbib's \string\BibitemShut}% + \let\BibitemShut\BibitemShut@ltx +}{}% +\let\bibliographystyle@latex\bibliographystyle +\def\bibliographystyle{\@booleantrue\bibliographystyle@sw\def\@bibstyle}% +\@booleanfalse\bibliographystyle@sw +\def\NAT@bibitem@cont{% + \let\bibitem@Stop\bibitemContinue@Stop + \let\bibitem@NoStop\bibitemContinue +}% +\def\bibitemNoStop{% + \@ifx@empty\@bibitemShut{.\spacefactor\@mmm\space}{\@bibitemShut}% +}% +\def\bibitemContinue{% + \@ifx@empty\@bibitemShut{;\spacefactor\@mmm\space}{\@bibitemShut}% +}% +\def\bibitemContinue@Stop{% + \@ifx@empty\@bibitemShut{\spacefactor\@mmm\space}{\@bibitemShut}% +}% +}% +\DeclareRobustCommand\onlinecite{\@onlinecite}% +\DeclareRobustCommand\textcite{\@textcite}% +\let\bibliography@latex\bibliography +\def\bibliography#1{% + \auto@bib@empty + \begingroup + \let\auto@bib@innerbib\@empty + \@ifx@empty{\pre@bibdata}{% + \bibliography@latex{#1}% + }{% + \@if@empty{#1}{% + \expandafter\bibliography@latex\expandafter{\pre@bibdata}% + }{% + \expandafter\bibliography@latex\expandafter{\pre@bibdata,#1}% + }% + }% + \endgroup +}% +\let\pre@bibdata\@empty +\newenvironment{rtx@thebibliography}[1]{% + \NAT@thebibliography{#1}% + \let\@TBN@opr\present@bibnote + \@FMN@list +}{% + \auto@bib@innerbib + \edef\@currentlabel{\arabic{NAT@ctr}}% + \label{LastBibItem}% + \endNAT@thebibliography + \aftergroup\auto@bib@empty +}% +\def\present@bibnote#1#2{% + \item[% + \textsuperscript{% + \normalfont + \Hy@raisedlink{\hyper@anchorstart{frontmatter.#1}\hyper@anchorend}% + \begingroup + \csname c@\@mpfn\endcsname#1\relax + \frontmatter@thefootnote + \endgroup + }% + ]#2\par +}% +\def\write@bibliographystyle{% + \@ifxundefined\@bibstyle{}{% + \expandafter\bibliographystyle@latex\expandafter{\@bibstyle}% + \bibliographystyle@sw{}{\@bibdataout@rev}% + }% + \global\let\write@bibliographystyle\relax +}% +\AtEndDocument{\write@bibliographystyle}% +\def\rtx@@citetp[#1]{\@ifnextchar[{\rtx@citex[#1]}{\rtx@citex[][#1]}}% +\def\rtx@citex[#1][#2]#3{% + \begingroup + \def\@tempa{[#1][#2]{#3}}% + \@ifx{\@cite\NAT@citesuper}{% + \leavevmode + \skip@\lastskip + \unskip + \super@cite@let + }{% + \super@cite@end + }% +}% +\def\super@cite@let{% + \futurelet\@let@token\super@cite@check +}% +\def\super@cite@end{% + \aftergroup\@citex\expandafter\endgroup\@tempa +}% +\def\super@cite@check{% + \@ifx{\@let@token\@sptoken}{% + \super@cite@end + }{% + \super@cite@swap + }% +}% +\long\def\super@cite@swap#1{% + \expandafter\@ifx\expandafter{\csname rtx@automove#1\endcsname\@empty}{% + #1% + \super@cite@let + }{% + \super@cite@end + #1% + }% +}% +\expandafter\let\csname rtx@automove.\endcsname\@empty +\expandafter\let\csname rtx@automove,\endcsname\@empty +\expandafter\let\csname rtx@automove:\endcsname\@empty +\expandafter\let\csname rtx@automove;\endcsname\@empty +\appdef\class@documenthook{% + \citeautoscript@sw{% + \@ifx{\@cite\NAT@citesuper}{% + \let\NAT@@citetp\rtx@@citetp + }{}% + }{}% +}% +\def\mini@note{\save@note\mini@notes}%Implicit #2 +\def\save@note#1#2{% + \stepcounter\@mpfn + \protected@xdef\@thefnmark{\thempfn}% + \@footnotemark + \expandafter\g@addto@macro + \expandafter#1% + \expandafter{% + \expandafter \@@footnotetext + \expandafter {\@thefnmark}{#2}% + }% +}% +\long\def\@@footnotetext#1{\def\@thefnmark{#1}\@footnotetext}% +\let\mini@notes\@empty +\def\rev@citemark#1{% + \expandafter\cite\expandafter{\@thefnmark}% +}% +\def\rev@endtext#1{% + \let\@endnotelabel\@thefnmark + \@endnotetext +}% +\def\endnote@ext{.end}% +\def\bibdata@app{Notes}% +\def\bibdata@ext{bib}% +\long\def\@endnotetext#1{% + \begingroup + \endnote@relax + \immediate\write\@bibdataout{% + @FOOTNOTE{% + \@endnotelabel,% + key="\@endnotelabel",% + note="#1"% + }% + }% + \endgroup +}% +\newwrite\@bibdataout +\def\endnote@relax{% + \let\label\relax \let\index\relax \let\glossary\relax + \let\cite \relax \let\ref \relax \let\pageref \relax + \let\( \relax \let\) \relax \let\\ \relax + \let~\relax + \let \protect \@unexpandable@protect + \newlinechar`\^^M% + \let\begin\relax \let\end\relax +}% +\appdef\class@documenthook{\@bibdataout@init}% +\def\@bibdataout@init{% + \immediate\openout\@bibdataout\pre@bibdata.\bibdata@ext\relax +}% +\def\@bibdataout@rev{% + \immediate\write\@bibdataout{% + @CONTROL{% + REVTEX42Control% + \eprint@enable@sw{}{,eprint="1"}% + }% + }% + \if@filesw + \immediate\write\@auxout{\string\citation{REVTEX42Control}}% + \fi +}% +\def\printendnotes{% + \class@warn{The \string\printendnotes\space command no longer serves any function. Please remove it from your document.}% +}% +\def\make@footnote@endnote{% + \footinbib@sw{% + \authoryear@sw{}{% + \ltx@footnote@push + \def\thempfn{Note\thefootnote}% + \let\ltx@footmark\rev@citemark + \let\ltx@foottext\rev@endtext + }% + }{}% +}% +\def\ltx@footnote@push{% + \let\ltx@footmark@latex\ltx@footmark + \let\ltx@foottext@latex\ltx@foottext + \let\thempfn@latex\thempfn + \def\ltx@footnote@pop{% + \let\ltx@footmark\ltx@footmark@latex + \let\ltx@foottext\ltx@foottext@latex + \let\thempfn\thempfn@latex + }% +}% +\appdef\class@documenthook{% + \make@footnote@endnote +}% +\def\auto@bib{% + \@ifx@empty\@FMN@list{% + \footinbib@sw{% + \@ifnum{\csname c@\@mpfn\endcsname>\z@}{% + \true@sw + }{% + \test@bbl@sw + }% + }{% + \test@bbl@sw + }% + }{% + \true@sw + }% + {% + \bibliography{}% + }{}% +}% +\def\auto@bib@empty{% + \let\auto@bib\@empty +}% +\def\test@bbl@sw{% + \setbox\z@\vbox\bgroup + \let\providecommand\providecommand@j@nk + \let\bibfield\@gobbletwo + \let\bibinfo\@gobbletwo + \let\translation\@gobble + \let\BibitemOpen\@empty + \let\bibitemStop\@empty + \let\bibitemNoStop\@empty + \let\EOS\@empty + \let\BibitemShut\@gobble + \let\bibAnnoteFile\@gobbletwo + \let\bibAnnote\@gobblethree + \let\textbf\@gobble + \let\emph\@gobble + \@booleanfalse\bibitem@sw + \let\bibitem\bibitem@set + \auto@bib@innerbib + \bibitem@sw{\aftergroup\true@sw}{\aftergroup\false@sw}% + \egroup +}% +\newcommand\bibitem@set[1][]{% + \bibitem@sw{}{% + \@booleantrue\bibitem@sw + \aftergroup\@booleantrue\aftergroup\bibitem@sw + }% +}% +\def\auto@bib@innerbib{% + \begingroup + \let@environment{thebibliography}{thebibliography@nogroup}% + \bibliography{}% + \endgroup +}% +\def\thebibliography@nogroup#1{% + \endgroup + \def\@currenvir{thebibliography}% +}% +\def\endthebibliography@nogroup{\begingroup}% +\long\def \@gobblethree #1#2#3{}% +\def\providecommand@j@nk#1[#2]{% + \@ifnum{#2=\z@}{\def\j@nk}{% + \@ifnum{#2=\@ne}{\def\j@nk##1}{% + \@ifnum{#2=\tw@}{\def\j@nk##1##2}{% + \@ifnum{#2=\thr@@}{\def\j@nk##1##2##3}{% + }% + }% + }% + }% +}% +\def\thepage{\@arabic\c@page}% +\appdef\setup@hook{% + \tabbingsep \labelsep + \leftmargin\leftmargini + \labelwidth\leftmargin\advance\labelwidth-\labelsep + \let\@listi\@listI + \@listi +}% +\appdef\class@documenthook{% + \global\c@page\@ne + \def\curr@envir{document}% + \mark@envir{\curr@envir}% +}% +\def\open@onecolumn{% + \open@column@one\@ne + \set@colht + \@floatplacement + \@dblfloatplacement +}% +\def\open@twocolumn{% + \open@column@mlt\tw@ + \set@colht + \@floatplacement + \@dblfloatplacement + \sloppy + \let\set@listindent\set@listindent@ +}% +\def\appendix{% + \par + \setcounter{section}\z@ + \setcounter{subsection}\z@ + \setcounter{subsubsection}\z@ + \def\thesubsection{\arabic{subsection}}% + \def\thesubsubsection{\alph{subsubsection}}% + \@addtoreset{equation}{section}% + \def\theequation@prefix{\thesection}% + \addtocontents{toc}{\protect\appendix}% + \@ifstar{% + \def\thesection{\unskip}% + \def\theequation@prefix{A.}% + }{% + \def\thesection{\Alph{section}}% + }% +}% +\def\title@column#1{% + \minipagefootnote@init + #1% + \minipagefootnote@foot +}% +\def\close@column{% + \newpage +}% +\def\galley@outdent{\rightmargin-\columnwidth\advance\rightmargin-\columnsep}% +\let\widetext@outdent\@empty +\newenvironment{widetext@galley}{% + \list{}{% + \topsep \z@skip + \listparindent \parindent + \itemindent \parindent + \leftmargin \z@ + \parsep \z@\@plus\p@ + \widetext@outdent + \relax + }% + \item\relax +}{ + \endlist +}% +\def\title@column@grid#1{% + \minipagefootnote@init + \onecolumngrid + \begingroup + \let\@footnotetext\frontmatter@footnotetext + \ltx@no@footnote + #1% + \endgroup + \twocolumngrid + \minipagefootnote@foot +}% +\def\close@column@grid{% + \balancelastpage@sw{% + \onecolumngrid + }{}% +}% +\newenvironment{widetext@grid}{% + \par\ignorespaces + \setbox\widetext@top\vbox{% + \hb@xt@\hsize{% + \leaders\hrule\hfil + \vrule\@height6\p@ + }% + }% + \setbox\widetext@bot\hb@xt@\hsize{% + \vrule\@depth6\p@ + \leaders\hrule\hfil + }% + \onecolumngrid + \vskip10\p@ + \dimen@\ht\widetext@top\advance\dimen@\dp\widetext@top + \cleaders\box\widetext@top\vskip\dimen@ + \vskip6\p@ + \prep@math@patch +}{% + \par + \vskip6\p@ + \setbox\widetext@bot\vbox{% + \hb@xt@\hsize{\hfil\box\widetext@bot}% + }% + \dimen@\ht\widetext@bot\advance\dimen@\dp\widetext@bot + \cleaders\box\widetext@bot\vskip\dimen@ + \vskip8.5\p@ + \twocolumngrid\global\@ignoretrue + \@endpetrue +}% +\newbox\widetext@top +\newbox\widetext@bot +\def\set@page@grid{% + \twocolumn@sw{% + \let\set@footnotewidth\set@footnotewidth@two + \let\compose@footnotes\compose@footnotes@two + \let@environment{widetext}{widetext@grid}% + \let\title@column\title@column@grid + \let\close@column\close@column@grid + }{% + \let@environment{widetext}{widetext@galley}% + \preprintsty@sw{% + }{% + \galley@sw{% + \let\widetext@outdent\galley@outdent + }{}% + }% + }% +}% +\appdef\setup@hook{\set@page@grid}% +\DeclareOldFontCommand{\rm}{\normalfont\rmfamily}{\mathrm} +\DeclareOldFontCommand{\sf}{\normalfont\sffamily}{\mathsf} +\DeclareOldFontCommand{\tt}{\normalfont\ttfamily}{\mathtt} +\DeclareOldFontCommand{\bf}{\normalfont\bfseries}{\mathbf} +\DeclareOldFontCommand{\it}{\normalfont\itshape}{\mathit} +\DeclareOldFontCommand{\sl}{\normalfont\slshape}{\@nomath\sl} +\DeclareOldFontCommand{\sc}{\normalfont\scshape}{\@nomath\sc} +\DeclareRobustCommand*\cal{\@fontswitch\relax\mathcal} +\DeclareRobustCommand*\mit{\@fontswitch\relax\mathnormal} +\def\today{\ifcase\month\or + January\or February\or March\or April\or May\or June\or + July\or August\or September\or October\or November\or December\fi + \space\number\day, \number\year} +\def\partname{Part} +\def\tocname{Contents} +\def\lofname{List of Figures} +\def\lotname{List of Tables} +\def\refname{References} +\def\indexname{Index} +\def\figurename{FIG.} +\def\figuresname{Figures}% +\def\tablename{TABLE} +\def\tablesname{Tables}% +\def\abstractname{Abstract} +\def\appendixesname{Appendixes}% +\def\appendixname{Appendix}% +\def\acknowledgmentsname{Acknowledgments} +\def\journalname{??} +\def\copyrightname{??} +\def\andname{and} +\def\@pacs@name{PACS numbers: }% +\def\@keys@name{Keywords: }% +\def\ppname{pp} +\def\numbername{number} +\def\volumename{volume} +\def\Dated@name{Dated: }% +\def\Received@name{Received }% +\def\Revised@name{Revised }% +\def\Accepted@name{Accepted }% +\def\Published@name{Published }% +\def\address{\replace@command\address\affiliation}% +\def\altaddress{\replace@command\altaddress\altaffiliation}% +\newenvironment{references}{% + \class@warn@end{The references environment is not supported; use thebibliography instead.} + \gdef\references{\thebibliography{}}\references +}{% + \endthebibliography +}% +\def\draft{% + \class@warn@end{Command \string\draft\space is obsolete;^^JInvoke option draft instead.}% + \@booleantrue\draft@sw +}% +\def\tighten{% + \class@warn@end{Command \string\tighten\space is obsolete;^^JInvoke option tightenlines instead.}% + \@booleantrue\tightenlines@sw +}% +\def\tableline{% + \noalign{% + \class@warn@end{Command \string\tableline\space is obsolete;^^JUse \string\colrule\space instead.}% + \global\let\tableline\colrule + }% + \tableline +}% +\def\case{\replace@command\case\frac}% +\def\slantfrac{\replace@command\slantfrac\frac}% +\def\tablenote{\replace@command\tablenote\footnote}% +\def\tablenotemark{\replace@command\tablenotemark\footnotemark}% +\def\tablenotetext{\replace@command\tablenotetext\footnotetext}% +\DeclareRobustCommand\REV@text[1]{% + \relax + \ifmmode + \mathchoice + {\hbox{{\everymath{\displaystyle }#1}}}% + {\hbox{{\everymath{\textstyle }#1}}}% + {\hbox{{\everymath{\scriptstyle }\let\f@size\sf@size\selectfont#1}}}% + {\hbox{{\everymath{\scriptscriptstyle}\let\f@size\ssf@size\selectfont#1}}}% + \glb@settings + \else + \mbox{#1}% + \fi +}% +\DeclareRobustCommand\REV@bbox[1]{% + \relax + \ifmmode + \mathchoice + {\hbox{{\everymath{\displaystyle }\boldmath$#1$}}}% + {\hbox{{\everymath{\textstyle }\boldmath$#1$}}}% + {\hbox{{\everymath{\scriptstyle }\boldmath$#1$}}}% + {\hbox{{\everymath{\scriptscriptstyle}\boldmath$#1$}}}% + \glb@settings + \else + \mbox{#1}% + \fi +}% +\DeclareRobustCommand\REV@bm[1]{% + \class@warn@end{To use \string\bm, please load the bm package!}% + \global\let\bm\relax +}% +\def\FL{\obsolete@command\FL}% +\def\FR{\obsolete@command\FR}% +\def\narrowtext{\obsolete@command\narrowtext}% +\def\mediumtext{\obsolete@command\mediumtext}% +\newenvironment{quasitable}{% + \let@environment{tabular}{longtable}% +}{% +}% +\let\text\REV@text +\let\bm\REV@bm +\appdef\setup@hook{% + \providecommand\bibinfo[2]{#2}% + \providecommand\eprint[2][]{#2}% +}% +\def\bbox#1{% + \class@warn@end{\string\bbox\space is obsolete,^^Jload the bm package and use \string\bm\space instead.}% + \global\let\bbox\relax +}% +\newenvironment{mathletters}{% + \class@warn@end{Environment {mathletters} is obsolete;^^Jload the amsmath package and use {subequations}!}% + \global\let\mathletters\@empty +}{% +}% +\def\eqnum#1{% + \class@warn@end{\string\eqnum\space is obsolete, load the amsmath package and use \string\tag!}% + \global\let\eqnum\@gobble +}% +\appdef\rtx@require@packages{% + \RequirePackage{revsymb4-2}% +}% +\appdef\class@documenthook{\revsymb@inithook}% +%% +\def\@startflt#1{% + \begingroup + %\toc@pre + \makeatletter + \@input{\jobname.#1}% + \if@filesw + \expandafter\newwrite\csname tf@#1\endcsname + \immediate\openout \csname tf@#1\endcsname \jobname.#1\relax + \fi + \@nobreakfalse + %\toc@post + \endgroup +}% +\def\att@TOC{toc}% +\def\print@toc#1{% + \begingroup + \expandafter\section + \expandafter*% + \expandafter{% + \csname#1name\endcsname + }% + \let\appendix\appendix@toc + \def\tempa{#1}% + \ifx\tempa\att@TOC%% + \@starttoc{#1}% + \else%% + \@startflt{#1}%% + \fi%% + \endgroup +}% +%% +\def\@LN@LLerror@org{% + \PackageError{lineno}{% + \string\linelabel\space without \string\linenumbers + }{% + Just see documentation. (New feature v4.11)% + }% + \@gobble +}% +\def\@LN@LLerror@ltx{% + \PackageWarning{lineno}{% + To make the \string\linelabel\space command work, you must issue the \string\linenumbers\ command + }% + \@gobble +}% +\appdef\class@documenthook{% + \@ifx{\@LN@LLerror\@LN@LLerror@org}{% + \class@info{Overriding \string\@LN@LLerror}% + \let\@LN@LLerror\@LN@LLerror@ltx + }{}% + \@ifpackageloaded{lineno}{% + \@ifxundefined{\set@linepenalties}{}{% + \def\prep@absbox{\set@linepenalties}% + \def\post@absbox{\let\@LN@parpgbrk\@empty\@linenumberpar}% + }% + }{}% +}% +\appdef\rtx@require@packages{% + \InputIfFileExists{\jobname.rty}{}{}% +}% +\@parse@class@options@society +\@process@society{aps}% +\@parse@class@options@\@journal +\expandafter\@process@journal\expandafter{\@journal@default}% +\@parse@class@options@\@pointsize +\expandafter\@process@pointsize\expandafter{\@pointsize@default}% +\@options +\rtx@require@packages +\appdef\setup@hook{\normalsize}% +\setup@hook +\endinput +%% +%% End of file `revtex4-2.cls'. From efa3704738497728c9522ac2e17b389e6405983b Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 22:17:21 +0200 Subject: [PATCH 021/129] Add LaTeX renderer and save_tex with vendored styles --- .../suggestions/project-summary-rendering.md | 2 +- docs/dev/plans/project-summary-rendering.md | 4 +- .../project/categories/report/default.py | 26 +- .../report/templates/tex/iucr.tex.j2 | 114 +++++++ .../report/templates/tex/revtex.tex.j2 | 119 +++++++ src/easydiffraction/report/tex_renderer.py | 294 ++++++++++++++++++ 6 files changed, 550 insertions(+), 9 deletions(-) create mode 100644 src/easydiffraction/report/templates/tex/iucr.tex.j2 create mode 100644 src/easydiffraction/report/templates/tex/revtex.tex.j2 create mode 100644 src/easydiffraction/report/tex_renderer.py diff --git a/docs/dev/adrs/suggestions/project-summary-rendering.md b/docs/dev/adrs/suggestions/project-summary-rendering.md index b9eeda9c6..5e6c8cc05 100644 --- a/docs/dev/adrs/suggestions/project-summary-rendering.md +++ b/docs/dev/adrs/suggestions/project-summary-rendering.md @@ -727,7 +727,7 @@ rebuilding the PDF. .html # ← 'html' in project.report.formats (this ADR §2) .pdf # ← 'pdf' in project.report.formats (this ADR §3.4) tex/ # ← 'tex' or 'pdf' in project.report.formats (this ADR §3) - .tex # main document; \input{}'s tables, \includegraphics figures + .tex # main document; tables, \includegraphics figures figures/ fit_.pdf # one per experiment, vector PDF (kaleido) styles/ # always-bundled — minimum to compile both styles diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index bb6992fb2..4ce93580a 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -690,7 +690,7 @@ generated-artifact exceptions. use setuptools. - Commit: `Vendor LaTeX styles with licenses and add report deps`. -- [ ] **P1.19 — LaTeX renderer + `save_tex(style='iucr')`** +- [x] **P1.19 — LaTeX renderer + `save_tex(style='iucr')`** - Files: new `src/easydiffraction/report/tex_renderer.py`; new `src/easydiffraction/report/templates/tex/iucr.tex.j2`; @@ -698,7 +698,7 @@ generated-artifact exceptions. - `Report.as_tex(style='iucr')` reads `data_context()`, renders the matching Jinja TeX template (driven by the `ReportStyleEnum` value), emits a `.tex` that - `\input{}`'s table partials and `\includegraphics{}`'s + contains summary tables and `\includegraphics{}`'s figures. - `Report.save_tex(style='iucr')` writes the rendered TeX plus its assets to `reports/tex/`: the main document, a diff --git a/src/easydiffraction/project/categories/report/default.py b/src/easydiffraction/project/categories/report/default.py index 9232efb5c..f660357dc 100644 --- a/src/easydiffraction/project/categories/report/default.py +++ b/src/easydiffraction/project/categories/report/default.py @@ -467,14 +467,28 @@ def save_tex(self, style: str = 'iucr') -> pathlib.Path: ------- pathlib.Path Path of the written TeX report. + """ + from easydiffraction.report.tex_renderer import save_tex_report # noqa: PLC0415 - Raises - ------ - NotImplementedError - Until the TeX writer lands in a later step. + return save_tex_report(self.project, self.data_context(), style=style) + + def as_tex(self, style: str = 'iucr') -> str: """ - del style - raise NotImplementedError('lands in P1.17 / P1.19 / P1.20') + Render the TeX report. + + Parameters + ---------- + style : str, default='iucr' + Report template style. + + Returns + ------- + str + Complete TeX report document. + """ + from easydiffraction.report.tex_renderer import render_tex_report # noqa: PLC0415 + + return render_tex_report(self.data_context(), style=style) def save_pdf(self, style: str = 'iucr') -> pathlib.Path: """ diff --git a/src/easydiffraction/report/templates/tex/iucr.tex.j2 b/src/easydiffraction/report/templates/tex/iucr.tex.j2 new file mode 100644 index 000000000..e6351fc3e --- /dev/null +++ b/src/easydiffraction/report/templates/tex/iucr.tex.j2 @@ -0,0 +1,114 @@ +\documentclass{styles/iucrjournals} +\graphicspath{{figures/}} + +\title{ {{- (publication.body.title or project.title or project.name or "EasyDiffraction report") | tex -}} } +{% if publication.authors %} +{% for author in publication.authors %} +\author{ {{- author.name | tex -}} } +{% endfor %} +{% else %} +\author{EasyDiffraction user} +{% endif %} + +\begin{document} +\maketitle + +{% if publication.body.abstract or publication.body.synopsis %} +\begin{abstract} +{{ (publication.body.abstract or publication.body.synopsis) | tex }} +\end{abstract} +{% endif %} + +\section{Project summary} + +\begin{tabular}{ll} +Project & {{ project.name | tex }} \\ +Title & {{ project.title | tex }} \\ +Phases & {{ project.n_phases }} \\ +Experiments & {{ project.n_experiments }} \\ +Generated & {{ metadata.generated_at | tex }} \\ +EasyDiffraction version & {{ metadata.easydiffraction_version | tex }} \\ +\end{tabular} + +\section{Refinement} + +\begin{tabular}{ll} +Reduced chi-square & {{ refinement.fit_result.reduced_chi_square | tex_number }} \\ +Free parameters & {{ refinement.parameters.free | tex_number }} \\ +Total parameters & {{ refinement.parameters.total | tex_number }} \\ +Constraints & {{ refinement.constraints | tex_number }} \\ +R factor & {{ refinement.fit_result.r_factor_all | tex_number }} \\ +Weighted R factor & {{ refinement.fit_result.wr_factor_all | tex_number }} \\ +\end{tabular} + +\section{Software} + +\begin{tabular}{lll} +Role & Name & Version \\ +Framework & {{ software.framework.name | tex }} & {{ software.framework.version | tex }} \\ +Calculator & {{ software.calculator.name | tex }} & {{ software.calculator.version | tex }} \\ +Minimizer & {{ software.minimizer.name | tex }} & {{ software.minimizer.version | tex }} \\ +\end{tabular} + +\section{Structures} + +{% for structure in structures %} +\subsection{ {{- structure.id | tex -}} } + +\begin{tabular}{ll} +Space group & {{ structure.space_group | tex }} \\ +Crystal system & {{ structure.crystal_system | tex }} \\ +$a$ & {{ structure.cell.length_a | tex_number }} \\ +$b$ & {{ structure.cell.length_b | tex_number }} \\ +$c$ & {{ structure.cell.length_c | tex_number }} \\ +$\alpha$ & {{ structure.cell.angle_alpha | tex_number }} \\ +$\beta$ & {{ structure.cell.angle_beta | tex_number }} \\ +$\gamma$ & {{ structure.cell.angle_gamma | tex_number }} \\ +\end{tabular} + +{% if structure.atom_sites %} +\begin{tabular}{lllllll} +Label & Type & $x$ & $y$ & $z$ & Occ. & ADP \\ +{% for atom in structure.atom_sites %} +{{ atom.label | tex }} & {{ atom.type_symbol | tex }} & {{ atom.fract_x | tex_number }} & {{ atom.fract_y | tex_number }} & {{ atom.fract_z | tex_number }} & {{ atom.occupancy | tex_number }} & {{ atom.adp_iso | tex_number }} \\ +{% endfor %} +\end{tabular} +{% endif %} +{% endfor %} + +\section{Experiments} + +{% for experiment in experiments %} +\subsection{ {{- experiment.id | tex -}} } + +\begin{tabular}{ll} +Sample form & {{ experiment.type.sample_form | tex }} \\ +Probe & {{ experiment.type.radiation_probe | tex }} \\ +Beam mode & {{ experiment.type.beam_mode | tex }} \\ +Scattering type & {{ experiment.type.scattering_type | tex }} \\ +Calculator & {{ experiment.calculator.type | tex }} \\ +Temperature & {{ experiment.diffrn.ambient_temperature | tex_number }} \\ +Pressure & {{ experiment.diffrn.ambient_pressure | tex_number }} \\ +\end{tabular} + +{% if tex.fit_figure_paths[experiment.id] %} +\begin{figure}[H] +\centering +\includegraphics[width=\linewidth]{ {{- tex.fit_figure_paths[experiment.id] -}} } +\caption{Observed and calculated fit for {{ experiment.id | tex }}.} +\end{figure} +{% endif %} +{% endfor %} + +\section{Publication metadata} + +\begin{tabular}{ll} +Journal & {{ publication.journal.name_full | tex }} \\ +Year & {{ publication.journal.year | tex }} \\ +DOI & {{ publication.journal.paper_doi | tex }} \\ +Contact author & {{ publication.contact_author.name | tex }} \\ +Contact email & {{ publication.contact_author.email | tex }} \\ +Keywords & {{ publication.body.keywords | tex }} \\ +\end{tabular} + +\end{document} diff --git a/src/easydiffraction/report/templates/tex/revtex.tex.j2 b/src/easydiffraction/report/templates/tex/revtex.tex.j2 new file mode 100644 index 000000000..780fc2732 --- /dev/null +++ b/src/easydiffraction/report/templates/tex/revtex.tex.j2 @@ -0,0 +1,119 @@ +\documentclass[aps,preprint]{styles/revtex4-2} +\usepackage{graphicx} +\graphicspath{{figures/}} + +\begin{document} + +\title{ {{- (publication.body.title or project.title or project.name or "EasyDiffraction report") | tex -}} } +{% if publication.authors %} +{% for author in publication.authors %} +\author{ {{- author.name | tex -}} } +{% if author.address %} +\affiliation{ {{- author.address | tex -}} } +{% endif %} +{% endfor %} +{% else %} +\author{EasyDiffraction user} +{% endif %} + +{% if publication.body.abstract or publication.body.synopsis %} +\begin{abstract} +{{ (publication.body.abstract or publication.body.synopsis) | tex }} +\end{abstract} +{% endif %} + +\maketitle + +\section{Project summary} + +\begin{tabular}{ll} +Project & {{ project.name | tex }} \\ +Title & {{ project.title | tex }} \\ +Phases & {{ project.n_phases }} \\ +Experiments & {{ project.n_experiments }} \\ +Generated & {{ metadata.generated_at | tex }} \\ +EasyDiffraction version & {{ metadata.easydiffraction_version | tex }} \\ +\end{tabular} + +\section{Refinement} + +\begin{tabular}{ll} +Reduced chi-square & {{ refinement.fit_result.reduced_chi_square | tex_number }} \\ +Free parameters & {{ refinement.parameters.free | tex_number }} \\ +Total parameters & {{ refinement.parameters.total | tex_number }} \\ +Constraints & {{ refinement.constraints | tex_number }} \\ +R factor & {{ refinement.fit_result.r_factor_all | tex_number }} \\ +Weighted R factor & {{ refinement.fit_result.wr_factor_all | tex_number }} \\ +\end{tabular} + +\section{Software} + +\begin{tabular}{lll} +Role & Name & Version \\ +Framework & {{ software.framework.name | tex }} & {{ software.framework.version | tex }} \\ +Calculator & {{ software.calculator.name | tex }} & {{ software.calculator.version | tex }} \\ +Minimizer & {{ software.minimizer.name | tex }} & {{ software.minimizer.version | tex }} \\ +\end{tabular} + +\section{Structures} + +{% for structure in structures %} +\subsection{ {{- structure.id | tex -}} } + +\begin{tabular}{ll} +Space group & {{ structure.space_group | tex }} \\ +Crystal system & {{ structure.crystal_system | tex }} \\ +$a$ & {{ structure.cell.length_a | tex_number }} \\ +$b$ & {{ structure.cell.length_b | tex_number }} \\ +$c$ & {{ structure.cell.length_c | tex_number }} \\ +$\alpha$ & {{ structure.cell.angle_alpha | tex_number }} \\ +$\beta$ & {{ structure.cell.angle_beta | tex_number }} \\ +$\gamma$ & {{ structure.cell.angle_gamma | tex_number }} \\ +\end{tabular} + +{% if structure.atom_sites %} +\begin{tabular}{lllllll} +Label & Type & $x$ & $y$ & $z$ & Occ. & ADP \\ +{% for atom in structure.atom_sites %} +{{ atom.label | tex }} & {{ atom.type_symbol | tex }} & {{ atom.fract_x | tex_number }} & {{ atom.fract_y | tex_number }} & {{ atom.fract_z | tex_number }} & {{ atom.occupancy | tex_number }} & {{ atom.adp_iso | tex_number }} \\ +{% endfor %} +\end{tabular} +{% endif %} +{% endfor %} + +\section{Experiments} + +{% for experiment in experiments %} +\subsection{ {{- experiment.id | tex -}} } + +\begin{tabular}{ll} +Sample form & {{ experiment.type.sample_form | tex }} \\ +Probe & {{ experiment.type.radiation_probe | tex }} \\ +Beam mode & {{ experiment.type.beam_mode | tex }} \\ +Scattering type & {{ experiment.type.scattering_type | tex }} \\ +Calculator & {{ experiment.calculator.type | tex }} \\ +Temperature & {{ experiment.diffrn.ambient_temperature | tex_number }} \\ +Pressure & {{ experiment.diffrn.ambient_pressure | tex_number }} \\ +\end{tabular} + +{% if tex.fit_figure_paths[experiment.id] %} +\begin{figure} +\centering +\includegraphics[width=\linewidth]{ {{- tex.fit_figure_paths[experiment.id] -}} } +\caption{Observed and calculated fit for {{ experiment.id | tex }}.} +\end{figure} +{% endif %} +{% endfor %} + +\section{Publication metadata} + +\begin{tabular}{ll} +Journal & {{ publication.journal.name_full | tex }} \\ +Year & {{ publication.journal.year | tex }} \\ +DOI & {{ publication.journal.paper_doi | tex }} \\ +Contact author & {{ publication.contact_author.name | tex }} \\ +Contact email & {{ publication.contact_author.email | tex }} \\ +Keywords & {{ publication.body.keywords | tex }} \\ +\end{tabular} + +\end{document} diff --git a/src/easydiffraction/report/tex_renderer.py b/src/easydiffraction/report/tex_renderer.py new file mode 100644 index 000000000..03f47a04c --- /dev/null +++ b/src/easydiffraction/report/tex_renderer.py @@ -0,0 +1,294 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Render project reports as LaTeX source bundles.""" + +from __future__ import annotations + +import pathlib +from importlib.resources import files + +from jinja2 import Environment +from jinja2 import PackageLoader + +from easydiffraction.report.enums import ReportStyleEnum + +_STYLE_TEMPLATE_NAMES = { + ReportStyleEnum.IUCR: 'tex/iucr.tex.j2', + ReportStyleEnum.REVTEX: 'tex/revtex.tex.j2', +} +_TEX_SPECIAL_CHARS = { + '\\': r'\textbackslash{}', + '&': r'\&', + '%': r'\%', + '$': r'\$', + '#': r'\#', + '_': r'\_', + '{': r'\{', + '}': r'\}', + '~': r'\textasciitilde{}', + '^': r'\textasciicircum{}', +} +_BROWSER_HINT = ( + 'Kaleido v1 requires a Chrome/Chromium browser for static image ' + 'export. Install Chrome or Chromium and re-run report generation.' +) + + +def tex_report_path( + project: object, + path: str | pathlib.Path | None = None, +) -> pathlib.Path: + """ + Return the target TeX report path for a project. + + Parameters + ---------- + project : object + Project instance. + path : str | pathlib.Path | None, default=None + Explicit report path. + + Returns + ------- + pathlib.Path + Resolved report path. + + Raises + ------ + FileNotFoundError + If no path is supplied and the project has not been saved. + """ + if path is not None: + return pathlib.Path(path) + + project_path = getattr(getattr(project, 'info', None), 'path', None) + if project_path is None: + msg = 'Project has no saved path. Save the project first.' + raise FileNotFoundError(msg) + + project_name = getattr(project, 'name', 'project') + return pathlib.Path(project_path) / 'reports' / 'tex' / f'{project_name}.tex' + + +def render_tex_report( + context: dict[str, object], + *, + style: str = 'iucr', +) -> str: + """ + Render a report data context as LaTeX. + + Parameters + ---------- + context : dict[str, object] + Data returned by ``Report.data_context()``. + style : str, default='iucr' + Report template style. + + Returns + ------- + str + Complete LaTeX document. + """ + style_member = ReportStyleEnum(style) + template_context = dict(context) + template_context['tex'] = { + 'fit_figure_paths': _fit_figure_paths(context), + } + template_name = _STYLE_TEMPLATE_NAMES[style_member] + return _environment().get_template(template_name).render(**template_context) + + +def save_tex_report( + project: object, + context: dict[str, object], + *, + style: str = 'iucr', + path: str | pathlib.Path | None = None, +) -> pathlib.Path: + """ + Write a TeX report bundle. + + Parameters + ---------- + project : object + Project instance. + context : dict[str, object] + Data returned by ``Report.data_context()``. + style : str, default='iucr' + Report template style. + path : str | pathlib.Path | None, default=None + Explicit report path. + + Returns + ------- + pathlib.Path + Path of the written main TeX document. + """ + output_path = tex_report_path(project, path) + tex_dir = output_path.parent + figures_dir = tex_dir / 'figures' + styles_dir = tex_dir / 'styles' + + tex_dir.mkdir(parents=True, exist_ok=True) + figures_dir.mkdir(parents=True, exist_ok=True) + styles_dir.mkdir(parents=True, exist_ok=True) + + figure_paths = _write_fit_figures(context, figures_dir) + template_context = dict(context) + template_context['tex'] = {'fit_figure_paths': figure_paths} + output_path.write_text( + _render_prepared_context(template_context, style=style), + encoding='utf-8', + ) + _copy_style_files(styles_dir) + return output_path + + +def _render_prepared_context( + context: dict[str, object], + *, + style: str, +) -> str: + """Render a context that already contains TeX asset paths.""" + style_member = ReportStyleEnum(style) + template_name = _STYLE_TEMPLATE_NAMES[style_member] + return _environment().get_template(template_name).render(**context) + + +def _environment() -> Environment: + """Return the Jinja environment for TeX report templates.""" + environment = Environment( + loader=PackageLoader('easydiffraction.report', 'templates'), + autoescape=False, + trim_blocks=True, + lstrip_blocks=True, + ) + environment.filters['tex'] = _tex_escape + environment.filters['tex_number'] = _tex_number + return environment + + +def _fit_figure_paths(context: dict[str, object]) -> dict[str, str]: + """Return relative TeX paths for fit figures.""" + return { + experiment_id: f'figures/{filename}' + for experiment_id, filename in _fit_figure_filenames(context).items() + } + + +def _write_fit_figures( + context: dict[str, object], + figures_dir: pathlib.Path, +) -> dict[str, str]: + """Write fit figures as vector PDFs and return relative paths.""" + figures = _fit_figures(context) + filenames = _fit_figure_filenames(context) + figure_paths = {} + for experiment_id, figure in figures.items(): + filename = filenames[experiment_id] + output_path = figures_dir / filename + _write_plotly_figure(figure, output_path, experiment_id=experiment_id) + figure_paths[experiment_id] = f'figures/{filename}' + return figure_paths + + +def _fit_figure_filenames(context: dict[str, object]) -> dict[str, str]: + """Return collision-safe filenames for fit figures.""" + used: set[str] = set() + filenames = {} + for experiment_id in _fit_figures(context): + stem = _safe_file_stem(experiment_id) + filename = f'fit_{stem}.pdf' + suffix = 2 + while filename in used: + filename = f'fit_{stem}_{suffix}.pdf' + suffix += 1 + used.add(filename) + filenames[experiment_id] = filename + return filenames + + +def _fit_figures(context: dict[str, object]) -> dict[str, object]: + """Return figure objects keyed by experiment id.""" + figures = context.get('figures') + if not isinstance(figures, dict): + return {} + + fit_figures = figures.get('fit_per_experiment') + if not isinstance(fit_figures, dict): + return {} + + return { + str(experiment_id): figure + for experiment_id, figure in fit_figures.items() + if figure is not None + } + + +def _write_plotly_figure( + figure: object, + output_path: pathlib.Path, + *, + experiment_id: str, +) -> None: + """Write one Plotly-like figure to PDF.""" + write_image = getattr(figure, 'write_image', None) + if not callable(write_image): + msg = ( + f"Report figure for experiment '{experiment_id}' does not provide " + 'write_image().' + ) + raise TypeError(msg) + + try: + write_image(str(output_path)) + except Exception as exc: + msg = ( + f"Could not export report figure for experiment '{experiment_id}' " + f"to '{output_path}'. {_BROWSER_HINT}" + ) + raise RuntimeError(msg) from exc + + +def _copy_style_files(styles_dir: pathlib.Path) -> None: + """Copy vendored LaTeX style files into a report bundle.""" + source = files('easydiffraction.report').joinpath( + 'templates', + 'tex', + 'styles', + ) + for resource in source.iterdir(): + if resource.is_file(): + (styles_dir / resource.name).write_bytes(resource.read_bytes()) + + +def _safe_file_stem(value: object) -> str: + """Return a filesystem-safe stem for generated report assets.""" + text = str(value) + safe = ''.join( + char if char.isalnum() or char in {'-', '_', '.'} else '_' for char in text + ) + return safe.strip('._-') or 'figure' + + +def _tex_number(value: object, digits: int = 6) -> str: + """Format a number for TeX output.""" + if isinstance(value, bool): + return _tex_escape(value) + if isinstance(value, (float, int)): + return f'{value:.{digits}g}' + return _tex_escape(value) + + +def _tex_escape(value: object) -> str: + """Escape user-provided text for TeX output.""" + if value is None: + return '' + if isinstance(value, (list, tuple, set)): + text = ', '.join(str(item) for item in value if item is not None) + else: + text = str(value) + text = text.replace('\r\n', '\n').replace('\r', '\n') + escaped = ''.join(_TEX_SPECIAL_CHARS.get(char, char) for char in text) + return escaped.replace('\n\n', r'\par ').replace('\n', ' ') From 27c948ab06bb4c52bd7fa5262dde411939d4a3f8 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 22:19:24 +0200 Subject: [PATCH 022/129] Add opportunistic PDF compilation via TeX subprocess --- docs/dev/plans/project-summary-rendering.md | 2 +- .../project/categories/report/default.py | 13 +- src/easydiffraction/report/pdf_compiler.py | 150 ++++++++++++++++++ 3 files changed, 156 insertions(+), 9 deletions(-) create mode 100644 src/easydiffraction/report/pdf_compiler.py diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 4ce93580a..498150d3f 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -709,7 +709,7 @@ generated-artifact exceptions. `Report.save_tex()` with the real call. - Commit: `Add LaTeX renderer and save_tex with vendored styles`. -- [ ] **P1.20 — PDF compilation + `save_pdf(style='iucr')`** +- [x] **P1.20 — PDF compilation + `save_pdf(style='iucr')`** - Files: new `src/easydiffraction/report/pdf_compiler.py`; modify diff --git a/src/easydiffraction/project/categories/report/default.py b/src/easydiffraction/project/categories/report/default.py index f660357dc..ce5552af6 100644 --- a/src/easydiffraction/project/categories/report/default.py +++ b/src/easydiffraction/project/categories/report/default.py @@ -502,15 +502,12 @@ def save_pdf(self, style: str = 'iucr') -> pathlib.Path: Returns ------- pathlib.Path - Path of the written PDF report. - - Raises - ------ - NotImplementedError - Until the PDF writer lands in a later step. + Path of the PDF report, or the intended PDF path when no + TeX engine is available. """ - del style - raise NotImplementedError('lands in P1.17 / P1.19 / P1.20') + from easydiffraction.report.pdf_compiler import save_pdf_report # noqa: PLC0415 + + return save_pdf_report(self.project, self.data_context(), style=style) def save(self) -> list[pathlib.Path]: """ diff --git a/src/easydiffraction/report/pdf_compiler.py b/src/easydiffraction/report/pdf_compiler.py new file mode 100644 index 000000000..a367283a6 --- /dev/null +++ b/src/easydiffraction/report/pdf_compiler.py @@ -0,0 +1,150 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Compile LaTeX report bundles into PDF reports.""" + +from __future__ import annotations + +import os +import pathlib +import shutil +import subprocess + +from easydiffraction.report.tex_renderer import save_tex_report +from easydiffraction.utils.logging import log + +_ENGINE_ORDER = ('tectonic', 'latexmk', 'pdflatex') +_INSTALL_HINT = """PDF skipped: no TeX engine on PATH. +Install one with: + pixi add tectonic + conda install -c conda-forge tectonic + # or any TeX Live distribution (latexmk / pdflatex) +Then re-run project.save() with 'pdf' in project.report.formats or +project.report.save_pdf().""" + + +def save_pdf_report( + project: object, + context: dict[str, object], + *, + style: str = 'iucr', +) -> pathlib.Path: + """ + Write a TeX bundle and compile it to PDF when possible. + + Parameters + ---------- + project : object + Project instance. + context : dict[str, object] + Data returned by ``Report.data_context()``. + style : str, default='iucr' + Report template style. + + Returns + ------- + pathlib.Path + Path of the PDF report, or the intended PDF path when no TeX + engine is available. + + Raises + ------ + RuntimeError + If a discovered TeX engine fails to compile the report. + """ + tex_path = save_tex_report(project, context, style=style) + pdf_path = tex_path.parent.parent / f'{tex_path.stem}.pdf' + engine = _find_engine() + if engine is None: + log.warning(_INSTALL_HINT) + return pdf_path + + _compile_pdf(engine, tex_path, pdf_path) + return pdf_path + + +def _find_engine() -> tuple[str, str] | None: + """Return the first available TeX engine.""" + for engine_name in _ENGINE_ORDER: + executable = shutil.which(engine_name) + if executable is not None: + return engine_name, executable + return None + + +def _compile_pdf( + engine: tuple[str, str], + tex_path: pathlib.Path, + pdf_path: pathlib.Path, +) -> None: + """Compile one TeX document with a discovered engine.""" + engine_name, executable = engine + command = _compile_command(engine_name, executable, tex_path, pdf_path.parent) + result = subprocess.run( + command, + cwd=tex_path.parent, + env=_compile_environment(tex_path), + text=True, + capture_output=True, + check=False, + ) + if result.returncode != 0: + msg = _compiler_error_message(engine_name, tex_path, result) + raise RuntimeError(msg) + if not pdf_path.is_file(): + msg = ( + f"TeX engine '{engine_name}' completed but did not write " + f"'{pdf_path}'." + ) + raise RuntimeError(msg) + + +def _compile_command( + engine_name: str, + executable: str, + tex_path: pathlib.Path, + pdf_dir: pathlib.Path, +) -> list[str]: + """Return the compile command for one engine.""" + if engine_name == 'tectonic': + return [executable, '--outdir', str(pdf_dir), tex_path.name] + if engine_name == 'latexmk': + return [ + executable, + '-pdf', + '-interaction=nonstopmode', + '-halt-on-error', + f'-outdir={pdf_dir}', + tex_path.name, + ] + return [ + executable, + '-interaction=nonstopmode', + '-halt-on-error', + '-output-directory', + str(pdf_dir), + tex_path.name, + ] + + +def _compile_environment(tex_path: pathlib.Path) -> dict[str, str]: + """Return a TeX subprocess environment with vendored styles.""" + environment = os.environ.copy() + styles_dir = tex_path.parent / 'styles' + texinputs = environment.get('TEXINPUTS', '') + environment['TEXINPUTS'] = f'{styles_dir}{os.pathsep}{texinputs}' + return environment + + +def _compiler_error_message( + engine_name: str, + tex_path: pathlib.Path, + result: subprocess.CompletedProcess[str], +) -> str: + """Return a concise compiler failure message.""" + details = (result.stderr or result.stdout).strip() + if len(details) > 4000: + details = details[-4000:] + return ( + f"TeX engine '{engine_name}' failed while compiling '{tex_path}'.\n" + f'{details}' + ) From 0ed6e660fda50ead85db3f0cf44f6806107932a6 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 22:21:28 +0200 Subject: [PATCH 023/129] Add crystal_system and measured_range properties for reports --- docs/dev/plans/project-summary-rendering.md | 2 +- .../datablocks/experiment/item/base.py | 46 +++++++++++++++++++ .../categories/space_group/default.py | 25 ++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 498150d3f..2d78e3b66 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -729,7 +729,7 @@ generated-artifact exceptions. `Report.save_pdf()` with the real call. - Commit: `Add opportunistic PDF compilation via TeX subprocess`. -- [ ] **P1.21 — Add `crystal_system` and `measured_range` properties** +- [x] **P1.21 — Add `crystal_system` and `measured_range` properties** - Files: `src/easydiffraction/datablocks/structure/categories/space_group/default.py`, `src/easydiffraction/datablocks/experiment/item/base.py`. diff --git a/src/easydiffraction/datablocks/experiment/item/base.py b/src/easydiffraction/datablocks/experiment/item/base.py index ee1de7fda..d1f4b4184 100644 --- a/src/easydiffraction/datablocks/experiment/item/base.py +++ b/src/easydiffraction/datablocks/experiment/item/base.py @@ -8,6 +8,8 @@ from typing import TYPE_CHECKING from typing import Any +import numpy as np + from easydiffraction.core.datablock import DatablockItem from easydiffraction.datablocks.experiment.categories.background.factory import BackgroundFactory from easydiffraction.datablocks.experiment.categories.calculator import CalculatorCategoryFactory @@ -37,6 +39,9 @@ from easydiffraction.datablocks.experiment.categories.experiment_type import ExperimentType from easydiffraction.datablocks.structure.collection import Structures +MeasuredRange = tuple[float, float, float | None] +_MEASURED_RANGE_UNIFORM_TOLERANCE = 0.01 + def intensity_category_for(experiment: object) -> object: """Return the category exposing measured and calculated values.""" @@ -257,6 +262,35 @@ def type(self) -> object: # TODO: Consider another name """Experiment type: sample form, probe, beam mode.""" return self._type + @property + def measured_range(self) -> MeasuredRange | None: + """Measured x-axis range as ``(min, max, inc)``.""" + values = self._measured_x_values() + if values is None or values.size == 0: + return None + + values = np.sort(values.astype(float, copy=False)) + range_min = float(values[0]) + range_max = float(values[-1]) + if values.size == 1: + return (range_min, range_max, None) + + increment = _representative_increment(values) + return (range_min, range_max, increment) + + def _measured_x_values(self) -> np.ndarray | None: + """Return the measured x-axis values for this experiment.""" + try: + category = intensity_category_for(self) + except AttributeError: + return None + values = getattr(category, 'unfiltered_x', None) + if values is None: + values = getattr(category, 'x', None) + if values is None: + return None + return np.asarray(values, dtype=float) + # ------------------------------------------------------------------ # Diffrn conditions (read-only, single type) # ------------------------------------------------------------------ @@ -377,6 +411,18 @@ def _intensity_category(self) -> object: raise AttributeError(msg) +def _representative_increment(values: np.ndarray) -> float | None: + """Return a representative increment for sorted x-axis values.""" + steps = np.diff(values) + median_step = float(np.median(steps)) + if median_step == 0: + return None + tolerance = abs(median_step) * _MEASURED_RANGE_UNIFORM_TOLERANCE + if np.max(np.abs(steps - median_step)) > tolerance: + return None + return median_step + + class ScExperimentBase(ExperimentBase): """Base class for all single crystal experiments.""" diff --git a/src/easydiffraction/datablocks/structure/categories/space_group/default.py b/src/easydiffraction/datablocks/structure/categories/space_group/default.py index 913addfef..59ae7facb 100644 --- a/src/easydiffraction/datablocks/structure/categories/space_group/default.py +++ b/src/easydiffraction/datablocks/structure/categories/space_group/default.py @@ -18,6 +18,16 @@ from easydiffraction.datablocks.structure.categories.space_group.factory import SpaceGroupFactory from easydiffraction.io.cif.handler import CifHandler +_CRYSTAL_SYSTEM_RANGES = ( + (1, 2, 'triclinic'), + (3, 15, 'monoclinic'), + (16, 74, 'orthorhombic'), + (75, 142, 'tetragonal'), + (143, 167, 'trigonal'), + (168, 194, 'hexagonal'), + (195, 230, 'cubic'), +) + @SpaceGroupFactory.register class SpaceGroup(CategoryItem): @@ -163,3 +173,18 @@ def it_coordinate_system_code(self) -> StringDescriptor: @it_coordinate_system_code.setter def it_coordinate_system_code(self, value: str) -> None: self._it_coordinate_system_code.value = value + + @property + def crystal_system(self) -> str: + """Crystal system derived from the H-M symbol.""" + it_number = get_it_number_by_name_hm_short(self.name_h_m.value) + return _crystal_system_from_it_number(it_number) + + +def _crystal_system_from_it_number(it_number: int) -> str: + """Return the crystal system for an International Tables number.""" + for start, stop, crystal_system in _CRYSTAL_SYSTEM_RANGES: + if start <= it_number <= stop: + return crystal_system + msg = f'Unknown International Tables number: {it_number}' + raise ValueError(msg) From 51e0108513f79b75d1a83a5ffed57c71c3b3c814 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 22:23:48 +0200 Subject: [PATCH 024/129] Add ed save-report CLI subcommand --- docs/dev/plans/project-summary-rendering.md | 23 ++-- src/easydiffraction/__main__.py | 122 +++++++++++++++++++- 2 files changed, 133 insertions(+), 12 deletions(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 2d78e3b66..416cc286d 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -243,10 +243,10 @@ re-litigate them, only implements them: equivalent — add `measured_range` property). **CLI (P1.22):** -- `src/easydiffraction/cli/save.py` (existing — match - `project.save()` no-arg behaviour reading config). -- `src/easydiffraction/cli/save_report.py` (new — `ed save-report - --cif --html --tex --pdf --style iucr` subcommand). +- `src/easydiffraction/__main__.py` (existing Typer module — + add config-driven `ed save PROJECT_DIR` and one-off + `ed save-report --cif --html --tex --pdf --style iucr`; + no `src/easydiffraction/cli/` package exists in this tree). **Tutorials / docs (P1.23):** - `docs/docs/tutorials/*.py` (audit for `project.report` usage; @@ -410,8 +410,8 @@ generated-artifact exceptions. (the moved `Report` class — same target for P1.6, P1.7, P1.20), `src/easydiffraction/project/project.py`, - `src/easydiffraction/cli/` (any commands exposing - `--check`). + `src/easydiffraction/__main__.py` (any commands exposing + `--check`; no such flag existed in the current CLI). - Delete `Report.check()` method. - Drop the `check=False` keyword from `Project.save()`. - Update any docstrings / tutorials that reference the @@ -761,9 +761,10 @@ generated-artifact exceptions. purely to feed the renderers via `data_context()`. - Commit: `Add crystal_system and measured_range properties for reports`. -- [ ] **P1.22 — CLI `ed save-report` subcommand** - - Files: new `src/easydiffraction/cli/save_report.py`; - modify `src/easydiffraction/cli/save.py`. +- [x] **P1.22 — CLI `ed save-report` subcommand** + - Files: modify `src/easydiffraction/__main__.py` + (the current Typer command module; there is no + `src/easydiffraction/cli/` package in this tree). - Add an `ed save-report` subcommand accepting `--cif`, `--html`, `--tex`, `--pdf`, `--style iucr`, `--offline` flags. Dispatches to @@ -771,8 +772,8 @@ generated-artifact exceptions. - `ed save-report` with no flags → clear error pointing at the configuration category (matches the Python `ValueError` from P1.6). - - `ed save` continues to read the persisted config; no flag - surface added on `ed save`. + - `ed save` reads the persisted config; no flag surface added + on `ed save`. - Commit: `Add ed save-report CLI subcommand`. - [ ] **P1.23 — Update tutorials and user-guide docs** diff --git a/src/easydiffraction/__main__.py b/src/easydiffraction/__main__.py index 201183c01..8aae26234 100644 --- a/src/easydiffraction/__main__.py +++ b/src/easydiffraction/__main__.py @@ -3,6 +3,7 @@ from __future__ import annotations +import pathlib import sys # Ensure UTF-8 output on all platforms (e.g. Windows with cp1252) @@ -13,11 +14,12 @@ import typer import easydiffraction as ed +from easydiffraction.report.enums import ReportStyleEnum app = typer.Typer(add_completion=False) _MIN_PROJECT_FIRST_ARG_COUNT = 2 -_PROJECT_COMMAND_NAMES = frozenset({'fit', 'display', 'undo'}) +_PROJECT_COMMAND_NAMES = frozenset({'fit', 'display', 'undo', 'save', 'save-report'}) _GLOBAL_COMMAND_NAMES = frozenset({ 'list-data', 'download-data', @@ -134,6 +136,51 @@ def _display_undo_summary( typer.echo(f'✅ Saved project to {project_dir}.') +def _selected_report_flags( + *, + cif: bool, + html: bool, + tex: bool, + pdf: bool, +) -> bool: + """Return whether at least one report format was selected.""" + return cif or html or tex or pdf + + +def _save_report_outputs( + project: object, + *, + cif: bool, + html: bool, + tex: bool, + pdf: bool, + style: str, + offline: bool, +) -> list[pathlib.Path]: + """Write selected one-off report outputs.""" + report = project.report + report_paths = [] + if cif: + report_paths.append(report.save_cif()) + if html: + report_paths.append(report.save_html(offline=offline)) + if tex: + report_paths.append(report.save_tex(style=style)) + if pdf: + report_paths.append(report.save_pdf(style=style)) + return report_paths + + +def _validated_report_style(style: str) -> str: + """Return a valid report style value.""" + try: + return ReportStyleEnum(style).value + except ValueError as exc: + allowed = ', '.join(member.value for member in ReportStyleEnum) + msg = f"Unknown report style '{style}'. Supported styles: {allowed}." + raise typer.BadParameter(msg) from exc + + def run_cli(args: list[str] | None = None) -> None: """ Run the EasyDiffraction CLI with project-first argument support. @@ -284,5 +331,78 @@ def undo( _display_undo_summary(project=project, project_dir=project_dir, dry=dry) +@app.command('save') +def save( + project_dir: str = typer.Argument( + ..., + help='Path to the project directory (must contain project.cif).', + ), +) -> None: + """Save a project using its persisted report configuration.""" + project = _load_project(project_dir) + project.save() + + +@app.command('save-report') +def save_report( + project_dir: str = typer.Argument( + ..., + help='Path to the project directory (must contain project.cif).', + ), + cif: bool = typer.Option( # noqa: FBT001 + False, # noqa: FBT003 + '--cif', + help='Write the IUCr CIF report.', + ), + html: bool = typer.Option( # noqa: FBT001 + False, # noqa: FBT003 + '--html', + help='Write the HTML report.', + ), + tex: bool = typer.Option( # noqa: FBT001 + False, # noqa: FBT003 + '--tex', + help='Write the TeX report bundle.', + ), + pdf: bool = typer.Option( # noqa: FBT001 + False, # noqa: FBT003 + '--pdf', + help='Write the PDF report.', + ), + style: str = typer.Option( + 'iucr', + '--style', + help='Report template style.', + ), + offline: bool = typer.Option( # noqa: FBT001 + False, # noqa: FBT003 + '--offline', + help='Embed HTML assets instead of loading them from a CDN.', + ), +) -> None: + """Write one-off reports without changing saved configuration.""" + if not _selected_report_flags(cif=cif, html=html, tex=tex, pdf=pdf): + typer.echo( + 'No report format selected. Use --cif, --html, --tex, or --pdf; ' + 'or configure project.report.formats and save the project.', + err=True, + ) + raise typer.Exit(code=1) + + style = _validated_report_style(style) + project = _load_project(project_dir) + report_paths = _save_report_outputs( + project, + cif=cif, + html=html, + tex=tex, + pdf=pdf, + style=style, + offline=offline, + ) + for report_path in report_paths: + typer.echo(f'Saved report: {report_path}') + + if __name__ == '__main__': run_cli() From 17fb00d954a8aa8e653222299aeac3792c5bf86f Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 26 May 2026 22:25:42 +0200 Subject: [PATCH 025/129] Update tutorials and user-guide docs for project.report --- docs/dev/plans/project-summary-rendering.md | 2 +- docs/docs/api-reference/report.md | 28 +++++++++- docs/docs/tutorials/ed-3.ipynb | 7 +-- docs/docs/tutorials/ed-3.py | 7 +-- .../user-guide/analysis-workflow/project.md | 8 +-- .../user-guide/analysis-workflow/report.md | 53 ++++++++++++++++--- 6 files changed, 88 insertions(+), 17 deletions(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 416cc286d..08f42a4eb 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -776,7 +776,7 @@ generated-artifact exceptions. on `ed save`. - Commit: `Add ed save-report CLI subcommand`. -- [ ] **P1.23 — Update tutorials and user-guide docs** +- [x] **P1.23 — Update tutorials and user-guide docs** - Files: `docs/docs/tutorials/*.py`, `docs/docs/tutorials/*.ipynb` (regenerated artefacts — explicitly staged because `pixi run notebook-prepare` diff --git a/docs/docs/api-reference/report.md b/docs/docs/api-reference/report.md index 7a3ae20f9..4e6ac9c43 100644 --- a/docs/docs/api-reference/report.md +++ b/docs/docs/api-reference/report.md @@ -1 +1,27 @@ -::: easydiffraction.report +# Report API + +::: easydiffraction.project.categories.report.default.Report + options: + members: + - cif + - html + - tex + - pdf + - style + - html_offline + - formats + - data_context + - save + - save_cif + - save_html + - as_html + - save_tex + - as_tex + - save_pdf + - show_report + +## Enums + +::: easydiffraction.report.enums.ReportFormatEnum + +::: easydiffraction.report.enums.ReportStyleEnum diff --git a/docs/docs/tutorials/ed-3.ipynb b/docs/docs/tutorials/ed-3.ipynb index acb57a326..664d4cd23 100644 --- a/docs/docs/tutorials/ed-3.ipynb +++ b/docs/docs/tutorials/ed-3.ipynb @@ -1546,7 +1546,7 @@ "id": "151", "metadata": {}, "source": [ - "#### Save Project State and Submission Report" + "#### Save Project State and HTML Report" ] }, { @@ -1556,7 +1556,8 @@ "metadata": {}, "outputs": [], "source": [ - "project.save(report=True)" + "project.report.formats = ['html']\n", + "project.save()" ] }, { @@ -1568,7 +1569,7 @@ "\n", "This final section shows how to review the results of the analysis.\n", "\n", - "The saved IUCr submission CIF is available under `reports/.cif`\n", + "The saved HTML report is available under `reports/.html`\n", "inside the project directory." ] }, diff --git a/docs/docs/tutorials/ed-3.py b/docs/docs/tutorials/ed-3.py index fe25bcc47..b3a14428f 100644 --- a/docs/docs/tutorials/ed-3.py +++ b/docs/docs/tutorials/ed-3.py @@ -620,17 +620,18 @@ project.display.pattern(expt_name='hrpt', x_min=38, x_max=41) # %% [markdown] -# #### Save Project State and Submission Report +# #### Save Project State and HTML Report # %% -project.save(report=True) +project.report.formats = ['html'] +project.save() # %% [markdown] # ## Step 5: Report # # This final section shows how to review the results of the analysis. # -# The saved IUCr submission CIF is available under `reports/.cif` +# The saved HTML report is available under `reports/.html` # inside the project directory. # %% [markdown] diff --git a/docs/docs/user-guide/analysis-workflow/project.md b/docs/docs/user-guide/analysis-workflow/project.md index 45fbbe042..5dd8e6b9e 100644 --- a/docs/docs/user-guide/analysis-workflow/project.md +++ b/docs/docs/user-guide/analysis-workflow/project.md @@ -86,8 +86,9 @@ The example below illustrates a typical **project structure** for a ├── 📁 analysis - Analysis settings and optional persisted Bayesian arrays. │ ├── 📄 analysis.cif - Settings for data analysis (minimizer, fit mode, constraints, persisted fit state). │ └── 📄 results.h5 - Optional Bayesian sidecar with posterior and predictive arrays. -└── 📁 reports - Optional IUCr submission reports. - └── 📄 La0.5Ba0.5CoO3.cif - Report written by project.save(report=True). +└── 📁 reports - Optional reports written from project.report config. + ├── 📄 La0.5Ba0.5CoO3.cif - IUCr submission report. + └── 📄 La0.5Ba0.5CoO3.html - HTML report. @@ -103,7 +104,8 @@ directory, showing the contents of all files in the project. If you save the project right after creating it, the project directory will only contain the `project.cif` file. The other folders and files will be created as you add structures, experiments, and set up the analysis. The - reports folder is created only when you call `project.save(report=True)`. + reports folder is created only when `project.report.formats` selects + at least one report format before `project.save()`. ### 1. project.cif diff --git a/docs/docs/user-guide/analysis-workflow/report.md b/docs/docs/user-guide/analysis-workflow/report.md index 18d5d00e4..d4810f3af 100644 --- a/docs/docs/user-guide/analysis-workflow/report.md +++ b/docs/docs/user-guide/analysis-workflow/report.md @@ -34,17 +34,58 @@ This command will display a structured report of the analysis results, including model parameters, fit statistics, and data visualizations. --> -## Saving a Submission Report +## Configuring Saved Reports -Regular project saves do not write a report file. To write an IUCr -journal-submission CIF, use: +Report output is controlled by `project.report`, a project-level +configuration category that is saved in `project.cif`. Regular +`project.save()` calls read this configuration and write the selected +report formats. + +| Setting | Type | Meaning | +| --- | --- | --- | +| `project.report.cif` | `bool` | Write an IUCr submission CIF. | +| `project.report.html` | `bool` | Write an HTML report. | +| `project.report.tex` | `bool` | Write a TeX report bundle. | +| `project.report.pdf` | `bool` | Write a PDF report when a TeX engine is available. | +| `project.report.style` | `str` | TeX/PDF template style: `'iucr'` or `'revtex'`. | +| `project.report.html_offline` | `bool` | Embed HTML assets instead of using CDN links. | + +The `formats` property is a compact way to set the four format flags: + +```python +project.report.formats = ['html', 'cif'] +project.save() +``` + +This writes `reports/.html` and `reports/.cif` inside +the project directory. + +## One-Off Report Saves + +Per-format methods write a report without changing the saved +configuration: ```python -project.save(report=True) +project.report.save_html() +project.report.save_cif() +project.report.save_tex(style='iucr') +project.report.save_pdf(style='iucr') +``` + +`save_pdf()` always writes the TeX bundle first. If no TeX engine is on +`PATH`, EasyDiffraction leaves the TeX files in `reports/tex/`, prints +a short install hint, and does not raise. + +The command line mirrors the same split: + +```bash +ed save path/to/project +ed save-report path/to/project --html --tex --pdf --style iucr ``` -The report is written to `reports/.cif` inside the saved -project directory. +`ed save` uses the persisted `project.report` configuration. +`ed save-report` is for one-off exports and requires at least one of +`--cif`, `--html`, `--tex`, or `--pdf`. /g,""),o=r.firstChild(r.body(r.parse(n,"text/html"))),i=r.node("mjx-assistive-mml",{unselectable:"on",display:this.display?"block":"inline"},[o]);r.setAttribute(r.firstChild(this.typesetRoot),"aria-hidden","true"),r.setStyle(this.typesetRoot,"position","relative"),r.append(this.typesetRoot,i)}this.state(c.STATE.ASSISTIVEMML)}},e}(t)}function d(t){var e;return e=function(t){function e(){for(var e=[],r=0;r0&&o[o.length-1])||6!==i[0]&&2!==i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.HTMLAdaptor=void 0;var s=function(t){function e(e){var r=t.call(this,e.document)||this;return r.window=e,r.parser=new e.DOMParser,r}return o(e,t),e.prototype.parse=function(t,e){return void 0===e&&(e="text/html"),this.parser.parseFromString(t,e)},e.prototype.create=function(t,e){return e?this.document.createElementNS(e,t):this.document.createElement(t)},e.prototype.text=function(t){return this.document.createTextNode(t)},e.prototype.head=function(t){return t.head||t},e.prototype.body=function(t){return t.body||t},e.prototype.root=function(t){return t.documentElement||t},e.prototype.doctype=function(t){return t.doctype?""):""},e.prototype.tags=function(t,e,r){void 0===r&&(r=null);var n=r?t.getElementsByTagNameNS(r,e):t.getElementsByTagName(e);return Array.from(n)},e.prototype.getElements=function(t,e){var r,n,o=[];try{for(var s=i(t),a=s.next();!a.done;a=s.next()){var l=a.value;"string"==typeof l?o=o.concat(Array.from(this.document.querySelectorAll(l))):Array.isArray(l)||l instanceof this.window.NodeList||l instanceof this.window.HTMLCollection?o=o.concat(Array.from(l)):o.push(l)}}catch(t){r={error:t}}finally{try{a&&!a.done&&(n=s.return)&&n.call(s)}finally{if(r)throw r.error}}return o},e.prototype.contains=function(t,e){return t.contains(e)},e.prototype.parent=function(t){return t.parentNode},e.prototype.append=function(t,e){return t.appendChild(e)},e.prototype.insert=function(t,e){return this.parent(e).insertBefore(t,e)},e.prototype.remove=function(t){return this.parent(t).removeChild(t)},e.prototype.replace=function(t,e){return this.parent(e).replaceChild(t,e)},e.prototype.clone=function(t){return t.cloneNode(!0)},e.prototype.split=function(t,e){return t.splitText(e)},e.prototype.next=function(t){return t.nextSibling},e.prototype.previous=function(t){return t.previousSibling},e.prototype.firstChild=function(t){return t.firstChild},e.prototype.lastChild=function(t){return t.lastChild},e.prototype.childNodes=function(t){return Array.from(t.childNodes)},e.prototype.childNode=function(t,e){return t.childNodes[e]},e.prototype.kind=function(t){var e=t.nodeType;return 1===e||3===e||8===e?t.nodeName.toLowerCase():""},e.prototype.value=function(t){return t.nodeValue||""},e.prototype.textContent=function(t){return t.textContent},e.prototype.innerHTML=function(t){return t.innerHTML},e.prototype.outerHTML=function(t){return t.outerHTML},e.prototype.serializeXML=function(t){return(new this.window.XMLSerializer).serializeToString(t)},e.prototype.setAttribute=function(t,e,r,n){return void 0===n&&(n=null),n?(e=n.replace(/.*\//,"")+":"+e.replace(/^.*:/,""),t.setAttributeNS(n,e,r)):t.setAttribute(e,r)},e.prototype.getAttribute=function(t,e){return t.getAttribute(e)},e.prototype.removeAttribute=function(t,e){return t.removeAttribute(e)},e.prototype.hasAttribute=function(t,e){return t.hasAttribute(e)},e.prototype.allAttributes=function(t){return Array.from(t.attributes).map((function(t){return{name:t.name,value:t.value}}))},e.prototype.addClass=function(t,e){t.classList?t.classList.add(e):t.className=(t.className+" "+e).trim()},e.prototype.removeClass=function(t,e){t.classList?t.classList.remove(e):t.className=t.className.split(/ /).filter((function(t){return t!==e})).join(" ")},e.prototype.hasClass=function(t,e){return t.classList?t.classList.contains(e):t.className.split(/ /).indexOf(e)>=0},e.prototype.setStyle=function(t,e,r){t.style[e]=r},e.prototype.getStyle=function(t,e){return t.style[e]},e.prototype.allStyles=function(t){return t.style.cssText},e.prototype.insertRules=function(t,e){var r,n;try{for(var o=i(e.reverse()),s=o.next();!s.done;s=o.next()){var a=s.value;try{t.sheet.insertRule(a,0)}catch(t){console.warn("MathJax: can't insert css rule '".concat(a,"': ").concat(t.message))}}}catch(t){r={error:t}}finally{try{s&&!s.done&&(n=o.return)&&n.call(o)}finally{if(r)throw r.error}}},e.prototype.fontSize=function(t){var e=this.window.getComputedStyle(t);return parseFloat(e.fontSize)},e.prototype.fontFamily=function(t){return this.window.getComputedStyle(t).fontFamily||""},e.prototype.nodeSize=function(t,e,r){if(void 0===e&&(e=1),void 0===r&&(r=!1),r&&t.getBBox){var n=t.getBBox();return[n.width/e,n.height/e]}return[t.offsetWidth/e,t.offsetHeight/e]},e.prototype.nodeBBox=function(t){var e=t.getBoundingClientRect();return{left:e.left,right:e.right,top:e.top,bottom:e.bottom}},e}(r(5009).AbstractDOMAdaptor);e.HTMLAdaptor=s},6191:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.browserAdaptor=void 0;var n=r(444);e.browserAdaptor=function(){return new n.HTMLAdaptor(window)}},9515:function(t,e,r){var n=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.MathJax=e.combineWithMathJax=e.combineDefaults=e.combineConfig=e.isObject=void 0;var o=r(3282);function i(t){return"object"==typeof t&&null!==t}function s(t,e){var r,o;try{for(var a=n(Object.keys(e)),l=a.next();!l.done;l=a.next()){var c=l.value;"__esModule"!==c&&(!i(t[c])||!i(e[c])||e[c]instanceof Promise?null!==e[c]&&void 0!==e[c]&&(t[c]=e[c]):s(t[c],e[c]))}}catch(t){r={error:t}}finally{try{l&&!l.done&&(o=a.return)&&o.call(a)}finally{if(r)throw r.error}}return t}e.isObject=i,e.combineConfig=s,e.combineDefaults=function t(e,r,o){var s,a;e[r]||(e[r]={}),e=e[r];try{for(var l=n(Object.keys(o)),c=l.next();!c.done;c=l.next()){var u=c.value;i(e[u])&&i(o[u])?t(e,u,o[u]):null==e[u]&&null!=o[u]&&(e[u]=o[u])}}catch(t){s={error:t}}finally{try{c&&!c.done&&(a=l.return)&&a.call(l)}finally{if(s)throw s.error}}return e},e.combineWithMathJax=function(t){return s(e.MathJax,t)},void 0===r.g.MathJax&&(r.g.MathJax={}),r.g.MathJax.version||(r.g.MathJax={version:o.VERSION,_:{},config:r.g.MathJax}),e.MathJax=r.g.MathJax},235:function(t,e,r){var n,o,i=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.CONFIG=e.MathJax=e.Loader=e.PathFilters=e.PackageError=e.Package=void 0;var s=r(9515),a=r(265),l=r(265);Object.defineProperty(e,"Package",{enumerable:!0,get:function(){return l.Package}}),Object.defineProperty(e,"PackageError",{enumerable:!0,get:function(){return l.PackageError}});var c,u=r(7525);if(e.PathFilters={source:function(t){return e.CONFIG.source.hasOwnProperty(t.name)&&(t.name=e.CONFIG.source[t.name]),!0},normalize:function(t){var e=t.name;return e.match(/^(?:[a-z]+:\/)?\/|[a-z]:\\|\[/i)||(t.name="[mathjax]/"+e.replace(/^\.\//,"")),t.addExtension&&!e.match(/\.[^\/]+$/)&&(t.name+=".js"),!0},prefix:function(t){for(var r;(r=t.name.match(/^\[([^\]]*)\]/))&&e.CONFIG.paths.hasOwnProperty(r[1]);)t.name=e.CONFIG.paths[r[1]]+t.name.substr(r[0].length);return!0}},function(t){var r=s.MathJax.version;t.versions=new Map,t.ready=function(){for(var t,e,r=[],n=0;n=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},s=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},a=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},i=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},s=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.AbstractDOMAdaptor=void 0;var n=function(){function t(t){void 0===t&&(t=null),this.document=t}return t.prototype.node=function(t,e,n,o){var i,s;void 0===e&&(e={}),void 0===n&&(n=[]);var a=this.create(t,o);this.setAttributes(a,e);try{for(var l=r(n),c=l.next();!c.done;c=l.next()){var u=c.value;this.append(a,u)}}catch(t){i={error:t}}finally{try{c&&!c.done&&(s=l.return)&&s.call(l)}finally{if(i)throw i.error}}return a},t.prototype.setAttributes=function(t,e){var n,o,i,s,a,l;if(e.style&&"string"!=typeof e.style)try{for(var c=r(Object.keys(e.style)),u=c.next();!u.done;u=c.next()){var p=u.value;this.setStyle(t,p.replace(/-([a-z])/g,(function(t,e){return e.toUpperCase()})),e.style[p])}}catch(t){n={error:t}}finally{try{u&&!u.done&&(o=c.return)&&o.call(c)}finally{if(n)throw n.error}}if(e.properties)try{for(var h=r(Object.keys(e.properties)),f=h.next();!f.done;f=h.next()){t[p=f.value]=e.properties[p]}}catch(t){i={error:t}}finally{try{f&&!f.done&&(s=h.return)&&s.call(h)}finally{if(i)throw i.error}}try{for(var d=r(Object.keys(e)),m=d.next();!m.done;m=d.next()){"style"===(p=m.value)&&"string"!=typeof e.style||"properties"===p||this.setAttribute(t,p,e[p])}}catch(t){a={error:t}}finally{try{m&&!m.done&&(l=d.return)&&l.call(d)}finally{if(a)throw a.error}}},t.prototype.replace=function(t,e){return this.insert(t,e),this.remove(e),e},t.prototype.childNode=function(t,e){return this.childNodes(t)[e]},t.prototype.allClasses=function(t){var e=this.getAttribute(t,"class");return e?e.replace(/ +/g," ").replace(/^ /,"").replace(/ $/,"").split(/ /):[]},t}();e.AbstractDOMAdaptor=n},3494:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.AbstractFindMath=void 0;var n=r(7233),o=function(){function t(t){var e=this.constructor;this.options=(0,n.userOptions)((0,n.defaultOptions)({},e.OPTIONS),t)}return t.OPTIONS={},t}();e.AbstractFindMath=o},3670:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.AbstractHandler=void 0;var i=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e}(r(5722).AbstractMathDocument),s=function(){function t(t,e){void 0===e&&(e=5),this.documentClass=i,this.adaptor=t,this.priority=e}return Object.defineProperty(t.prototype,"name",{get:function(){return this.constructor.NAME},enumerable:!1,configurable:!0}),t.prototype.handlesDocument=function(t){return!1},t.prototype.create=function(t,e){return new this.documentClass(t,this.adaptor,e)},t.NAME="generic",t}();e.AbstractHandler=s},805:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.HandlerList=void 0;var s=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.register=function(t){return this.add(t,t.priority)},e.prototype.unregister=function(t){this.remove(t)},e.prototype.handlesDocument=function(t){var e,r;try{for(var n=i(this),o=n.next();!o.done;o=n.next()){var s=o.value.item;if(s.handlesDocument(t))return s}}catch(t){e={error:t}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(e)throw e.error}}throw new Error("Can't find handler for document")},e.prototype.document=function(t,e){return void 0===e&&(e=null),this.handlesDocument(t).create(t,e)},e}(r(8666).PrioritizedList);e.HandlerList=s},9206:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.AbstractInputJax=void 0;var n=r(7233),o=r(7525),i=function(){function t(t){void 0===t&&(t={}),this.adaptor=null,this.mmlFactory=null;var e=this.constructor;this.options=(0,n.userOptions)((0,n.defaultOptions)({},e.OPTIONS),t),this.preFilters=new o.FunctionList,this.postFilters=new o.FunctionList}return Object.defineProperty(t.prototype,"name",{get:function(){return this.constructor.NAME},enumerable:!1,configurable:!0}),t.prototype.setAdaptor=function(t){this.adaptor=t},t.prototype.setMmlFactory=function(t){this.mmlFactory=t},t.prototype.initialize=function(){},t.prototype.reset=function(){for(var t=[],e=0;e=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},s=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},a=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o=e&&a.item.renderDoc(t))return}}catch(t){r={error:t}}finally{try{s&&!s.done&&(n=o.return)&&n.call(o)}finally{if(r)throw r.error}}},e.prototype.renderMath=function(t,e,r){var n,o;void 0===r&&(r=h.STATE.UNPROCESSED);try{for(var s=i(this.items),a=s.next();!a.done;a=s.next()){var l=a.value;if(l.priority>=r&&l.item.renderMath(t,e))return}}catch(t){n={error:t}}finally{try{a&&!a.done&&(o=s.return)&&o.call(s)}finally{if(n)throw n.error}}},e.prototype.renderConvert=function(t,e,r){var n,o;void 0===r&&(r=h.STATE.LAST);try{for(var s=i(this.items),a=s.next();!a.done;a=s.next()){var l=a.value;if(l.priority>r)return;if(l.item.convert&&l.item.renderMath(t,e))return}}catch(t){n={error:t}}finally{try{a&&!a.done&&(o=s.return)&&o.call(s)}finally{if(n)throw n.error}}},e.prototype.findID=function(t){var e,r;try{for(var n=i(this.items),o=n.next();!o.done;o=n.next()){var s=o.value;if(s.item.id===t)return s.item}}catch(t){e={error:t}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(e)throw e.error}}return null},e}(r(8666).PrioritizedList);e.RenderList=m,e.resetOptions={all:!1,processed:!1,inputJax:null,outputJax:null},e.resetAllOptions={all:!0,processed:!0,inputJax:[],outputJax:[]};var y=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.compile=function(t){return null},e}(c.AbstractInputJax),g=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.typeset=function(t,e){return void 0===e&&(e=null),null},e.prototype.escaped=function(t,e){return null},e}(u.AbstractOutputJax),b=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e}(p.AbstractMathList),v=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e}(h.AbstractMathItem),_=function(){function t(e,r,n){var o=this,i=this.constructor;this.document=e,this.options=(0,l.userOptions)((0,l.defaultOptions)({},i.OPTIONS),n),this.math=new(this.options.MathList||b),this.renderActions=m.create(this.options.renderActions),this.processed=new t.ProcessBits,this.outputJax=this.options.OutputJax||new g;var s=this.options.InputJax||[new y];Array.isArray(s)||(s=[s]),this.inputJax=s,this.adaptor=r,this.outputJax.setAdaptor(r),this.inputJax.map((function(t){return t.setAdaptor(r)})),this.mmlFactory=this.options.MmlFactory||new f.MmlFactory,this.inputJax.map((function(t){return t.setMmlFactory(o.mmlFactory)})),this.outputJax.initialize(),this.inputJax.map((function(t){return t.initialize()}))}return Object.defineProperty(t.prototype,"kind",{get:function(){return this.constructor.KIND},enumerable:!1,configurable:!0}),t.prototype.addRenderAction=function(t){for(var e=[],r=1;r=r&&this.state(r-1),t.renderActions.renderMath(this,t,r)},t.prototype.convert=function(t,r){void 0===r&&(r=e.STATE.LAST),t.renderActions.renderConvert(this,t,r)},t.prototype.compile=function(t){this.state()=e.STATE.INSERTED&&this.removeFromDocument(r),t=e.STATE.TYPESET&&(this.outputData={}),t=e.STATE.COMPILED&&(this.inputData={}),this._state=t),this._state},t.prototype.reset=function(t){void 0===t&&(t=!1),this.state(e.STATE.UNPROCESSED,t)},t}();e.AbstractMathItem=r,e.STATE={UNPROCESSED:0,FINDMATH:10,COMPILED:20,CONVERT:100,METRICS:110,RERENDER:125,TYPESET:150,INSERTED:200,LAST:1e4},e.newState=function(t,r){if(t in e.STATE)throw Error("State "+t+" already exists");e.STATE[t]=r}},9e3:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.AbstractMathList=void 0;var i=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.isBefore=function(t,e){return t.start.i=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.Attributes=e.INHERIT=void 0,e.INHERIT="_inherit_";var n=function(){function t(t,e){this.global=e,this.defaults=Object.create(e),this.inherited=Object.create(this.defaults),this.attributes=Object.create(this.inherited),Object.assign(this.defaults,t)}return t.prototype.set=function(t,e){this.attributes[t]=e},t.prototype.setList=function(t){Object.assign(this.attributes,t)},t.prototype.get=function(t){var r=this.attributes[t];return r===e.INHERIT&&(r=this.global[t]),r},t.prototype.getExplicit=function(t){if(this.attributes.hasOwnProperty(t))return this.attributes[t]},t.prototype.getList=function(){for(var t,e,n=[],o=0;o=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.MathMLVisitor=void 0;var s=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.document=null,e}return o(e,t),e.prototype.visitTree=function(t,e){this.document=e;var r=e.createElement("top");return this.visitNode(t,r),this.document=null,r.firstChild},e.prototype.visitTextNode=function(t,e){e.appendChild(this.document.createTextNode(t.getText()))},e.prototype.visitXMLNode=function(t,e){e.appendChild(t.getXML().cloneNode(!0))},e.prototype.visitInferredMrowNode=function(t,e){var r,n;try{for(var o=i(t.childNodes),s=o.next();!s.done;s=o.next()){var a=s.value;this.visitNode(a,e)}}catch(t){r={error:t}}finally{try{s&&!s.done&&(n=o.return)&&n.call(o)}finally{if(r)throw r.error}}},e.prototype.visitDefault=function(t,e){var r,n,o=this.document.createElement(t.kind);this.addAttributes(t,o);try{for(var s=i(t.childNodes),a=s.next();!a.done;a=s.next()){var l=a.value;this.visitNode(l,o)}}catch(t){r={error:t}}finally{try{a&&!a.done&&(n=s.return)&&n.call(s)}finally{if(r)throw r.error}}e.appendChild(o)},e.prototype.addAttributes=function(t,e){var r,n,o=t.attributes,s=o.getExplicitNames();try{for(var a=i(s),l=a.next();!l.done;l=a.next()){var c=l.value;e.setAttribute(c,o.getExplicit(c).toString())}}catch(t){r={error:t}}finally{try{l&&!l.done&&(n=a.return)&&n.call(a)}finally{if(r)throw r.error}}},e}(r(6325).MmlVisitor);e.MathMLVisitor=s},3909:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.MmlFactory=void 0;var i=r(7860),s=r(6336),a=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),Object.defineProperty(e.prototype,"MML",{get:function(){return this.node},enumerable:!1,configurable:!0}),e.defaultNodes=s.MML,e}(i.AbstractNodeFactory);e.MmlFactory=a},9007:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__assign||function(){return i=Object.assign||function(t){for(var e,r=1,n=arguments.length;r=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},a=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s};Object.defineProperty(e,"__esModule",{value:!0}),e.XMLNode=e.TextNode=e.AbstractMmlEmptyNode=e.AbstractMmlBaseNode=e.AbstractMmlLayoutNode=e.AbstractMmlTokenNode=e.AbstractMmlNode=e.indentAttributes=e.TEXCLASSNAMES=e.TEXCLASS=void 0;var l=r(91),c=r(4596);e.TEXCLASS={ORD:0,OP:1,BIN:2,REL:3,OPEN:4,CLOSE:5,PUNCT:6,INNER:7,VCENTER:8,NONE:-1},e.TEXCLASSNAMES=["ORD","OP","BIN","REL","OPEN","CLOSE","PUNCT","INNER","VCENTER"];var u=["","thinmathspace","mediummathspace","thickmathspace"],p=[[0,-1,2,3,0,0,0,1],[-1,-1,0,3,0,0,0,1],[2,2,0,0,2,0,0,2],[3,3,0,0,3,0,0,3],[0,0,0,0,0,0,0,0],[0,-1,2,3,0,0,0,1],[1,1,0,1,1,1,1,1],[1,-1,2,3,1,0,1,1]];e.indentAttributes=["indentalign","indentalignfirst","indentshift","indentshiftfirst"];var h=function(t){function r(e,r,n){void 0===r&&(r={}),void 0===n&&(n=[]);var o=t.call(this,e)||this;return o.prevClass=null,o.prevLevel=null,o.texclass=null,o.arity<0&&(o.childNodes=[e.create("inferredMrow")],o.childNodes[0].parent=o),o.setChildren(n),o.attributes=new l.Attributes(e.getNodeClass(o.kind).defaults,e.getNodeClass("math").defaults),o.attributes.setList(r),o}return o(r,t),r.prototype.copy=function(t){var e,r,n,o;void 0===t&&(t=!1);var a=this.factory.create(this.kind);if(a.properties=i({},this.properties),this.attributes){var l=this.attributes.getAllAttributes();try{for(var c=s(Object.keys(l)),u=c.next();!u.done;u=c.next()){var p=u.value;("id"!==p||t)&&a.attributes.set(p,l[p])}}catch(t){e={error:t}}finally{try{u&&!u.done&&(r=c.return)&&r.call(c)}finally{if(e)throw e.error}}}if(this.childNodes&&this.childNodes.length){var h=this.childNodes;1===h.length&&h[0].isInferred&&(h=h[0].childNodes);try{for(var f=s(h),d=f.next();!d.done;d=f.next()){var m=d.value;m?a.appendChild(m.copy()):a.childNodes.push(null)}}catch(t){n={error:t}}finally{try{d&&!d.done&&(o=f.return)&&o.call(f)}finally{if(n)throw n.error}}}return a},Object.defineProperty(r.prototype,"texClass",{get:function(){return this.texclass},set:function(t){this.texclass=t},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"isToken",{get:function(){return!1},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"isEmbellished",{get:function(){return!1},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"isSpacelike",{get:function(){return!1},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"linebreakContainer",{get:function(){return!1},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"hasNewLine",{get:function(){return!1},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"arity",{get:function(){return 1/0},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"isInferred",{get:function(){return!1},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"Parent",{get:function(){for(var t=this.parent;t&&t.notParent;)t=t.Parent;return t},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"notParent",{get:function(){return!1},enumerable:!1,configurable:!0}),r.prototype.setChildren=function(e){return this.arity<0?this.childNodes[0].setChildren(e):t.prototype.setChildren.call(this,e)},r.prototype.appendChild=function(e){var r,n,o=this;if(this.arity<0)return this.childNodes[0].appendChild(e),e;if(e.isInferred){if(this.arity===1/0)return e.childNodes.forEach((function(e){return t.prototype.appendChild.call(o,e)})),e;var i=e;(e=this.factory.create("mrow")).setChildren(i.childNodes),e.attributes=i.attributes;try{for(var a=s(i.getPropertyNames()),l=a.next();!l.done;l=a.next()){var c=l.value;e.setProperty(c,i.getProperty(c))}}catch(t){r={error:t}}finally{try{l&&!l.done&&(n=a.return)&&n.call(a)}finally{if(r)throw r.error}}}return t.prototype.appendChild.call(this,e)},r.prototype.replaceChild=function(e,r){return this.arity<0?(this.childNodes[0].replaceChild(e,r),e):t.prototype.replaceChild.call(this,e,r)},r.prototype.core=function(){return this},r.prototype.coreMO=function(){return this},r.prototype.coreIndex=function(){return 0},r.prototype.childPosition=function(){for(var t,e,r=this,n=r.parent;n&&n.notParent;)r=n,n=n.parent;if(n){var o=0;try{for(var i=s(n.childNodes),a=i.next();!a.done;a=i.next()){if(a.value===r)return o;o++}}catch(e){t={error:e}}finally{try{a&&!a.done&&(e=i.return)&&e.call(i)}finally{if(t)throw t.error}}}return null},r.prototype.setTeXclass=function(t){return this.getPrevClass(t),null!=this.texClass?this:t},r.prototype.updateTeXclass=function(t){t&&(this.prevClass=t.prevClass,this.prevLevel=t.prevLevel,t.prevClass=t.prevLevel=null,this.texClass=t.texClass)},r.prototype.getPrevClass=function(t){t&&(this.prevClass=t.texClass,this.prevLevel=t.attributes.get("scriptlevel"))},r.prototype.texSpacing=function(){var t=null!=this.prevClass?this.prevClass:e.TEXCLASS.NONE,r=this.texClass||e.TEXCLASS.ORD;if(t===e.TEXCLASS.NONE||r===e.TEXCLASS.NONE)return"";t===e.TEXCLASS.VCENTER&&(t=e.TEXCLASS.ORD),r===e.TEXCLASS.VCENTER&&(r=e.TEXCLASS.ORD);var n=p[t][r];return(this.prevLevel>0||this.attributes.get("scriptlevel")>0)&&n>=0?"":u[Math.abs(n)]},r.prototype.hasSpacingAttributes=function(){return this.isEmbellished&&this.coreMO().hasSpacingAttributes()},r.prototype.setInheritedAttributes=function(t,e,n,o){var i,l;void 0===t&&(t={}),void 0===e&&(e=!1),void 0===n&&(n=0),void 0===o&&(o=!1);var c=this.attributes.getAllDefaults();try{for(var u=s(Object.keys(t)),p=u.next();!p.done;p=u.next()){var h=p.value;if(c.hasOwnProperty(h)||r.alwaysInherit.hasOwnProperty(h)){var f=a(t[h],2),d=f[0],m=f[1];((r.noInherit[d]||{})[this.kind]||{})[h]||this.attributes.setInherited(h,m)}}}catch(t){i={error:t}}finally{try{p&&!p.done&&(l=u.return)&&l.call(u)}finally{if(i)throw i.error}}void 0===this.attributes.getExplicit("displaystyle")&&this.attributes.setInherited("displaystyle",e),void 0===this.attributes.getExplicit("scriptlevel")&&this.attributes.setInherited("scriptlevel",n),o&&this.setProperty("texprimestyle",o);var y=this.arity;if(y>=0&&y!==1/0&&(1===y&&0===this.childNodes.length||1!==y&&this.childNodes.length!==y))if(y=0&&e!==1/0&&(1===e&&0===this.childNodes.length||1!==e&&this.childNodes.length!==e)&&this.mError('Wrong number of children for "'+this.kind+'" node',t,!0),this.verifyChildren(t)}},r.prototype.verifyAttributes=function(t){var e,r;if(t.checkAttributes){var n=this.attributes,o=[];try{for(var i=s(n.getExplicitNames()),a=i.next();!a.done;a=i.next()){var l=a.value;"data-"===l.substr(0,5)||void 0!==n.getDefault(l)||l.match(/^(?:class|style|id|(?:xlink:)?href)$/)||o.push(l)}}catch(t){e={error:t}}finally{try{a&&!a.done&&(r=i.return)&&r.call(i)}finally{if(e)throw e.error}}o.length&&this.mError("Unknown attributes for "+this.kind+" node: "+o.join(", "),t)}},r.prototype.verifyChildren=function(t){var e,r;try{for(var n=s(this.childNodes),o=n.next();!o.done;o=n.next()){o.value.verifyTree(t)}}catch(t){e={error:t}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(e)throw e.error}}},r.prototype.mError=function(t,e,r){if(void 0===r&&(r=!1),this.parent&&this.parent.isKind("merror"))return null;var n=this.factory.create("merror");if(n.attributes.set("data-mjx-message",t),e.fullErrors||r){var o=this.factory.create("mtext"),i=this.factory.create("text");i.setText(e.fullErrors?t:this.kind),o.appendChild(i),n.appendChild(o),this.parent.replaceChild(n,this)}else this.parent.replaceChild(n,this),n.appendChild(this);return n},r.defaults={mathbackground:l.INHERIT,mathcolor:l.INHERIT,mathsize:l.INHERIT,dir:l.INHERIT},r.noInherit={mstyle:{mpadded:{width:!0,height:!0,depth:!0,lspace:!0,voffset:!0},mtable:{width:!0,height:!0,depth:!0,align:!0}},maligngroup:{mrow:{groupalign:!0},mtable:{groupalign:!0}}},r.alwaysInherit={scriptminsize:!0,scriptsizemultiplier:!0},r.verifyDefaults={checkArity:!0,checkAttributes:!1,fullErrors:!1,fixMmultiscripts:!0,fixMtables:!0},r}(c.AbstractNode);e.AbstractMmlNode=h;var f=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),Object.defineProperty(e.prototype,"isToken",{get:function(){return!0},enumerable:!1,configurable:!0}),e.prototype.getText=function(){var t,e,r="";try{for(var n=s(this.childNodes),o=n.next();!o.done;o=n.next()){var i=o.value;i instanceof g&&(r+=i.getText())}}catch(e){t={error:e}}finally{try{o&&!o.done&&(e=n.return)&&e.call(n)}finally{if(t)throw t.error}}return r},e.prototype.setChildInheritedAttributes=function(t,e,r,n){var o,i;try{for(var a=s(this.childNodes),l=a.next();!l.done;l=a.next()){var c=l.value;c instanceof h&&c.setInheritedAttributes(t,e,r,n)}}catch(t){o={error:t}}finally{try{l&&!l.done&&(i=a.return)&&i.call(a)}finally{if(o)throw o.error}}},e.prototype.walkTree=function(t,e){var r,n;t(this,e);try{for(var o=s(this.childNodes),i=o.next();!i.done;i=o.next()){var a=i.value;a instanceof h&&a.walkTree(t,e)}}catch(t){r={error:t}}finally{try{i&&!i.done&&(n=o.return)&&n.call(o)}finally{if(r)throw r.error}}return e},e.defaults=i(i({},h.defaults),{mathvariant:"normal",mathsize:l.INHERIT}),e}(h);e.AbstractMmlTokenNode=f;var d=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),Object.defineProperty(e.prototype,"isSpacelike",{get:function(){return this.childNodes[0].isSpacelike},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"isEmbellished",{get:function(){return this.childNodes[0].isEmbellished},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"arity",{get:function(){return-1},enumerable:!1,configurable:!0}),e.prototype.core=function(){return this.childNodes[0]},e.prototype.coreMO=function(){return this.childNodes[0].coreMO()},e.prototype.setTeXclass=function(t){return t=this.childNodes[0].setTeXclass(t),this.updateTeXclass(this.childNodes[0]),t},e.defaults=h.defaults,e}(h);e.AbstractMmlLayoutNode=d;var m=function(t){function r(){return null!==t&&t.apply(this,arguments)||this}return o(r,t),Object.defineProperty(r.prototype,"isEmbellished",{get:function(){return this.childNodes[0].isEmbellished},enumerable:!1,configurable:!0}),r.prototype.core=function(){return this.childNodes[0]},r.prototype.coreMO=function(){return this.childNodes[0].coreMO()},r.prototype.setTeXclass=function(t){var r,n;this.getPrevClass(t),this.texClass=e.TEXCLASS.ORD;var o=this.childNodes[0];o?this.isEmbellished||o.isKind("mi")?(t=o.setTeXclass(t),this.updateTeXclass(this.core())):(o.setTeXclass(null),t=this):t=this;try{for(var i=s(this.childNodes.slice(1)),a=i.next();!a.done;a=i.next()){var l=a.value;l&&l.setTeXclass(null)}}catch(t){r={error:t}}finally{try{a&&!a.done&&(n=i.return)&&n.call(i)}finally{if(r)throw r.error}}return t},r.defaults=h.defaults,r}(h);e.AbstractMmlBaseNode=m;var y=function(t){function r(){return null!==t&&t.apply(this,arguments)||this}return o(r,t),Object.defineProperty(r.prototype,"isToken",{get:function(){return!1},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"isEmbellished",{get:function(){return!1},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"isSpacelike",{get:function(){return!1},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"linebreakContainer",{get:function(){return!1},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"hasNewLine",{get:function(){return!1},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"arity",{get:function(){return 0},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"isInferred",{get:function(){return!1},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"notParent",{get:function(){return!1},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"Parent",{get:function(){return this.parent},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"texClass",{get:function(){return e.TEXCLASS.NONE},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"prevClass",{get:function(){return e.TEXCLASS.NONE},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"prevLevel",{get:function(){return 0},enumerable:!1,configurable:!0}),r.prototype.hasSpacingAttributes=function(){return!1},Object.defineProperty(r.prototype,"attributes",{get:function(){return null},enumerable:!1,configurable:!0}),r.prototype.core=function(){return this},r.prototype.coreMO=function(){return this},r.prototype.coreIndex=function(){return 0},r.prototype.childPosition=function(){return 0},r.prototype.setTeXclass=function(t){return t},r.prototype.texSpacing=function(){return""},r.prototype.setInheritedAttributes=function(t,e,r,n){},r.prototype.inheritAttributesFrom=function(t){},r.prototype.verifyTree=function(t){},r.prototype.mError=function(t,e,r){return void 0===r&&(r=!1),null},r}(c.AbstractEmptyNode);e.AbstractMmlEmptyNode=y;var g=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.text="",e}return o(e,t),Object.defineProperty(e.prototype,"kind",{get:function(){return"text"},enumerable:!1,configurable:!0}),e.prototype.getText=function(){return this.text},e.prototype.setText=function(t){return this.text=t,this},e.prototype.copy=function(){return this.factory.create(this.kind).setText(this.getText())},e.prototype.toString=function(){return this.text},e}(y);e.TextNode=g;var b=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.xml=null,e.adaptor=null,e}return o(e,t),Object.defineProperty(e.prototype,"kind",{get:function(){return"XML"},enumerable:!1,configurable:!0}),e.prototype.getXML=function(){return this.xml},e.prototype.setXML=function(t,e){return void 0===e&&(e=null),this.xml=t,this.adaptor=e,this},e.prototype.getSerializedXML=function(){return this.adaptor.serializeXML(this.xml)},e.prototype.copy=function(){return this.factory.create(this.kind).setXML(this.adaptor.clone(this.xml))},e.prototype.toString=function(){return"XML data"},e}(y);e.XMLNode=b},3948:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__assign||function(){return i=Object.assign||function(t){for(var e,r=1,n=arguments.length;rthis.childNodes.length&&(t=1),this.attributes.set("selection",t)},e.defaults=i(i({},s.AbstractMmlNode.defaults),{actiontype:"toggle",selection:1}),e}(s.AbstractMmlNode);e.MmlMaction=a},142:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__assign||function(){return i=Object.assign||function(t){for(var e,r=1,n=arguments.length;r=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.MmlMfenced=void 0;var a=r(9007),l=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.texclass=a.TEXCLASS.INNER,e.separators=[],e.open=null,e.close=null,e}return o(e,t),Object.defineProperty(e.prototype,"kind",{get:function(){return"mfenced"},enumerable:!1,configurable:!0}),e.prototype.setTeXclass=function(t){this.getPrevClass(t),this.open&&(t=this.open.setTeXclass(t)),this.childNodes[0]&&(t=this.childNodes[0].setTeXclass(t));for(var e=1,r=this.childNodes.length;e=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.MmlMfrac=void 0;var a=r(9007),l=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),Object.defineProperty(e.prototype,"kind",{get:function(){return"mfrac"},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"arity",{get:function(){return 2},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"linebreakContainer",{get:function(){return!0},enumerable:!1,configurable:!0}),e.prototype.setTeXclass=function(t){var e,r;this.getPrevClass(t);try{for(var n=s(this.childNodes),o=n.next();!o.done;o=n.next()){o.value.setTeXclass(null)}}catch(t){e={error:t}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(e)throw e.error}}return this},e.prototype.setChildInheritedAttributes=function(t,e,r,n){(!e||r>0)&&r++,this.childNodes[0].setInheritedAttributes(t,!1,r,n),this.childNodes[1].setInheritedAttributes(t,!1,r,!0)},e.defaults=i(i({},a.AbstractMmlBaseNode.defaults),{linethickness:"medium",numalign:"center",denomalign:"center",bevelled:!1}),e}(a.AbstractMmlBaseNode);e.MmlMfrac=l},3985:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__assign||function(){return i=Object.assign||function(t){for(var e,r=1,n=arguments.length;r1&&r.match(e.operatorName)&&"normal"===this.attributes.get("mathvariant")&&void 0===this.getProperty("autoOP")&&void 0===this.getProperty("texClass")&&(this.texClass=s.TEXCLASS.OP,this.setProperty("autoOP",!0)),this},e.defaults=i({},s.AbstractMmlTokenNode.defaults),e.operatorName=/^[a-z][a-z0-9]*$/i,e.singleCharacter=/^[\uD800-\uDBFF]?.[\u0300-\u036F\u1AB0-\u1ABE\u1DC0-\u1DFF\u20D0-\u20EF]*$/,e}(s.AbstractMmlTokenNode);e.MmlMi=a},6405:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__assign||function(){return i=Object.assign||function(t){for(var e,r=1,n=arguments.length;r0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},a=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.MmlMo=void 0;var l=r(9007),c=r(4082),u=r(505),p=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e._texClass=null,e.lspace=5/18,e.rspace=5/18,e}return o(e,t),Object.defineProperty(e.prototype,"texClass",{get:function(){if(null===this._texClass){var t=this.getText(),e=s(this.handleExplicitForm(this.getForms()),3),r=e[0],n=e[1],o=e[2],i=this.constructor.OPTABLE,a=i[r][t]||i[n][t]||i[o][t];return a?a[2]:l.TEXCLASS.REL}return this._texClass},set:function(t){this._texClass=t},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"kind",{get:function(){return"mo"},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"isEmbellished",{get:function(){return!0},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"hasNewLine",{get:function(){return"newline"===this.attributes.get("linebreak")},enumerable:!1,configurable:!0}),e.prototype.coreParent=function(){for(var t=this,e=this,r=this.factory.getNodeClass("math");e&&e.isEmbellished&&e.coreMO()===this&&!(e instanceof r);)t=e,e=e.parent;return t},e.prototype.coreText=function(t){if(!t)return"";if(t.isEmbellished)return t.coreMO().getText();for(;((t.isKind("mrow")||t.isKind("TeXAtom")&&t.texClass!==l.TEXCLASS.VCENTER||t.isKind("mstyle")||t.isKind("mphantom"))&&1===t.childNodes.length||t.isKind("munderover"))&&t.childNodes[0];)t=t.childNodes[0];return t.isToken?t.getText():""},e.prototype.hasSpacingAttributes=function(){return this.attributes.isSet("lspace")||this.attributes.isSet("rspace")},Object.defineProperty(e.prototype,"isAccent",{get:function(){var t=!1,e=this.coreParent().parent;if(e){var r=e.isKind("mover")?e.childNodes[e.over].coreMO()?"accent":"":e.isKind("munder")?e.childNodes[e.under].coreMO()?"accentunder":"":e.isKind("munderover")?this===e.childNodes[e.over].coreMO()?"accent":this===e.childNodes[e.under].coreMO()?"accentunder":"":"";if(r)t=void 0!==e.attributes.getExplicit(r)?t:this.attributes.get("accent")}return t},enumerable:!1,configurable:!0}),e.prototype.setTeXclass=function(t){var e=this.attributes.getList("form","fence"),r=e.form,n=e.fence;return void 0===this.getProperty("texClass")&&(this.attributes.isSet("lspace")||this.attributes.isSet("rspace"))?null:(n&&this.texClass===l.TEXCLASS.REL&&("prefix"===r&&(this.texClass=l.TEXCLASS.OPEN),"postfix"===r&&(this.texClass=l.TEXCLASS.CLOSE)),this.adjustTeXclass(t))},e.prototype.adjustTeXclass=function(t){var e=this.texClass,r=this.prevClass;if(e===l.TEXCLASS.NONE)return t;if(t?(!t.getProperty("autoOP")||e!==l.TEXCLASS.BIN&&e!==l.TEXCLASS.REL||(r=t.texClass=l.TEXCLASS.ORD),r=this.prevClass=t.texClass||l.TEXCLASS.ORD,this.prevLevel=this.attributes.getInherited("scriptlevel")):r=this.prevClass=l.TEXCLASS.NONE,e!==l.TEXCLASS.BIN||r!==l.TEXCLASS.NONE&&r!==l.TEXCLASS.BIN&&r!==l.TEXCLASS.OP&&r!==l.TEXCLASS.REL&&r!==l.TEXCLASS.OPEN&&r!==l.TEXCLASS.PUNCT)if(r!==l.TEXCLASS.BIN||e!==l.TEXCLASS.REL&&e!==l.TEXCLASS.CLOSE&&e!==l.TEXCLASS.PUNCT){if(e===l.TEXCLASS.BIN){for(var n=this,o=this.parent;o&&o.parent&&o.isEmbellished&&(1===o.childNodes.length||!o.isKind("mrow")&&o.core()===n);)n=o,o=o.parent;o.childNodes[o.childNodes.length-1]===n&&(this.texClass=l.TEXCLASS.ORD)}}else t.texClass=this.prevClass=l.TEXCLASS.ORD;else this.texClass=l.TEXCLASS.ORD;return this},e.prototype.setInheritedAttributes=function(e,r,n,o){void 0===e&&(e={}),void 0===r&&(r=!1),void 0===n&&(n=0),void 0===o&&(o=!1),t.prototype.setInheritedAttributes.call(this,e,r,n,o);var i=this.getText();this.checkOperatorTable(i),this.checkPseudoScripts(i),this.checkPrimes(i),this.checkMathAccent(i)},e.prototype.checkOperatorTable=function(t){var e,r,n=s(this.handleExplicitForm(this.getForms()),3),o=n[0],i=n[1],l=n[2];this.attributes.setInherited("form",o);var u=this.constructor.OPTABLE,p=u[o][t]||u[i][t]||u[l][t];if(p){void 0===this.getProperty("texClass")&&(this.texClass=p[2]);try{for(var h=a(Object.keys(p[3]||{})),f=h.next();!f.done;f=h.next()){var d=f.value;this.attributes.setInherited(d,p[3][d])}}catch(t){e={error:t}}finally{try{f&&!f.done&&(r=h.return)&&r.call(h)}finally{if(e)throw e.error}}this.lspace=(p[0]+1)/18,this.rspace=(p[1]+1)/18}else{var m=(0,c.getRange)(t);if(m){void 0===this.getProperty("texClass")&&(this.texClass=m[2]);var y=this.constructor.MMLSPACING[m[2]];this.lspace=(y[0]+1)/18,this.rspace=(y[1]+1)/18}}},e.prototype.getForms=function(){for(var t=this,e=this.parent,r=this.Parent;r&&r.isEmbellished;)t=e,e=r.parent,r=r.Parent;if(e&&e.isKind("mrow")&&1!==e.nonSpaceLength()){if(e.firstNonSpace()===t)return["prefix","infix","postfix"];if(e.lastNonSpace()===t)return["postfix","infix","prefix"]}return["infix","prefix","postfix"]},e.prototype.handleExplicitForm=function(t){if(this.attributes.isSet("form")){var e=this.attributes.get("form");t=[e].concat(t.filter((function(t){return t!==e})))}return t},e.prototype.checkPseudoScripts=function(t){var e=this.constructor.pseudoScripts;if(t.match(e)){var r=this.coreParent().Parent,n=!r||!(r.isKind("msubsup")&&!r.isKind("msub"));this.setProperty("pseudoscript",n),n&&(this.attributes.setInherited("lspace",0),this.attributes.setInherited("rspace",0))}},e.prototype.checkPrimes=function(t){var e=this.constructor.primes;if(t.match(e)){var r=this.constructor.remapPrimes,n=(0,u.unicodeString)((0,u.unicodeChars)(t).map((function(t){return r[t]})));this.setProperty("primes",n)}},e.prototype.checkMathAccent=function(t){var e=this.Parent;if(void 0===this.getProperty("mathaccent")&&e&&e.isKind("munderover")){var r=e.childNodes[0];if(!r.isEmbellished||r.coreMO()!==this){var n=this.constructor.mathaccents;t.match(n)&&this.setProperty("mathaccent",!0)}}},e.defaults=i(i({},l.AbstractMmlTokenNode.defaults),{form:"infix",fence:!1,separator:!1,lspace:"thickmathspace",rspace:"thickmathspace",stretchy:!1,symmetric:!1,maxsize:"infinity",minsize:"0em",largeop:!1,movablelimits:!1,accent:!1,linebreak:"auto",lineleading:"1ex",linebreakstyle:"before",indentalign:"auto",indentshift:"0",indenttarget:"",indentalignfirst:"indentalign",indentshiftfirst:"indentshift",indentalignlast:"indentalign",indentshiftlast:"indentshift"}),e.MMLSPACING=c.MMLSPACING,e.OPTABLE=c.OPTABLE,e.pseudoScripts=new RegExp(["^[\"'*`","\xaa","\xb0","\xb2-\xb4","\xb9","\xba","\u2018-\u201f","\u2032-\u2037\u2057","\u2070\u2071","\u2074-\u207f","\u2080-\u208e","]+$"].join("")),e.primes=new RegExp(["^[\"'`","\u2018-\u201f","]+$"].join("")),e.remapPrimes={34:8243,39:8242,96:8245,8216:8245,8217:8242,8218:8242,8219:8245,8220:8246,8221:8243,8222:8243,8223:8246},e.mathaccents=new RegExp(["^[","\xb4\u0301\u02ca","`\u0300\u02cb","\xa8\u0308","~\u0303\u02dc","\xaf\u0304\u02c9","\u02d8\u0306","\u02c7\u030c","^\u0302\u02c6","\u2192\u20d7","\u02d9\u0307","\u02da\u030a","\u20db","\u20dc","]$"].join("")),e}(l.AbstractMmlTokenNode);e.MmlMo=p},7238:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__assign||function(){return i=Object.assign||function(t){for(var e,r=1,n=arguments.length;r=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.MmlInferredMrow=e.MmlMrow=void 0;var a=r(9007),l=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e._core=null,e}return o(e,t),Object.defineProperty(e.prototype,"kind",{get:function(){return"mrow"},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"isSpacelike",{get:function(){var t,e;try{for(var r=s(this.childNodes),n=r.next();!n.done;n=r.next()){if(!n.value.isSpacelike)return!1}}catch(e){t={error:e}}finally{try{n&&!n.done&&(e=r.return)&&e.call(r)}finally{if(t)throw t.error}}return!0},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"isEmbellished",{get:function(){var t,e,r=!1,n=0;try{for(var o=s(this.childNodes),i=o.next();!i.done;i=o.next()){var a=i.value;if(a)if(a.isEmbellished){if(r)return!1;r=!0,this._core=n}else if(!a.isSpacelike)return!1;n++}}catch(e){t={error:e}}finally{try{i&&!i.done&&(e=o.return)&&e.call(o)}finally{if(t)throw t.error}}return r},enumerable:!1,configurable:!0}),e.prototype.core=function(){return this.isEmbellished&&null!=this._core?this.childNodes[this._core]:this},e.prototype.coreMO=function(){return this.isEmbellished&&null!=this._core?this.childNodes[this._core].coreMO():this},e.prototype.nonSpaceLength=function(){var t,e,r=0;try{for(var n=s(this.childNodes),o=n.next();!o.done;o=n.next()){var i=o.value;i&&!i.isSpacelike&&r++}}catch(e){t={error:e}}finally{try{o&&!o.done&&(e=n.return)&&e.call(n)}finally{if(t)throw t.error}}return r},e.prototype.firstNonSpace=function(){var t,e;try{for(var r=s(this.childNodes),n=r.next();!n.done;n=r.next()){var o=n.value;if(o&&!o.isSpacelike)return o}}catch(e){t={error:e}}finally{try{n&&!n.done&&(e=r.return)&&e.call(r)}finally{if(t)throw t.error}}return null},e.prototype.lastNonSpace=function(){for(var t=this.childNodes.length;--t>=0;){var e=this.childNodes[t];if(e&&!e.isSpacelike)return e}return null},e.prototype.setTeXclass=function(t){var e,r,n,o;if(null!=this.getProperty("open")||null!=this.getProperty("close")){this.getPrevClass(t),t=null;try{for(var i=s(this.childNodes),l=i.next();!l.done;l=i.next()){t=l.value.setTeXclass(t)}}catch(t){e={error:t}}finally{try{l&&!l.done&&(r=i.return)&&r.call(i)}finally{if(e)throw e.error}}null==this.texClass&&(this.texClass=a.TEXCLASS.INNER)}else{try{for(var c=s(this.childNodes),u=c.next();!u.done;u=c.next()){t=u.value.setTeXclass(t)}}catch(t){n={error:t}}finally{try{u&&!u.done&&(o=c.return)&&o.call(c)}finally{if(n)throw n.error}}this.childNodes[0]&&this.updateTeXclass(this.childNodes[0])}return t},e.defaults=i({},a.AbstractMmlNode.defaults),e}(a.AbstractMmlNode);e.MmlMrow=l;var c=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),Object.defineProperty(e.prototype,"kind",{get:function(){return"inferredMrow"},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"isInferred",{get:function(){return!0},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"notParent",{get:function(){return!0},enumerable:!1,configurable:!0}),e.prototype.toString=function(){return"["+this.childNodes.join(",")+"]"},e.defaults=l.defaults,e}(l);e.MmlInferredMrow=c},7265:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__assign||function(){return i=Object.assign||function(t){for(var e,r=1,n=arguments.length;r=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.MmlMtable=void 0;var a=r(9007),l=r(505),c=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.properties={useHeight:!0},e.texclass=a.TEXCLASS.ORD,e}return o(e,t),Object.defineProperty(e.prototype,"kind",{get:function(){return"mtable"},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"linebreakContainer",{get:function(){return!0},enumerable:!1,configurable:!0}),e.prototype.setInheritedAttributes=function(e,r,n,o){var i,l;try{for(var c=s(a.indentAttributes),u=c.next();!u.done;u=c.next()){var p=u.value;e[p]&&this.attributes.setInherited(p,e[p][1]),void 0!==this.attributes.getExplicit(p)&&delete this.attributes.getAllAttributes()[p]}}catch(t){i={error:t}}finally{try{u&&!u.done&&(l=c.return)&&l.call(c)}finally{if(i)throw i.error}}t.prototype.setInheritedAttributes.call(this,e,r,n,o)},e.prototype.setChildInheritedAttributes=function(t,e,r,n){var o,i,a,c;try{for(var u=s(this.childNodes),p=u.next();!p.done;p=u.next()){(y=p.value).isKind("mtr")||this.replaceChild(this.factory.create("mtr"),y).appendChild(y)}}catch(t){o={error:t}}finally{try{p&&!p.done&&(i=u.return)&&i.call(u)}finally{if(o)throw o.error}}r=this.getProperty("scriptlevel")||r,e=!(!this.attributes.getExplicit("displaystyle")&&!this.attributes.getDefault("displaystyle")),t=this.addInheritedAttributes(t,{columnalign:this.attributes.get("columnalign"),rowalign:"center"});var h=this.attributes.getExplicit("data-cramped"),f=(0,l.split)(this.attributes.get("rowalign"));try{for(var d=s(this.childNodes),m=d.next();!m.done;m=d.next()){var y=m.value;t.rowalign[1]=f.shift()||t.rowalign[1],y.setInheritedAttributes(t,e,r,!!h)}}catch(t){a={error:t}}finally{try{m&&!m.done&&(c=d.return)&&c.call(d)}finally{if(a)throw a.error}}},e.prototype.verifyChildren=function(e){for(var r=null,n=this.factory,o=0;o=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.MmlMlabeledtr=e.MmlMtr=void 0;var a=r(9007),l=r(91),c=r(505),u=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),Object.defineProperty(e.prototype,"kind",{get:function(){return"mtr"},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"linebreakContainer",{get:function(){return!0},enumerable:!1,configurable:!0}),e.prototype.setChildInheritedAttributes=function(t,e,r,n){var o,i,a,l;try{for(var u=s(this.childNodes),p=u.next();!p.done;p=u.next()){(m=p.value).isKind("mtd")||this.replaceChild(this.factory.create("mtd"),m).appendChild(m)}}catch(t){o={error:t}}finally{try{p&&!p.done&&(i=u.return)&&i.call(u)}finally{if(o)throw o.error}}var h=(0,c.split)(this.attributes.get("columnalign"));1===this.arity&&h.unshift(this.parent.attributes.get("side")),t=this.addInheritedAttributes(t,{rowalign:this.attributes.get("rowalign"),columnalign:"center"});try{for(var f=s(this.childNodes),d=f.next();!d.done;d=f.next()){var m=d.value;t.columnalign[1]=h.shift()||t.columnalign[1],m.setInheritedAttributes(t,e,r,n)}}catch(t){a={error:t}}finally{try{d&&!d.done&&(l=f.return)&&l.call(f)}finally{if(a)throw a.error}}},e.prototype.verifyChildren=function(e){var r,n;if(!this.parent||this.parent.isKind("mtable")){try{for(var o=s(this.childNodes),i=o.next();!i.done;i=o.next()){var a=i.value;if(!a.isKind("mtd"))this.replaceChild(this.factory.create("mtd"),a).appendChild(a),e.fixMtables||a.mError("Children of "+this.kind+" must be mtd",e)}}catch(t){r={error:t}}finally{try{i&&!i.done&&(n=o.return)&&n.call(o)}finally{if(r)throw r.error}}t.prototype.verifyChildren.call(this,e)}else this.mError(this.kind+" can only be a child of an mtable",e,!0)},e.prototype.setTeXclass=function(t){var e,r;this.getPrevClass(t);try{for(var n=s(this.childNodes),o=n.next();!o.done;o=n.next()){o.value.setTeXclass(null)}}catch(t){e={error:t}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(e)throw e.error}}return this},e.defaults=i(i({},a.AbstractMmlNode.defaults),{rowalign:l.INHERIT,columnalign:l.INHERIT,groupalign:l.INHERIT}),e}(a.AbstractMmlNode);e.MmlMtr=u;var p=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),Object.defineProperty(e.prototype,"kind",{get:function(){return"mlabeledtr"},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"arity",{get:function(){return 1},enumerable:!1,configurable:!0}),e}(u);e.MmlMlabeledtr=p},5184:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__assign||function(){return i=Object.assign||function(t){for(var e,r=1,n=arguments.length;r=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.OPTABLE=e.MMLSPACING=e.getRange=e.RANGES=e.MO=e.OPDEF=void 0;var o=r(9007);function i(t,e,r,n){return void 0===r&&(r=o.TEXCLASS.BIN),void 0===n&&(n=null),[t,e,r,n]}e.OPDEF=i,e.MO={ORD:i(0,0,o.TEXCLASS.ORD),ORD11:i(1,1,o.TEXCLASS.ORD),ORD21:i(2,1,o.TEXCLASS.ORD),ORD02:i(0,2,o.TEXCLASS.ORD),ORD55:i(5,5,o.TEXCLASS.ORD),NONE:i(0,0,o.TEXCLASS.NONE),OP:i(1,2,o.TEXCLASS.OP,{largeop:!0,movablelimits:!0,symmetric:!0}),OPFIXED:i(1,2,o.TEXCLASS.OP,{largeop:!0,movablelimits:!0}),INTEGRAL:i(0,1,o.TEXCLASS.OP,{largeop:!0,symmetric:!0}),INTEGRAL2:i(1,2,o.TEXCLASS.OP,{largeop:!0,symmetric:!0}),BIN3:i(3,3,o.TEXCLASS.BIN),BIN4:i(4,4,o.TEXCLASS.BIN),BIN01:i(0,1,o.TEXCLASS.BIN),BIN5:i(5,5,o.TEXCLASS.BIN),TALLBIN:i(4,4,o.TEXCLASS.BIN,{stretchy:!0}),BINOP:i(4,4,o.TEXCLASS.BIN,{largeop:!0,movablelimits:!0}),REL:i(5,5,o.TEXCLASS.REL),REL1:i(1,1,o.TEXCLASS.REL,{stretchy:!0}),REL4:i(4,4,o.TEXCLASS.REL),RELSTRETCH:i(5,5,o.TEXCLASS.REL,{stretchy:!0}),RELACCENT:i(5,5,o.TEXCLASS.REL,{accent:!0}),WIDEREL:i(5,5,o.TEXCLASS.REL,{accent:!0,stretchy:!0}),OPEN:i(0,0,o.TEXCLASS.OPEN,{fence:!0,stretchy:!0,symmetric:!0}),CLOSE:i(0,0,o.TEXCLASS.CLOSE,{fence:!0,stretchy:!0,symmetric:!0}),INNER:i(0,0,o.TEXCLASS.INNER),PUNCT:i(0,3,o.TEXCLASS.PUNCT),ACCENT:i(0,0,o.TEXCLASS.ORD,{accent:!0}),WIDEACCENT:i(0,0,o.TEXCLASS.ORD,{accent:!0,stretchy:!0})},e.RANGES=[[32,127,o.TEXCLASS.REL,"mo"],[160,191,o.TEXCLASS.ORD,"mo"],[192,591,o.TEXCLASS.ORD,"mi"],[688,879,o.TEXCLASS.ORD,"mo"],[880,6688,o.TEXCLASS.ORD,"mi"],[6832,6911,o.TEXCLASS.ORD,"mo"],[6912,7615,o.TEXCLASS.ORD,"mi"],[7616,7679,o.TEXCLASS.ORD,"mo"],[7680,8191,o.TEXCLASS.ORD,"mi"],[8192,8303,o.TEXCLASS.ORD,"mo"],[8304,8351,o.TEXCLASS.ORD,"mo"],[8448,8527,o.TEXCLASS.ORD,"mi"],[8528,8591,o.TEXCLASS.ORD,"mn"],[8592,8703,o.TEXCLASS.REL,"mo"],[8704,8959,o.TEXCLASS.BIN,"mo"],[8960,9215,o.TEXCLASS.ORD,"mo"],[9312,9471,o.TEXCLASS.ORD,"mn"],[9472,10223,o.TEXCLASS.ORD,"mo"],[10224,10239,o.TEXCLASS.REL,"mo"],[10240,10495,o.TEXCLASS.ORD,"mtext"],[10496,10623,o.TEXCLASS.REL,"mo"],[10624,10751,o.TEXCLASS.ORD,"mo"],[10752,11007,o.TEXCLASS.BIN,"mo"],[11008,11055,o.TEXCLASS.ORD,"mo"],[11056,11087,o.TEXCLASS.REL,"mo"],[11088,11263,o.TEXCLASS.ORD,"mo"],[11264,11744,o.TEXCLASS.ORD,"mi"],[11776,11903,o.TEXCLASS.ORD,"mo"],[11904,12255,o.TEXCLASS.ORD,"mi","normal"],[12272,12351,o.TEXCLASS.ORD,"mo"],[12352,42143,o.TEXCLASS.ORD,"mi","normal"],[42192,43055,o.TEXCLASS.ORD,"mi"],[43056,43071,o.TEXCLASS.ORD,"mn"],[43072,55295,o.TEXCLASS.ORD,"mi"],[63744,64255,o.TEXCLASS.ORD,"mi","normal"],[64256,65023,o.TEXCLASS.ORD,"mi"],[65024,65135,o.TEXCLASS.ORD,"mo"],[65136,65791,o.TEXCLASS.ORD,"mi"],[65792,65935,o.TEXCLASS.ORD,"mn"],[65936,74751,o.TEXCLASS.ORD,"mi","normal"],[74752,74879,o.TEXCLASS.ORD,"mn"],[74880,113823,o.TEXCLASS.ORD,"mi","normal"],[113824,119391,o.TEXCLASS.ORD,"mo"],[119648,119679,o.TEXCLASS.ORD,"mn"],[119808,120781,o.TEXCLASS.ORD,"mi"],[120782,120831,o.TEXCLASS.ORD,"mn"],[122624,129023,o.TEXCLASS.ORD,"mo"],[129024,129279,o.TEXCLASS.REL,"mo"],[129280,129535,o.TEXCLASS.ORD,"mo"],[131072,195103,o.TEXCLASS.ORD,"mi","normnal"]],e.getRange=function(t){var r,o,i=t.codePointAt(0);try{for(var s=n(e.RANGES),a=s.next();!a.done;a=s.next()){var l=a.value;if(i<=l[1]){if(i>=l[0])return l;break}}}catch(t){r={error:t}}finally{try{a&&!a.done&&(o=s.return)&&o.call(s)}finally{if(r)throw r.error}}return null},e.MMLSPACING=[[0,0],[1,2],[3,3],[4,4],[0,0],[0,0],[0,3]],e.OPTABLE={prefix:{"(":e.MO.OPEN,"+":e.MO.BIN01,"-":e.MO.BIN01,"[":e.MO.OPEN,"{":e.MO.OPEN,"|":e.MO.OPEN,"||":[0,0,o.TEXCLASS.BIN,{fence:!0,stretchy:!0,symmetric:!0}],"|||":[0,0,o.TEXCLASS.ORD,{fence:!0,stretchy:!0,symmetric:!0}],"\xac":e.MO.ORD21,"\xb1":e.MO.BIN01,"\u2016":[0,0,o.TEXCLASS.ORD,{fence:!0,stretchy:!0}],"\u2018":[0,0,o.TEXCLASS.OPEN,{fence:!0}],"\u201c":[0,0,o.TEXCLASS.OPEN,{fence:!0}],"\u2145":e.MO.ORD21,"\u2146":i(2,0,o.TEXCLASS.ORD),"\u2200":e.MO.ORD21,"\u2202":e.MO.ORD21,"\u2203":e.MO.ORD21,"\u2204":e.MO.ORD21,"\u2207":e.MO.ORD21,"\u220f":e.MO.OP,"\u2210":e.MO.OP,"\u2211":e.MO.OP,"\u2212":e.MO.BIN01,"\u2213":e.MO.BIN01,"\u221a":[1,1,o.TEXCLASS.ORD,{stretchy:!0}],"\u221b":e.MO.ORD11,"\u221c":e.MO.ORD11,"\u2220":e.MO.ORD,"\u2221":e.MO.ORD,"\u2222":e.MO.ORD,"\u222b":e.MO.INTEGRAL,"\u222c":e.MO.INTEGRAL,"\u222d":e.MO.INTEGRAL,"\u222e":e.MO.INTEGRAL,"\u222f":e.MO.INTEGRAL,"\u2230":e.MO.INTEGRAL,"\u2231":e.MO.INTEGRAL,"\u2232":e.MO.INTEGRAL,"\u2233":e.MO.INTEGRAL,"\u22c0":e.MO.OP,"\u22c1":e.MO.OP,"\u22c2":e.MO.OP,"\u22c3":e.MO.OP,"\u2308":e.MO.OPEN,"\u230a":e.MO.OPEN,"\u2329":e.MO.OPEN,"\u2772":e.MO.OPEN,"\u27e6":e.MO.OPEN,"\u27e8":e.MO.OPEN,"\u27ea":e.MO.OPEN,"\u27ec":e.MO.OPEN,"\u27ee":e.MO.OPEN,"\u2980":[0,0,o.TEXCLASS.ORD,{fence:!0,stretchy:!0}],"\u2983":e.MO.OPEN,"\u2985":e.MO.OPEN,"\u2987":e.MO.OPEN,"\u2989":e.MO.OPEN,"\u298b":e.MO.OPEN,"\u298d":e.MO.OPEN,"\u298f":e.MO.OPEN,"\u2991":e.MO.OPEN,"\u2993":e.MO.OPEN,"\u2995":e.MO.OPEN,"\u2997":e.MO.OPEN,"\u29fc":e.MO.OPEN,"\u2a00":e.MO.OP,"\u2a01":e.MO.OP,"\u2a02":e.MO.OP,"\u2a03":e.MO.OP,"\u2a04":e.MO.OP,"\u2a05":e.MO.OP,"\u2a06":e.MO.OP,"\u2a07":e.MO.OP,"\u2a08":e.MO.OP,"\u2a09":e.MO.OP,"\u2a0a":e.MO.OP,"\u2a0b":e.MO.INTEGRAL2,"\u2a0c":e.MO.INTEGRAL,"\u2a0d":e.MO.INTEGRAL2,"\u2a0e":e.MO.INTEGRAL2,"\u2a0f":e.MO.INTEGRAL2,"\u2a10":e.MO.OP,"\u2a11":e.MO.OP,"\u2a12":e.MO.OP,"\u2a13":e.MO.OP,"\u2a14":e.MO.OP,"\u2a15":e.MO.INTEGRAL2,"\u2a16":e.MO.INTEGRAL2,"\u2a17":e.MO.INTEGRAL2,"\u2a18":e.MO.INTEGRAL2,"\u2a19":e.MO.INTEGRAL2,"\u2a1a":e.MO.INTEGRAL2,"\u2a1b":e.MO.INTEGRAL2,"\u2a1c":e.MO.INTEGRAL2,"\u2afc":e.MO.OP,"\u2aff":e.MO.OP},postfix:{"!!":i(1,0),"!":[1,0,o.TEXCLASS.CLOSE,null],'"':e.MO.ACCENT,"&":e.MO.ORD,")":e.MO.CLOSE,"++":i(0,0),"--":i(0,0),"..":i(0,0),"...":e.MO.ORD,"'":e.MO.ACCENT,"]":e.MO.CLOSE,"^":e.MO.WIDEACCENT,_:e.MO.WIDEACCENT,"`":e.MO.ACCENT,"|":e.MO.CLOSE,"}":e.MO.CLOSE,"~":e.MO.WIDEACCENT,"||":[0,0,o.TEXCLASS.BIN,{fence:!0,stretchy:!0,symmetric:!0}],"|||":[0,0,o.TEXCLASS.ORD,{fence:!0,stretchy:!0,symmetric:!0}],"\xa8":e.MO.ACCENT,"\xaa":e.MO.ACCENT,"\xaf":e.MO.WIDEACCENT,"\xb0":e.MO.ORD,"\xb2":e.MO.ACCENT,"\xb3":e.MO.ACCENT,"\xb4":e.MO.ACCENT,"\xb8":e.MO.ACCENT,"\xb9":e.MO.ACCENT,"\xba":e.MO.ACCENT,"\u02c6":e.MO.WIDEACCENT,"\u02c7":e.MO.WIDEACCENT,"\u02c9":e.MO.WIDEACCENT,"\u02ca":e.MO.ACCENT,"\u02cb":e.MO.ACCENT,"\u02cd":e.MO.WIDEACCENT,"\u02d8":e.MO.ACCENT,"\u02d9":e.MO.ACCENT,"\u02da":e.MO.ACCENT,"\u02dc":e.MO.WIDEACCENT,"\u02dd":e.MO.ACCENT,"\u02f7":e.MO.WIDEACCENT,"\u0302":e.MO.WIDEACCENT,"\u0311":e.MO.ACCENT,"\u03f6":e.MO.REL,"\u2016":[0,0,o.TEXCLASS.ORD,{fence:!0,stretchy:!0}],"\u2019":[0,0,o.TEXCLASS.CLOSE,{fence:!0}],"\u201a":e.MO.ACCENT,"\u201b":e.MO.ACCENT,"\u201d":[0,0,o.TEXCLASS.CLOSE,{fence:!0}],"\u201e":e.MO.ACCENT,"\u201f":e.MO.ACCENT,"\u2032":e.MO.ORD,"\u2033":e.MO.ACCENT,"\u2034":e.MO.ACCENT,"\u2035":e.MO.ACCENT,"\u2036":e.MO.ACCENT,"\u2037":e.MO.ACCENT,"\u203e":e.MO.WIDEACCENT,"\u2057":e.MO.ACCENT,"\u20db":e.MO.ACCENT,"\u20dc":e.MO.ACCENT,"\u2309":e.MO.CLOSE,"\u230b":e.MO.CLOSE,"\u232a":e.MO.CLOSE,"\u23b4":e.MO.WIDEACCENT,"\u23b5":e.MO.WIDEACCENT,"\u23dc":e.MO.WIDEACCENT,"\u23dd":e.MO.WIDEACCENT,"\u23de":e.MO.WIDEACCENT,"\u23df":e.MO.WIDEACCENT,"\u23e0":e.MO.WIDEACCENT,"\u23e1":e.MO.WIDEACCENT,"\u25a0":e.MO.BIN3,"\u25a1":e.MO.BIN3,"\u25aa":e.MO.BIN3,"\u25ab":e.MO.BIN3,"\u25ad":e.MO.BIN3,"\u25ae":e.MO.BIN3,"\u25af":e.MO.BIN3,"\u25b0":e.MO.BIN3,"\u25b1":e.MO.BIN3,"\u25b2":e.MO.BIN4,"\u25b4":e.MO.BIN4,"\u25b6":e.MO.BIN4,"\u25b7":e.MO.BIN4,"\u25b8":e.MO.BIN4,"\u25bc":e.MO.BIN4,"\u25be":e.MO.BIN4,"\u25c0":e.MO.BIN4,"\u25c1":e.MO.BIN4,"\u25c2":e.MO.BIN4,"\u25c4":e.MO.BIN4,"\u25c5":e.MO.BIN4,"\u25c6":e.MO.BIN4,"\u25c7":e.MO.BIN4,"\u25c8":e.MO.BIN4,"\u25c9":e.MO.BIN4,"\u25cc":e.MO.BIN4,"\u25cd":e.MO.BIN4,"\u25ce":e.MO.BIN4,"\u25cf":e.MO.BIN4,"\u25d6":e.MO.BIN4,"\u25d7":e.MO.BIN4,"\u25e6":e.MO.BIN4,"\u266d":e.MO.ORD02,"\u266e":e.MO.ORD02,"\u266f":e.MO.ORD02,"\u2773":e.MO.CLOSE,"\u27e7":e.MO.CLOSE,"\u27e9":e.MO.CLOSE,"\u27eb":e.MO.CLOSE,"\u27ed":e.MO.CLOSE,"\u27ef":e.MO.CLOSE,"\u2980":[0,0,o.TEXCLASS.ORD,{fence:!0,stretchy:!0}],"\u2984":e.MO.CLOSE,"\u2986":e.MO.CLOSE,"\u2988":e.MO.CLOSE,"\u298a":e.MO.CLOSE,"\u298c":e.MO.CLOSE,"\u298e":e.MO.CLOSE,"\u2990":e.MO.CLOSE,"\u2992":e.MO.CLOSE,"\u2994":e.MO.CLOSE,"\u2996":e.MO.CLOSE,"\u2998":e.MO.CLOSE,"\u29fd":e.MO.CLOSE},infix:{"!=":e.MO.BIN4,"#":e.MO.ORD,$:e.MO.ORD,"%":[3,3,o.TEXCLASS.ORD,null],"&&":e.MO.BIN4,"":e.MO.ORD,"*":e.MO.BIN3,"**":i(1,1),"*=":e.MO.BIN4,"+":e.MO.BIN4,"+=":e.MO.BIN4,",":[0,3,o.TEXCLASS.PUNCT,{linebreakstyle:"after",separator:!0}],"-":e.MO.BIN4,"-=":e.MO.BIN4,"->":e.MO.BIN5,".":[0,3,o.TEXCLASS.PUNCT,{separator:!0}],"/":e.MO.ORD11,"//":i(1,1),"/=":e.MO.BIN4,":":[1,2,o.TEXCLASS.REL,null],":=":e.MO.BIN4,";":[0,3,o.TEXCLASS.PUNCT,{linebreakstyle:"after",separator:!0}],"<":e.MO.REL,"<=":e.MO.BIN5,"<>":i(1,1),"=":e.MO.REL,"==":e.MO.BIN4,">":e.MO.REL,">=":e.MO.BIN5,"?":[1,1,o.TEXCLASS.CLOSE,null],"@":e.MO.ORD11,"\\":e.MO.ORD,"^":e.MO.ORD11,_:e.MO.ORD11,"|":[2,2,o.TEXCLASS.ORD,{fence:!0,stretchy:!0,symmetric:!0}],"||":[2,2,o.TEXCLASS.BIN,{fence:!0,stretchy:!0,symmetric:!0}],"|||":[2,2,o.TEXCLASS.ORD,{fence:!0,stretchy:!0,symmetric:!0}],"\xb1":e.MO.BIN4,"\xb7":e.MO.BIN4,"\xd7":e.MO.BIN4,"\xf7":e.MO.BIN4,"\u02b9":e.MO.ORD,"\u0300":e.MO.ACCENT,"\u0301":e.MO.ACCENT,"\u0303":e.MO.WIDEACCENT,"\u0304":e.MO.ACCENT,"\u0306":e.MO.ACCENT,"\u0307":e.MO.ACCENT,"\u0308":e.MO.ACCENT,"\u030c":e.MO.ACCENT,"\u0332":e.MO.WIDEACCENT,"\u0338":e.MO.REL4,"\u2015":[0,0,o.TEXCLASS.ORD,{stretchy:!0}],"\u2017":[0,0,o.TEXCLASS.ORD,{stretchy:!0}],"\u2020":e.MO.BIN3,"\u2021":e.MO.BIN3,"\u2022":e.MO.BIN4,"\u2026":e.MO.INNER,"\u2043":e.MO.BIN4,"\u2044":e.MO.TALLBIN,"\u2061":e.MO.NONE,"\u2062":e.MO.NONE,"\u2063":[0,0,o.TEXCLASS.NONE,{linebreakstyle:"after",separator:!0}],"\u2064":e.MO.NONE,"\u20d7":e.MO.ACCENT,"\u2111":e.MO.ORD,"\u2113":e.MO.ORD,"\u2118":e.MO.ORD,"\u211c":e.MO.ORD,"\u2190":e.MO.WIDEREL,"\u2191":e.MO.RELSTRETCH,"\u2192":e.MO.WIDEREL,"\u2193":e.MO.RELSTRETCH,"\u2194":e.MO.WIDEREL,"\u2195":e.MO.RELSTRETCH,"\u2196":e.MO.RELSTRETCH,"\u2197":e.MO.RELSTRETCH,"\u2198":e.MO.RELSTRETCH,"\u2199":e.MO.RELSTRETCH,"\u219a":e.MO.RELACCENT,"\u219b":e.MO.RELACCENT,"\u219c":e.MO.WIDEREL,"\u219d":e.MO.WIDEREL,"\u219e":e.MO.WIDEREL,"\u219f":e.MO.WIDEREL,"\u21a0":e.MO.WIDEREL,"\u21a1":e.MO.RELSTRETCH,"\u21a2":e.MO.WIDEREL,"\u21a3":e.MO.WIDEREL,"\u21a4":e.MO.WIDEREL,"\u21a5":e.MO.RELSTRETCH,"\u21a6":e.MO.WIDEREL,"\u21a7":e.MO.RELSTRETCH,"\u21a8":e.MO.RELSTRETCH,"\u21a9":e.MO.WIDEREL,"\u21aa":e.MO.WIDEREL,"\u21ab":e.MO.WIDEREL,"\u21ac":e.MO.WIDEREL,"\u21ad":e.MO.WIDEREL,"\u21ae":e.MO.RELACCENT,"\u21af":e.MO.RELSTRETCH,"\u21b0":e.MO.RELSTRETCH,"\u21b1":e.MO.RELSTRETCH,"\u21b2":e.MO.RELSTRETCH,"\u21b3":e.MO.RELSTRETCH,"\u21b4":e.MO.RELSTRETCH,"\u21b5":e.MO.RELSTRETCH,"\u21b6":e.MO.RELACCENT,"\u21b7":e.MO.RELACCENT,"\u21b8":e.MO.REL,"\u21b9":e.MO.WIDEREL,"\u21ba":e.MO.REL,"\u21bb":e.MO.REL,"\u21bc":e.MO.WIDEREL,"\u21bd":e.MO.WIDEREL,"\u21be":e.MO.RELSTRETCH,"\u21bf":e.MO.RELSTRETCH,"\u21c0":e.MO.WIDEREL,"\u21c1":e.MO.WIDEREL,"\u21c2":e.MO.RELSTRETCH,"\u21c3":e.MO.RELSTRETCH,"\u21c4":e.MO.WIDEREL,"\u21c5":e.MO.RELSTRETCH,"\u21c6":e.MO.WIDEREL,"\u21c7":e.MO.WIDEREL,"\u21c8":e.MO.RELSTRETCH,"\u21c9":e.MO.WIDEREL,"\u21ca":e.MO.RELSTRETCH,"\u21cb":e.MO.WIDEREL,"\u21cc":e.MO.WIDEREL,"\u21cd":e.MO.RELACCENT,"\u21ce":e.MO.RELACCENT,"\u21cf":e.MO.RELACCENT,"\u21d0":e.MO.WIDEREL,"\u21d1":e.MO.RELSTRETCH,"\u21d2":e.MO.WIDEREL,"\u21d3":e.MO.RELSTRETCH,"\u21d4":e.MO.WIDEREL,"\u21d5":e.MO.RELSTRETCH,"\u21d6":e.MO.RELSTRETCH,"\u21d7":e.MO.RELSTRETCH,"\u21d8":e.MO.RELSTRETCH,"\u21d9":e.MO.RELSTRETCH,"\u21da":e.MO.WIDEREL,"\u21db":e.MO.WIDEREL,"\u21dc":e.MO.WIDEREL,"\u21dd":e.MO.WIDEREL,"\u21de":e.MO.REL,"\u21df":e.MO.REL,"\u21e0":e.MO.WIDEREL,"\u21e1":e.MO.RELSTRETCH,"\u21e2":e.MO.WIDEREL,"\u21e3":e.MO.RELSTRETCH,"\u21e4":e.MO.WIDEREL,"\u21e5":e.MO.WIDEREL,"\u21e6":e.MO.WIDEREL,"\u21e7":e.MO.RELSTRETCH,"\u21e8":e.MO.WIDEREL,"\u21e9":e.MO.RELSTRETCH,"\u21ea":e.MO.RELSTRETCH,"\u21eb":e.MO.RELSTRETCH,"\u21ec":e.MO.RELSTRETCH,"\u21ed":e.MO.RELSTRETCH,"\u21ee":e.MO.RELSTRETCH,"\u21ef":e.MO.RELSTRETCH,"\u21f0":e.MO.WIDEREL,"\u21f1":e.MO.REL,"\u21f2":e.MO.REL,"\u21f3":e.MO.RELSTRETCH,"\u21f4":e.MO.RELACCENT,"\u21f5":e.MO.RELSTRETCH,"\u21f6":e.MO.WIDEREL,"\u21f7":e.MO.RELACCENT,"\u21f8":e.MO.RELACCENT,"\u21f9":e.MO.RELACCENT,"\u21fa":e.MO.RELACCENT,"\u21fb":e.MO.RELACCENT,"\u21fc":e.MO.RELACCENT,"\u21fd":e.MO.WIDEREL,"\u21fe":e.MO.WIDEREL,"\u21ff":e.MO.WIDEREL,"\u2201":i(1,2,o.TEXCLASS.ORD),"\u2205":e.MO.ORD,"\u2206":e.MO.BIN3,"\u2208":e.MO.REL,"\u2209":e.MO.REL,"\u220a":e.MO.REL,"\u220b":e.MO.REL,"\u220c":e.MO.REL,"\u220d":e.MO.REL,"\u220e":e.MO.BIN3,"\u2212":e.MO.BIN4,"\u2213":e.MO.BIN4,"\u2214":e.MO.BIN4,"\u2215":e.MO.TALLBIN,"\u2216":e.MO.BIN4,"\u2217":e.MO.BIN4,"\u2218":e.MO.BIN4,"\u2219":e.MO.BIN4,"\u221d":e.MO.REL,"\u221e":e.MO.ORD,"\u221f":e.MO.REL,"\u2223":e.MO.REL,"\u2224":e.MO.REL,"\u2225":e.MO.REL,"\u2226":e.MO.REL,"\u2227":e.MO.BIN4,"\u2228":e.MO.BIN4,"\u2229":e.MO.BIN4,"\u222a":e.MO.BIN4,"\u2234":e.MO.REL,"\u2235":e.MO.REL,"\u2236":e.MO.REL,"\u2237":e.MO.REL,"\u2238":e.MO.BIN4,"\u2239":e.MO.REL,"\u223a":e.MO.BIN4,"\u223b":e.MO.REL,"\u223c":e.MO.REL,"\u223d":e.MO.REL,"\u223d\u0331":e.MO.BIN3,"\u223e":e.MO.REL,"\u223f":e.MO.BIN3,"\u2240":e.MO.BIN4,"\u2241":e.MO.REL,"\u2242":e.MO.REL,"\u2242\u0338":e.MO.REL,"\u2243":e.MO.REL,"\u2244":e.MO.REL,"\u2245":e.MO.REL,"\u2246":e.MO.REL,"\u2247":e.MO.REL,"\u2248":e.MO.REL,"\u2249":e.MO.REL,"\u224a":e.MO.REL,"\u224b":e.MO.REL,"\u224c":e.MO.REL,"\u224d":e.MO.REL,"\u224e":e.MO.REL,"\u224e\u0338":e.MO.REL,"\u224f":e.MO.REL,"\u224f\u0338":e.MO.REL,"\u2250":e.MO.REL,"\u2251":e.MO.REL,"\u2252":e.MO.REL,"\u2253":e.MO.REL,"\u2254":e.MO.REL,"\u2255":e.MO.REL,"\u2256":e.MO.REL,"\u2257":e.MO.REL,"\u2258":e.MO.REL,"\u2259":e.MO.REL,"\u225a":e.MO.REL,"\u225b":e.MO.REL,"\u225c":e.MO.REL,"\u225d":e.MO.REL,"\u225e":e.MO.REL,"\u225f":e.MO.REL,"\u2260":e.MO.REL,"\u2261":e.MO.REL,"\u2262":e.MO.REL,"\u2263":e.MO.REL,"\u2264":e.MO.REL,"\u2265":e.MO.REL,"\u2266":e.MO.REL,"\u2266\u0338":e.MO.REL,"\u2267":e.MO.REL,"\u2268":e.MO.REL,"\u2269":e.MO.REL,"\u226a":e.MO.REL,"\u226a\u0338":e.MO.REL,"\u226b":e.MO.REL,"\u226b\u0338":e.MO.REL,"\u226c":e.MO.REL,"\u226d":e.MO.REL,"\u226e":e.MO.REL,"\u226f":e.MO.REL,"\u2270":e.MO.REL,"\u2271":e.MO.REL,"\u2272":e.MO.REL,"\u2273":e.MO.REL,"\u2274":e.MO.REL,"\u2275":e.MO.REL,"\u2276":e.MO.REL,"\u2277":e.MO.REL,"\u2278":e.MO.REL,"\u2279":e.MO.REL,"\u227a":e.MO.REL,"\u227b":e.MO.REL,"\u227c":e.MO.REL,"\u227d":e.MO.REL,"\u227e":e.MO.REL,"\u227f":e.MO.REL,"\u227f\u0338":e.MO.REL,"\u2280":e.MO.REL,"\u2281":e.MO.REL,"\u2282":e.MO.REL,"\u2282\u20d2":e.MO.REL,"\u2283":e.MO.REL,"\u2283\u20d2":e.MO.REL,"\u2284":e.MO.REL,"\u2285":e.MO.REL,"\u2286":e.MO.REL,"\u2287":e.MO.REL,"\u2288":e.MO.REL,"\u2289":e.MO.REL,"\u228a":e.MO.REL,"\u228b":e.MO.REL,"\u228c":e.MO.BIN4,"\u228d":e.MO.BIN4,"\u228e":e.MO.BIN4,"\u228f":e.MO.REL,"\u228f\u0338":e.MO.REL,"\u2290":e.MO.REL,"\u2290\u0338":e.MO.REL,"\u2291":e.MO.REL,"\u2292":e.MO.REL,"\u2293":e.MO.BIN4,"\u2294":e.MO.BIN4,"\u2295":e.MO.BIN4,"\u2296":e.MO.BIN4,"\u2297":e.MO.BIN4,"\u2298":e.MO.BIN4,"\u2299":e.MO.BIN4,"\u229a":e.MO.BIN4,"\u229b":e.MO.BIN4,"\u229c":e.MO.BIN4,"\u229d":e.MO.BIN4,"\u229e":e.MO.BIN4,"\u229f":e.MO.BIN4,"\u22a0":e.MO.BIN4,"\u22a1":e.MO.BIN4,"\u22a2":e.MO.REL,"\u22a3":e.MO.REL,"\u22a4":e.MO.ORD55,"\u22a5":e.MO.REL,"\u22a6":e.MO.REL,"\u22a7":e.MO.REL,"\u22a8":e.MO.REL,"\u22a9":e.MO.REL,"\u22aa":e.MO.REL,"\u22ab":e.MO.REL,"\u22ac":e.MO.REL,"\u22ad":e.MO.REL,"\u22ae":e.MO.REL,"\u22af":e.MO.REL,"\u22b0":e.MO.REL,"\u22b1":e.MO.REL,"\u22b2":e.MO.REL,"\u22b3":e.MO.REL,"\u22b4":e.MO.REL,"\u22b5":e.MO.REL,"\u22b6":e.MO.REL,"\u22b7":e.MO.REL,"\u22b8":e.MO.REL,"\u22b9":e.MO.REL,"\u22ba":e.MO.BIN4,"\u22bb":e.MO.BIN4,"\u22bc":e.MO.BIN4,"\u22bd":e.MO.BIN4,"\u22be":e.MO.BIN3,"\u22bf":e.MO.BIN3,"\u22c4":e.MO.BIN4,"\u22c5":e.MO.BIN4,"\u22c6":e.MO.BIN4,"\u22c7":e.MO.BIN4,"\u22c8":e.MO.REL,"\u22c9":e.MO.BIN4,"\u22ca":e.MO.BIN4,"\u22cb":e.MO.BIN4,"\u22cc":e.MO.BIN4,"\u22cd":e.MO.REL,"\u22ce":e.MO.BIN4,"\u22cf":e.MO.BIN4,"\u22d0":e.MO.REL,"\u22d1":e.MO.REL,"\u22d2":e.MO.BIN4,"\u22d3":e.MO.BIN4,"\u22d4":e.MO.REL,"\u22d5":e.MO.REL,"\u22d6":e.MO.REL,"\u22d7":e.MO.REL,"\u22d8":e.MO.REL,"\u22d9":e.MO.REL,"\u22da":e.MO.REL,"\u22db":e.MO.REL,"\u22dc":e.MO.REL,"\u22dd":e.MO.REL,"\u22de":e.MO.REL,"\u22df":e.MO.REL,"\u22e0":e.MO.REL,"\u22e1":e.MO.REL,"\u22e2":e.MO.REL,"\u22e3":e.MO.REL,"\u22e4":e.MO.REL,"\u22e5":e.MO.REL,"\u22e6":e.MO.REL,"\u22e7":e.MO.REL,"\u22e8":e.MO.REL,"\u22e9":e.MO.REL,"\u22ea":e.MO.REL,"\u22eb":e.MO.REL,"\u22ec":e.MO.REL,"\u22ed":e.MO.REL,"\u22ee":e.MO.ORD55,"\u22ef":e.MO.INNER,"\u22f0":e.MO.REL,"\u22f1":[5,5,o.TEXCLASS.INNER,null],"\u22f2":e.MO.REL,"\u22f3":e.MO.REL,"\u22f4":e.MO.REL,"\u22f5":e.MO.REL,"\u22f6":e.MO.REL,"\u22f7":e.MO.REL,"\u22f8":e.MO.REL,"\u22f9":e.MO.REL,"\u22fa":e.MO.REL,"\u22fb":e.MO.REL,"\u22fc":e.MO.REL,"\u22fd":e.MO.REL,"\u22fe":e.MO.REL,"\u22ff":e.MO.REL,"\u2305":e.MO.BIN3,"\u2306":e.MO.BIN3,"\u2322":e.MO.REL4,"\u2323":e.MO.REL4,"\u2329":e.MO.OPEN,"\u232a":e.MO.CLOSE,"\u23aa":e.MO.ORD,"\u23af":[0,0,o.TEXCLASS.ORD,{stretchy:!0}],"\u23b0":e.MO.OPEN,"\u23b1":e.MO.CLOSE,"\u2500":e.MO.ORD,"\u25b3":e.MO.BIN4,"\u25b5":e.MO.BIN4,"\u25b9":e.MO.BIN4,"\u25bd":e.MO.BIN4,"\u25bf":e.MO.BIN4,"\u25c3":e.MO.BIN4,"\u25ef":e.MO.BIN3,"\u2660":e.MO.ORD,"\u2661":e.MO.ORD,"\u2662":e.MO.ORD,"\u2663":e.MO.ORD,"\u2758":e.MO.REL,"\u27f0":e.MO.RELSTRETCH,"\u27f1":e.MO.RELSTRETCH,"\u27f5":e.MO.WIDEREL,"\u27f6":e.MO.WIDEREL,"\u27f7":e.MO.WIDEREL,"\u27f8":e.MO.WIDEREL,"\u27f9":e.MO.WIDEREL,"\u27fa":e.MO.WIDEREL,"\u27fb":e.MO.WIDEREL,"\u27fc":e.MO.WIDEREL,"\u27fd":e.MO.WIDEREL,"\u27fe":e.MO.WIDEREL,"\u27ff":e.MO.WIDEREL,"\u2900":e.MO.RELACCENT,"\u2901":e.MO.RELACCENT,"\u2902":e.MO.RELACCENT,"\u2903":e.MO.RELACCENT,"\u2904":e.MO.RELACCENT,"\u2905":e.MO.RELACCENT,"\u2906":e.MO.RELACCENT,"\u2907":e.MO.RELACCENT,"\u2908":e.MO.REL,"\u2909":e.MO.REL,"\u290a":e.MO.RELSTRETCH,"\u290b":e.MO.RELSTRETCH,"\u290c":e.MO.WIDEREL,"\u290d":e.MO.WIDEREL,"\u290e":e.MO.WIDEREL,"\u290f":e.MO.WIDEREL,"\u2910":e.MO.WIDEREL,"\u2911":e.MO.RELACCENT,"\u2912":e.MO.RELSTRETCH,"\u2913":e.MO.RELSTRETCH,"\u2914":e.MO.RELACCENT,"\u2915":e.MO.RELACCENT,"\u2916":e.MO.RELACCENT,"\u2917":e.MO.RELACCENT,"\u2918":e.MO.RELACCENT,"\u2919":e.MO.RELACCENT,"\u291a":e.MO.RELACCENT,"\u291b":e.MO.RELACCENT,"\u291c":e.MO.RELACCENT,"\u291d":e.MO.RELACCENT,"\u291e":e.MO.RELACCENT,"\u291f":e.MO.RELACCENT,"\u2920":e.MO.RELACCENT,"\u2921":e.MO.RELSTRETCH,"\u2922":e.MO.RELSTRETCH,"\u2923":e.MO.REL,"\u2924":e.MO.REL,"\u2925":e.MO.REL,"\u2926":e.MO.REL,"\u2927":e.MO.REL,"\u2928":e.MO.REL,"\u2929":e.MO.REL,"\u292a":e.MO.REL,"\u292b":e.MO.REL,"\u292c":e.MO.REL,"\u292d":e.MO.REL,"\u292e":e.MO.REL,"\u292f":e.MO.REL,"\u2930":e.MO.REL,"\u2931":e.MO.REL,"\u2932":e.MO.REL,"\u2933":e.MO.RELACCENT,"\u2934":e.MO.REL,"\u2935":e.MO.REL,"\u2936":e.MO.REL,"\u2937":e.MO.REL,"\u2938":e.MO.REL,"\u2939":e.MO.REL,"\u293a":e.MO.RELACCENT,"\u293b":e.MO.RELACCENT,"\u293c":e.MO.RELACCENT,"\u293d":e.MO.RELACCENT,"\u293e":e.MO.REL,"\u293f":e.MO.REL,"\u2940":e.MO.REL,"\u2941":e.MO.REL,"\u2942":e.MO.RELACCENT,"\u2943":e.MO.RELACCENT,"\u2944":e.MO.RELACCENT,"\u2945":e.MO.RELACCENT,"\u2946":e.MO.RELACCENT,"\u2947":e.MO.RELACCENT,"\u2948":e.MO.RELACCENT,"\u2949":e.MO.REL,"\u294a":e.MO.RELACCENT,"\u294b":e.MO.RELACCENT,"\u294c":e.MO.REL,"\u294d":e.MO.REL,"\u294e":e.MO.WIDEREL,"\u294f":e.MO.RELSTRETCH,"\u2950":e.MO.WIDEREL,"\u2951":e.MO.RELSTRETCH,"\u2952":e.MO.WIDEREL,"\u2953":e.MO.WIDEREL,"\u2954":e.MO.RELSTRETCH,"\u2955":e.MO.RELSTRETCH,"\u2956":e.MO.RELSTRETCH,"\u2957":e.MO.RELSTRETCH,"\u2958":e.MO.RELSTRETCH,"\u2959":e.MO.RELSTRETCH,"\u295a":e.MO.WIDEREL,"\u295b":e.MO.WIDEREL,"\u295c":e.MO.RELSTRETCH,"\u295d":e.MO.RELSTRETCH,"\u295e":e.MO.WIDEREL,"\u295f":e.MO.WIDEREL,"\u2960":e.MO.RELSTRETCH,"\u2961":e.MO.RELSTRETCH,"\u2962":e.MO.RELACCENT,"\u2963":e.MO.REL,"\u2964":e.MO.RELACCENT,"\u2965":e.MO.REL,"\u2966":e.MO.RELACCENT,"\u2967":e.MO.RELACCENT,"\u2968":e.MO.RELACCENT,"\u2969":e.MO.RELACCENT,"\u296a":e.MO.RELACCENT,"\u296b":e.MO.RELACCENT,"\u296c":e.MO.RELACCENT,"\u296d":e.MO.RELACCENT,"\u296e":e.MO.RELSTRETCH,"\u296f":e.MO.RELSTRETCH,"\u2970":e.MO.RELACCENT,"\u2971":e.MO.RELACCENT,"\u2972":e.MO.RELACCENT,"\u2973":e.MO.RELACCENT,"\u2974":e.MO.RELACCENT,"\u2975":e.MO.RELACCENT,"\u2976":e.MO.RELACCENT,"\u2977":e.MO.RELACCENT,"\u2978":e.MO.RELACCENT,"\u2979":e.MO.RELACCENT,"\u297a":e.MO.RELACCENT,"\u297b":e.MO.RELACCENT,"\u297c":e.MO.RELACCENT,"\u297d":e.MO.RELACCENT,"\u297e":e.MO.REL,"\u297f":e.MO.REL,"\u2981":e.MO.BIN3,"\u2982":e.MO.BIN3,"\u2999":e.MO.BIN3,"\u299a":e.MO.BIN3,"\u299b":e.MO.BIN3,"\u299c":e.MO.BIN3,"\u299d":e.MO.BIN3,"\u299e":e.MO.BIN3,"\u299f":e.MO.BIN3,"\u29a0":e.MO.BIN3,"\u29a1":e.MO.BIN3,"\u29a2":e.MO.BIN3,"\u29a3":e.MO.BIN3,"\u29a4":e.MO.BIN3,"\u29a5":e.MO.BIN3,"\u29a6":e.MO.BIN3,"\u29a7":e.MO.BIN3,"\u29a8":e.MO.BIN3,"\u29a9":e.MO.BIN3,"\u29aa":e.MO.BIN3,"\u29ab":e.MO.BIN3,"\u29ac":e.MO.BIN3,"\u29ad":e.MO.BIN3,"\u29ae":e.MO.BIN3,"\u29af":e.MO.BIN3,"\u29b0":e.MO.BIN3,"\u29b1":e.MO.BIN3,"\u29b2":e.MO.BIN3,"\u29b3":e.MO.BIN3,"\u29b4":e.MO.BIN3,"\u29b5":e.MO.BIN3,"\u29b6":e.MO.BIN4,"\u29b7":e.MO.BIN4,"\u29b8":e.MO.BIN4,"\u29b9":e.MO.BIN4,"\u29ba":e.MO.BIN4,"\u29bb":e.MO.BIN4,"\u29bc":e.MO.BIN4,"\u29bd":e.MO.BIN4,"\u29be":e.MO.BIN4,"\u29bf":e.MO.BIN4,"\u29c0":e.MO.REL,"\u29c1":e.MO.REL,"\u29c2":e.MO.BIN3,"\u29c3":e.MO.BIN3,"\u29c4":e.MO.BIN4,"\u29c5":e.MO.BIN4,"\u29c6":e.MO.BIN4,"\u29c7":e.MO.BIN4,"\u29c8":e.MO.BIN4,"\u29c9":e.MO.BIN3,"\u29ca":e.MO.BIN3,"\u29cb":e.MO.BIN3,"\u29cc":e.MO.BIN3,"\u29cd":e.MO.BIN3,"\u29ce":e.MO.REL,"\u29cf":e.MO.REL,"\u29cf\u0338":e.MO.REL,"\u29d0":e.MO.REL,"\u29d0\u0338":e.MO.REL,"\u29d1":e.MO.REL,"\u29d2":e.MO.REL,"\u29d3":e.MO.REL,"\u29d4":e.MO.REL,"\u29d5":e.MO.REL,"\u29d6":e.MO.BIN4,"\u29d7":e.MO.BIN4,"\u29d8":e.MO.BIN3,"\u29d9":e.MO.BIN3,"\u29db":e.MO.BIN3,"\u29dc":e.MO.BIN3,"\u29dd":e.MO.BIN3,"\u29de":e.MO.REL,"\u29df":e.MO.BIN3,"\u29e0":e.MO.BIN3,"\u29e1":e.MO.REL,"\u29e2":e.MO.BIN4,"\u29e3":e.MO.REL,"\u29e4":e.MO.REL,"\u29e5":e.MO.REL,"\u29e6":e.MO.REL,"\u29e7":e.MO.BIN3,"\u29e8":e.MO.BIN3,"\u29e9":e.MO.BIN3,"\u29ea":e.MO.BIN3,"\u29eb":e.MO.BIN3,"\u29ec":e.MO.BIN3,"\u29ed":e.MO.BIN3,"\u29ee":e.MO.BIN3,"\u29ef":e.MO.BIN3,"\u29f0":e.MO.BIN3,"\u29f1":e.MO.BIN3,"\u29f2":e.MO.BIN3,"\u29f3":e.MO.BIN3,"\u29f4":e.MO.REL,"\u29f5":e.MO.BIN4,"\u29f6":e.MO.BIN4,"\u29f7":e.MO.BIN4,"\u29f8":e.MO.BIN3,"\u29f9":e.MO.BIN3,"\u29fa":e.MO.BIN3,"\u29fb":e.MO.BIN3,"\u29fe":e.MO.BIN4,"\u29ff":e.MO.BIN4,"\u2a1d":e.MO.BIN3,"\u2a1e":e.MO.BIN3,"\u2a1f":e.MO.BIN3,"\u2a20":e.MO.BIN3,"\u2a21":e.MO.BIN3,"\u2a22":e.MO.BIN4,"\u2a23":e.MO.BIN4,"\u2a24":e.MO.BIN4,"\u2a25":e.MO.BIN4,"\u2a26":e.MO.BIN4,"\u2a27":e.MO.BIN4,"\u2a28":e.MO.BIN4,"\u2a29":e.MO.BIN4,"\u2a2a":e.MO.BIN4,"\u2a2b":e.MO.BIN4,"\u2a2c":e.MO.BIN4,"\u2a2d":e.MO.BIN4,"\u2a2e":e.MO.BIN4,"\u2a2f":e.MO.BIN4,"\u2a30":e.MO.BIN4,"\u2a31":e.MO.BIN4,"\u2a32":e.MO.BIN4,"\u2a33":e.MO.BIN4,"\u2a34":e.MO.BIN4,"\u2a35":e.MO.BIN4,"\u2a36":e.MO.BIN4,"\u2a37":e.MO.BIN4,"\u2a38":e.MO.BIN4,"\u2a39":e.MO.BIN4,"\u2a3a":e.MO.BIN4,"\u2a3b":e.MO.BIN4,"\u2a3c":e.MO.BIN4,"\u2a3d":e.MO.BIN4,"\u2a3e":e.MO.BIN4,"\u2a3f":e.MO.BIN4,"\u2a40":e.MO.BIN4,"\u2a41":e.MO.BIN4,"\u2a42":e.MO.BIN4,"\u2a43":e.MO.BIN4,"\u2a44":e.MO.BIN4,"\u2a45":e.MO.BIN4,"\u2a46":e.MO.BIN4,"\u2a47":e.MO.BIN4,"\u2a48":e.MO.BIN4,"\u2a49":e.MO.BIN4,"\u2a4a":e.MO.BIN4,"\u2a4b":e.MO.BIN4,"\u2a4c":e.MO.BIN4,"\u2a4d":e.MO.BIN4,"\u2a4e":e.MO.BIN4,"\u2a4f":e.MO.BIN4,"\u2a50":e.MO.BIN4,"\u2a51":e.MO.BIN4,"\u2a52":e.MO.BIN4,"\u2a53":e.MO.BIN4,"\u2a54":e.MO.BIN4,"\u2a55":e.MO.BIN4,"\u2a56":e.MO.BIN4,"\u2a57":e.MO.BIN4,"\u2a58":e.MO.BIN4,"\u2a59":e.MO.REL,"\u2a5a":e.MO.BIN4,"\u2a5b":e.MO.BIN4,"\u2a5c":e.MO.BIN4,"\u2a5d":e.MO.BIN4,"\u2a5e":e.MO.BIN4,"\u2a5f":e.MO.BIN4,"\u2a60":e.MO.BIN4,"\u2a61":e.MO.BIN4,"\u2a62":e.MO.BIN4,"\u2a63":e.MO.BIN4,"\u2a64":e.MO.BIN4,"\u2a65":e.MO.BIN4,"\u2a66":e.MO.REL,"\u2a67":e.MO.REL,"\u2a68":e.MO.REL,"\u2a69":e.MO.REL,"\u2a6a":e.MO.REL,"\u2a6b":e.MO.REL,"\u2a6c":e.MO.REL,"\u2a6d":e.MO.REL,"\u2a6e":e.MO.REL,"\u2a6f":e.MO.REL,"\u2a70":e.MO.REL,"\u2a71":e.MO.BIN4,"\u2a72":e.MO.BIN4,"\u2a73":e.MO.REL,"\u2a74":e.MO.REL,"\u2a75":e.MO.REL,"\u2a76":e.MO.REL,"\u2a77":e.MO.REL,"\u2a78":e.MO.REL,"\u2a79":e.MO.REL,"\u2a7a":e.MO.REL,"\u2a7b":e.MO.REL,"\u2a7c":e.MO.REL,"\u2a7d":e.MO.REL,"\u2a7d\u0338":e.MO.REL,"\u2a7e":e.MO.REL,"\u2a7e\u0338":e.MO.REL,"\u2a7f":e.MO.REL,"\u2a80":e.MO.REL,"\u2a81":e.MO.REL,"\u2a82":e.MO.REL,"\u2a83":e.MO.REL,"\u2a84":e.MO.REL,"\u2a85":e.MO.REL,"\u2a86":e.MO.REL,"\u2a87":e.MO.REL,"\u2a88":e.MO.REL,"\u2a89":e.MO.REL,"\u2a8a":e.MO.REL,"\u2a8b":e.MO.REL,"\u2a8c":e.MO.REL,"\u2a8d":e.MO.REL,"\u2a8e":e.MO.REL,"\u2a8f":e.MO.REL,"\u2a90":e.MO.REL,"\u2a91":e.MO.REL,"\u2a92":e.MO.REL,"\u2a93":e.MO.REL,"\u2a94":e.MO.REL,"\u2a95":e.MO.REL,"\u2a96":e.MO.REL,"\u2a97":e.MO.REL,"\u2a98":e.MO.REL,"\u2a99":e.MO.REL,"\u2a9a":e.MO.REL,"\u2a9b":e.MO.REL,"\u2a9c":e.MO.REL,"\u2a9d":e.MO.REL,"\u2a9e":e.MO.REL,"\u2a9f":e.MO.REL,"\u2aa0":e.MO.REL,"\u2aa1":e.MO.REL,"\u2aa1\u0338":e.MO.REL,"\u2aa2":e.MO.REL,"\u2aa2\u0338":e.MO.REL,"\u2aa3":e.MO.REL,"\u2aa4":e.MO.REL,"\u2aa5":e.MO.REL,"\u2aa6":e.MO.REL,"\u2aa7":e.MO.REL,"\u2aa8":e.MO.REL,"\u2aa9":e.MO.REL,"\u2aaa":e.MO.REL,"\u2aab":e.MO.REL,"\u2aac":e.MO.REL,"\u2aad":e.MO.REL,"\u2aae":e.MO.REL,"\u2aaf":e.MO.REL,"\u2aaf\u0338":e.MO.REL,"\u2ab0":e.MO.REL,"\u2ab0\u0338":e.MO.REL,"\u2ab1":e.MO.REL,"\u2ab2":e.MO.REL,"\u2ab3":e.MO.REL,"\u2ab4":e.MO.REL,"\u2ab5":e.MO.REL,"\u2ab6":e.MO.REL,"\u2ab7":e.MO.REL,"\u2ab8":e.MO.REL,"\u2ab9":e.MO.REL,"\u2aba":e.MO.REL,"\u2abb":e.MO.REL,"\u2abc":e.MO.REL,"\u2abd":e.MO.REL,"\u2abe":e.MO.REL,"\u2abf":e.MO.REL,"\u2ac0":e.MO.REL,"\u2ac1":e.MO.REL,"\u2ac2":e.MO.REL,"\u2ac3":e.MO.REL,"\u2ac4":e.MO.REL,"\u2ac5":e.MO.REL,"\u2ac6":e.MO.REL,"\u2ac7":e.MO.REL,"\u2ac8":e.MO.REL,"\u2ac9":e.MO.REL,"\u2aca":e.MO.REL,"\u2acb":e.MO.REL,"\u2acc":e.MO.REL,"\u2acd":e.MO.REL,"\u2ace":e.MO.REL,"\u2acf":e.MO.REL,"\u2ad0":e.MO.REL,"\u2ad1":e.MO.REL,"\u2ad2":e.MO.REL,"\u2ad3":e.MO.REL,"\u2ad4":e.MO.REL,"\u2ad5":e.MO.REL,"\u2ad6":e.MO.REL,"\u2ad7":e.MO.REL,"\u2ad8":e.MO.REL,"\u2ad9":e.MO.REL,"\u2ada":e.MO.REL,"\u2adb":e.MO.REL,"\u2add":e.MO.REL,"\u2add\u0338":e.MO.REL,"\u2ade":e.MO.REL,"\u2adf":e.MO.REL,"\u2ae0":e.MO.REL,"\u2ae1":e.MO.REL,"\u2ae2":e.MO.REL,"\u2ae3":e.MO.REL,"\u2ae4":e.MO.REL,"\u2ae5":e.MO.REL,"\u2ae6":e.MO.REL,"\u2ae7":e.MO.REL,"\u2ae8":e.MO.REL,"\u2ae9":e.MO.REL,"\u2aea":e.MO.REL,"\u2aeb":e.MO.REL,"\u2aec":e.MO.REL,"\u2aed":e.MO.REL,"\u2aee":e.MO.REL,"\u2aef":e.MO.REL,"\u2af0":e.MO.REL,"\u2af1":e.MO.REL,"\u2af2":e.MO.REL,"\u2af3":e.MO.REL,"\u2af4":e.MO.BIN4,"\u2af5":e.MO.BIN4,"\u2af6":e.MO.BIN4,"\u2af7":e.MO.REL,"\u2af8":e.MO.REL,"\u2af9":e.MO.REL,"\u2afa":e.MO.REL,"\u2afb":e.MO.BIN4,"\u2afd":e.MO.BIN4,"\u2afe":e.MO.BIN3,"\u2b45":e.MO.RELSTRETCH,"\u2b46":e.MO.RELSTRETCH,"\u3008":e.MO.OPEN,"\u3009":e.MO.CLOSE,"\ufe37":e.MO.WIDEACCENT,"\ufe38":e.MO.WIDEACCENT}},e.OPTABLE.infix["^"]=e.MO.WIDEREL,e.OPTABLE.infix._=e.MO.WIDEREL,e.OPTABLE.infix["\u2adc"]=e.MO.REL},9259:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},s=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s};Object.defineProperty(e,"__esModule",{value:!0}),e.SerializedMmlVisitor=e.toEntity=e.DATAMJX=void 0;var a=r(6325),l=r(9007),c=r(450);e.DATAMJX="data-mjx-";e.toEntity=function(t){return"&#x"+t.codePointAt(0).toString(16).toUpperCase()+";"};var u=function(t){function r(){return null!==t&&t.apply(this,arguments)||this}return o(r,t),r.prototype.visitTree=function(t){return this.visitNode(t,"")},r.prototype.visitTextNode=function(t,e){return this.quoteHTML(t.getText())},r.prototype.visitXMLNode=function(t,e){return e+t.getSerializedXML()},r.prototype.visitInferredMrowNode=function(t,e){var r,n,o=[];try{for(var s=i(t.childNodes),a=s.next();!a.done;a=s.next()){var l=a.value;o.push(this.visitNode(l,e))}}catch(t){r={error:t}}finally{try{a&&!a.done&&(n=s.return)&&n.call(s)}finally{if(r)throw r.error}}return o.join("\n")},r.prototype.visitTeXAtomNode=function(t,e){var r=this.childNodeMml(t,e+" ","\n");return e+""+(r.match(/\S/)?"\n"+r+e:"")+""},r.prototype.visitAnnotationNode=function(t,e){return e+""+this.childNodeMml(t,"","")+""},r.prototype.visitDefault=function(t,e){var r=t.kind,n=s(t.isToken||0===t.childNodes.length?["",""]:["\n",e],2),o=n[0],i=n[1],a=this.childNodeMml(t,e+" ",o);return e+"<"+r+this.getAttributes(t)+">"+(a.match(/\S/)?o+a+i:"")+""},r.prototype.childNodeMml=function(t,e,r){var n,o,s="";try{for(var a=i(t.childNodes),l=a.next();!l.done;l=a.next()){var c=l.value;s+=this.visitNode(c,e)+r}}catch(t){n={error:t}}finally{try{l&&!l.done&&(o=a.return)&&o.call(a)}finally{if(n)throw n.error}}return s},r.prototype.getAttributes=function(t){var e,r,n=[],o=this.constructor.defaultAttributes[t.kind]||{},s=Object.assign({},o,this.getDataAttributes(t),t.attributes.getAllAttributes()),a=this.constructor.variants;s.hasOwnProperty("mathvariant")&&a.hasOwnProperty(s.mathvariant)&&(s.mathvariant=a[s.mathvariant]);try{for(var l=i(Object.keys(s)),c=l.next();!c.done;c=l.next()){var u=c.value,p=String(s[u]);void 0!==p&&n.push(u+'="'+this.quoteHTML(p)+'"')}}catch(t){e={error:t}}finally{try{c&&!c.done&&(r=l.return)&&r.call(l)}finally{if(e)throw e.error}}return n.length?" "+n.join(" "):""},r.prototype.getDataAttributes=function(t){var e={},r=t.attributes.getExplicit("mathvariant"),n=this.constructor.variants;r&&n.hasOwnProperty(r)&&this.setDataAttribute(e,"variant",r),t.getProperty("variantForm")&&this.setDataAttribute(e,"alternate","1"),t.getProperty("pseudoscript")&&this.setDataAttribute(e,"pseudoscript","true"),!1===t.getProperty("autoOP")&&this.setDataAttribute(e,"auto-op","false");var o=t.getProperty("scriptalign");o&&this.setDataAttribute(e,"script-align",o);var i=t.getProperty("texClass");if(void 0!==i){var s=!0;if(i===l.TEXCLASS.OP&&t.isKind("mi")){var a=t.getText();s=!(a.length>1&&a.match(c.MmlMi.operatorName))}s&&this.setDataAttribute(e,"texclass",i<0?"NONE":l.TEXCLASSNAMES[i])}return t.getProperty("scriptlevel")&&!1===t.getProperty("useHeight")&&this.setDataAttribute(e,"smallmatrix","true"),e},r.prototype.setDataAttribute=function(t,r,n){t[e.DATAMJX+r]=n},r.prototype.quoteHTML=function(t){return t.replace(/&/g,"&").replace(//g,">").replace(/\"/g,""").replace(/[\uD800-\uDBFF]./g,e.toEntity).replace(/[\u0080-\uD7FF\uE000-\uFFFF]/g,e.toEntity)},r.variants={"-tex-calligraphic":"script","-tex-bold-calligraphic":"bold-script","-tex-oldstyle":"normal","-tex-bold-oldstyle":"bold","-tex-mathit":"italic"},r.defaultAttributes={math:{xmlns:"http://www.w3.org/1998/Math/MathML"}},r}(a.MmlVisitor);e.SerializedMmlVisitor=u},2975:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.AbstractOutputJax=void 0;var n=r(7233),o=r(7525),i=function(){function t(t){void 0===t&&(t={}),this.adaptor=null;var e=this.constructor;this.options=(0,n.userOptions)((0,n.defaultOptions)({},e.OPTIONS),t),this.postFilters=new o.FunctionList}return Object.defineProperty(t.prototype,"name",{get:function(){return this.constructor.NAME},enumerable:!1,configurable:!0}),t.prototype.setAdaptor=function(t){this.adaptor=t},t.prototype.initialize=function(){},t.prototype.reset=function(){for(var t=[],e=0;e=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},n=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},o=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.AbstractEmptyNode=e.AbstractNode=void 0;var s=function(){function t(t,e,r){var n,o;void 0===e&&(e={}),void 0===r&&(r=[]),this.factory=t,this.parent=null,this.properties={},this.childNodes=[];try{for(var s=i(Object.keys(e)),a=s.next();!a.done;a=s.next()){var l=a.value;this.setProperty(l,e[l])}}catch(t){n={error:t}}finally{try{a&&!a.done&&(o=s.return)&&o.call(s)}finally{if(n)throw n.error}}r.length&&this.setChildren(r)}return Object.defineProperty(t.prototype,"kind",{get:function(){return"unknown"},enumerable:!1,configurable:!0}),t.prototype.setProperty=function(t,e){this.properties[t]=e},t.prototype.getProperty=function(t){return this.properties[t]},t.prototype.getPropertyNames=function(){return Object.keys(this.properties)},t.prototype.getAllProperties=function(){return this.properties},t.prototype.removeProperty=function(){for(var t,e,r=[],n=0;n=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},o=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},i=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},s=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},a=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.HTMLDocument=void 0;var l=r(5722),c=r(7233),u=r(3363),p=r(3335),h=r(5138),f=r(4474),d=function(t){function e(e,r,n){var o=this,i=s((0,c.separateOptions)(n,h.HTMLDomStrings.OPTIONS),2),a=i[0],l=i[1];return(o=t.call(this,e,r,a)||this).domStrings=o.options.DomStrings||new h.HTMLDomStrings(l),o.domStrings.adaptor=r,o.styles=[],o}return o(e,t),e.prototype.findPosition=function(t,e,r,n){var o,i,l=this.adaptor;try{for(var c=a(n[t]),u=c.next();!u.done;u=c.next()){var p=u.value,h=s(p,2),f=h[0],d=h[1];if(e<=d&&"#text"===l.kind(f))return{node:f,n:Math.max(e,0),delim:r};e-=d}}catch(t){o={error:t}}finally{try{u&&!u.done&&(i=c.return)&&i.call(c)}finally{if(o)throw o.error}}return{node:null,n:0,delim:r}},e.prototype.mathItem=function(t,e,r){var n=t.math,o=this.findPosition(t.n,t.start.n,t.open,r),i=this.findPosition(t.n,t.end.n,t.close,r);return new this.options.MathItem(n,e,t.display,o,i)},e.prototype.findMath=function(t){var e,r,n,o,i,l,u,p,h;if(!this.processed.isSet("findMath")){this.adaptor.document=this.document,t=(0,c.userOptions)({elements:this.options.elements||[this.adaptor.body(this.document)]},t);try{for(var f=a(this.adaptor.getElements(t.elements,this.document)),d=f.next();!d.done;d=f.next()){var m=d.value,y=s([null,null],2),g=y[0],b=y[1];try{for(var v=(n=void 0,a(this.inputJax)),_=v.next();!_.done;_=v.next()){var S=_.value,M=new this.options.MathList;if(S.processStrings){null===g&&(g=(i=s(this.domStrings.find(m),2))[0],b=i[1]);try{for(var O=(l=void 0,a(S.findMath(g))),x=O.next();!x.done;x=O.next()){var E=x.value;M.push(this.mathItem(E,S,b))}}catch(t){l={error:t}}finally{try{x&&!x.done&&(u=O.return)&&u.call(O)}finally{if(l)throw l.error}}}else try{for(var A=(p=void 0,a(S.findMath(m))),C=A.next();!C.done;C=A.next()){E=C.value;var T=new this.options.MathItem(E.math,S,E.display,E.start,E.end);M.push(T)}}catch(t){p={error:t}}finally{try{C&&!C.done&&(h=A.return)&&h.call(A)}finally{if(p)throw p.error}}this.math.merge(M)}}catch(t){n={error:t}}finally{try{_&&!_.done&&(o=v.return)&&o.call(v)}finally{if(n)throw n.error}}}}catch(t){e={error:t}}finally{try{d&&!d.done&&(r=f.return)&&r.call(f)}finally{if(e)throw e.error}}this.processed.set("findMath")}return this},e.prototype.updateDocument=function(){return this.processed.isSet("updateDocument")||(this.addPageElements(),this.addStyleSheet(),t.prototype.updateDocument.call(this),this.processed.set("updateDocument")),this},e.prototype.addPageElements=function(){var t=this.adaptor.body(this.document),e=this.documentPageElements();e&&this.adaptor.append(t,e)},e.prototype.addStyleSheet=function(){var t=this.documentStyleSheet(),e=this.adaptor;if(t&&!e.parent(t)){var r=e.head(this.document),n=this.findSheet(r,e.getAttribute(t,"id"));n?e.replace(t,n):e.append(r,t)}},e.prototype.findSheet=function(t,e){var r,n;if(e)try{for(var o=a(this.adaptor.tags(t,"style")),i=o.next();!i.done;i=o.next()){var s=i.value;if(this.adaptor.getAttribute(s,"id")===e)return s}}catch(t){r={error:t}}finally{try{i&&!i.done&&(n=o.return)&&n.call(o)}finally{if(r)throw r.error}}return null},e.prototype.removeFromDocument=function(t){var e,r;if(void 0===t&&(t=!1),this.processed.isSet("updateDocument"))try{for(var n=a(this.math),o=n.next();!o.done;o=n.next()){var i=o.value;i.state()>=f.STATE.INSERTED&&i.state(f.STATE.TYPESET,t)}}catch(t){e={error:t}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(e)throw e.error}}return this.processed.clear("updateDocument"),this},e.prototype.documentStyleSheet=function(){return this.outputJax.styleSheet(this)},e.prototype.documentPageElements=function(){return this.outputJax.pageElements(this)},e.prototype.addStyles=function(t){this.styles.push(t)},e.prototype.getStyles=function(){return this.styles},e.KIND="HTML",e.OPTIONS=i(i({},l.AbstractMathDocument.OPTIONS),{renderActions:(0,c.expandable)(i(i({},l.AbstractMathDocument.OPTIONS.renderActions),{styles:[f.STATE.INSERTED+1,"","updateStyleSheet",!1]})),MathList:p.HTMLMathList,MathItem:u.HTMLMathItem,DomStrings:null}),e}(l.AbstractMathDocument);e.HTMLDocument=d},5138:function(t,e,r){var n=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s};Object.defineProperty(e,"__esModule",{value:!0}),e.HTMLDomStrings=void 0;var o=r(7233),i=function(){function t(t){void 0===t&&(t=null);var e=this.constructor;this.options=(0,o.userOptions)((0,o.defaultOptions)({},e.OPTIONS),t),this.init(),this.getPatterns()}return t.prototype.init=function(){this.strings=[],this.string="",this.snodes=[],this.nodes=[],this.stack=[]},t.prototype.getPatterns=function(){var t=(0,o.makeArray)(this.options.skipHtmlTags),e=(0,o.makeArray)(this.options.ignoreHtmlClass),r=(0,o.makeArray)(this.options.processHtmlClass);this.skipHtmlTags=new RegExp("^(?:"+t.join("|")+")$","i"),this.ignoreHtmlClass=new RegExp("(?:^| )(?:"+e.join("|")+")(?: |$)"),this.processHtmlClass=new RegExp("(?:^| )(?:"+r+")(?: |$)")},t.prototype.pushString=function(){this.string.match(/\S/)&&(this.strings.push(this.string),this.nodes.push(this.snodes)),this.string="",this.snodes=[]},t.prototype.extendString=function(t,e){this.snodes.push([t,e.length]),this.string+=e},t.prototype.handleText=function(t,e){return e||this.extendString(t,this.adaptor.value(t)),this.adaptor.next(t)},t.prototype.handleTag=function(t,e){if(!e){var r=this.options.includeHtmlTags[this.adaptor.kind(t)];this.extendString(t,r)}return this.adaptor.next(t)},t.prototype.handleContainer=function(t,e){this.pushString();var r=this.adaptor.getAttribute(t,"class")||"",n=this.adaptor.kind(t)||"",o=this.processHtmlClass.exec(r),i=t;return!this.adaptor.firstChild(t)||this.adaptor.getAttribute(t,"data-MJX")||!o&&this.skipHtmlTags.exec(n)?i=this.adaptor.next(t):(this.adaptor.next(t)&&this.stack.push([this.adaptor.next(t),e]),i=this.adaptor.firstChild(t),e=(e||this.ignoreHtmlClass.exec(r))&&!o),[i,e]},t.prototype.handleOther=function(t,e){return this.pushString(),this.adaptor.next(t)},t.prototype.find=function(t){var e,r;this.init();for(var o=this.adaptor.next(t),i=!1,s=this.options.includeHtmlTags;t&&t!==o;){var a=this.adaptor.kind(t);"#text"===a?t=this.handleText(t,i):s.hasOwnProperty(a)?t=this.handleTag(t,i):a?(t=(e=n(this.handleContainer(t,i),2))[0],i=e[1]):t=this.handleOther(t,i),!t&&this.stack.length&&(this.pushString(),t=(r=n(this.stack.pop(),2))[0],i=r[1])}this.pushString();var l=[this.strings,this.nodes];return this.init(),l},t.OPTIONS={skipHtmlTags:["script","noscript","style","textarea","pre","code","annotation","annotation-xml"],includeHtmlTags:{br:"\n",wbr:"","#comment":""},ignoreHtmlClass:"mathjax_ignore",processHtmlClass:"mathjax_process"},t}();e.HTMLDomStrings=i},3726:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.HTMLHandler=void 0;var i=r(3670),s=r(3683),a=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.documentClass=s.HTMLDocument,e}return o(e,t),e.prototype.handlesDocument=function(t){var e=this.adaptor;if("string"==typeof t)try{t=e.parse(t,"text/html")}catch(t){}return t instanceof e.window.Document||t instanceof e.window.HTMLElement||t instanceof e.window.DocumentFragment},e.prototype.create=function(e,r){var n=this.adaptor;if("string"==typeof e)e=n.parse(e,"text/html");else if(e instanceof n.window.HTMLElement||e instanceof n.window.DocumentFragment){var o=e;e=n.parse("","text/html"),n.append(n.body(e),o)}return t.prototype.create.call(this,e,r)},e}(i.AbstractHandler);e.HTMLHandler=a},3363:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.HTMLMathItem=void 0;var i=r(4474),s=function(t){function e(e,r,n,o,i){return void 0===n&&(n=!0),void 0===o&&(o={node:null,n:0,delim:""}),void 0===i&&(i={node:null,n:0,delim:""}),t.call(this,e,r,n,o,i)||this}return o(e,t),Object.defineProperty(e.prototype,"adaptor",{get:function(){return this.inputJax.adaptor},enumerable:!1,configurable:!0}),e.prototype.updateDocument=function(t){if(this.state()=i.STATE.TYPESET){var e=this.adaptor,r=this.start.node,n=e.text("");if(t){var o=this.start.delim+this.math+this.end.delim;if(this.inputJax.processStrings)n=e.text(o);else{var s=e.parse(o,"text/html");n=e.firstChild(e.body(s))}}e.parent(r)&&e.replace(n,r),this.start.node=this.end.node=n,this.start.n=this.end.n=0}},e}(i.AbstractMathItem);e.HTMLMathItem=s},3335:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.HTMLMathList=void 0;var i=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e}(r(9e3).AbstractMathList);e.HTMLMathList=i},2892:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s};Object.defineProperty(e,"__esModule",{value:!0}),e.MathML=void 0;var s=r(9206),a=r(7233),l=r(7525),c=r(625),u=r(2769),p=function(t){function e(e){void 0===e&&(e={});var r=this,n=i((0,a.separateOptions)(e,c.FindMathML.OPTIONS,u.MathMLCompile.OPTIONS),3),o=n[0],s=n[1],p=n[2];return(r=t.call(this,o)||this).findMathML=r.options.FindMathML||new c.FindMathML(s),r.mathml=r.options.MathMLCompile||new u.MathMLCompile(p),r.mmlFilters=new l.FunctionList,r}return o(e,t),e.prototype.setAdaptor=function(e){t.prototype.setAdaptor.call(this,e),this.findMathML.adaptor=e,this.mathml.adaptor=e},e.prototype.setMmlFactory=function(e){t.prototype.setMmlFactory.call(this,e),this.mathml.setMmlFactory(e)},Object.defineProperty(e.prototype,"processStrings",{get:function(){return!1},enumerable:!1,configurable:!0}),e.prototype.compile=function(t,e){var r=t.start.node;if(!r||!t.end.node||this.options.forceReparse||"#text"===this.adaptor.kind(r)){var n=this.executeFilters(this.preFilters,t,e,(t.math||"").trim()),o=this.checkForErrors(this.adaptor.parse(n,"text/"+this.options.parseAs)),i=this.adaptor.body(o);1!==this.adaptor.childNodes(i).length&&this.error("MathML must consist of a single element"),r=this.adaptor.remove(this.adaptor.firstChild(i)),"math"!==this.adaptor.kind(r).replace(/^[a-z]+:/,"")&&this.error("MathML must be formed by a element, not <"+this.adaptor.kind(r)+">")}return r=this.executeFilters(this.mmlFilters,t,e,r),this.executeFilters(this.postFilters,t,e,this.mathml.compile(r))},e.prototype.checkForErrors=function(t){var e=this.adaptor.tags(this.adaptor.body(t),"parsererror")[0];return e&&(""===this.adaptor.textContent(e)&&this.error("Error processing MathML"),this.options.parseError.call(this,e)),t},e.prototype.error=function(t){throw new Error(t)},e.prototype.findMath=function(t){return this.findMathML.findMath(t)},e.NAME="MathML",e.OPTIONS=(0,a.defaultOptions)({parseAs:"html",forceReparse:!1,FindMathML:null,MathMLCompile:null,parseError:function(t){this.error(this.adaptor.textContent(t).replace(/\n.*/g,""))}},s.AbstractInputJax.OPTIONS),e}(s.AbstractInputJax);e.MathML=p},625:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.FindMathML=void 0;var s=r(3494),a="http://www.w3.org/1998/Math/MathML",l=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.findMath=function(t){var e=new Set;this.findMathNodes(t,e),this.findMathPrefixed(t,e);var r=this.adaptor.root(this.adaptor.document);return"html"===this.adaptor.kind(r)&&0===e.size&&this.findMathNS(t,e),this.processMath(e)},e.prototype.findMathNodes=function(t,e){var r,n;try{for(var o=i(this.adaptor.tags(t,"math")),s=o.next();!s.done;s=o.next()){var a=s.value;e.add(a)}}catch(t){r={error:t}}finally{try{s&&!s.done&&(n=o.return)&&n.call(o)}finally{if(r)throw r.error}}},e.prototype.findMathPrefixed=function(t,e){var r,n,o,s,l=this.adaptor.root(this.adaptor.document);try{for(var c=i(this.adaptor.allAttributes(l)),u=c.next();!u.done;u=c.next()){var p=u.value;if("xmlns:"===p.name.substr(0,6)&&p.value===a){var h=p.name.substr(6);try{for(var f=(o=void 0,i(this.adaptor.tags(t,h+":math"))),d=f.next();!d.done;d=f.next()){var m=d.value;e.add(m)}}catch(t){o={error:t}}finally{try{d&&!d.done&&(s=f.return)&&s.call(f)}finally{if(o)throw o.error}}}}}catch(t){r={error:t}}finally{try{u&&!u.done&&(n=c.return)&&n.call(c)}finally{if(r)throw r.error}}},e.prototype.findMathNS=function(t,e){var r,n;try{for(var o=i(this.adaptor.tags(t,"math",a)),s=o.next();!s.done;s=o.next()){var l=s.value;e.add(l)}}catch(t){r={error:t}}finally{try{s&&!s.done&&(n=o.return)&&n.call(o)}finally{if(r)throw r.error}}},e.prototype.processMath=function(t){var e,r,n=[];try{for(var o=i(Array.from(t)),s=o.next();!s.done;s=o.next()){var a=s.value,l="block"===this.adaptor.getAttribute(a,"display")||"display"===this.adaptor.getAttribute(a,"mode"),c={node:a,n:0,delim:""},u={node:a,n:0,delim:""};n.push({math:this.adaptor.outerHTML(a),start:c,end:u,display:l})}}catch(t){e={error:t}}finally{try{s&&!s.done&&(r=o.return)&&r.call(o)}finally{if(e)throw e.error}}return n},e.OPTIONS={},e}(s.AbstractFindMath);e.FindMathML=l},2769:function(t,e,r){var n=this&&this.__assign||function(){return n=Object.assign||function(t){for(var e,r=1,n=arguments.length;r=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.MathMLCompile=void 0;var l=r(9007),c=r(7233),u=s(r(5368)),p=function(){function t(t){void 0===t&&(t={});var e=this.constructor;this.options=(0,c.userOptions)((0,c.defaultOptions)({},e.OPTIONS),t)}return t.prototype.setMmlFactory=function(t){this.factory=t},t.prototype.compile=function(t){var e=this.makeNode(t);return e.verifyTree(this.options.verify),e.setInheritedAttributes({},!1,0,!1),e.walkTree(this.markMrows),e},t.prototype.makeNode=function(t){var e,r,n=this.adaptor,o=!1,i=n.kind(t).replace(/^.*:/,""),s=n.getAttribute(t,"data-mjx-texclass")||"";s&&(s=this.filterAttribute("data-mjx-texclass",s)||"");var c=s&&"mrow"===i?"TeXAtom":i;try{for(var u=a(this.filterClassList(n.allClasses(t))),p=u.next();!p.done;p=u.next()){var h=p.value;h.match(/^MJX-TeXAtom-/)&&"mrow"===i?(s=h.substr(12),c="TeXAtom"):"MJX-fixedlimits"===h&&(o=!0)}}catch(t){e={error:t}}finally{try{p&&!p.done&&(r=u.return)&&r.call(u)}finally{if(e)throw e.error}}this.factory.getNodeClass(c)||this.error('Unknown node type "'+c+'"');var f=this.factory.create(c);return"TeXAtom"!==c||"OP"!==s||o||(f.setProperty("movesupsub",!0),f.attributes.setInherited("movablelimits",!0)),s&&(f.texClass=l.TEXCLASS[s],f.setProperty("texClass",f.texClass)),this.addAttributes(f,t),this.checkClass(f,t),this.addChildren(f,t),f},t.prototype.addAttributes=function(t,e){var r,n,o=!1;try{for(var i=a(this.adaptor.allAttributes(e)),s=i.next();!s.done;s=i.next()){var l=s.value,c=l.name,u=this.filterAttribute(c,l.value);if(null!==u&&"xmlns"!==c)if("data-mjx-"===c.substr(0,9))switch(c.substr(9)){case"alternate":t.setProperty("variantForm",!0);break;case"variant":t.attributes.set("mathvariant",u),o=!0;break;case"smallmatrix":t.setProperty("scriptlevel",1),t.setProperty("useHeight",!1);break;case"accent":t.setProperty("mathaccent","true"===u);break;case"auto-op":t.setProperty("autoOP","true"===u);break;case"script-align":t.setProperty("scriptalign",u)}else if("class"!==c){var p=u.toLowerCase();"true"===p||"false"===p?t.attributes.set(c,"true"===p):o&&"mathvariant"===c||t.attributes.set(c,u)}}}catch(t){r={error:t}}finally{try{s&&!s.done&&(n=i.return)&&n.call(i)}finally{if(r)throw r.error}}},t.prototype.filterAttribute=function(t,e){return e},t.prototype.filterClassList=function(t){return t},t.prototype.addChildren=function(t,e){var r,n;if(0!==t.arity){var o=this.adaptor;try{for(var i=a(o.childNodes(e)),s=i.next();!s.done;s=i.next()){var l=s.value,c=o.kind(l);if("#comment"!==c)if("#text"===c)this.addText(t,l);else if(t.isKind("annotation-xml"))t.appendChild(this.factory.create("XML").setXML(l,o));else{var u=t.appendChild(this.makeNode(l));0===u.arity&&o.childNodes(l).length&&(this.options.fixMisplacedChildren?this.addChildren(t,l):u.mError("There should not be children for "+u.kind+" nodes",this.options.verify,!0))}}}catch(t){r={error:t}}finally{try{s&&!s.done&&(n=i.return)&&n.call(i)}finally{if(r)throw r.error}}}},t.prototype.addText=function(t,e){var r=this.adaptor.value(e);(t.isToken||t.getProperty("isChars"))&&t.arity?(t.isToken&&(r=u.translate(r),r=this.trimSpace(r)),t.appendChild(this.factory.create("text").setText(r))):r.match(/\S/)&&this.error('Unexpected text node "'+r+'"')},t.prototype.checkClass=function(t,e){var r,n,o=[];try{for(var i=a(this.filterClassList(this.adaptor.allClasses(e))),s=i.next();!s.done;s=i.next()){var l=s.value;"MJX-"===l.substr(0,4)?"MJX-variant"===l?t.setProperty("variantForm",!0):"MJX-TeXAtom"!==l.substr(0,11)&&t.attributes.set("mathvariant",this.fixCalligraphic(l.substr(3))):o.push(l)}}catch(t){r={error:t}}finally{try{s&&!s.done&&(n=i.return)&&n.call(i)}finally{if(r)throw r.error}}o.length&&t.attributes.set("class",o.join(" "))},t.prototype.fixCalligraphic=function(t){return t.replace(/caligraphic/,"calligraphic")},t.prototype.markMrows=function(t){if(t.isKind("mrow")&&!t.isInferred&&t.childNodes.length>=2){var e=t.childNodes[0],r=t.childNodes[t.childNodes.length-1];e.isKind("mo")&&e.attributes.get("fence")&&e.attributes.get("stretchy")&&r.isKind("mo")&&r.attributes.get("fence")&&r.attributes.get("stretchy")&&(e.childNodes.length&&t.setProperty("open",e.getText()),r.childNodes.length&&t.setProperty("close",r.getText()))}},t.prototype.trimSpace=function(t){return t.replace(/[\t\n\r]/g," ").replace(/^ +/,"").replace(/ +$/,"").replace(/ +/g," ")},t.prototype.error=function(t){throw new Error(t)},t.OPTIONS={MmlFactory:null,fixMisplacedChildren:!0,verify:n({},l.AbstractMmlNode.verifyDefaults),translateEntities:!0},t}();e.MathMLCompile=p},8462:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__assign||function(){return i=Object.assign||function(t){for(var e,r=1,n=arguments.length;r0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},a=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0}),e.TeX=void 0;var l=r(9206),c=r(7233),u=r(7073),p=a(r(4676)),h=a(r(1256)),f=a(r(8417)),d=a(r(3971)),m=a(r(8562)),y=r(6521),g=r(9899);r(2942);var b=function(t){function e(r){void 0===r&&(r={});var n=this,o=s((0,c.separateOptions)(r,e.OPTIONS,u.FindTeX.OPTIONS),3),i=o[0],a=o[1],l=o[2];(n=t.call(this,a)||this).findTeX=n.options.FindTeX||new u.FindTeX(l);var h=n.options.packages,f=n.configuration=e.configure(h),d=n._parseOptions=new m.default(f,[n.options,y.TagsFactory.OPTIONS]);return(0,c.userOptions)(d.options,i),f.config(n),e.tags(d,f),n.postFilters.add(p.default.cleanSubSup,-6),n.postFilters.add(p.default.setInherited,-5),n.postFilters.add(p.default.moveLimits,-4),n.postFilters.add(p.default.cleanStretchy,-3),n.postFilters.add(p.default.cleanAttributes,-2),n.postFilters.add(p.default.combineRelations,-1),n}return o(e,t),e.configure=function(t){var e=new g.ParserConfiguration(t,["tex"]);return e.init(),e},e.tags=function(t,e){y.TagsFactory.addTags(e.tags),y.TagsFactory.setDefault(t.options.tags),t.tags=y.TagsFactory.getDefault(),t.tags.configuration=t},e.prototype.setMmlFactory=function(e){t.prototype.setMmlFactory.call(this,e),this._parseOptions.nodeFactory.setMmlFactory(e)},Object.defineProperty(e.prototype,"parseOptions",{get:function(){return this._parseOptions},enumerable:!1,configurable:!0}),e.prototype.reset=function(t){void 0===t&&(t=0),this.parseOptions.tags.reset(t)},e.prototype.compile=function(t,e){this.parseOptions.clear(),this.executeFilters(this.preFilters,t,e,this.parseOptions);var r,n,o=t.display;this.latex=t.math,this.parseOptions.tags.startEquation(t);try{var i=new f.default(this.latex,{display:o,isInner:!1},this.parseOptions);r=i.mml(),n=i.stack.global}catch(t){if(!(t instanceof d.default))throw t;this.parseOptions.error=!0,r=this.options.formatError(this,t)}return r=this.parseOptions.nodeFactory.create("node","math",[r]),(null==n?void 0:n.indentalign)&&h.default.setAttribute(r,"indentalign",n.indentalign),o&&h.default.setAttribute(r,"display","block"),this.parseOptions.tags.finishEquation(t),this.parseOptions.root=r,this.executeFilters(this.postFilters,t,e,this.parseOptions),this.mathNode=this.parseOptions.root,this.mathNode},e.prototype.findMath=function(t){return this.findTeX.findMath(t)},e.prototype.formatError=function(t){var e=t.message.replace(/\n.*/,"");return this.parseOptions.nodeFactory.create("error",e,t.id,this.latex)},e.NAME="TeX",e.OPTIONS=i(i({},l.AbstractInputJax.OPTIONS),{FindTeX:null,packages:["base"],digits:/^(?:[0-9]+(?:\{,\}[0-9]{3})*(?:\.[0-9]*)?|\.[0-9]+)/,maxBuffer:5120,formatError:function(t,e){return t.formatError(e)}}),e}(l.AbstractInputJax);e.TeX=b},9899:function(t,e,r){var n=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},o=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s};Object.defineProperty(e,"__esModule",{value:!0}),e.ParserConfiguration=e.ConfigurationHandler=e.Configuration=void 0;var i,s=r(7233),a=r(2947),l=r(7525),c=r(8666),u=r(6521),p=function(){function t(t,e,r,n,o,i,s,a,l,c,u,p,h){void 0===e&&(e={}),void 0===r&&(r={}),void 0===n&&(n={}),void 0===o&&(o={}),void 0===i&&(i={}),void 0===s&&(s={}),void 0===a&&(a=[]),void 0===l&&(l=[]),void 0===c&&(c=null),void 0===u&&(u=null),this.name=t,this.handler=e,this.fallback=r,this.items=n,this.tags=o,this.options=i,this.nodes=s,this.preprocessors=a,this.postprocessors=l,this.initMethod=c,this.configMethod=u,this.priority=p,this.parser=h,this.handler=Object.assign({character:[],delimiter:[],macro:[],environment:[]},e)}return t.makeProcessor=function(t,e){return Array.isArray(t)?t:[t,e]},t._create=function(e,r){var n=this;void 0===r&&(r={});var o=r.priority||c.PrioritizedList.DEFAULTPRIORITY,i=r.init?this.makeProcessor(r.init,o):null,s=r.config?this.makeProcessor(r.config,o):null,a=(r.preprocessors||[]).map((function(t){return n.makeProcessor(t,o)})),l=(r.postprocessors||[]).map((function(t){return n.makeProcessor(t,o)})),u=r.parser||"tex";return new t(e,r.handler||{},r.fallback||{},r.items||{},r.tags||{},r.options||{},r.nodes||{},a,l,i,s,o,u)},t.create=function(e,r){void 0===r&&(r={});var n=t._create(e,r);return i.set(e,n),n},t.local=function(e){return void 0===e&&(e={}),t._create("",e)},Object.defineProperty(t.prototype,"init",{get:function(){return this.initMethod?this.initMethod[0]:null},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"config",{get:function(){return this.configMethod?this.configMethod[0]:null},enumerable:!1,configurable:!0}),t}();e.Configuration=p,function(t){var e=new Map;t.set=function(t,r){e.set(t,r)},t.get=function(t){return e.get(t)},t.keys=function(){return e.keys()}}(i=e.ConfigurationHandler||(e.ConfigurationHandler={}));var h=function(){function t(t,e){var r,o,i,s;void 0===e&&(e=["tex"]),this.initMethod=new l.FunctionList,this.configMethod=new l.FunctionList,this.configurations=new c.PrioritizedList,this.parsers=[],this.handlers=new a.SubHandlers,this.items={},this.tags={},this.options={},this.nodes={},this.parsers=e;try{for(var u=n(t.slice().reverse()),p=u.next();!p.done;p=u.next()){var h=p.value;this.addPackage(h)}}catch(t){r={error:t}}finally{try{p&&!p.done&&(o=u.return)&&o.call(u)}finally{if(r)throw r.error}}try{for(var f=n(this.configurations),d=f.next();!d.done;d=f.next()){var m=d.value,y=m.item,g=m.priority;this.append(y,g)}}catch(t){i={error:t}}finally{try{d&&!d.done&&(s=f.return)&&s.call(f)}finally{if(i)throw i.error}}}return t.prototype.init=function(){this.initMethod.execute(this)},t.prototype.config=function(t){var e,r;this.configMethod.execute(this,t);try{for(var o=n(this.configurations),i=o.next();!i.done;i=o.next()){var s=i.value;this.addFilters(t,s.item)}}catch(t){e={error:t}}finally{try{i&&!i.done&&(r=o.return)&&r.call(o)}finally{if(e)throw e.error}}},t.prototype.addPackage=function(t){var e="string"==typeof t?t:t[0],r=this.getPackage(e);r&&this.configurations.add(r,"string"==typeof t?r.priority:t[1])},t.prototype.add=function(t,e,r){var o,i;void 0===r&&(r={});var a=this.getPackage(t);this.append(a),this.configurations.add(a,a.priority),this.init();var l=e.parseOptions;l.nodeFactory.setCreators(a.nodes);try{for(var c=n(Object.keys(a.items)),p=c.next();!p.done;p=c.next()){var h=p.value;l.itemFactory.setNodeClass(h,a.items[h])}}catch(t){o={error:t}}finally{try{p&&!p.done&&(i=c.return)&&i.call(c)}finally{if(o)throw o.error}}u.TagsFactory.addTags(a.tags),(0,s.defaultOptions)(l.options,a.options),(0,s.userOptions)(l.options,r),this.addFilters(e,a),a.config&&a.config(this,e)},t.prototype.getPackage=function(t){var e=i.get(t);if(e&&this.parsers.indexOf(e.parser)<0)throw Error("Package ".concat(t," doesn't target the proper parser"));return e},t.prototype.append=function(t,e){e=e||t.priority,t.initMethod&&this.initMethod.add(t.initMethod[0],t.initMethod[1]),t.configMethod&&this.configMethod.add(t.configMethod[0],t.configMethod[1]),this.handlers.add(t.handler,t.fallback,e),Object.assign(this.items,t.items),Object.assign(this.tags,t.tags),(0,s.defaultOptions)(this.options,t.options),Object.assign(this.nodes,t.nodes)},t.prototype.addFilters=function(t,e){var r,i,s,a;try{for(var l=n(e.preprocessors),c=l.next();!c.done;c=l.next()){var u=o(c.value,2),p=u[0],h=u[1];t.preFilters.add(p,h)}}catch(t){r={error:t}}finally{try{c&&!c.done&&(i=l.return)&&i.call(l)}finally{if(r)throw r.error}}try{for(var f=n(e.postprocessors),d=f.next();!d.done;d=f.next()){var m=o(d.value,2),y=m[0];h=m[1];t.postFilters.add(y,h)}}catch(t){s={error:t}}finally{try{d&&!d.done&&(a=f.return)&&a.call(f)}finally{if(s)throw s.error}}},t}();e.ParserConfiguration=h},4676:function(t,e,r){var n=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},o=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0});var i,s=r(9007),a=o(r(1256));!function(t){t.cleanStretchy=function(t){var e,r,o=t.data;try{for(var i=n(o.getList("fixStretchy")),s=i.next();!s.done;s=i.next()){var l=s.value;if(a.default.getProperty(l,"fixStretchy")){var c=a.default.getForm(l);c&&c[3]&&c[3].stretchy&&a.default.setAttribute(l,"stretchy",!1);var u=l.parent;if(!(a.default.getTexClass(l)||c&&c[2])){var p=o.nodeFactory.create("node","TeXAtom",[l]);u.replaceChild(p,l),p.inheritAttributesFrom(l)}a.default.removeProperties(l,"fixStretchy")}}}catch(t){e={error:t}}finally{try{s&&!s.done&&(r=i.return)&&r.call(i)}finally{if(e)throw e.error}}},t.cleanAttributes=function(t){t.data.root.walkTree((function(t,e){var r,o,i=t.attributes;if(i){var s=new Set((i.get("mjx-keep-attrs")||"").split(/ /));delete i.getAllAttributes()["mjx-keep-attrs"];try{for(var a=n(i.getExplicitNames()),l=a.next();!l.done;l=a.next()){var c=l.value;s.has(c)||i.attributes[c]!==t.attributes.getInherited(c)||delete i.attributes[c]}}catch(t){r={error:t}}finally{try{l&&!l.done&&(o=a.return)&&o.call(a)}finally{if(r)throw r.error}}}}),{})},t.combineRelations=function(t){var o,i,l,c,u=[];try{for(var p=n(t.data.getList("mo")),h=p.next();!h.done;h=p.next()){var f=h.value;if(!f.getProperty("relationsCombined")&&f.parent&&(!f.parent||a.default.isType(f.parent,"mrow"))&&a.default.getTexClass(f)===s.TEXCLASS.REL){for(var d=f.parent,m=void 0,y=d.childNodes,g=y.indexOf(f)+1,b=a.default.getProperty(f,"variantForm");g0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s};Object.defineProperty(e,"__esModule",{value:!0}),e.FindTeX=void 0;var s=r(3494),a=r(505),l=r(4474),c=function(t){function e(e){var r=t.call(this,e)||this;return r.getPatterns(),r}return o(e,t),e.prototype.getPatterns=function(){var t=this,e=this.options,r=[],n=[],o=[];this.end={},this.env=this.sub=0;var i=1;e.inlineMath.forEach((function(e){return t.addPattern(r,e,!1)})),e.displayMath.forEach((function(e){return t.addPattern(r,e,!0)})),r.length&&n.push(r.sort(a.sortLength).join("|")),e.processEnvironments&&(n.push("\\\\begin\\s*\\{([^}]*)\\}"),this.env=i,i++),e.processEscapes&&o.push("\\\\([\\\\$])"),e.processRefs&&o.push("(\\\\(?:eq)?ref\\s*\\{[^}]*\\})"),o.length&&(n.push("("+o.join("|")+")"),this.sub=i),this.start=new RegExp(n.join("|"),"g"),this.hasPatterns=n.length>0},e.prototype.addPattern=function(t,e,r){var n=i(e,2),o=n[0],s=n[1];t.push((0,a.quotePattern)(o)),this.end[o]=[s,r,this.endPattern(s)]},e.prototype.endPattern=function(t,e){return new RegExp((e||(0,a.quotePattern)(t))+"|\\\\(?:[a-zA-Z]|.)|[{}]","g")},e.prototype.findEnd=function(t,e,r,n){for(var o,s=i(n,3),a=s[0],c=s[1],u=s[2],p=u.lastIndex=r.index+r[0].length,h=0;o=u.exec(t);){if((o[1]||o[0])===a&&0===h)return(0,l.protoItem)(r[0],t.substr(p,o.index-p),o[0],e,r.index,o.index+o[0].length,c);"{"===o[0]?h++:"}"===o[0]&&h&&h--}return null},e.prototype.findMathInString=function(t,e,r){var n,o;for(this.start.lastIndex=0;n=this.start.exec(r);){if(void 0!==n[this.env]&&this.env){var i="\\\\end\\s*(\\{"+(0,a.quotePattern)(n[this.env])+"\\})";(o=this.findEnd(r,e,n,["{"+n[this.env]+"}",!0,this.endPattern(null,i)]))&&(o.math=o.open+o.math+o.close,o.open=o.close="")}else if(void 0!==n[this.sub]&&this.sub){var s=n[this.sub];i=n.index+n[this.sub].length;o=2===s.length?(0,l.protoItem)("",s.substr(1),"",e,n.index,i):(0,l.protoItem)("",s,"",e,n.index,i,!1)}else o=this.findEnd(r,e,n,this.end[n[0]]);o&&(t.push(o),this.start.lastIndex=o.end.n)}},e.prototype.findMath=function(t){var e=[];if(this.hasPatterns)for(var r=0,n=t.length;r=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},o=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s};Object.defineProperty(e,"__esModule",{value:!0}),e.SubHandlers=e.SubHandler=e.MapHandler=void 0;var i,s=r(8666),a=r(7525);!function(t){var e=new Map;t.register=function(t){e.set(t.name,t)},t.getMap=function(t){return e.get(t)}}(i=e.MapHandler||(e.MapHandler={}));var l=function(){function t(){this._configuration=new s.PrioritizedList,this._fallback=new a.FunctionList}return t.prototype.add=function(t,e,r){var o,a;void 0===r&&(r=s.PrioritizedList.DEFAULTPRIORITY);try{for(var l=n(t.slice().reverse()),c=l.next();!c.done;c=l.next()){var u=c.value,p=i.getMap(u);if(!p)return void this.warn("Configuration "+u+" not found! Omitted.");this._configuration.add(p,r)}}catch(t){o={error:t}}finally{try{c&&!c.done&&(a=l.return)&&a.call(l)}finally{if(o)throw o.error}}e&&this._fallback.add(e,r)},t.prototype.parse=function(t){var e,r;try{for(var i=n(this._configuration),s=i.next();!s.done;s=i.next()){var a=s.value.item.parse(t);if(a)return a}}catch(t){e={error:t}}finally{try{s&&!s.done&&(r=i.return)&&r.call(i)}finally{if(e)throw e.error}}var l=o(t,2),c=l[0],u=l[1];Array.from(this._fallback)[0].item(c,u)},t.prototype.lookup=function(t){var e=this.applicable(t);return e?e.lookup(t):null},t.prototype.contains=function(t){return!!this.applicable(t)},t.prototype.toString=function(){var t,e,r=[];try{for(var o=n(this._configuration),i=o.next();!i.done;i=o.next()){var s=i.value.item;r.push(s.name)}}catch(e){t={error:e}}finally{try{i&&!i.done&&(e=o.return)&&e.call(o)}finally{if(t)throw t.error}}return r.join(", ")},t.prototype.applicable=function(t){var e,r;try{for(var o=n(this._configuration),i=o.next();!i.done;i=o.next()){var s=i.value.item;if(s.contains(t))return s}}catch(t){e={error:t}}finally{try{i&&!i.done&&(r=o.return)&&r.call(o)}finally{if(e)throw e.error}}return null},t.prototype.retrieve=function(t){var e,r;try{for(var o=n(this._configuration),i=o.next();!i.done;i=o.next()){var s=i.value.item;if(s.name===t)return s}}catch(t){e={error:t}}finally{try{i&&!i.done&&(r=o.return)&&r.call(o)}finally{if(e)throw e.error}}return null},t.prototype.warn=function(t){console.log("TexParser Warning: "+t)},t}();e.SubHandler=l;var c=function(){function t(){this.map=new Map}return t.prototype.add=function(t,e,r){var o,i;void 0===r&&(r=s.PrioritizedList.DEFAULTPRIORITY);try{for(var a=n(Object.keys(t)),c=a.next();!c.done;c=a.next()){var u=c.value,p=this.get(u);p||(p=new l,this.set(u,p)),p.add(t[u],e[u],r)}}catch(t){o={error:t}}finally{try{c&&!c.done&&(i=a.return)&&i.call(a)}finally{if(o)throw o.error}}},t.prototype.set=function(t,e){this.map.set(t,e)},t.prototype.get=function(t){return this.map.get(t)},t.prototype.retrieve=function(t){var e,r;try{for(var o=n(this.map.values()),i=o.next();!i.done;i=o.next()){var s=i.value.retrieve(t);if(s)return s}}catch(t){e={error:t}}finally{try{i&&!i.done&&(r=o.return)&&r.call(o)}finally{if(e)throw e.error}}return null},t.prototype.keys=function(){return this.map.keys()},t}();e.SubHandlers=c},8929:function(t,e,r){var n=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},o=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},o=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},i=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},o=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o1&&(r.autoOP=!1));var o=t.create("token","mi",r,e);t.Push(o)},t.digit=function(t,e){var r,n=t.configuration.options.digits,o=t.string.slice(t.i-1).match(n),i=c.default.getFontDef(t);o?(r=t.create("token","mn",i,o[0].replace(/[{}]/g,"")),t.i+=o[0].length-1):r=t.create("token","mo",i,e),t.Push(r)},t.controlSequence=function(t,e){var r=t.GetCS();t.parse("macro",[t,r])},t.mathchar0mi=function(t,e){var r=e.attributes||{mathvariant:l.TexConstant.Variant.ITALIC},n=t.create("token","mi",r,e.char);t.Push(n)},t.mathchar0mo=function(t,e){var r=e.attributes||{};r.stretchy=!1;var n=t.create("token","mo",r,e.char);a.default.setProperty(n,"fixStretchy",!0),t.configuration.addNode("fixStretchy",n),t.Push(n)},t.mathchar7=function(t,e){var r=e.attributes||{mathvariant:l.TexConstant.Variant.NORMAL};t.stack.env.font&&(r.mathvariant=t.stack.env.font);var n=t.create("token","mi",r,e.char);t.Push(n)},t.delimiter=function(t,e){var r=e.attributes||{};r=Object.assign({fence:!1,stretchy:!1},r);var n=t.create("token","mo",r,e.char);t.Push(n)},t.environment=function(t,e,r,i){var s=i[0],a=t.itemFactory.create("begin").setProperties({name:e,end:s});a=r.apply(void 0,o([t,a],n(i.slice(1)),!1)),t.Push(a)}}(s||(s={})),e.default=s},8562:function(t,e,r){var n=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},o=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},s=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0});var a=s(r(5453)),l=r(8929),c=s(r(1256)),u=r(7233),p=function(){function t(t,e){void 0===e&&(e=[]),this.options={},this.packageData=new Map,this.parsers=[],this.root=null,this.nodeLists={},this.error=!1,this.handlers=t.handlers,this.nodeFactory=new l.NodeFactory,this.nodeFactory.configuration=this,this.nodeFactory.setCreators(t.nodes),this.itemFactory=new a.default(t.items),this.itemFactory.configuration=this,u.defaultOptions.apply(void 0,o([this.options],n(e),!1)),(0,u.defaultOptions)(this.options,t.options)}return t.prototype.pushParser=function(t){this.parsers.unshift(t)},t.prototype.popParser=function(){this.parsers.shift()},Object.defineProperty(t.prototype,"parser",{get:function(){return this.parsers[0]},enumerable:!1,configurable:!0}),t.prototype.clear=function(){this.parsers=[],this.root=null,this.nodeLists={},this.error=!1,this.tags.resetTag()},t.prototype.addNode=function(t,e){var r=this.nodeLists[t];if(r||(r=this.nodeLists[t]=[]),r.push(e),e.kind!==t){var n=c.default.getProperty(e,"in-lists")||"",o=(n?n.split(/,/):[]).concat(t).join(",");c.default.setProperty(e,"in-lists",o)}},t.prototype.getList=function(t){var e,r,n=this.nodeLists[t]||[],o=[];try{for(var s=i(n),a=s.next();!a.done;a=s.next()){var l=a.value;this.inTree(l)&&o.push(l)}}catch(t){e={error:t}}finally{try{a&&!a.done&&(r=s.return)&&r.call(s)}finally{if(e)throw e.error}}return this.nodeLists[t]=o,o},t.prototype.removeFromList=function(t,e){var r,n,o=this.nodeLists[t]||[];try{for(var s=i(e),a=s.next();!a.done;a=s.next()){var l=a.value,c=o.indexOf(l);c>=0&&o.splice(c,1)}}catch(t){r={error:t}}finally{try{a&&!a.done&&(n=s.return)&&n.call(s)}finally{if(r)throw r.error}}},t.prototype.inTree=function(t){for(;t&&t!==this.root;)t=t.parent;return!!t},t}();e.default=p},1130:function(t,e,r){var n=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},o=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},i=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0});var s,a=r(9007),l=i(r(1256)),c=i(r(8417)),u=i(r(3971)),p=r(5368);!function(t){var e=7.2,r={em:function(t){return t},ex:function(t){return.43*t},pt:function(t){return t/10},pc:function(t){return 1.2*t},px:function(t){return t*e/72},in:function(t){return t*e},cm:function(t){return t*e/2.54},mm:function(t){return t*e/25.4},mu:function(t){return t/18}},i="([-+]?([.,]\\d+|\\d+([.,]\\d*)?))",s="(pt|em|ex|mu|px|mm|cm|in|pc)",h=RegExp("^\\s*"+i+"\\s*"+s+"\\s*$"),f=RegExp("^\\s*"+i+"\\s*"+s+" ?");function d(t,e){void 0===e&&(e=!1);var o=t.match(e?f:h);return o?function(t){var e=n(t,3),o=e[0],i=e[1],s=e[2];if("mu"!==i)return[o,i,s];return[m(r[i](parseFloat(o||"1"))).slice(0,-2),"em",s]}([o[1].replace(/,/,"."),o[4],o[0].length]):[null,null,0]}function m(t){return Math.abs(t)<6e-4?"0em":t.toFixed(3).replace(/\.?0+$/,"")+"em"}function y(t,e,r){"{"!==e&&"}"!==e||(e="\\"+e);var n="{\\bigg"+r+" "+e+"}",o="{\\big"+r+" "+e+"}";return new c.default("\\mathchoice"+n+o+o+o,{},t).mml()}function g(t,e,r){e=e.replace(/^\s+/,p.entities.nbsp).replace(/\s+$/,p.entities.nbsp);var n=t.create("text",e);return t.create("node","mtext",[],r,n)}function b(t,e,r){if(r.match(/^[a-z]/i)&&e.match(/(^|[^\\])(\\\\)*\\[a-z]+$/i)&&(e+=" "),e.length+r.length>t.configuration.options.maxBuffer)throw new u.default("MaxBufferSize","MathJax internal buffer size exceeded; is there a recursive macro call?");return e+r}function v(t,e){for(;e>0;)t=t.trim().slice(1,-1),e--;return t.trim()}function _(t,e){for(var r=t.length,n=0,o="",i=0,s=0,a=!0,l=!1;in&&(s=n)),n++;break;case"}":n&&n--,(a||l)&&(s--,l=!0),a=!1;break;default:if(!n&&-1!==e.indexOf(c))return[l?"true":v(o,s),c,t.slice(i)];a=!1,l=!1}o+=c}if(n)throw new u.default("ExtraOpenMissingClose","Extra open brace or missing close brace");return[l?"true":v(o,s),"",t.slice(i)]}t.matchDimen=d,t.dimen2em=function(t){var e=n(d(t),2),o=e[0],i=e[1],s=parseFloat(o||"1"),a=r[i];return a?a(s):0},t.Em=m,t.cols=function(){for(var t=[],e=0;e1&&(l=[t.create("node","mrow",l)]),l},t.internalText=g,t.underOver=function(e,r,n,o,i){if(t.checkMovableLimits(r),l.default.isType(r,"munderover")&&l.default.isEmbellished(r)){l.default.setProperties(l.default.getCoreMO(r),{lspace:0,rspace:0});var s=e.create("node","mo",[],{rspace:0});r=e.create("node","mrow",[s,r])}var c=e.create("node","munderover",[r]);l.default.setChild(c,"over"===o?c.over:c.under,n);var u=c;return i&&(u=e.create("node","TeXAtom",[c],{texClass:a.TEXCLASS.OP,movesupsub:!0})),l.default.setProperty(u,"subsupOK",!0),u},t.checkMovableLimits=function(t){var e=l.default.isType(t,"mo")?l.default.getForm(t):null;(l.default.getProperty(t,"movablelimits")||e&&e[3]&&e[3].movablelimits)&&l.default.setProperties(t,{movablelimits:!1})},t.trimSpaces=function(t){if("string"!=typeof t)return t;var e=t.trim();return e.match(/\\$/)&&t.match(/ $/)&&(e+=" "),e},t.setArrayAlign=function(e,r){return"t"===(r=t.trimSpaces(r||""))?e.arraydef.align="baseline 1":"b"===r?e.arraydef.align="baseline -1":"c"===r?e.arraydef.align="axis":r&&(e.arraydef.align=r),e},t.substituteArgs=function(t,e,r){for(var n="",o="",i=0;ie.length)throw new u.default("IllegalMacroParam","Illegal macro parameter reference");o=b(t,b(t,o,n),e[parseInt(s,10)-1]),n=""}else n+=s}return b(t,o,n)},t.addArgs=b,t.checkMaxMacros=function(t,e){if(void 0===e&&(e=!0),!(++t.macroCount<=t.configuration.options.maxMacros))throw e?new u.default("MaxMacroSub1","MathJax maximum macro substitution count exceeded; is here a recursive macro call?"):new u.default("MaxMacroSub2","MathJax maximum substitution count exceeded; is there a recursive latex environment?")},t.checkEqnEnv=function(t){if(t.stack.global.eqnenv)throw new u.default("ErroneousNestingEq","Erroneous nesting of equation structures");t.stack.global.eqnenv=!0},t.copyNode=function(t,e){var r=t.copy(),n=e.configuration;return r.walkTree((function(t){var e,r;n.addNode(t.kind,t);var i=(t.getProperty("in-lists")||"").split(/,/);try{for(var s=o(i),a=s.next();!a.done;a=s.next()){var l=a.value;l&&n.addNode(l,t)}}catch(t){e={error:t}}finally{try{a&&!a.done&&(r=s.return)&&r.call(s)}finally{if(e)throw e.error}}})),r},t.MmlFilterAttribute=function(t,e,r){return r},t.getFontDef=function(t){var e=t.stack.env.font;return e?{mathvariant:e}:{}},t.keyvalOptions=function(t,e,r){var i,s;void 0===e&&(e=null),void 0===r&&(r=!1);var a=function(t){var e,r,o,i,s,a={},l=t;for(;l;)i=(e=n(_(l,["=",","]),3))[0],o=e[1],l=e[2],"="===o?(s=(r=n(_(l,[","]),3))[0],o=r[1],l=r[2],s="false"===s||"true"===s?JSON.parse(s):s,a[i]=s):i&&(a[i]=!0);return a}(t);if(e)try{for(var l=o(Object.keys(a)),c=l.next();!c.done;c=l.next()){var p=c.value;if(!e.hasOwnProperty(p)){if(r)throw new u.default("InvalidOption","Invalid option: %1",p);delete a[p]}}}catch(t){i={error:t}}finally{try{c&&!c.done&&(s=l.return)&&s.call(l)}finally{if(i)throw i.error}}return a}}(s||(s={})),e.default=s},9497:function(t,e,r){var n=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},o=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},i=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},s=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},l=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0}),e.BaseItem=e.MmlStack=void 0;var c=l(r(3971)),u=function(){function t(t){this._nodes=t}return Object.defineProperty(t.prototype,"nodes",{get:function(){return this._nodes},enumerable:!1,configurable:!0}),t.prototype.Push=function(){for(var t,e=[],r=0;r0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},s=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},a=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},s=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0}),e.TagsFactory=e.AllTags=e.NoTags=e.AbstractTags=e.TagInfo=e.Label=void 0;var a=s(r(8417)),l=function(t,e){void 0===t&&(t="???"),void 0===e&&(e=""),this.tag=t,this.id=e};e.Label=l;var c=function(t,e,r,n,o,i,s,a){void 0===t&&(t=""),void 0===e&&(e=!1),void 0===r&&(r=!1),void 0===n&&(n=null),void 0===o&&(o=""),void 0===i&&(i=""),void 0===s&&(s=!1),void 0===a&&(a=""),this.env=t,this.taggable=e,this.defaultTags=r,this.tag=n,this.tagId=o,this.tagFormat=i,this.noTag=s,this.labelId=a};e.TagInfo=c;var u=function(){function t(){this.counter=0,this.allCounter=0,this.configuration=null,this.ids={},this.allIds={},this.labels={},this.allLabels={},this.redo=!1,this.refUpdate=!1,this.currentTag=new c,this.history=[],this.stack=[],this.enTag=function(t,e){var r=this.configuration.nodeFactory,n=r.create("node","mtd",[t]),o=r.create("node","mlabeledtr",[e,n]);return r.create("node","mtable",[o],{side:this.configuration.options.tagSide,minlabelspacing:this.configuration.options.tagIndent,displaystyle:!0})}}return t.prototype.start=function(t,e,r){this.currentTag&&this.stack.push(this.currentTag),this.currentTag=new c(t,e,r)},Object.defineProperty(t.prototype,"env",{get:function(){return this.currentTag.env},enumerable:!1,configurable:!0}),t.prototype.end=function(){this.history.push(this.currentTag),this.currentTag=this.stack.pop()},t.prototype.tag=function(t,e){this.currentTag.tag=t,this.currentTag.tagFormat=e?t:this.formatTag(t),this.currentTag.noTag=!1},t.prototype.notag=function(){this.tag("",!0),this.currentTag.noTag=!0},Object.defineProperty(t.prototype,"noTag",{get:function(){return this.currentTag.noTag},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"label",{get:function(){return this.currentTag.labelId},set:function(t){this.currentTag.labelId=t},enumerable:!1,configurable:!0}),t.prototype.formatUrl=function(t,e){return e+"#"+encodeURIComponent(t)},t.prototype.formatTag=function(t){return"("+t+")"},t.prototype.formatId=function(t){return"mjx-eqn:"+t.replace(/\s/g,"_")},t.prototype.formatNumber=function(t){return t.toString()},t.prototype.autoTag=function(){null==this.currentTag.tag&&(this.counter++,this.tag(this.formatNumber(this.counter),!1))},t.prototype.clearTag=function(){this.label="",this.tag(null,!0),this.currentTag.tagId=""},t.prototype.getTag=function(t){if(void 0===t&&(t=!1),t)return this.autoTag(),this.makeTag();var e=this.currentTag;return e.taggable&&!e.noTag&&(e.defaultTags&&this.autoTag(),e.tag)?this.makeTag():null},t.prototype.resetTag=function(){this.history=[],this.redo=!1,this.refUpdate=!1,this.clearTag()},t.prototype.reset=function(t){void 0===t&&(t=0),this.resetTag(),this.counter=this.allCounter=t,this.allLabels={},this.allIds={}},t.prototype.startEquation=function(t){this.history=[],this.stack=[],this.clearTag(),this.currentTag=new c("",void 0,void 0),this.labels={},this.ids={},this.counter=this.allCounter,this.redo=!1;var e=t.inputData.recompile;e&&(this.refUpdate=!0,this.counter=e.counter)},t.prototype.finishEquation=function(t){this.redo&&(t.inputData.recompile={state:t.state(),counter:this.allCounter}),this.refUpdate||(this.allCounter=this.counter),Object.assign(this.allIds,this.ids),Object.assign(this.allLabels,this.labels)},t.prototype.finalize=function(t,e){if(!e.display||this.currentTag.env||null==this.currentTag.tag)return t;var r=this.makeTag();return this.enTag(t,r)},t.prototype.makeId=function(){this.currentTag.tagId=this.formatId(this.configuration.options.useLabelIds&&this.label||this.currentTag.tag)},t.prototype.makeTag=function(){this.makeId(),this.label&&(this.labels[this.label]=new l(this.currentTag.tag,this.currentTag.tagId));var t=new a.default("\\text{"+this.currentTag.tagFormat+"}",{},this.configuration).mml();return this.configuration.nodeFactory.create("node","mtd",[t],{id:this.currentTag.tagId})},t}();e.AbstractTags=u;var p=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.autoTag=function(){},e.prototype.getTag=function(){return this.currentTag.tag?t.prototype.getTag.call(this):null},e}(u);e.NoTags=p;var h=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.finalize=function(t,e){if(!e.display||this.history.find((function(t){return t.taggable})))return t;var r=this.getTag(!0);return this.enTag(t,r)},e}(u);e.AllTags=h,function(t){var e=new Map([["none",p],["all",h]]),r="none";t.OPTIONS={tags:r,tagSide:"right",tagIndent:"0.8em",useLabelIds:!0,ignoreDuplicateLabels:!1},t.add=function(t,r){e.set(t,r)},t.addTags=function(e){var r,n;try{for(var o=i(Object.keys(e)),s=o.next();!s.done;s=o.next()){var a=s.value;t.add(a,e[a])}}catch(t){r={error:t}}finally{try{s&&!s.done&&(n=o.return)&&n.call(o)}finally{if(r)throw r.error}}},t.create=function(t){var n=e.get(t)||e.get(r);if(!n)throw Error("Unknown tags class");return new n},t.setDefault=function(t){r=t},t.getDefault=function(){return t.create(r)}}(e.TagsFactory||(e.TagsFactory={}))},8317:function(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.TexConstant=void 0,function(t){t.Variant={NORMAL:"normal",BOLD:"bold",ITALIC:"italic",BOLDITALIC:"bold-italic",DOUBLESTRUCK:"double-struck",FRAKTUR:"fraktur",BOLDFRAKTUR:"bold-fraktur",SCRIPT:"script",BOLDSCRIPT:"bold-script",SANSSERIF:"sans-serif",BOLDSANSSERIF:"bold-sans-serif",SANSSERIFITALIC:"sans-serif-italic",SANSSERIFBOLDITALIC:"sans-serif-bold-italic",MONOSPACE:"monospace",INITIAL:"inital",TAILED:"tailed",LOOPED:"looped",STRETCHED:"stretched",CALLIGRAPHIC:"-tex-calligraphic",BOLDCALLIGRAPHIC:"-tex-bold-calligraphic",OLDSTYLE:"-tex-oldstyle",BOLDOLDSTYLE:"-tex-bold-oldstyle",MATHITALIC:"-tex-mathit"},t.Form={PREFIX:"prefix",INFIX:"infix",POSTFIX:"postfix"},t.LineBreak={AUTO:"auto",NEWLINE:"newline",NOBREAK:"nobreak",GOODBREAK:"goodbreak",BADBREAK:"badbreak"},t.LineBreakStyle={BEFORE:"before",AFTER:"after",DUPLICATE:"duplicate",INFIXLINBREAKSTYLE:"infixlinebreakstyle"},t.IndentAlign={LEFT:"left",CENTER:"center",RIGHT:"right",AUTO:"auto",ID:"id",INDENTALIGN:"indentalign"},t.IndentShift={INDENTSHIFT:"indentshift"},t.LineThickness={THIN:"thin",MEDIUM:"medium",THICK:"thick"},t.Notation={LONGDIV:"longdiv",ACTUARIAL:"actuarial",PHASORANGLE:"phasorangle",RADICAL:"radical",BOX:"box",ROUNDEDBOX:"roundedbox",CIRCLE:"circle",LEFT:"left",RIGHT:"right",TOP:"top",BOTTOM:"bottom",UPDIAGONALSTRIKE:"updiagonalstrike",DOWNDIAGONALSTRIKE:"downdiagonalstrike",VERTICALSTRIKE:"verticalstrike",HORIZONTALSTRIKE:"horizontalstrike",NORTHEASTARROW:"northeastarrow",MADRUWB:"madruwb",UPDIAGONALARROW:"updiagonalarrow"},t.Align={TOP:"top",BOTTOM:"bottom",CENTER:"center",BASELINE:"baseline",AXIS:"axis",LEFT:"left",RIGHT:"right"},t.Lines={NONE:"none",SOLID:"solid",DASHED:"dashed"},t.Side={LEFT:"left",RIGHT:"right",LEFTOVERLAP:"leftoverlap",RIGHTOVERLAP:"rightoverlap"},t.Width={AUTO:"auto",FIT:"fit"},t.Actiontype={TOGGLE:"toggle",STATUSLINE:"statusline",TOOLTIP:"tooltip",INPUT:"input"},t.Overflow={LINBREAK:"linebreak",SCROLL:"scroll",ELIDE:"elide",TRUNCATE:"truncate",SCALE:"scale"},t.Unit={EM:"em",EX:"ex",PX:"px",IN:"in",CM:"cm",MM:"mm",PT:"pt",PC:"pc"}}(e.TexConstant||(e.TexConstant={}))},3971:function(t,e){Object.defineProperty(e,"__esModule",{value:!0});var r=function(){function t(e,r){for(var n=[],o=2;o="0"&&s<="9")n[o]=r[parseInt(n[o],10)-1],"number"==typeof n[o]&&(n[o]=n[o].toString());else if("{"===s){if((s=n[o].substr(1))>="0"&&s<="9")n[o]=r[parseInt(n[o].substr(1,n[o].length-2),10)-1],"number"==typeof n[o]&&(n[o]=n[o].toString());else n[o].match(/^\{([a-z]+):%(\d+)\|(.*)\}$/)&&(n[o]="%"+n[o])}null==n[o]&&(n[o]="???")}return n.join("")},t.pattern=/%(\d+|\{\d+\}|\{[a-z]+:\%\d+(?:\|(?:%\{\d+\}|%.|[^\}])*)+\}|.)/g,t}();e.default=r},8417:function(t,e,r){var n=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},o=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},i=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;oe)throw new u.default("XalignOverflow","Extra %1 in row of %2","&",this.name)},e.prototype.EndRow=function(){for(var e,r=this.row,n=this.getProperty("xalignat");r.lengththis.maxrow&&(this.maxrow=this.row.length),t.prototype.EndRow.call(this);var o=this.table[this.table.length-1];if(this.getProperty("zeroWidthLabel")&&o.isKind("mlabeledtr")){var s=c.default.getChildren(o)[0],a=this.factory.configuration.options.tagSide,l=i({width:0},"right"===a?{lspace:"-1width"}:{}),u=this.create("node","mpadded",c.default.getChildren(s),l);s.setChildren([u])}},e.prototype.EndTable=function(){(t.prototype.EndTable.call(this),this.center)&&(this.maxrow<=2&&(delete this.arraydef.width,delete this.global.indentalign))},e}(a.EqnArrayItem);e.FlalignItem=f},7379:function(t,e,r){var n=this&&this.__createBinding||(Object.create?function(t,e,r,n){void 0===n&&(n=r);var o=Object.getOwnPropertyDescriptor(e,r);o&&!("get"in o?!e.__esModule:o.writable||o.configurable)||(o={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,n,o)}:function(t,e,r,n){void 0===n&&(n=r),t[n]=e[r]}),o=this&&this.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),i=this&&this.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var r in t)"default"!==r&&Object.prototype.hasOwnProperty.call(t,r)&&n(e,t,r);return o(e,t),e},s=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0});var a=r(4387),l=i(r(9140)),c=r(8317),u=s(r(5450)),p=s(r(1130)),h=r(9007),f=r(6010);new l.CharacterMap("AMSmath-mathchar0mo",u.default.mathchar0mo,{iiiint:["\u2a0c",{texClass:h.TEXCLASS.OP}]}),new l.RegExpMap("AMSmath-operatorLetter",a.AmsMethods.operatorLetter,/[-*]/i),new l.CommandMap("AMSmath-macros",{mathring:["Accent","02DA"],nobreakspace:"Tilde",negmedspace:["Spacer",f.MATHSPACE.negativemediummathspace],negthickspace:["Spacer",f.MATHSPACE.negativethickmathspace],idotsint:["MultiIntegral","\\int\\cdots\\int"],dddot:["Accent","20DB"],ddddot:["Accent","20DC"],sideset:"SideSet",boxed:["Macro","\\fbox{$\\displaystyle{#1}$}",1],tag:"HandleTag",notag:"HandleNoTag",eqref:["HandleRef",!0],substack:["Macro","\\begin{subarray}{c}#1\\end{subarray}",1],injlim:["NamedOp","inj lim"],projlim:["NamedOp","proj lim"],varliminf:["Macro","\\mathop{\\underline{\\mmlToken{mi}{lim}}}"],varlimsup:["Macro","\\mathop{\\overline{\\mmlToken{mi}{lim}}}"],varinjlim:["Macro","\\mathop{\\underrightarrow{\\mmlToken{mi}{lim}}}"],varprojlim:["Macro","\\mathop{\\underleftarrow{\\mmlToken{mi}{lim}}}"],DeclareMathOperator:"HandleDeclareOp",operatorname:"HandleOperatorName",genfrac:"Genfrac",frac:["Genfrac","","","",""],tfrac:["Genfrac","","","","1"],dfrac:["Genfrac","","","","0"],binom:["Genfrac","(",")","0",""],tbinom:["Genfrac","(",")","0","1"],dbinom:["Genfrac","(",")","0","0"],cfrac:"CFrac",shoveleft:["HandleShove",c.TexConstant.Align.LEFT],shoveright:["HandleShove",c.TexConstant.Align.RIGHT],xrightarrow:["xArrow",8594,5,10],xleftarrow:["xArrow",8592,10,5]},a.AmsMethods),new l.EnvironmentMap("AMSmath-environment",u.default.environment,{"equation*":["Equation",null,!1],"eqnarray*":["EqnArray",null,!1,!0,"rcl",p.default.cols(0,f.MATHSPACE.thickmathspace),".5em"],align:["EqnArray",null,!0,!0,"rl",p.default.cols(0,2)],"align*":["EqnArray",null,!1,!0,"rl",p.default.cols(0,2)],multline:["Multline",null,!0],"multline*":["Multline",null,!1],split:["EqnArray",null,!1,!1,"rl",p.default.cols(0)],gather:["EqnArray",null,!0,!0,"c"],"gather*":["EqnArray",null,!1,!0,"c"],alignat:["AlignAt",null,!0,!0],"alignat*":["AlignAt",null,!1,!0],alignedat:["AlignAt",null,!1,!1],aligned:["AmsEqnArray",null,null,null,"rl",p.default.cols(0,2),".5em","D"],gathered:["AmsEqnArray",null,null,null,"c",null,".5em","D"],xalignat:["XalignAt",null,!0,!0],"xalignat*":["XalignAt",null,!1,!0],xxalignat:["XalignAt",null,!1,!1],flalign:["FlalignArray",null,!0,!1,!0,"rlc","auto auto fit"],"flalign*":["FlalignArray",null,!1,!1,!0,"rlc","auto auto fit"],subarray:["Array",null,null,null,null,p.default.cols(0),"0.1em","S",1],smallmatrix:["Array",null,null,null,"c",p.default.cols(1/3),".2em","S",1],matrix:["Array",null,null,null,"c"],pmatrix:["Array",null,"(",")","c"],bmatrix:["Array",null,"[","]","c"],Bmatrix:["Array",null,"\\{","\\}","c"],vmatrix:["Array",null,"\\vert","\\vert","c"],Vmatrix:["Array",null,"\\Vert","\\Vert","c"],cases:["Array",null,"\\{",".","ll",null,".2em","T"]},a.AmsMethods),new l.DelimiterMap("AMSmath-delimiter",u.default.delimiter,{"\\lvert":["|",{texClass:h.TEXCLASS.OPEN}],"\\rvert":["|",{texClass:h.TEXCLASS.CLOSE}],"\\lVert":["\u2016",{texClass:h.TEXCLASS.OPEN}],"\\rVert":["\u2016",{texClass:h.TEXCLASS.CLOSE}]}),new l.CharacterMap("AMSsymbols-mathchar0mi",u.default.mathchar0mi,{digamma:"\u03dd",varkappa:"\u03f0",varGamma:["\u0393",{mathvariant:c.TexConstant.Variant.ITALIC}],varDelta:["\u0394",{mathvariant:c.TexConstant.Variant.ITALIC}],varTheta:["\u0398",{mathvariant:c.TexConstant.Variant.ITALIC}],varLambda:["\u039b",{mathvariant:c.TexConstant.Variant.ITALIC}],varXi:["\u039e",{mathvariant:c.TexConstant.Variant.ITALIC}],varPi:["\u03a0",{mathvariant:c.TexConstant.Variant.ITALIC}],varSigma:["\u03a3",{mathvariant:c.TexConstant.Variant.ITALIC}],varUpsilon:["\u03a5",{mathvariant:c.TexConstant.Variant.ITALIC}],varPhi:["\u03a6",{mathvariant:c.TexConstant.Variant.ITALIC}],varPsi:["\u03a8",{mathvariant:c.TexConstant.Variant.ITALIC}],varOmega:["\u03a9",{mathvariant:c.TexConstant.Variant.ITALIC}],beth:"\u2136",gimel:"\u2137",daleth:"\u2138",backprime:["\u2035",{variantForm:!0}],hslash:"\u210f",varnothing:["\u2205",{variantForm:!0}],blacktriangle:"\u25b4",triangledown:["\u25bd",{variantForm:!0}],blacktriangledown:"\u25be",square:"\u25fb",Box:"\u25fb",blacksquare:"\u25fc",lozenge:"\u25ca",Diamond:"\u25ca",blacklozenge:"\u29eb",circledS:["\u24c8",{mathvariant:c.TexConstant.Variant.NORMAL}],bigstar:"\u2605",sphericalangle:"\u2222",measuredangle:"\u2221",nexists:"\u2204",complement:"\u2201",mho:"\u2127",eth:["\xf0",{mathvariant:c.TexConstant.Variant.NORMAL}],Finv:"\u2132",diagup:"\u2571",Game:"\u2141",diagdown:"\u2572",Bbbk:["k",{mathvariant:c.TexConstant.Variant.DOUBLESTRUCK}],yen:"\xa5",circledR:"\xae",checkmark:"\u2713",maltese:"\u2720"}),new l.CharacterMap("AMSsymbols-mathchar0mo",u.default.mathchar0mo,{dotplus:"\u2214",ltimes:"\u22c9",smallsetminus:["\u2216",{variantForm:!0}],rtimes:"\u22ca",Cap:"\u22d2",doublecap:"\u22d2",leftthreetimes:"\u22cb",Cup:"\u22d3",doublecup:"\u22d3",rightthreetimes:"\u22cc",barwedge:"\u22bc",curlywedge:"\u22cf",veebar:"\u22bb",curlyvee:"\u22ce",doublebarwedge:"\u2a5e",boxminus:"\u229f",circleddash:"\u229d",boxtimes:"\u22a0",circledast:"\u229b",boxdot:"\u22a1",circledcirc:"\u229a",boxplus:"\u229e",centerdot:["\u22c5",{variantForm:!0}],divideontimes:"\u22c7",intercal:"\u22ba",leqq:"\u2266",geqq:"\u2267",leqslant:"\u2a7d",geqslant:"\u2a7e",eqslantless:"\u2a95",eqslantgtr:"\u2a96",lesssim:"\u2272",gtrsim:"\u2273",lessapprox:"\u2a85",gtrapprox:"\u2a86",approxeq:"\u224a",lessdot:"\u22d6",gtrdot:"\u22d7",lll:"\u22d8",llless:"\u22d8",ggg:"\u22d9",gggtr:"\u22d9",lessgtr:"\u2276",gtrless:"\u2277",lesseqgtr:"\u22da",gtreqless:"\u22db",lesseqqgtr:"\u2a8b",gtreqqless:"\u2a8c",doteqdot:"\u2251",Doteq:"\u2251",eqcirc:"\u2256",risingdotseq:"\u2253",circeq:"\u2257",fallingdotseq:"\u2252",triangleq:"\u225c",backsim:"\u223d",thicksim:["\u223c",{variantForm:!0}],backsimeq:"\u22cd",thickapprox:["\u2248",{variantForm:!0}],subseteqq:"\u2ac5",supseteqq:"\u2ac6",Subset:"\u22d0",Supset:"\u22d1",sqsubset:"\u228f",sqsupset:"\u2290",preccurlyeq:"\u227c",succcurlyeq:"\u227d",curlyeqprec:"\u22de",curlyeqsucc:"\u22df",precsim:"\u227e",succsim:"\u227f",precapprox:"\u2ab7",succapprox:"\u2ab8",vartriangleleft:"\u22b2",lhd:"\u22b2",vartriangleright:"\u22b3",rhd:"\u22b3",trianglelefteq:"\u22b4",unlhd:"\u22b4",trianglerighteq:"\u22b5",unrhd:"\u22b5",vDash:["\u22a8",{variantForm:!0}],Vdash:"\u22a9",Vvdash:"\u22aa",smallsmile:["\u2323",{variantForm:!0}],shortmid:["\u2223",{variantForm:!0}],smallfrown:["\u2322",{variantForm:!0}],shortparallel:["\u2225",{variantForm:!0}],bumpeq:"\u224f",between:"\u226c",Bumpeq:"\u224e",pitchfork:"\u22d4",varpropto:["\u221d",{variantForm:!0}],backepsilon:"\u220d",blacktriangleleft:"\u25c2",blacktriangleright:"\u25b8",therefore:"\u2234",because:"\u2235",eqsim:"\u2242",vartriangle:["\u25b3",{variantForm:!0}],Join:"\u22c8",nless:"\u226e",ngtr:"\u226f",nleq:"\u2270",ngeq:"\u2271",nleqslant:["\u2a87",{variantForm:!0}],ngeqslant:["\u2a88",{variantForm:!0}],nleqq:["\u2270",{variantForm:!0}],ngeqq:["\u2271",{variantForm:!0}],lneq:"\u2a87",gneq:"\u2a88",lneqq:"\u2268",gneqq:"\u2269",lvertneqq:["\u2268",{variantForm:!0}],gvertneqq:["\u2269",{variantForm:!0}],lnsim:"\u22e6",gnsim:"\u22e7",lnapprox:"\u2a89",gnapprox:"\u2a8a",nprec:"\u2280",nsucc:"\u2281",npreceq:["\u22e0",{variantForm:!0}],nsucceq:["\u22e1",{variantForm:!0}],precneqq:"\u2ab5",succneqq:"\u2ab6",precnsim:"\u22e8",succnsim:"\u22e9",precnapprox:"\u2ab9",succnapprox:"\u2aba",nsim:"\u2241",ncong:"\u2247",nshortmid:["\u2224",{variantForm:!0}],nshortparallel:["\u2226",{variantForm:!0}],nmid:"\u2224",nparallel:"\u2226",nvdash:"\u22ac",nvDash:"\u22ad",nVdash:"\u22ae",nVDash:"\u22af",ntriangleleft:"\u22ea",ntriangleright:"\u22eb",ntrianglelefteq:"\u22ec",ntrianglerighteq:"\u22ed",nsubseteq:"\u2288",nsupseteq:"\u2289",nsubseteqq:["\u2288",{variantForm:!0}],nsupseteqq:["\u2289",{variantForm:!0}],subsetneq:"\u228a",supsetneq:"\u228b",varsubsetneq:["\u228a",{variantForm:!0}],varsupsetneq:["\u228b",{variantForm:!0}],subsetneqq:"\u2acb",supsetneqq:"\u2acc",varsubsetneqq:["\u2acb",{variantForm:!0}],varsupsetneqq:["\u2acc",{variantForm:!0}],leftleftarrows:"\u21c7",rightrightarrows:"\u21c9",leftrightarrows:"\u21c6",rightleftarrows:"\u21c4",Lleftarrow:"\u21da",Rrightarrow:"\u21db",twoheadleftarrow:"\u219e",twoheadrightarrow:"\u21a0",leftarrowtail:"\u21a2",rightarrowtail:"\u21a3",looparrowleft:"\u21ab",looparrowright:"\u21ac",leftrightharpoons:"\u21cb",rightleftharpoons:["\u21cc",{variantForm:!0}],curvearrowleft:"\u21b6",curvearrowright:"\u21b7",circlearrowleft:"\u21ba",circlearrowright:"\u21bb",Lsh:"\u21b0",Rsh:"\u21b1",upuparrows:"\u21c8",downdownarrows:"\u21ca",upharpoonleft:"\u21bf",upharpoonright:"\u21be",downharpoonleft:"\u21c3",restriction:"\u21be",multimap:"\u22b8",downharpoonright:"\u21c2",leftrightsquigarrow:"\u21ad",rightsquigarrow:"\u21dd",leadsto:"\u21dd",dashrightarrow:"\u21e2",dashleftarrow:"\u21e0",nleftarrow:"\u219a",nrightarrow:"\u219b",nLeftarrow:"\u21cd",nRightarrow:"\u21cf",nleftrightarrow:"\u21ae",nLeftrightarrow:"\u21ce"}),new l.DelimiterMap("AMSsymbols-delimiter",u.default.delimiter,{"\\ulcorner":"\u231c","\\urcorner":"\u231d","\\llcorner":"\u231e","\\lrcorner":"\u231f"}),new l.CommandMap("AMSsymbols-macros",{implies:["Macro","\\;\\Longrightarrow\\;"],impliedby:["Macro","\\;\\Longleftarrow\\;"]},a.AmsMethods)},4387:function(t,e,r){var n=this&&this.__assign||function(){return n=Object.assign||function(t){for(var e,r=1,n=arguments.length;r0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},i=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0}),e.NEW_OPS=e.AmsMethods=void 0;var s=i(r(1130)),a=i(r(5450)),l=i(r(1256)),c=r(8317),u=i(r(8417)),p=i(r(3971)),h=r(8803),f=i(r(7693)),d=r(9007);function m(t){if(!t||t.isInferred&&0===t.childNodes.length)return[null,null];if(t.isKind("msubsup")&&y(t))return[t,null];var e=l.default.getChildAt(t,0);return t.isInferred&&e&&y(e)?(t.childNodes.splice(0,1),[e,t]):[null,t]}function y(t){var e=t.childNodes[0];return e&&e.isKind("mi")&&""===e.getText()}e.AmsMethods={},e.AmsMethods.AmsEqnArray=function(t,e,r,n,o,i,a){var l=t.GetBrackets("\\begin{"+e.getName()+"}"),c=f.default.EqnArray(t,e,r,n,o,i,a);return s.default.setArrayAlign(c,l)},e.AmsMethods.AlignAt=function(t,r,n,o){var i,a,l=r.getName(),c="",u=[];if(o||(a=t.GetBrackets("\\begin{"+l+"}")),(i=t.GetArgument("\\begin{"+l+"}")).match(/[^0-9]/))throw new p.default("PositiveIntegerArg","Argument to %1 must me a positive integer","\\begin{"+l+"}");for(var h=parseInt(i,10);h>0;)c+="rl",u.push("0em 0em"),h--;var f=u.join(" ");if(o)return e.AmsMethods.EqnArray(t,r,n,o,c,f);var d=e.AmsMethods.EqnArray(t,r,n,o,c,f);return s.default.setArrayAlign(d,a)},e.AmsMethods.Multline=function(t,e,r){t.Push(e),s.default.checkEqnEnv(t);var n=t.itemFactory.create("multline",r,t.stack);return n.arraydef={displaystyle:!0,rowspacing:".5em",columnspacing:"100%",width:t.options.ams.multlineWidth,side:t.options.tagSide,minlabelspacing:t.options.tagIndent,framespacing:t.options.ams.multlineIndent+" 0",frame:"","data-width-includes-label":!0},n},e.AmsMethods.XalignAt=function(t,r,n,o){var i=t.GetArgument("\\begin{"+r.getName()+"}");if(i.match(/[^0-9]/))throw new p.default("PositiveIntegerArg","Argument to %1 must me a positive integer","\\begin{"+r.getName()+"}");var s=o?"crl":"rlc",a=o?"fit auto auto":"auto auto fit",l=e.AmsMethods.FlalignArray(t,r,n,o,!1,s,a,!0);return l.setProperty("xalignat",2*parseInt(i)),l},e.AmsMethods.FlalignArray=function(t,e,r,n,o,i,a,l){void 0===l&&(l=!1),t.Push(e),s.default.checkEqnEnv(t),i=i.split("").join(" ").replace(/r/g,"right").replace(/l/g,"left").replace(/c/g,"center");var c=t.itemFactory.create("flalign",e.getName(),r,n,o,t.stack);return c.arraydef={width:"100%",displaystyle:!0,columnalign:i,columnspacing:"0em",columnwidth:a,rowspacing:"3pt",side:t.options.tagSide,minlabelspacing:l?"0":t.options.tagIndent,"data-width-includes-label":!0},c.setProperty("zeroWidthLabel",l),c},e.NEW_OPS="ams-declare-ops",e.AmsMethods.HandleDeclareOp=function(t,r){var n=t.GetStar()?"*":"",o=s.default.trimSpaces(t.GetArgument(r));"\\"===o.charAt(0)&&(o=o.substr(1));var i=t.GetArgument(r);t.configuration.handlers.retrieve(e.NEW_OPS).add(o,new h.Macro(o,e.AmsMethods.Macro,["\\operatorname".concat(n,"{").concat(i,"}")]))},e.AmsMethods.HandleOperatorName=function(t,e){var r=t.GetStar(),o=s.default.trimSpaces(t.GetArgument(e)),i=new u.default(o,n(n({},t.stack.env),{font:c.TexConstant.Variant.NORMAL,multiLetterIdentifiers:/^[-*a-z]+/i,operatorLetters:!0}),t.configuration).mml();if(i.isKind("mi")||(i=t.create("node","TeXAtom",[i])),l.default.setProperties(i,{movesupsub:r,movablelimits:!0,texClass:d.TEXCLASS.OP}),!r){var a=t.GetNext(),p=t.i;"\\"===a&&++t.i&&"limits"!==t.GetCS()&&(t.i=p)}t.Push(i)},e.AmsMethods.SideSet=function(t,e){var r=o(m(t.ParseArg(e)),2),n=r[0],i=r[1],a=o(m(t.ParseArg(e)),2),c=a[0],u=a[1],p=t.ParseArg(e),h=p;n&&(i?n.replaceChild(t.create("node","mphantom",[t.create("node","mpadded",[s.default.copyNode(p,t)],{width:0})]),l.default.getChildAt(n,0)):(h=t.create("node","mmultiscripts",[p]),c&&l.default.appendChildren(h,[l.default.getChildAt(c,1)||t.create("node","none"),l.default.getChildAt(c,2)||t.create("node","none")]),l.default.setProperty(h,"scriptalign","left"),l.default.appendChildren(h,[t.create("node","mprescripts"),l.default.getChildAt(n,1)||t.create("node","none"),l.default.getChildAt(n,2)||t.create("node","none")]))),c&&h===p&&(c.replaceChild(p,l.default.getChildAt(c,0)),h=c);var f=t.create("node","TeXAtom",[],{texClass:d.TEXCLASS.OP,movesupsub:!0,movablelimits:!0});i&&(n&&f.appendChild(n),f.appendChild(i)),f.appendChild(h),u&&f.appendChild(u),t.Push(f)},e.AmsMethods.operatorLetter=function(t,e){return!!t.stack.env.operatorLetters&&a.default.variable(t,e)},e.AmsMethods.MultiIntegral=function(t,e,r){var n=t.GetNext();if("\\"===n){var o=t.i;n=t.GetArgument(e),t.i=o,"\\limits"===n&&(r="\\idotsint"===e?"\\!\\!\\mathop{\\,\\,"+r+"}":"\\!\\!\\!\\mathop{\\,\\,\\,"+r+"}")}t.string=r+" "+t.string.slice(t.i),t.i=0},e.AmsMethods.xArrow=function(t,e,r,n,o){var i={width:"+"+s.default.Em((n+o)/18),lspace:s.default.Em(n/18)},a=t.GetBrackets(e),c=t.ParseArg(e),p=t.create("node","mspace",[],{depth:".25em"}),h=t.create("token","mo",{stretchy:!0,texClass:d.TEXCLASS.REL},String.fromCodePoint(r));h=t.create("node","mstyle",[h],{scriptlevel:0});var f=t.create("node","munderover",[h]),m=t.create("node","mpadded",[c,p],i);if(l.default.setAttribute(m,"voffset","-.2em"),l.default.setAttribute(m,"height","-.2em"),l.default.setChild(f,f.over,m),a){var y=new u.default(a,t.stack.env,t.configuration).mml(),g=t.create("node","mspace",[],{height:".75em"});m=t.create("node","mpadded",[y,g],i),l.default.setAttribute(m,"voffset",".15em"),l.default.setAttribute(m,"depth","-.15em"),l.default.setChild(f,f.under,m)}l.default.setProperty(f,"subsupOK",!0),t.Push(f)},e.AmsMethods.HandleShove=function(t,e,r){var n=t.stack.Top();if("multline"!==n.kind)throw new p.default("CommandOnlyAllowedInEnv","%1 only allowed in %2 environment",t.currentCS,"multline");if(n.Size())throw new p.default("CommandAtTheBeginingOfLine","%1 must come at the beginning of the line",t.currentCS);n.setProperty("shove",r)},e.AmsMethods.CFrac=function(t,e){var r=s.default.trimSpaces(t.GetBrackets(e,"")),n=t.GetArgument(e),o=t.GetArgument(e),i={l:c.TexConstant.Align.LEFT,r:c.TexConstant.Align.RIGHT,"":""},a=new u.default("\\strut\\textstyle{"+n+"}",t.stack.env,t.configuration).mml(),h=new u.default("\\strut\\textstyle{"+o+"}",t.stack.env,t.configuration).mml(),f=t.create("node","mfrac",[a,h]);if(null==(r=i[r]))throw new p.default("IllegalAlign","Illegal alignment specified in %1",t.currentCS);r&&l.default.setProperties(f,{numalign:r,denomalign:r}),t.Push(f)},e.AmsMethods.Genfrac=function(t,e,r,n,o,i){null==r&&(r=t.GetDelimiterArg(e)),null==n&&(n=t.GetDelimiterArg(e)),null==o&&(o=t.GetArgument(e)),null==i&&(i=s.default.trimSpaces(t.GetArgument(e)));var a=t.ParseArg(e),c=t.ParseArg(e),u=t.create("node","mfrac",[a,c]);if(""!==o&&l.default.setAttribute(u,"linethickness",o),(r||n)&&(l.default.setProperty(u,"withDelims",!0),u=s.default.fixedFence(t.configuration,r,u,n)),""!==i){var h=parseInt(i,10),f=["D","T","S","SS"][h];if(null==f)throw new p.default("BadMathStyleFor","Bad math style for %1",t.currentCS);u=t.create("node","mstyle",[u]),"D"===f?l.default.setProperties(u,{displaystyle:!0,scriptlevel:0}):l.default.setProperties(u,{displaystyle:!1,scriptlevel:h-1})}t.Push(u)},e.AmsMethods.HandleTag=function(t,e){if(!t.tags.currentTag.taggable&&t.tags.env)throw new p.default("CommandNotAllowedInEnv","%1 not allowed in %2 environment",t.currentCS,t.tags.env);if(t.tags.currentTag.tag)throw new p.default("MultipleCommand","Multiple %1",t.currentCS);var r=t.GetStar(),n=s.default.trimSpaces(t.GetArgument(e));t.tags.tag(n,r)},e.AmsMethods.HandleNoTag=f.default.HandleNoTag,e.AmsMethods.HandleRef=f.default.HandleRef,e.AmsMethods.Macro=f.default.Macro,e.AmsMethods.Accent=f.default.Accent,e.AmsMethods.Tilde=f.default.Tilde,e.AmsMethods.Array=f.default.Array,e.AmsMethods.Spacer=f.default.Spacer,e.AmsMethods.NamedOp=f.default.NamedOp,e.AmsMethods.EqnArray=f.default.EqnArray,e.AmsMethods.Equation=f.default.Equation},1275:function(t,e,r){var n=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},o=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.AutoloadConfiguration=void 0;var i=r(9899),s=r(9140),a=r(8803),l=r(7741),c=r(265),u=r(7233);function p(t,e,r,i){var s,a,u,p;if(c.Package.packages.has(t.options.require.prefix+r)){var d=t.options.autoload[r],m=n(2===d.length&&Array.isArray(d[0])?d:[d,[]],2),y=m[0],g=m[1];try{for(var b=o(y),v=b.next();!v.done;v=b.next()){var _=v.value;h.remove(_)}}catch(t){s={error:t}}finally{try{v&&!v.done&&(a=b.return)&&a.call(b)}finally{if(s)throw s.error}}try{for(var S=o(g),M=S.next();!M.done;M=S.next()){var O=M.value;f.remove(O)}}catch(t){u={error:t}}finally{try{M&&!M.done&&(p=S.return)&&p.call(S)}finally{if(u)throw u.error}}t.string=(i?e+" ":"\\begin{"+e.slice(1)+"}")+t.string.slice(t.i),t.i=0}(0,l.RequireLoad)(t,r)}var h=new s.CommandMap("autoload-macros",{},{}),f=new s.CommandMap("autoload-environments",{},{});e.AutoloadConfiguration=i.Configuration.create("autoload",{handler:{macro:["autoload-macros"],environment:["autoload-environments"]},options:{autoload:(0,u.expandable)({action:["toggle","mathtip","texttip"],amscd:[[],["CD"]],bbox:["bbox"],boldsymbol:["boldsymbol"],braket:["bra","ket","braket","set","Bra","Ket","Braket","Set","ketbra","Ketbra"],bussproofs:[[],["prooftree"]],cancel:["cancel","bcancel","xcancel","cancelto"],color:["color","definecolor","textcolor","colorbox","fcolorbox"],enclose:["enclose"],extpfeil:["xtwoheadrightarrow","xtwoheadleftarrow","xmapsto","xlongequal","xtofrom","Newextarrow"],html:["href","class","style","cssId"],mhchem:["ce","pu"],newcommand:["newcommand","renewcommand","newenvironment","renewenvironment","def","let"],unicode:["unicode"],verb:["verb"]})},config:function(t,e){var r,i,s,c,u,d,m=e.parseOptions,y=m.handlers.get("macro"),g=m.handlers.get("environment"),b=m.options.autoload;m.packageData.set("autoload",{Autoload:p});try{for(var v=o(Object.keys(b)),_=v.next();!_.done;_=v.next()){var S=_.value,M=b[S],O=n(2===M.length&&Array.isArray(M[0])?M:[M,[]],2),x=O[0],E=O[1];try{for(var A=(s=void 0,o(x)),C=A.next();!C.done;C=A.next()){var T=C.value;y.lookup(T)&&"color"!==T||h.add(T,new a.Macro(T,p,[S,!0]))}}catch(t){s={error:t}}finally{try{C&&!C.done&&(c=A.return)&&c.call(A)}finally{if(s)throw s.error}}try{for(var N=(u=void 0,o(E)),w=N.next();!w.done;w=N.next()){var L=w.value;g.lookup(L)||f.add(L,new a.Macro(L,p,[S,!1]))}}catch(t){u={error:t}}finally{try{w&&!w.done&&(d=N.return)&&d.call(N)}finally{if(u)throw u.error}}}}catch(t){r={error:t}}finally{try{_&&!_.done&&(i=v.return)&&i.call(v)}finally{if(r)throw r.error}}m.packageData.get("require")||l.RequireConfiguration.config(t,e)},init:function(t){t.options.require||(0,u.defaultOptions)(t.options,l.RequireConfiguration.options)},priority:10})},2942:function(t,e,r){var n,o,i=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),s=this&&this.__createBinding||(Object.create?function(t,e,r,n){void 0===n&&(n=r);var o=Object.getOwnPropertyDescriptor(e,r);o&&!("get"in o?!e.__esModule:o.writable||o.configurable)||(o={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,n,o)}:function(t,e,r,n){void 0===n&&(n=r),t[n]=e[r]}),a=this&&this.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),l=this&&this.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var r in t)"default"!==r&&Object.prototype.hasOwnProperty.call(t,r)&&s(e,t,r);return a(e,t),e},c=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},u=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0}),e.BaseConfiguration=e.BaseTags=e.Other=void 0;var p=r(9899),h=r(2947),f=u(r(3971)),d=u(r(1256)),m=r(9140),y=l(r(1181)),g=r(6521);r(1267);var b=r(4082);function v(t,e){var r=t.stack.env.font?{mathvariant:t.stack.env.font}:{},n=h.MapHandler.getMap("remap").lookup(e),o=(0,b.getRange)(e),i=o?o[3]:"mo",s=t.create("token",i,r,n?n.char:e);o[4]&&s.attributes.set("mathvariant",o[4]),"mo"===i&&(d.default.setProperty(s,"fixStretchy",!0),t.configuration.addNode("fixStretchy",s)),t.Push(s)}new m.CharacterMap("remap",null,{"-":"\u2212","*":"\u2217","`":"\u2018"}),e.Other=v;var _=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return i(e,t),e}(g.AbstractTags);e.BaseTags=_,e.BaseConfiguration=p.Configuration.create("base",{handler:{character:["command","special","letter","digit"],delimiter:["delimiter"],macro:["delimiter","macros","mathchar0mi","mathchar0mo","mathchar7"],environment:["environment"]},fallback:{character:v,macro:function(t,e){throw new f.default("UndefinedControlSequence","Undefined control sequence %1","\\"+e)},environment:function(t,e){throw new f.default("UnknownEnv","Unknown environment '%1'",e)}},items:(o={},o[y.StartItem.prototype.kind]=y.StartItem,o[y.StopItem.prototype.kind]=y.StopItem,o[y.OpenItem.prototype.kind]=y.OpenItem,o[y.CloseItem.prototype.kind]=y.CloseItem,o[y.PrimeItem.prototype.kind]=y.PrimeItem,o[y.SubsupItem.prototype.kind]=y.SubsupItem,o[y.OverItem.prototype.kind]=y.OverItem,o[y.LeftItem.prototype.kind]=y.LeftItem,o[y.Middle.prototype.kind]=y.Middle,o[y.RightItem.prototype.kind]=y.RightItem,o[y.BeginItem.prototype.kind]=y.BeginItem,o[y.EndItem.prototype.kind]=y.EndItem,o[y.StyleItem.prototype.kind]=y.StyleItem,o[y.PositionItem.prototype.kind]=y.PositionItem,o[y.CellItem.prototype.kind]=y.CellItem,o[y.MmlItem.prototype.kind]=y.MmlItem,o[y.FnItem.prototype.kind]=y.FnItem,o[y.NotItem.prototype.kind]=y.NotItem,o[y.NonscriptItem.prototype.kind]=y.NonscriptItem,o[y.DotsItem.prototype.kind]=y.DotsItem,o[y.ArrayItem.prototype.kind]=y.ArrayItem,o[y.EqnArrayItem.prototype.kind]=y.EqnArrayItem,o[y.EquationItem.prototype.kind]=y.EquationItem,o),options:{maxMacros:1e3,baseURL:"undefined"==typeof document||0===document.getElementsByTagName("base").length?"":String(document.location).replace(/#.*$/,"")},tags:{base:_},postprocessors:[[function(t){var e,r,n=t.data;try{for(var o=c(n.getList("nonscript")),i=o.next();!i.done;i=o.next()){var s=i.value;if(s.attributes.get("scriptlevel")>0){var a=s.parent;if(a.childNodes.splice(a.childIndex(s),1),n.removeFromList(s.kind,[s]),s.isKind("mrow")){var l=s.childNodes[0];n.removeFromList("mstyle",[l]),n.removeFromList("mspace",l.childNodes[0].childNodes)}}else s.isKind("mrow")&&(s.parent.replaceChild(s.childNodes[0],s),n.removeFromList("mrow",[s]))}}catch(t){e={error:t}}finally{try{i&&!i.done&&(r=o.return)&&r.call(o)}finally{if(e)throw e.error}}},-4]]})},1181:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},s=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;othis.maxrow&&(this.maxrow=this.row.length);var t="mtr",e=this.factory.configuration.tags.getTag();e&&(this.row=[e].concat(this.row),t="mlabeledtr"),this.factory.configuration.tags.clearTag();var r=this.create("node",t,this.row);this.table.push(r),this.row=[]},e.prototype.EndTable=function(){t.prototype.EndTable.call(this),this.factory.configuration.tags.end(),this.extendArray("columnalign",this.maxrow),this.extendArray("columnwidth",this.maxrow),this.extendArray("columnspacing",this.maxrow-1)},e.prototype.extendArray=function(t,e){if(this.arraydef[t]){var r=this.arraydef[t].split(/ /),n=s([],i(r),!1);if(n.length>1){for(;n.length",succ:"\u227b",prec:"\u227a",approx:"\u2248",succeq:"\u2ab0",preceq:"\u2aaf",supset:"\u2283",subset:"\u2282",supseteq:"\u2287",subseteq:"\u2286",in:"\u2208",ni:"\u220b",notin:"\u2209",owns:"\u220b",gg:"\u226b",ll:"\u226a",sim:"\u223c",simeq:"\u2243",perp:"\u22a5",equiv:"\u2261",asymp:"\u224d",smile:"\u2323",frown:"\u2322",ne:"\u2260",neq:"\u2260",cong:"\u2245",doteq:"\u2250",bowtie:"\u22c8",models:"\u22a8",notChar:"\u29f8",Leftrightarrow:"\u21d4",Leftarrow:"\u21d0",Rightarrow:"\u21d2",leftrightarrow:"\u2194",leftarrow:"\u2190",gets:"\u2190",rightarrow:"\u2192",to:["\u2192",{accent:!1}],mapsto:"\u21a6",leftharpoonup:"\u21bc",leftharpoondown:"\u21bd",rightharpoonup:"\u21c0",rightharpoondown:"\u21c1",nearrow:"\u2197",searrow:"\u2198",nwarrow:"\u2196",swarrow:"\u2199",rightleftharpoons:"\u21cc",hookrightarrow:"\u21aa",hookleftarrow:"\u21a9",longleftarrow:"\u27f5",Longleftarrow:"\u27f8",longrightarrow:"\u27f6",Longrightarrow:"\u27f9",Longleftrightarrow:"\u27fa",longleftrightarrow:"\u27f7",longmapsto:"\u27fc",ldots:"\u2026",cdots:"\u22ef",vdots:"\u22ee",ddots:"\u22f1",dotsc:"\u2026",dotsb:"\u22ef",dotsm:"\u22ef",dotsi:"\u22ef",dotso:"\u2026",ldotp:[".",{texClass:h.TEXCLASS.PUNCT}],cdotp:["\u22c5",{texClass:h.TEXCLASS.PUNCT}],colon:[":",{texClass:h.TEXCLASS.PUNCT}]}),new a.CharacterMap("mathchar7",u.default.mathchar7,{Gamma:"\u0393",Delta:"\u0394",Theta:"\u0398",Lambda:"\u039b",Xi:"\u039e",Pi:"\u03a0",Sigma:"\u03a3",Upsilon:"\u03a5",Phi:"\u03a6",Psi:"\u03a8",Omega:"\u03a9",_:"_","#":"#",$:"$","%":"%","&":"&",And:"&"}),new a.DelimiterMap("delimiter",u.default.delimiter,{"(":"(",")":")","[":"[","]":"]","<":"\u27e8",">":"\u27e9","\\lt":"\u27e8","\\gt":"\u27e9","/":"/","|":["|",{texClass:h.TEXCLASS.ORD}],".":"","\\\\":"\\","\\lmoustache":"\u23b0","\\rmoustache":"\u23b1","\\lgroup":"\u27ee","\\rgroup":"\u27ef","\\arrowvert":"\u23d0","\\Arrowvert":"\u2016","\\bracevert":"\u23aa","\\Vert":["\u2016",{texClass:h.TEXCLASS.ORD}],"\\|":["\u2016",{texClass:h.TEXCLASS.ORD}],"\\vert":["|",{texClass:h.TEXCLASS.ORD}],"\\uparrow":"\u2191","\\downarrow":"\u2193","\\updownarrow":"\u2195","\\Uparrow":"\u21d1","\\Downarrow":"\u21d3","\\Updownarrow":"\u21d5","\\backslash":"\\","\\rangle":"\u27e9","\\langle":"\u27e8","\\rbrace":"}","\\lbrace":"{","\\}":"}","\\{":"{","\\rceil":"\u2309","\\lceil":"\u2308","\\rfloor":"\u230b","\\lfloor":"\u230a","\\lbrack":"[","\\rbrack":"]"}),new a.CommandMap("macros",{displaystyle:["SetStyle","D",!0,0],textstyle:["SetStyle","T",!1,0],scriptstyle:["SetStyle","S",!1,1],scriptscriptstyle:["SetStyle","SS",!1,2],rm:["SetFont",l.TexConstant.Variant.NORMAL],mit:["SetFont",l.TexConstant.Variant.ITALIC],oldstyle:["SetFont",l.TexConstant.Variant.OLDSTYLE],cal:["SetFont",l.TexConstant.Variant.CALLIGRAPHIC],it:["SetFont",l.TexConstant.Variant.MATHITALIC],bf:["SetFont",l.TexConstant.Variant.BOLD],bbFont:["SetFont",l.TexConstant.Variant.DOUBLESTRUCK],scr:["SetFont",l.TexConstant.Variant.SCRIPT],frak:["SetFont",l.TexConstant.Variant.FRAKTUR],sf:["SetFont",l.TexConstant.Variant.SANSSERIF],tt:["SetFont",l.TexConstant.Variant.MONOSPACE],mathrm:["MathFont",l.TexConstant.Variant.NORMAL],mathup:["MathFont",l.TexConstant.Variant.NORMAL],mathnormal:["MathFont",""],mathbf:["MathFont",l.TexConstant.Variant.BOLD],mathbfup:["MathFont",l.TexConstant.Variant.BOLD],mathit:["MathFont",l.TexConstant.Variant.MATHITALIC],mathbfit:["MathFont",l.TexConstant.Variant.BOLDITALIC],mathbb:["MathFont",l.TexConstant.Variant.DOUBLESTRUCK],Bbb:["MathFont",l.TexConstant.Variant.DOUBLESTRUCK],mathfrak:["MathFont",l.TexConstant.Variant.FRAKTUR],mathbffrak:["MathFont",l.TexConstant.Variant.BOLDFRAKTUR],mathscr:["MathFont",l.TexConstant.Variant.SCRIPT],mathbfscr:["MathFont",l.TexConstant.Variant.BOLDSCRIPT],mathsf:["MathFont",l.TexConstant.Variant.SANSSERIF],mathsfup:["MathFont",l.TexConstant.Variant.SANSSERIF],mathbfsf:["MathFont",l.TexConstant.Variant.BOLDSANSSERIF],mathbfsfup:["MathFont",l.TexConstant.Variant.BOLDSANSSERIF],mathsfit:["MathFont",l.TexConstant.Variant.SANSSERIFITALIC],mathbfsfit:["MathFont",l.TexConstant.Variant.SANSSERIFBOLDITALIC],mathtt:["MathFont",l.TexConstant.Variant.MONOSPACE],mathcal:["MathFont",l.TexConstant.Variant.CALLIGRAPHIC],mathbfcal:["MathFont",l.TexConstant.Variant.BOLDCALLIGRAPHIC],symrm:["MathFont",l.TexConstant.Variant.NORMAL],symup:["MathFont",l.TexConstant.Variant.NORMAL],symnormal:["MathFont",""],symbf:["MathFont",l.TexConstant.Variant.BOLD],symbfup:["MathFont",l.TexConstant.Variant.BOLD],symit:["MathFont",l.TexConstant.Variant.ITALIC],symbfit:["MathFont",l.TexConstant.Variant.BOLDITALIC],symbb:["MathFont",l.TexConstant.Variant.DOUBLESTRUCK],symfrak:["MathFont",l.TexConstant.Variant.FRAKTUR],symbffrak:["MathFont",l.TexConstant.Variant.BOLDFRAKTUR],symscr:["MathFont",l.TexConstant.Variant.SCRIPT],symbfscr:["MathFont",l.TexConstant.Variant.BOLDSCRIPT],symsf:["MathFont",l.TexConstant.Variant.SANSSERIF],symsfup:["MathFont",l.TexConstant.Variant.SANSSERIF],symbfsf:["MathFont",l.TexConstant.Variant.BOLDSANSSERIF],symbfsfup:["MathFont",l.TexConstant.Variant.BOLDSANSSERIF],symsfit:["MathFont",l.TexConstant.Variant.SANSSERIFITALIC],symbfsfit:["MathFont",l.TexConstant.Variant.SANSSERIFBOLDITALIC],symtt:["MathFont",l.TexConstant.Variant.MONOSPACE],symcal:["MathFont",l.TexConstant.Variant.CALLIGRAPHIC],symbfcal:["MathFont",l.TexConstant.Variant.BOLDCALLIGRAPHIC],textrm:["HBox",null,l.TexConstant.Variant.NORMAL],textup:["HBox",null,l.TexConstant.Variant.NORMAL],textnormal:["HBox"],textit:["HBox",null,l.TexConstant.Variant.ITALIC],textbf:["HBox",null,l.TexConstant.Variant.BOLD],textsf:["HBox",null,l.TexConstant.Variant.SANSSERIF],texttt:["HBox",null,l.TexConstant.Variant.MONOSPACE],tiny:["SetSize",.5],Tiny:["SetSize",.6],scriptsize:["SetSize",.7],small:["SetSize",.85],normalsize:["SetSize",1],large:["SetSize",1.2],Large:["SetSize",1.44],LARGE:["SetSize",1.73],huge:["SetSize",2.07],Huge:["SetSize",2.49],arcsin:"NamedFn",arccos:"NamedFn",arctan:"NamedFn",arg:"NamedFn",cos:"NamedFn",cosh:"NamedFn",cot:"NamedFn",coth:"NamedFn",csc:"NamedFn",deg:"NamedFn",det:"NamedOp",dim:"NamedFn",exp:"NamedFn",gcd:"NamedOp",hom:"NamedFn",inf:"NamedOp",ker:"NamedFn",lg:"NamedFn",lim:"NamedOp",liminf:["NamedOp","lim inf"],limsup:["NamedOp","lim sup"],ln:"NamedFn",log:"NamedFn",max:"NamedOp",min:"NamedOp",Pr:"NamedOp",sec:"NamedFn",sin:"NamedFn",sinh:"NamedFn",sup:"NamedOp",tan:"NamedFn",tanh:"NamedFn",limits:["Limits",1],nolimits:["Limits",0],overline:["UnderOver","2015"],underline:["UnderOver","2015"],overbrace:["UnderOver","23DE",1],underbrace:["UnderOver","23DF",1],overparen:["UnderOver","23DC"],underparen:["UnderOver","23DD"],overrightarrow:["UnderOver","2192"],underrightarrow:["UnderOver","2192"],overleftarrow:["UnderOver","2190"],underleftarrow:["UnderOver","2190"],overleftrightarrow:["UnderOver","2194"],underleftrightarrow:["UnderOver","2194"],overset:"Overset",underset:"Underset",overunderset:"Overunderset",stackrel:["Macro","\\mathrel{\\mathop{#2}\\limits^{#1}}",2],stackbin:["Macro","\\mathbin{\\mathop{#2}\\limits^{#1}}",2],over:"Over",overwithdelims:"Over",atop:"Over",atopwithdelims:"Over",above:"Over",abovewithdelims:"Over",brace:["Over","{","}"],brack:["Over","[","]"],choose:["Over","(",")"],frac:"Frac",sqrt:"Sqrt",root:"Root",uproot:["MoveRoot","upRoot"],leftroot:["MoveRoot","leftRoot"],left:"LeftRight",right:"LeftRight",middle:"LeftRight",llap:"Lap",rlap:"Lap",raise:"RaiseLower",lower:"RaiseLower",moveleft:"MoveLeftRight",moveright:"MoveLeftRight",",":["Spacer",f.MATHSPACE.thinmathspace],":":["Spacer",f.MATHSPACE.mediummathspace],">":["Spacer",f.MATHSPACE.mediummathspace],";":["Spacer",f.MATHSPACE.thickmathspace],"!":["Spacer",f.MATHSPACE.negativethinmathspace],enspace:["Spacer",.5],quad:["Spacer",1],qquad:["Spacer",2],thinspace:["Spacer",f.MATHSPACE.thinmathspace],negthinspace:["Spacer",f.MATHSPACE.negativethinmathspace],hskip:"Hskip",hspace:"Hskip",kern:"Hskip",mskip:"Hskip",mspace:"Hskip",mkern:"Hskip",rule:"rule",Rule:["Rule"],Space:["Rule","blank"],nonscript:"Nonscript",big:["MakeBig",h.TEXCLASS.ORD,.85],Big:["MakeBig",h.TEXCLASS.ORD,1.15],bigg:["MakeBig",h.TEXCLASS.ORD,1.45],Bigg:["MakeBig",h.TEXCLASS.ORD,1.75],bigl:["MakeBig",h.TEXCLASS.OPEN,.85],Bigl:["MakeBig",h.TEXCLASS.OPEN,1.15],biggl:["MakeBig",h.TEXCLASS.OPEN,1.45],Biggl:["MakeBig",h.TEXCLASS.OPEN,1.75],bigr:["MakeBig",h.TEXCLASS.CLOSE,.85],Bigr:["MakeBig",h.TEXCLASS.CLOSE,1.15],biggr:["MakeBig",h.TEXCLASS.CLOSE,1.45],Biggr:["MakeBig",h.TEXCLASS.CLOSE,1.75],bigm:["MakeBig",h.TEXCLASS.REL,.85],Bigm:["MakeBig",h.TEXCLASS.REL,1.15],biggm:["MakeBig",h.TEXCLASS.REL,1.45],Biggm:["MakeBig",h.TEXCLASS.REL,1.75],mathord:["TeXAtom",h.TEXCLASS.ORD],mathop:["TeXAtom",h.TEXCLASS.OP],mathopen:["TeXAtom",h.TEXCLASS.OPEN],mathclose:["TeXAtom",h.TEXCLASS.CLOSE],mathbin:["TeXAtom",h.TEXCLASS.BIN],mathrel:["TeXAtom",h.TEXCLASS.REL],mathpunct:["TeXAtom",h.TEXCLASS.PUNCT],mathinner:["TeXAtom",h.TEXCLASS.INNER],vcenter:["TeXAtom",h.TEXCLASS.VCENTER],buildrel:"BuildRel",hbox:["HBox",0],text:"HBox",mbox:["HBox",0],fbox:"FBox",boxed:["Macro","\\fbox{$\\displaystyle{#1}$}",1],framebox:"FrameBox",strut:"Strut",mathstrut:["Macro","\\vphantom{(}"],phantom:"Phantom",vphantom:["Phantom",1,0],hphantom:["Phantom",0,1],smash:"Smash",acute:["Accent","00B4"],grave:["Accent","0060"],ddot:["Accent","00A8"],tilde:["Accent","007E"],bar:["Accent","00AF"],breve:["Accent","02D8"],check:["Accent","02C7"],hat:["Accent","005E"],vec:["Accent","2192"],dot:["Accent","02D9"],widetilde:["Accent","007E",1],widehat:["Accent","005E",1],matrix:"Matrix",array:"Matrix",pmatrix:["Matrix","(",")"],cases:["Matrix","{","","left left",null,".1em",null,!0],eqalign:["Matrix",null,null,"right left",(0,f.em)(f.MATHSPACE.thickmathspace),".5em","D"],displaylines:["Matrix",null,null,"center",null,".5em","D"],cr:"Cr","\\":"CrLaTeX",newline:["CrLaTeX",!0],hline:["HLine","solid"],hdashline:["HLine","dashed"],eqalignno:["Matrix",null,null,"right left",(0,f.em)(f.MATHSPACE.thickmathspace),".5em","D",null,"right"],leqalignno:["Matrix",null,null,"right left",(0,f.em)(f.MATHSPACE.thickmathspace),".5em","D",null,"left"],hfill:"HFill",hfil:"HFill",hfilll:"HFill",bmod:["Macro",'\\mmlToken{mo}[lspace="thickmathspace" rspace="thickmathspace"]{mod}'],pmod:["Macro","\\pod{\\mmlToken{mi}{mod}\\kern 6mu #1}",1],mod:["Macro","\\mathchoice{\\kern18mu}{\\kern12mu}{\\kern12mu}{\\kern12mu}\\mmlToken{mi}{mod}\\,\\,#1",1],pod:["Macro","\\mathchoice{\\kern18mu}{\\kern8mu}{\\kern8mu}{\\kern8mu}(#1)",1],iff:["Macro","\\;\\Longleftrightarrow\\;"],skew:["Macro","{{#2{#3\\mkern#1mu}\\mkern-#1mu}{}}",3],pmb:["Macro","\\rlap{#1}\\kern1px{#1}",1],TeX:["Macro","T\\kern-.14em\\lower.5ex{E}\\kern-.115em X"],LaTeX:["Macro","L\\kern-.325em\\raise.21em{\\scriptstyle{A}}\\kern-.17em\\TeX"]," ":["Macro","\\text{ }"],not:"Not",dots:"Dots",space:"Tilde","\xa0":"Tilde",begin:"BeginEnd",end:"BeginEnd",label:"HandleLabel",ref:"HandleRef",nonumber:"HandleNoTag",mathchoice:"MathChoice",mmlToken:"MmlToken"},c.default),new a.EnvironmentMap("environment",u.default.environment,{array:["AlignedArray"],equation:["Equation",null,!0],eqnarray:["EqnArray",null,!0,!0,"rcl",p.default.cols(0,f.MATHSPACE.thickmathspace),".5em"]},c.default),new a.CharacterMap("not_remap",null,{"\u2190":"\u219a","\u2192":"\u219b","\u2194":"\u21ae","\u21d0":"\u21cd","\u21d2":"\u21cf","\u21d4":"\u21ce","\u2208":"\u2209","\u220b":"\u220c","\u2223":"\u2224","\u2225":"\u2226","\u223c":"\u2241","~":"\u2241","\u2243":"\u2244","\u2245":"\u2247","\u2248":"\u2249","\u224d":"\u226d","=":"\u2260","\u2261":"\u2262","<":"\u226e",">":"\u226f","\u2264":"\u2270","\u2265":"\u2271","\u2272":"\u2274","\u2273":"\u2275","\u2276":"\u2278","\u2277":"\u2279","\u227a":"\u2280","\u227b":"\u2281","\u2282":"\u2284","\u2283":"\u2285","\u2286":"\u2288","\u2287":"\u2289","\u22a2":"\u22ac","\u22a8":"\u22ad","\u22a9":"\u22ae","\u22ab":"\u22af","\u227c":"\u22e0","\u227d":"\u22e1","\u2291":"\u22e2","\u2292":"\u22e3","\u22b2":"\u22ea","\u22b3":"\u22eb","\u22b4":"\u22ec","\u22b5":"\u22ed","\u2203":"\u2204"})},7693:function(t,e,r){var n=this&&this.__assign||function(){return n=Object.assign||function(t){for(var e,r=1,n=arguments.length;r0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},l=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0});var c=s(r(1181)),u=l(r(1256)),p=l(r(3971)),h=l(r(8417)),f=r(8317),d=l(r(1130)),m=r(9007),y=r(6521),g=r(6010),b=r(5368),v=r(7233),_={},S={fontfamily:1,fontsize:1,fontweight:1,fontstyle:1,color:1,background:1,id:1,class:1,href:1,style:1};function M(t,e){var r=t.stack.env,n=r.inRoot;r.inRoot=!0;var o=new h.default(e,r,t.configuration),i=o.mml(),s=o.stack.global;if(s.leftRoot||s.upRoot){var a={};s.leftRoot&&(a.width=s.leftRoot),s.upRoot&&(a.voffset=s.upRoot,a.height=s.upRoot),i=t.create("node","mpadded",[i],a)}return r.inRoot=n,i}_.Open=function(t,e){t.Push(t.itemFactory.create("open"))},_.Close=function(t,e){t.Push(t.itemFactory.create("close"))},_.Tilde=function(t,e){t.Push(t.create("token","mtext",{},b.entities.nbsp))},_.Space=function(t,e){},_.Superscript=function(t,e){var r,n,o;t.GetNext().match(/\d/)&&(t.string=t.string.substr(0,t.i+1)+" "+t.string.substr(t.i+1));var i=t.stack.Top();i.isKind("prime")?(o=(r=a(i.Peek(2),2))[0],n=r[1],t.stack.Pop()):(o=t.stack.Prev())||(o=t.create("token","mi",{},""));var s=u.default.getProperty(o,"movesupsub"),l=u.default.isType(o,"msubsup")?o.sup:o.over;if(u.default.isType(o,"msubsup")&&!u.default.isType(o,"msup")&&u.default.getChildAt(o,o.sup)||u.default.isType(o,"munderover")&&!u.default.isType(o,"mover")&&u.default.getChildAt(o,o.over)&&!u.default.getProperty(o,"subsupOK"))throw new p.default("DoubleExponent","Double exponent: use braces to clarify");u.default.isType(o,"msubsup")&&!u.default.isType(o,"msup")||(s?((!u.default.isType(o,"munderover")||u.default.isType(o,"mover")||u.default.getChildAt(o,o.over))&&(o=t.create("node","munderover",[o],{movesupsub:!0})),l=o.over):l=(o=t.create("node","msubsup",[o])).sup),t.Push(t.itemFactory.create("subsup",o).setProperties({position:l,primes:n,movesupsub:s}))},_.Subscript=function(t,e){var r,n,o;t.GetNext().match(/\d/)&&(t.string=t.string.substr(0,t.i+1)+" "+t.string.substr(t.i+1));var i=t.stack.Top();i.isKind("prime")?(o=(r=a(i.Peek(2),2))[0],n=r[1],t.stack.Pop()):(o=t.stack.Prev())||(o=t.create("token","mi",{},""));var s=u.default.getProperty(o,"movesupsub"),l=u.default.isType(o,"msubsup")?o.sub:o.under;if(u.default.isType(o,"msubsup")&&!u.default.isType(o,"msup")&&u.default.getChildAt(o,o.sub)||u.default.isType(o,"munderover")&&!u.default.isType(o,"mover")&&u.default.getChildAt(o,o.under)&&!u.default.getProperty(o,"subsupOK"))throw new p.default("DoubleSubscripts","Double subscripts: use braces to clarify");u.default.isType(o,"msubsup")&&!u.default.isType(o,"msup")||(s?((!u.default.isType(o,"munderover")||u.default.isType(o,"mover")||u.default.getChildAt(o,o.under))&&(o=t.create("node","munderover",[o],{movesupsub:!0})),l=o.under):l=(o=t.create("node","msubsup",[o])).sub),t.Push(t.itemFactory.create("subsup",o).setProperties({position:l,primes:n,movesupsub:s}))},_.Prime=function(t,e){var r=t.stack.Prev();if(r||(r=t.create("node","mi")),u.default.isType(r,"msubsup")&&!u.default.isType(r,"msup")&&u.default.getChildAt(r,r.sup))throw new p.default("DoubleExponentPrime","Prime causes double exponent: use braces to clarify");var n="";t.i--;do{n+=b.entities.prime,t.i++,e=t.GetNext()}while("'"===e||e===b.entities.rsquo);n=["","\u2032","\u2033","\u2034","\u2057"][n.length]||n;var o=t.create("token","mo",{variantForm:!0},n);t.Push(t.itemFactory.create("prime",r,o))},_.Comment=function(t,e){for(;t.i=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},i=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0}),e.ConfigMacrosConfiguration=void 0;var s=r(9899),a=r(7233),l=r(9140),c=i(r(5450)),u=r(8803),p=i(r(1110)),h=r(6793),f="configmacros-map",d="configmacros-env-map";e.ConfigMacrosConfiguration=s.Configuration.create("configmacros",{init:function(t){new l.CommandMap(f,{},{}),new l.EnvironmentMap(d,c.default.environment,{},{}),t.append(s.Configuration.local({handler:{macro:[f],environment:[d]},priority:3}))},config:function(t,e){!function(t){var e,r,n=t.parseOptions.handlers.retrieve(f),i=t.parseOptions.options.macros;try{for(var s=o(Object.keys(i)),a=s.next();!a.done;a=s.next()){var l=a.value,c="string"==typeof i[l]?[i[l]]:i[l],h=Array.isArray(c[2])?new u.Macro(l,p.default.MacroWithTemplate,c.slice(0,2).concat(c[2])):new u.Macro(l,p.default.Macro,c);n.add(l,h)}}catch(t){e={error:t}}finally{try{a&&!a.done&&(r=s.return)&&r.call(s)}finally{if(e)throw e.error}}}(e),function(t){var e,r,n=t.parseOptions.handlers.retrieve(d),i=t.parseOptions.options.environments;try{for(var s=o(Object.keys(i)),a=s.next();!a.done;a=s.next()){var l=a.value;n.add(l,new u.Macro(l,p.default.BeginEnv,[!0].concat(i[l])))}}catch(t){e={error:t}}finally{try{a&&!a.done&&(r=s.return)&&r.call(s)}finally{if(e)throw e.error}}}(e)},items:(n={},n[h.BeginEnvItem.prototype.kind]=h.BeginEnvItem,n),options:{macros:(0,a.expandable)({}),environments:(0,a.expandable)({})}})},1496:function(t,e,r){var n,o=this&&this.__createBinding||(Object.create?function(t,e,r,n){void 0===n&&(n=r);var o=Object.getOwnPropertyDescriptor(e,r);o&&!("get"in o?!e.__esModule:o.writable||o.configurable)||(o={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,n,o)}:function(t,e,r,n){void 0===n&&(n=r),t[n]=e[r]}),i=this&&this.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),s=this&&this.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var r in t)"default"!==r&&Object.prototype.hasOwnProperty.call(t,r)&&o(e,t,r);return i(e,t),e},a=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0}),e.NewcommandConfiguration=void 0;var l=r(9899),c=r(6793),u=a(r(5579));r(5117);var p=a(r(5450)),h=s(r(9140));e.NewcommandConfiguration=l.Configuration.create("newcommand",{handler:{macro:["Newcommand-macros"]},items:(n={},n[c.BeginEnvItem.prototype.kind]=c.BeginEnvItem,n),options:{maxMacros:1e3},init:function(t){new h.DelimiterMap(u.default.NEW_DELIMITER,p.default.delimiter,{}),new h.CommandMap(u.default.NEW_COMMAND,{},{}),new h.EnvironmentMap(u.default.NEW_ENVIRONMENT,p.default.environment,{},{}),t.append(l.Configuration.local({handler:{character:[],delimiter:[u.default.NEW_DELIMITER],macro:[u.default.NEW_DELIMITER,u.default.NEW_COMMAND],environment:[u.default.NEW_ENVIRONMENT]},priority:-1}))}})},6793:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0}),e.BeginEnvItem=void 0;var s=i(r(3971)),a=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),Object.defineProperty(e.prototype,"kind",{get:function(){return"beginEnv"},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"isOpen",{get:function(){return!0},enumerable:!1,configurable:!0}),e.prototype.checkItem=function(e){if(e.isKind("end")){if(e.getName()!==this.getName())throw new s.default("EnvBadEnd","\\begin{%1} ended with \\end{%2}",this.getName(),e.getName());return[[this.factory.create("mml",this.toMml())],!0]}if(e.isKind("stop"))throw new s.default("EnvMissingEnd","Missing \\end{%1}",this.getName());return t.prototype.checkItem.call(this,e)},e}(r(8292).BaseItem);e.BeginEnvItem=a},5117:function(t,e,r){var n=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0});var o=n(r(1110));new(r(9140).CommandMap)("Newcommand-macros",{newcommand:"NewCommand",renewcommand:"NewCommand",newenvironment:"NewEnvironment",renewenvironment:"NewEnvironment",def:"MacroDef",let:"Let"},o.default)},1110:function(t,e,r){var n=this&&this.__createBinding||(Object.create?function(t,e,r,n){void 0===n&&(n=r);var o=Object.getOwnPropertyDescriptor(e,r);o&&!("get"in o?!e.__esModule:o.writable||o.configurable)||(o={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,n,o)}:function(t,e,r,n){void 0===n&&(n=r),t[n]=e[r]}),o=this&&this.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),i=this&&this.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var r in t)"default"!==r&&Object.prototype.hasOwnProperty.call(t,r)&&n(e,t,r);return o(e,t),e},s=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0});var a=s(r(3971)),l=i(r(9140)),c=s(r(7693)),u=s(r(1130)),p=s(r(5579)),h={NewCommand:function(t,e){var r=p.default.GetCsNameArgument(t,e),n=p.default.GetArgCount(t,e),o=t.GetBrackets(e),i=t.GetArgument(e);p.default.addMacro(t,r,h.Macro,[i,n,o])},NewEnvironment:function(t,e){var r=u.default.trimSpaces(t.GetArgument(e)),n=p.default.GetArgCount(t,e),o=t.GetBrackets(e),i=t.GetArgument(e),s=t.GetArgument(e);p.default.addEnvironment(t,r,h.BeginEnv,[!0,i,s,n,o])},MacroDef:function(t,e){var r=p.default.GetCSname(t,e),n=p.default.GetTemplate(t,e,"\\"+r),o=t.GetArgument(e);n instanceof Array?p.default.addMacro(t,r,h.MacroWithTemplate,[o].concat(n)):p.default.addMacro(t,r,h.Macro,[o,n])},Let:function(t,e){var r=p.default.GetCSname(t,e),n=t.GetNext();"="===n&&(t.i++,n=t.GetNext());var o=t.configuration.handlers;if("\\"!==n){t.i++;var i=o.get("delimiter").lookup(n);i?p.default.addDelimiter(t,"\\"+r,i.char,i.attributes):p.default.addMacro(t,r,h.Macro,[n])}else{e=p.default.GetCSname(t,e);var s=o.get("delimiter").lookup("\\"+e);if(s)return void p.default.addDelimiter(t,"\\"+r,s.char,s.attributes);var a=o.get("macro").applicable(e);if(!a)return;if(a instanceof l.MacroMap){var c=a.lookup(e);return void p.default.addMacro(t,r,c.func,c.args,c.symbol)}s=a.lookup(e);var u=p.default.disassembleSymbol(r,s);p.default.addMacro(t,r,(function(t,e){for(var r=[],n=2;n0?[i.toString()].concat(o):i;t.i++}throw new s.default("MissingReplacementString","Missing replacement string for definition of %1",e)},t.GetParameter=function(t,r,n){if(null==n)return t.GetArgument(r);for(var o=t.i,i=0,a=0;t.i=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.NoUndefinedConfiguration=void 0;var o=r(9899);e.NoUndefinedConfiguration=o.Configuration.create("noundefined",{fallback:{macro:function(t,e){var r,o,i=t.create("text","\\"+e),s=t.options.noundefined||{},a={};try{for(var l=n(["color","background","size"]),c=l.next();!c.done;c=l.next()){var u=c.value;s[u]&&(a["math"+u]=s[u])}}catch(t){r={error:t}}finally{try{c&&!c.done&&(o=l.return)&&o.call(l)}finally{if(r)throw r.error}}t.Push(t.create("node","mtext",[],a,i))}},options:{noundefined:{color:"red",background:"",size:""}},priority:3})},7741:function(t,e,r){var n=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},o=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},i=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.CHTML=void 0;var u=r(3055),p=r(4139),h=r(9261),f=r(6797),d=r(2760),m=l(r(6010)),y=r(505),g=function(t){function e(e){void 0===e&&(e=null);var r=t.call(this,e,h.CHTMLWrapperFactory,d.TeXFont)||this;return r.chtmlStyles=null,r.font.adaptiveCSS(r.options.adaptiveCSS),r.wrapperUsage=new f.Usage,r}return o(e,t),e.prototype.escaped=function(t,e){return this.setDocument(e),this.html("span",{},[this.text(t.math)])},e.prototype.styleSheet=function(r){if(this.chtmlStyles){if(this.options.adaptiveCSS){var n=new p.CssStyles;this.addWrapperStyles(n),this.updateFontStyles(n),this.adaptor.insertRules(this.chtmlStyles,n.getStyleRules())}return this.chtmlStyles}var o=this.chtmlStyles=t.prototype.styleSheet.call(this,r);return this.adaptor.setAttribute(o,"id",e.STYLESHEETID),this.wrapperUsage.update(),o},e.prototype.updateFontStyles=function(t){t.addStyles(this.font.updateStyles({}))},e.prototype.addWrapperStyles=function(e){var r,n;if(this.options.adaptiveCSS)try{for(var o=c(this.wrapperUsage.update()),i=o.next();!i.done;i=o.next()){var s=i.value,a=this.factory.getNodeClass(s);a&&this.addClassStyles(a,e)}}catch(t){r={error:t}}finally{try{i&&!i.done&&(n=o.return)&&n.call(o)}finally{if(r)throw r.error}}else t.prototype.addWrapperStyles.call(this,e)},e.prototype.addClassStyles=function(e,r){var n,o=e;o.autoStyle&&"unknown"!==o.kind&&r.addStyles(((n={})["mjx-"+o.kind]={display:"inline-block","text-align":"left"},n)),this.wrapperUsage.add(o.kind),t.prototype.addClassStyles.call(this,e,r)},e.prototype.processMath=function(t,e){this.factory.wrap(t).toCHTML(e)},e.prototype.clearCache=function(){this.cssStyles.clear(),this.font.clearCache(),this.wrapperUsage.clear(),this.chtmlStyles=null},e.prototype.reset=function(){this.clearCache()},e.prototype.unknownText=function(t,e,r){void 0===r&&(r=null);var n={},o=100/this.math.metrics.scale;if(100!==o&&(n["font-size"]=this.fixed(o,1)+"%",n.padding=m.em(75/o)+" 0 "+m.em(20/o)+" 0"),"-explicitFont"!==e){var i=(0,y.unicodeChars)(t);(1!==i.length||i[0]<119808||i[0]>120831)&&this.cssFontStyles(this.font.getCssFont(e),n)}if(null!==r){var s=this.math.metrics;n.width=Math.round(r*s.em*s.scale)+"px"}return this.html("mjx-utext",{variant:e,style:n},[this.text(t)])},e.prototype.measureTextNode=function(t){var e=this.adaptor,r=e.clone(t);e.setStyle(r,"font-family",e.getStyle(r,"font-family").replace(/MJXZERO, /g,""));var n=this.html("mjx-measure-text",{style:{position:"absolute","white-space":"nowrap"}},[r]);e.append(e.parent(this.math.start.node),this.container),e.append(this.container,n);var o=e.nodeSize(r,this.math.metrics.em)[0]/this.math.metrics.scale;return e.remove(this.container),e.remove(n),{w:o,h:.75,d:.2}},e.NAME="CHTML",e.OPTIONS=i(i({},u.CommonOutputJax.OPTIONS),{adaptiveCSS:!0,matchFontHeight:!0}),e.commonStyles={'mjx-container[jax="CHTML"]':{"line-height":0},'mjx-container [space="1"]':{"margin-left":".111em"},'mjx-container [space="2"]':{"margin-left":".167em"},'mjx-container [space="3"]':{"margin-left":".222em"},'mjx-container [space="4"]':{"margin-left":".278em"},'mjx-container [space="5"]':{"margin-left":".333em"},'mjx-container [rspace="1"]':{"margin-right":".111em"},'mjx-container [rspace="2"]':{"margin-right":".167em"},'mjx-container [rspace="3"]':{"margin-right":".222em"},'mjx-container [rspace="4"]':{"margin-right":".278em"},'mjx-container [rspace="5"]':{"margin-right":".333em"},'mjx-container [size="s"]':{"font-size":"70.7%"},'mjx-container [size="ss"]':{"font-size":"50%"},'mjx-container [size="Tn"]':{"font-size":"60%"},'mjx-container [size="sm"]':{"font-size":"85%"},'mjx-container [size="lg"]':{"font-size":"120%"},'mjx-container [size="Lg"]':{"font-size":"144%"},'mjx-container [size="LG"]':{"font-size":"173%"},'mjx-container [size="hg"]':{"font-size":"207%"},'mjx-container [size="HG"]':{"font-size":"249%"},'mjx-container [width="full"]':{width:"100%"},"mjx-box":{display:"inline-block"},"mjx-block":{display:"block"},"mjx-itable":{display:"inline-table"},"mjx-row":{display:"table-row"},"mjx-row > *":{display:"table-cell"},"mjx-mtext":{display:"inline-block"},"mjx-mstyle":{display:"inline-block"},"mjx-merror":{display:"inline-block",color:"red","background-color":"yellow"},"mjx-mphantom":{visibility:"hidden"},"_::-webkit-full-page-media, _:future, :root mjx-container":{"will-change":"opacity"}},e.STYLESHEETID="MJX-CHTML-styles",e}(u.CommonOutputJax);e.CHTML=g},8042:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__assign||function(){return i=Object.assign||function(t){for(var e,r=1,n=arguments.length;r=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},c=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s};Object.defineProperty(e,"__esModule",{value:!0}),e.AddCSS=e.CHTMLFontData=void 0;var u=r(5884),p=r(6797),h=r(6010);a(r(5884),e);var f=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.charUsage=new p.Usage,e.delimUsage=new p.Usage,e}return o(e,t),e.charOptions=function(e,r){return t.charOptions.call(this,e,r)},e.prototype.adaptiveCSS=function(t){this.options.adaptiveCSS=t},e.prototype.clearCache=function(){this.options.adaptiveCSS&&(this.charUsage.clear(),this.delimUsage.clear())},e.prototype.createVariant=function(e,r,n){void 0===r&&(r=null),void 0===n&&(n=null),t.prototype.createVariant.call(this,e,r,n);var o=this.constructor;this.variant[e].classes=o.defaultVariantClasses[e],this.variant[e].letter=o.defaultVariantLetters[e]},e.prototype.defineChars=function(r,n){var o,i;t.prototype.defineChars.call(this,r,n);var s=this.variant[r].letter;try{for(var a=l(Object.keys(n)),c=a.next();!c.done;c=a.next()){var u=c.value,p=e.charOptions(n,parseInt(u));void 0===p.f&&(p.f=s)}}catch(t){o={error:t}}finally{try{c&&!c.done&&(i=a.return)&&i.call(a)}finally{if(o)throw o.error}}},Object.defineProperty(e.prototype,"styles",{get:function(){var t=this.constructor,e=i({},t.defaultStyles);return this.addFontURLs(e,t.defaultFonts,this.options.fontURL),this.options.adaptiveCSS?this.updateStyles(e):this.allStyles(e),e},enumerable:!1,configurable:!0}),e.prototype.updateStyles=function(t){var e,r,n,o;try{for(var i=l(this.delimUsage.update()),s=i.next();!s.done;s=i.next()){var a=s.value;this.addDelimiterStyles(t,a,this.delimiters[a])}}catch(t){e={error:t}}finally{try{s&&!s.done&&(r=i.return)&&r.call(i)}finally{if(e)throw e.error}}try{for(var u=l(this.charUsage.update()),p=u.next();!p.done;p=u.next()){var h=c(p.value,2),f=h[0],d=(a=h[1],this.variant[f]);this.addCharStyles(t,d.letter,a,d.chars[a])}}catch(t){n={error:t}}finally{try{p&&!p.done&&(o=u.return)&&o.call(u)}finally{if(n)throw n.error}}return t},e.prototype.allStyles=function(t){var e,r,n,o,i,s;try{for(var a=l(Object.keys(this.delimiters)),c=a.next();!c.done;c=a.next()){var u=c.value,p=parseInt(u);this.addDelimiterStyles(t,p,this.delimiters[p])}}catch(t){e={error:t}}finally{try{c&&!c.done&&(r=a.return)&&r.call(a)}finally{if(e)throw e.error}}try{for(var h=l(Object.keys(this.variant)),f=h.next();!f.done;f=h.next()){var d=f.value,m=this.variant[d],y=m.letter;try{for(var g=(i=void 0,l(Object.keys(m.chars))),b=g.next();!b.done;b=g.next()){u=b.value,p=parseInt(u);var v=m.chars[p];(v[3]||{}).smp||(v.length<4&&(v[3]={}),this.addCharStyles(t,y,p,v))}}catch(t){i={error:t}}finally{try{b&&!b.done&&(s=g.return)&&s.call(g)}finally{if(i)throw i.error}}}}catch(t){n={error:t}}finally{try{f&&!f.done&&(o=h.return)&&o.call(h)}finally{if(n)throw n.error}}},e.prototype.addFontURLs=function(t,e,r){var n,o;try{for(var s=l(Object.keys(e)),a=s.next();!a.done;a=s.next()){var c=a.value,u=i({},e[c]);u.src=u.src.replace(/%%URL%%/,r),t[c]=u}}catch(t){n={error:t}}finally{try{a&&!a.done&&(o=s.return)&&o.call(s)}finally{if(n)throw n.error}}},e.prototype.addDelimiterStyles=function(t,e,r){var n=this.charSelector(e);r.c&&r.c!==e&&(t[".mjx-stretched mjx-c"+(n=this.charSelector(r.c))+"::before"]={content:this.charContent(r.c)}),r.stretch&&(1===r.dir?this.addDelimiterVStyles(t,n,r):this.addDelimiterHStyles(t,n,r))},e.prototype.addDelimiterVStyles=function(t,e,r){var n=r.HDW,o=c(r.stretch,4),i=o[0],s=o[1],a=o[2],l=o[3],u=this.addDelimiterVPart(t,e,"beg",i,n);this.addDelimiterVPart(t,e,"ext",s,n);var p=this.addDelimiterVPart(t,e,"end",a,n),h={};if(l){var f=this.addDelimiterVPart(t,e,"mid",l,n);h.height="50%",t["mjx-stretchy-v"+e+" > mjx-mid"]={"margin-top":this.em(-f/2),"margin-bottom":this.em(-f/2)}}u&&(h["border-top-width"]=this.em0(u-.03)),p&&(h["border-bottom-width"]=this.em0(p-.03),t["mjx-stretchy-v"+e+" > mjx-end"]={"margin-top":this.em(-p)}),Object.keys(h).length&&(t["mjx-stretchy-v"+e+" > mjx-ext"]=h)},e.prototype.addDelimiterVPart=function(t,e,r,n,o){if(!n)return 0;var i=this.getDelimiterData(n),s=(o[2]-i[2])/2,a={content:this.charContent(n)};return"ext"!==r?a.padding=this.padding(i,s):(a.width=this.em0(o[2]),s&&(a["padding-left"]=this.em0(s))),t["mjx-stretchy-v"+e+" mjx-"+r+" mjx-c::before"]=a,i[0]+i[1]},e.prototype.addDelimiterHStyles=function(t,e,r){var n=c(r.stretch,4),o=n[0],i=n[1],s=n[2],a=n[3],l=r.HDW;this.addDelimiterHPart(t,e,"beg",o,l),this.addDelimiterHPart(t,e,"ext",i,l),this.addDelimiterHPart(t,e,"end",s,l),a&&(this.addDelimiterHPart(t,e,"mid",a,l),t["mjx-stretchy-h"+e+" > mjx-ext"]={width:"50%"})},e.prototype.addDelimiterHPart=function(t,e,r,n,o){if(n){var i=this.getDelimiterData(n)[3],s={content:i&&i.c?'"'+i.c+'"':this.charContent(n)};s.padding=this.padding(o,0,-o[2]),t["mjx-stretchy-h"+e+" mjx-"+r+" mjx-c::before"]=s}},e.prototype.addCharStyles=function(t,e,r,n){var o=n[3],i=void 0!==o.f?o.f:e;t["mjx-c"+this.charSelector(r)+(i?".TEX-"+i:"")+"::before"]={padding:this.padding(n,0,o.ic||0),content:null!=o.c?'"'+o.c+'"':this.charContent(r)}},e.prototype.getDelimiterData=function(t){return this.getChar("-smallop",t)},e.prototype.em=function(t){return(0,h.em)(t)},e.prototype.em0=function(t){return(0,h.em)(Math.max(0,t))},e.prototype.padding=function(t,e,r){var n=c(t,3),o=n[0],i=n[1];return void 0===e&&(e=0),void 0===r&&(r=0),[o,n[2]+r,i,e].map(this.em0).join(" ")},e.prototype.charContent=function(t){return'"'+(t>=32&&t<=126&&34!==t&&39!==t&&92!==t?String.fromCharCode(t):"\\"+t.toString(16).toUpperCase())+'"'},e.prototype.charSelector=function(t){return".mjx-c"+t.toString(16).toUpperCase()},e.OPTIONS=i(i({},u.FontData.OPTIONS),{fontURL:"js/output/chtml/fonts/tex-woff-v2"}),e.JAX="CHTML",e.defaultVariantClasses={},e.defaultVariantLetters={},e.defaultStyles={"mjx-c::before":{display:"block",width:0}},e.defaultFonts={"@font-face /* 0 */":{"font-family":"MJXZERO",src:'url("%%URL%%/MathJax_Zero.woff") format("woff")'}},e}(u.FontData);e.CHTMLFontData=f,e.AddCSS=function(t,e){var r,n;try{for(var o=l(Object.keys(e)),i=o.next();!i.done;i=o.next()){var s=i.value,a=parseInt(s);Object.assign(u.FontData.charOptions(t,a),e[a])}}catch(t){r={error:t}}finally{try{i&&!i.done&&(n=o.return)&&n.call(o)}finally{if(r)throw r.error}}return t}},8270:function(t,e,r){var n=this&&this.__createBinding||(Object.create?function(t,e,r,n){void 0===n&&(n=r);var o=Object.getOwnPropertyDescriptor(e,r);o&&!("get"in o?!e.__esModule:o.writable||o.configurable)||(o={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,n,o)}:function(t,e,r,n){void 0===n&&(n=r),t[n]=e[r]}),o=this&&this.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),i=this&&this.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var r in t)"default"!==r&&Object.prototype.hasOwnProperty.call(t,r)&&n(e,t,r);return o(e,t),e},s=this&&this.__exportStar||function(t,e){for(var r in t)"default"===r||Object.prototype.hasOwnProperty.call(e,r)||n(e,t,r)},a=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s};Object.defineProperty(e,"__esModule",{value:!0}),e.Arrow=e.DiagonalArrow=e.DiagonalStrike=e.Border2=e.Border=e.RenderElement=void 0;var l=i(r(5552));s(r(5552),e);e.RenderElement=function(t,e){return void 0===e&&(e=""),function(r,n){var o=r.adjustBorder(r.html("mjx-"+t));if(e){var i=r.getOffset(e);if(r.thickness!==l.THICKNESS||i){var s="translate".concat(e,"(").concat(r.em(r.thickness/2-i),")");r.adaptor.setStyle(o,"transform",s)}}r.adaptor.append(r.chtml,o)}};e.Border=function(t){return l.CommonBorder((function(e,r){e.adaptor.setStyle(r,"border-"+t,e.em(e.thickness)+" solid")}))(t)};e.Border2=function(t,e,r){return l.CommonBorder2((function(t,n){var o=t.em(t.thickness)+" solid";t.adaptor.setStyle(n,"border-"+e,o),t.adaptor.setStyle(n,"border-"+r,o)}))(t,e,r)};e.DiagonalStrike=function(t,e){return l.CommonDiagonalStrike((function(t){return function(r,n){var o=r.getBBox(),i=o.w,s=o.h,l=o.d,c=a(r.getArgMod(i,s+l),2),u=c[0],p=c[1],h=e*r.thickness/2,f=r.adjustBorder(r.html(t,{style:{width:r.em(p),transform:"rotate("+r.fixed(-e*u)+"rad) translateY("+h+"em)"}}));r.adaptor.append(r.chtml,f)}}))(t)};e.DiagonalArrow=function(t){return l.CommonDiagonalArrow((function(t,e){t.adaptor.append(t.chtml,e)}))(t)};e.Arrow=function(t){return l.CommonArrow((function(t,e){t.adaptor.append(t.chtml,e)}))(t)}},6797:function(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.Usage=void 0;var r=function(){function t(){this.used=new Set,this.needsUpdate=[]}return t.prototype.add=function(t){var e=JSON.stringify(t);this.used.has(e)||this.needsUpdate.push(t),this.used.add(e)},t.prototype.has=function(t){return this.used.has(JSON.stringify(t))},t.prototype.clear=function(){this.used.clear(),this.needsUpdate=[]},t.prototype.update=function(){var t=this.needsUpdate;return this.needsUpdate=[],t},t}();e.Usage=r},5355:function(t,e,r){var n,o,i=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),s=this&&this.__createBinding||(Object.create?function(t,e,r,n){void 0===n&&(n=r);var o=Object.getOwnPropertyDescriptor(e,r);o&&!("get"in o?!e.__esModule:o.writable||o.configurable)||(o={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,n,o)}:function(t,e,r,n){void 0===n&&(n=r),t[n]=e[r]}),a=this&&this.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),l=this&&this.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var r in t)"default"!==r&&Object.prototype.hasOwnProperty.call(t,r)&&s(e,t,r);return a(e,t),e},c=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},u=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s};Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLWrapper=e.SPACE=e.FONTSIZE=void 0;var p=l(r(6010)),h=r(7519),f=r(6469);e.FONTSIZE={"70.7%":"s","70%":"s","50%":"ss","60%":"Tn","85%":"sm","120%":"lg","144%":"Lg","173%":"LG","207%":"hg","249%":"HG"},e.SPACE=((o={})[p.em(2/18)]="1",o[p.em(3/18)]="2",o[p.em(4/18)]="3",o[p.em(5/18)]="4",o[p.em(6/18)]="5",o);var d=function(t){function r(){var e=null!==t&&t.apply(this,arguments)||this;return e.chtml=null,e}return i(r,t),r.prototype.toCHTML=function(t){var e,r,n=this.standardCHTMLnode(t);try{for(var o=c(this.childNodes),i=o.next();!i.done;i=o.next()){i.value.toCHTML(n)}}catch(t){e={error:t}}finally{try{i&&!i.done&&(r=o.return)&&r.call(o)}finally{if(e)throw e.error}}},r.prototype.standardCHTMLnode=function(t){this.markUsed();var e=this.createCHTMLnode(t);return this.handleStyles(),this.handleVariant(),this.handleScale(),this.handleColor(),this.handleSpace(),this.handleAttributes(),this.handlePWidth(),e},r.prototype.markUsed=function(){this.jax.wrapperUsage.add(this.kind)},r.prototype.createCHTMLnode=function(t){var e=this.node.attributes.get("href");return e&&(t=this.adaptor.append(t,this.html("a",{href:e}))),this.chtml=this.adaptor.append(t,this.html("mjx-"+this.node.kind)),this.chtml},r.prototype.handleStyles=function(){if(this.styles){var t=this.styles.cssText;if(t){this.adaptor.setAttribute(this.chtml,"style",t);var e=this.styles.get("font-family");e&&this.adaptor.setStyle(this.chtml,"font-family","MJXZERO, "+e)}}},r.prototype.handleVariant=function(){this.node.isToken&&"-explicitFont"!==this.variant&&this.adaptor.setAttribute(this.chtml,"class",(this.font.getVariant(this.variant)||this.font.getVariant("normal")).classes)},r.prototype.handleScale=function(){this.setScale(this.chtml,this.bbox.rscale)},r.prototype.setScale=function(t,r){var n=Math.abs(r-1)<.001?1:r;if(t&&1!==n){var o=this.percent(n);e.FONTSIZE[o]?this.adaptor.setAttribute(t,"size",e.FONTSIZE[o]):this.adaptor.setStyle(t,"fontSize",o)}return t},r.prototype.handleSpace=function(){var t,r;try{for(var n=c([[this.bbox.L,"space","marginLeft"],[this.bbox.R,"rspace","marginRight"]]),o=n.next();!o.done;o=n.next()){var i=o.value,s=u(i,3),a=s[0],l=s[1],p=s[2];if(a){var h=this.em(a);e.SPACE[h]?this.adaptor.setAttribute(this.chtml,l,e.SPACE[h]):this.adaptor.setStyle(this.chtml,p,h)}}}catch(e){t={error:e}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(t)throw t.error}}},r.prototype.handleColor=function(){var t=this.node.attributes,e=t.getExplicit("mathcolor"),r=t.getExplicit("color"),n=t.getExplicit("mathbackground"),o=t.getExplicit("background");(e||r)&&this.adaptor.setStyle(this.chtml,"color",e||r),(n||o)&&this.adaptor.setStyle(this.chtml,"backgroundColor",n||o)},r.prototype.handleAttributes=function(){var t,e,n,o,i=this.node.attributes,s=i.getAllDefaults(),a=r.skipAttributes;try{for(var l=c(i.getExplicitNames()),u=l.next();!u.done;u=l.next()){var p=u.value;!1!==a[p]&&(p in s||a[p]||this.adaptor.hasAttribute(this.chtml,p))||this.adaptor.setAttribute(this.chtml,p,i.getExplicit(p))}}catch(e){t={error:e}}finally{try{u&&!u.done&&(e=l.return)&&e.call(l)}finally{if(t)throw t.error}}if(i.get("class")){var h=i.get("class").trim().split(/ +/);try{for(var f=c(h),d=f.next();!d.done;d=f.next()){var m=d.value;this.adaptor.addClass(this.chtml,m)}}catch(t){n={error:t}}finally{try{d&&!d.done&&(o=f.return)&&o.call(f)}finally{if(n)throw n.error}}}},r.prototype.handlePWidth=function(){this.bbox.pwidth&&(this.bbox.pwidth===f.BBox.fullWidth?this.adaptor.setAttribute(this.chtml,"width","full"):this.adaptor.setStyle(this.chtml,"width",this.bbox.pwidth))},r.prototype.setIndent=function(t,e,r){var n=this.adaptor;if("center"===e||"left"===e){var o=this.getBBox().L;n.setStyle(t,"margin-left",this.em(r+o))}if("center"===e||"right"===e){var i=this.getBBox().R;n.setStyle(t,"margin-right",this.em(-r+i))}},r.prototype.drawBBox=function(){var t=this.getBBox(),e=t.w,r=t.h,n=t.d,o=t.R,i=this.html("mjx-box",{style:{opacity:.25,"margin-left":this.em(-e-o)}},[this.html("mjx-box",{style:{height:this.em(r),width:this.em(e),"background-color":"red"}}),this.html("mjx-box",{style:{height:this.em(n),width:this.em(e),"margin-left":this.em(-e),"vertical-align":this.em(-n),"background-color":"green"}})]),s=this.chtml||this.parent.chtml,a=this.adaptor.getAttribute(s,"size");a&&this.adaptor.setAttribute(i,"size",a);var l=this.adaptor.getStyle(s,"fontSize");l&&this.adaptor.setStyle(i,"fontSize",l),this.adaptor.append(this.adaptor.parent(s),i),this.adaptor.setStyle(s,"backgroundColor","#FFEE00")},r.prototype.html=function(t,e,r){return void 0===e&&(e={}),void 0===r&&(r=[]),this.jax.html(t,e,r)},r.prototype.text=function(t){return this.jax.text(t)},r.prototype.char=function(t){return this.font.charSelector(t).substr(1)},r.kind="unknown",r.autoStyle=!0,r}(h.CommonWrapper);e.CHTMLWrapper=d},9261:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLWrapperFactory=void 0;var i=r(4420),s=r(9086),a=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.defaultNodes=s.CHTMLWrappers,e}(i.CommonWrapperFactory);e.CHTMLWrapperFactory=a},9086:function(t,e,r){var n;Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLWrappers=void 0;var o=r(5355),i=r(804),s=r(1653),a=r(6287),l=r(6460),c=r(4597),u=r(1259),p=r(2970),h=r(5964),f=r(8147),d=r(4798),m=r(2275),y=r(9063),g=r(5610),b=r(8776),v=r(4300),_=r(6590),S=r(6781),M=r(8002),O=r(3571),x=r(7056),E=r(8102),A=r(6911),C=r(421),T=r(95),N=r(1148);e.CHTMLWrappers=((n={})[i.CHTMLmath.kind]=i.CHTMLmath,n[d.CHTMLmrow.kind]=d.CHTMLmrow,n[d.CHTMLinferredMrow.kind]=d.CHTMLinferredMrow,n[s.CHTMLmi.kind]=s.CHTMLmi,n[a.CHTMLmo.kind]=a.CHTMLmo,n[l.CHTMLmn.kind]=l.CHTMLmn,n[c.CHTMLms.kind]=c.CHTMLms,n[u.CHTMLmtext.kind]=u.CHTMLmtext,n[p.CHTMLmspace.kind]=p.CHTMLmspace,n[h.CHTMLmpadded.kind]=h.CHTMLmpadded,n[f.CHTMLmenclose.kind]=f.CHTMLmenclose,n[y.CHTMLmfrac.kind]=y.CHTMLmfrac,n[g.CHTMLmsqrt.kind]=g.CHTMLmsqrt,n[b.CHTMLmroot.kind]=b.CHTMLmroot,n[v.CHTMLmsub.kind]=v.CHTMLmsub,n[v.CHTMLmsup.kind]=v.CHTMLmsup,n[v.CHTMLmsubsup.kind]=v.CHTMLmsubsup,n[_.CHTMLmunder.kind]=_.CHTMLmunder,n[_.CHTMLmover.kind]=_.CHTMLmover,n[_.CHTMLmunderover.kind]=_.CHTMLmunderover,n[S.CHTMLmmultiscripts.kind]=S.CHTMLmmultiscripts,n[m.CHTMLmfenced.kind]=m.CHTMLmfenced,n[M.CHTMLmtable.kind]=M.CHTMLmtable,n[O.CHTMLmtr.kind]=O.CHTMLmtr,n[O.CHTMLmlabeledtr.kind]=O.CHTMLmlabeledtr,n[x.CHTMLmtd.kind]=x.CHTMLmtd,n[E.CHTMLmaction.kind]=E.CHTMLmaction,n[A.CHTMLmglyph.kind]=A.CHTMLmglyph,n[C.CHTMLsemantics.kind]=C.CHTMLsemantics,n[C.CHTMLannotation.kind]=C.CHTMLannotation,n[C.CHTMLannotationXML.kind]=C.CHTMLannotationXML,n[C.CHTMLxml.kind]=C.CHTMLxml,n[T.CHTMLTeXAtom.kind]=T.CHTMLTeXAtom,n[N.CHTMLTextNode.kind]=N.CHTMLTextNode,n[o.CHTMLWrapper.kind]=o.CHTMLWrapper,n)},95:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLTeXAtom=void 0;var i=r(5355),s=r(9800),a=r(3948),l=r(9007),c=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.toCHTML=function(e){if(t.prototype.toCHTML.call(this,e),this.adaptor.setAttribute(this.chtml,"texclass",l.TEXCLASSNAMES[this.node.texClass]),this.node.texClass===l.TEXCLASS.VCENTER){var r=this.childNodes[0].getBBox(),n=r.h,o=(n+r.d)/2+this.font.params.axis_height-n;this.adaptor.setStyle(this.chtml,"verticalAlign",this.em(o))}},e.kind=a.TeXAtom.prototype.kind,e}((0,s.CommonTeXAtomMixin)(i.CHTMLWrapper));e.CHTMLTeXAtom=c},1148:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLTextNode=void 0;var s=r(9007),a=r(5355),l=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.toCHTML=function(t){var e,r;this.markUsed();var n=this.adaptor,o=this.parent.variant,s=this.node.getText();if(0!==s.length)if("-explicitFont"===o)n.append(t,this.jax.unknownText(s,o,this.getBBox().w));else{var a=this.remappedText(s,o);try{for(var l=i(a),c=l.next();!c.done;c=l.next()){var u=c.value,p=this.getVariantChar(o,u)[3],h=p.f?" TEX-"+p.f:"",f=p.unknown?this.jax.unknownText(String.fromCodePoint(u),o):this.html("mjx-c",{class:this.char(u)+h});n.append(t,f),!p.unknown&&this.font.charUsage.add([o,u])}}catch(t){e={error:t}}finally{try{c&&!c.done&&(r=l.return)&&r.call(l)}finally{if(e)throw e.error}}}},e.kind=s.TextNode.prototype.kind,e.autoStyle=!1,e.styles={"mjx-c":{display:"inline-block"},"mjx-utext":{display:"inline-block",padding:".75em 0 .2em 0"}},e}((0,r(1160).CommonTextNodeMixin)(a.CHTMLWrapper));e.CHTMLTextNode=l},8102:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLmaction=void 0;var i=r(5355),s=r(1956),a=r(1956),l=r(9145),c=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.toCHTML=function(t){var e=this.standardCHTMLnode(t);this.selected.toCHTML(e),this.action(this,this.data)},e.prototype.setEventHandler=function(t,e){this.chtml.addEventListener(t,e)},e.kind=l.MmlMaction.prototype.kind,e.styles={"mjx-maction":{position:"relative"},"mjx-maction > mjx-tool":{display:"none",position:"absolute",bottom:0,right:0,width:0,height:0,"z-index":500},"mjx-tool > mjx-tip":{display:"inline-block",padding:".2em",border:"1px solid #888","font-size":"70%","background-color":"#F8F8F8",color:"black","box-shadow":"2px 2px 5px #AAAAAA"},"mjx-maction[toggle]":{cursor:"pointer"},"mjx-status":{display:"block",position:"fixed",left:"1em",bottom:"1em","min-width":"25%",padding:".2em .4em",border:"1px solid #888","font-size":"90%","background-color":"#F8F8F8",color:"black"}},e.actions=new Map([["toggle",[function(t,e){t.adaptor.setAttribute(t.chtml,"toggle",t.node.attributes.get("selection"));var r=t.factory.jax.math,n=t.factory.jax.document,o=t.node;t.setEventHandler("click",(function(t){r.end.node||(r.start.node=r.end.node=r.typesetRoot,r.start.n=r.end.n=0),o.nextToggleSelection(),r.rerender(n),t.stopPropagation()}))},{}]],["tooltip",[function(t,e){var r=t.childNodes[1];if(r)if(r.node.isKind("mtext")){var n=r.node.getText();t.adaptor.setAttribute(t.chtml,"title",n)}else{var o=t.adaptor,i=o.append(t.chtml,t.html("mjx-tool",{style:{bottom:t.em(-t.dy),right:t.em(-t.dx)}},[t.html("mjx-tip")]));r.toCHTML(o.firstChild(i)),t.setEventHandler("mouseover",(function(r){e.stopTimers(t,e);var n=setTimeout((function(){return o.setStyle(i,"display","block")}),e.postDelay);e.hoverTimer.set(t,n),r.stopPropagation()})),t.setEventHandler("mouseout",(function(r){e.stopTimers(t,e);var n=setTimeout((function(){return o.setStyle(i,"display","")}),e.clearDelay);e.clearTimer.set(t,n),r.stopPropagation()}))}},a.TooltipData]],["statusline",[function(t,e){var r=t.childNodes[1];if(r&&r.node.isKind("mtext")){var n=t.adaptor,o=r.node.getText();n.setAttribute(t.chtml,"statusline",o),t.setEventHandler("mouseover",(function(r){if(null===e.status){var i=n.body(n.document);e.status=n.append(i,t.html("mjx-status",{},[t.text(o)]))}r.stopPropagation()})),t.setEventHandler("mouseout",(function(t){e.status&&(n.remove(e.status),e.status=null),t.stopPropagation()}))}},{status:null}]]]),e}((0,s.CommonMactionMixin)(i.CHTMLWrapper));e.CHTMLmaction=c},804:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s};Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLmath=void 0;var s=r(5355),a=r(7490),l=r(3233),c=r(6469),u=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.toCHTML=function(e){t.prototype.toCHTML.call(this,e);var r=this.chtml,n=this.adaptor;"block"===this.node.attributes.get("display")?(n.setAttribute(r,"display","true"),n.setAttribute(e,"display","true"),this.handleDisplay(e)):this.handleInline(e),n.addClass(r,"MJX-TEX")},e.prototype.handleDisplay=function(t){var e=this.adaptor,r=i(this.getAlignShift(),2),n=r[0],o=r[1];if("center"!==n&&e.setAttribute(t,"justify",n),this.bbox.pwidth===c.BBox.fullWidth){if(e.setAttribute(t,"width","full"),this.jax.table){var s=this.jax.table.getOuterBBox(),a=s.L,l=s.w,u=s.R;"right"===n?u=Math.max(u||-o,-o):"left"===n?a=Math.max(a||o,o):"center"===n&&(l+=2*Math.abs(o));var p=this.em(Math.max(0,a+l+u));e.setStyle(t,"min-width",p),e.setStyle(this.jax.table.chtml,"min-width",p)}}else this.setIndent(this.chtml,n,o)},e.prototype.handleInline=function(t){var e=this.adaptor,r=e.getStyle(this.chtml,"margin-right");r&&(e.setStyle(this.chtml,"margin-right",""),e.setStyle(t,"margin-right",r),e.setStyle(t,"width","0"))},e.prototype.setChildPWidths=function(e,r,n){return void 0===r&&(r=null),void 0===n&&(n=!0),!!this.parent&&t.prototype.setChildPWidths.call(this,e,r,n)},e.kind=l.MmlMath.prototype.kind,e.styles={"mjx-math":{"line-height":0,"text-align":"left","text-indent":0,"font-style":"normal","font-weight":"normal","font-size":"100%","font-size-adjust":"none","letter-spacing":"normal","border-collapse":"collapse","word-wrap":"normal","word-spacing":"normal","white-space":"nowrap",direction:"ltr",padding:"1px 0"},'mjx-container[jax="CHTML"][display="true"]':{display:"block","text-align":"center",margin:"1em 0"},'mjx-container[jax="CHTML"][display="true"][width="full"]':{display:"flex"},'mjx-container[jax="CHTML"][display="true"] mjx-math':{padding:0},'mjx-container[jax="CHTML"][justify="left"]':{"text-align":"left"},'mjx-container[jax="CHTML"][justify="right"]':{"text-align":"right"}},e}((0,a.CommonMathMixin)(s.CHTMLWrapper));e.CHTMLmath=u},8147:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__createBinding||(Object.create?function(t,e,r,n){void 0===n&&(n=r);var o=Object.getOwnPropertyDescriptor(e,r);o&&!("get"in o?!e.__esModule:o.writable||o.configurable)||(o={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,n,o)}:function(t,e,r,n){void 0===n&&(n=r),t[n]=e[r]}),s=this&&this.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),a=this&&this.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var r in t)"default"!==r&&Object.prototype.hasOwnProperty.call(t,r)&&i(e,t,r);return s(e,t),e},l=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},c=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s};Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLmenclose=void 0;var u=r(5355),p=r(7313),h=a(r(8270)),f=r(6661),d=r(6010);function m(t,e){return Math.atan2(t,e).toFixed(3).replace(/\.?0+$/,"")}var y=m(h.ARROWDX,h.ARROWY),g=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.toCHTML=function(t){var e,r,n,o,i=this.adaptor,s=this.standardCHTMLnode(t),a=i.append(s,this.html("mjx-box"));this.renderChild?this.renderChild(this,a):this.childNodes[0].toCHTML(a);try{for(var c=l(Object.keys(this.notations)),u=c.next();!u.done;u=c.next()){var p=u.value,f=this.notations[p];!f.renderChild&&f.renderer(this,a)}}catch(t){e={error:t}}finally{try{u&&!u.done&&(r=c.return)&&r.call(c)}finally{if(e)throw e.error}}var d=this.getPadding();try{for(var m=l(h.sideNames),y=m.next();!y.done;y=m.next()){var g=y.value,b=h.sideIndex[g];d[b]>0&&i.setStyle(a,"padding-"+g,this.em(d[b]))}}catch(t){n={error:t}}finally{try{y&&!y.done&&(o=m.return)&&o.call(m)}finally{if(n)throw n.error}}},e.prototype.arrow=function(t,e,r,n,o){void 0===n&&(n=""),void 0===o&&(o=0);var i=this.getBBox().w,s={width:this.em(t)};i!==t&&(s.left=this.em((i-t)/2)),e&&(s.transform="rotate("+this.fixed(e)+"rad)");var a=this.html("mjx-arrow",{style:s},[this.html("mjx-aline"),this.html("mjx-rthead"),this.html("mjx-rbhead")]);return r&&(this.adaptor.append(a,this.html("mjx-lthead")),this.adaptor.append(a,this.html("mjx-lbhead")),this.adaptor.setAttribute(a,"double","true")),this.adjustArrow(a,r),this.moveArrow(a,n,o),a},e.prototype.adjustArrow=function(t,e){var r=this,n=this.thickness,o=this.arrowhead;if(o.x!==h.ARROWX||o.y!==h.ARROWY||o.dx!==h.ARROWDX||n!==h.THICKNESS){var i=c([n*o.x,n*o.y].map((function(t){return r.em(t)})),2),s=i[0],a=i[1],l=m(o.dx,o.y),u=c(this.adaptor.childNodes(t),5),p=u[0],f=u[1],d=u[2],y=u[3],g=u[4];this.adjustHead(f,[a,"0","1px",s],l),this.adjustHead(d,["1px","0",a,s],"-"+l),this.adjustHead(y,[a,s,"1px","0"],"-"+l),this.adjustHead(g,["1px",s,a,"0"],l),this.adjustLine(p,n,o.x,e)}},e.prototype.adjustHead=function(t,e,r){t&&(this.adaptor.setStyle(t,"border-width",e.join(" ")),this.adaptor.setStyle(t,"transform","skewX("+r+"rad)"))},e.prototype.adjustLine=function(t,e,r,n){this.adaptor.setStyle(t,"borderTop",this.em(e)+" solid"),this.adaptor.setStyle(t,"top",this.em(-e/2)),this.adaptor.setStyle(t,"right",this.em(e*(r-1))),n&&this.adaptor.setStyle(t,"left",this.em(e*(r-1)))},e.prototype.moveArrow=function(t,e,r){if(r){var n=this.adaptor.getStyle(t,"transform");this.adaptor.setStyle(t,"transform","translate".concat(e,"(").concat(this.em(-r),")").concat(n?" "+n:""))}},e.prototype.adjustBorder=function(t){return this.thickness!==h.THICKNESS&&this.adaptor.setStyle(t,"borderWidth",this.em(this.thickness)),t},e.prototype.adjustThickness=function(t){return this.thickness!==h.THICKNESS&&this.adaptor.setStyle(t,"strokeWidth",this.fixed(this.thickness)),t},e.prototype.fixed=function(t,e){return void 0===e&&(e=3),Math.abs(t)<6e-4?"0":t.toFixed(e).replace(/\.?0+$/,"")},e.prototype.em=function(e){return t.prototype.em.call(this,e)},e.kind=f.MmlMenclose.prototype.kind,e.styles={"mjx-menclose":{position:"relative"},"mjx-menclose > mjx-dstrike":{display:"inline-block",left:0,top:0,position:"absolute","border-top":h.SOLID,"transform-origin":"top left"},"mjx-menclose > mjx-ustrike":{display:"inline-block",left:0,bottom:0,position:"absolute","border-top":h.SOLID,"transform-origin":"bottom left"},"mjx-menclose > mjx-hstrike":{"border-top":h.SOLID,position:"absolute",left:0,right:0,bottom:"50%",transform:"translateY("+(0,d.em)(h.THICKNESS/2)+")"},"mjx-menclose > mjx-vstrike":{"border-left":h.SOLID,position:"absolute",top:0,bottom:0,right:"50%",transform:"translateX("+(0,d.em)(h.THICKNESS/2)+")"},"mjx-menclose > mjx-rbox":{position:"absolute",top:0,bottom:0,right:0,left:0,border:h.SOLID,"border-radius":(0,d.em)(h.THICKNESS+h.PADDING)},"mjx-menclose > mjx-cbox":{position:"absolute",top:0,bottom:0,right:0,left:0,border:h.SOLID,"border-radius":"50%"},"mjx-menclose > mjx-arrow":{position:"absolute",left:0,bottom:"50%",height:0,width:0},"mjx-menclose > mjx-arrow > *":{display:"block",position:"absolute","transform-origin":"bottom","border-left":(0,d.em)(h.THICKNESS*h.ARROWX)+" solid","border-right":0,"box-sizing":"border-box"},"mjx-menclose > mjx-arrow > mjx-aline":{left:0,top:(0,d.em)(-h.THICKNESS/2),right:(0,d.em)(h.THICKNESS*(h.ARROWX-1)),height:0,"border-top":(0,d.em)(h.THICKNESS)+" solid","border-left":0},"mjx-menclose > mjx-arrow[double] > mjx-aline":{left:(0,d.em)(h.THICKNESS*(h.ARROWX-1)),height:0},"mjx-menclose > mjx-arrow > mjx-rthead":{transform:"skewX("+y+"rad)",right:0,bottom:"-1px","border-bottom":"1px solid transparent","border-top":(0,d.em)(h.THICKNESS*h.ARROWY)+" solid transparent"},"mjx-menclose > mjx-arrow > mjx-rbhead":{transform:"skewX(-"+y+"rad)","transform-origin":"top",right:0,top:"-1px","border-top":"1px solid transparent","border-bottom":(0,d.em)(h.THICKNESS*h.ARROWY)+" solid transparent"},"mjx-menclose > mjx-arrow > mjx-lthead":{transform:"skewX(-"+y+"rad)",left:0,bottom:"-1px","border-left":0,"border-right":(0,d.em)(h.THICKNESS*h.ARROWX)+" solid","border-bottom":"1px solid transparent","border-top":(0,d.em)(h.THICKNESS*h.ARROWY)+" solid transparent"},"mjx-menclose > mjx-arrow > mjx-lbhead":{transform:"skewX("+y+"rad)","transform-origin":"top",left:0,top:"-1px","border-left":0,"border-right":(0,d.em)(h.THICKNESS*h.ARROWX)+" solid","border-top":"1px solid transparent","border-bottom":(0,d.em)(h.THICKNESS*h.ARROWY)+" solid transparent"},"mjx-menclose > dbox":{position:"absolute",top:0,bottom:0,left:(0,d.em)(-1.5*h.PADDING),width:(0,d.em)(3*h.PADDING),border:(0,d.em)(h.THICKNESS)+" solid","border-radius":"50%","clip-path":"inset(0 0 0 "+(0,d.em)(1.5*h.PADDING)+")","box-sizing":"border-box"}},e.notations=new Map([h.Border("top"),h.Border("right"),h.Border("bottom"),h.Border("left"),h.Border2("actuarial","top","right"),h.Border2("madruwb","bottom","right"),h.DiagonalStrike("up",1),h.DiagonalStrike("down",-1),["horizontalstrike",{renderer:h.RenderElement("hstrike","Y"),bbox:function(t){return[0,t.padding,0,t.padding]}}],["verticalstrike",{renderer:h.RenderElement("vstrike","X"),bbox:function(t){return[t.padding,0,t.padding,0]}}],["box",{renderer:function(t,e){t.adaptor.setStyle(e,"border",t.em(t.thickness)+" solid")},bbox:h.fullBBox,border:h.fullBorder,remove:"left right top bottom"}],["roundedbox",{renderer:h.RenderElement("rbox"),bbox:h.fullBBox}],["circle",{renderer:h.RenderElement("cbox"),bbox:h.fullBBox}],["phasorangle",{renderer:function(t,e){var r=t.getBBox(),n=r.h,o=r.d,i=c(t.getArgMod(1.75*t.padding,n+o),2),s=i[0],a=i[1],l=t.thickness*Math.sin(s)*.9;t.adaptor.setStyle(e,"border-bottom",t.em(t.thickness)+" solid");var u=t.adjustBorder(t.html("mjx-ustrike",{style:{width:t.em(a),transform:"translateX("+t.em(l)+") rotate("+t.fixed(-s)+"rad)"}}));t.adaptor.append(t.chtml,u)},bbox:function(t){var e=t.padding/2,r=t.thickness;return[2*e,e,e+r,3*e+r]},border:function(t){return[0,0,t.thickness,0]},remove:"bottom"}],h.Arrow("up"),h.Arrow("down"),h.Arrow("left"),h.Arrow("right"),h.Arrow("updown"),h.Arrow("leftright"),h.DiagonalArrow("updiagonal"),h.DiagonalArrow("northeast"),h.DiagonalArrow("southeast"),h.DiagonalArrow("northwest"),h.DiagonalArrow("southwest"),h.DiagonalArrow("northeastsouthwest"),h.DiagonalArrow("northwestsoutheast"),["longdiv",{renderer:function(t,e){var r=t.adaptor;r.setStyle(e,"border-top",t.em(t.thickness)+" solid");var n=r.append(t.chtml,t.html("dbox")),o=t.thickness,i=t.padding;o!==h.THICKNESS&&r.setStyle(n,"border-width",t.em(o)),i!==h.PADDING&&(r.setStyle(n,"left",t.em(-1.5*i)),r.setStyle(n,"width",t.em(3*i)),r.setStyle(n,"clip-path","inset(0 0 0 "+t.em(1.5*i)+")"))},bbox:function(t){var e=t.padding,r=t.thickness;return[e+r,e,e,2*e+r/2]}}],["radical",{renderer:function(t,e){t.msqrt.toCHTML(e);var r=t.sqrtTRBL();t.adaptor.setStyle(t.msqrt.chtml,"margin",r.map((function(e){return t.em(-e)})).join(" "))},init:function(t){t.msqrt=t.createMsqrt(t.childNodes[0])},bbox:function(t){return t.sqrtTRBL()},renderChild:!0}]]),e}((0,p.CommonMencloseMixin)(u.CHTMLWrapper));e.CHTMLmenclose=g},2275:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLmfenced=void 0;var i=r(5355),s=r(7555),a=r(5410),l=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.toCHTML=function(t){var e=this.standardCHTMLnode(t);this.mrow.toCHTML(e)},e.kind=a.MmlMfenced.prototype.kind,e}((0,s.CommonMfencedMixin)(i.CHTMLWrapper));e.CHTMLmfenced=l},9063:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__assign||function(){return i=Object.assign||function(t){for(var e,r=1,n=arguments.length;r *":{"font-size":"2000%"},"mjx-dbox":{display:"block","font-size":"5%"},"mjx-num":{display:"block","text-align":"center"},"mjx-den":{display:"block","text-align":"center"},"mjx-mfrac[bevelled] > mjx-num":{display:"inline-block"},"mjx-mfrac[bevelled] > mjx-den":{display:"inline-block"},'mjx-den[align="right"], mjx-num[align="right"]':{"text-align":"right"},'mjx-den[align="left"], mjx-num[align="left"]':{"text-align":"left"},"mjx-nstrut":{display:"inline-block",height:".054em",width:0,"vertical-align":"-.054em"},'mjx-nstrut[type="d"]':{height:".217em","vertical-align":"-.217em"},"mjx-dstrut":{display:"inline-block",height:".505em",width:0},'mjx-dstrut[type="d"]':{height:".726em"},"mjx-line":{display:"block","box-sizing":"border-box","min-height":"1px",height:".06em","border-top":".06em solid",margin:".06em -.1em",overflow:"hidden"},'mjx-line[type="d"]':{margin:".18em -.1em"}},e}((0,a.CommonMfracMixin)(s.CHTMLWrapper));e.CHTMLmfrac=c},6911:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLmglyph=void 0;var i=r(5355),s=r(5636),a=r(3985),l=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.toCHTML=function(t){var e=this.standardCHTMLnode(t);if(this.charWrapper)this.charWrapper.toCHTML(e);else{var r=this.node.attributes.getList("src","alt"),n=r.src,o=r.alt,i={width:this.em(this.width),height:this.em(this.height)};this.valign&&(i.verticalAlign=this.em(this.valign));var s=this.html("img",{src:n,style:i,alt:o,title:o});this.adaptor.append(e,s)}},e.kind=a.MmlMglyph.prototype.kind,e.styles={"mjx-mglyph > img":{display:"inline-block",border:0,padding:0}},e}((0,s.CommonMglyphMixin)(i.CHTMLWrapper));e.CHTMLmglyph=l},1653:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLmi=void 0;var i=r(5355),s=r(5723),a=r(450),l=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.kind=a.MmlMi.prototype.kind,e}((0,s.CommonMiMixin)(i.CHTMLWrapper));e.CHTMLmi=l},6781:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s};Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLmmultiscripts=void 0;var s=r(4300),a=r(8009),l=r(6405),c=r(505),u=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.toCHTML=function(t){var e=this.standardCHTMLnode(t),r=this.scriptData,n=this.node.getProperty("scriptalign")||"right left",o=i((0,c.split)(n+" "+n),2),s=o[0],a=o[1],l=this.combinePrePost(r.sub,r.psub),u=this.combinePrePost(r.sup,r.psup),p=i(this.getUVQ(l,u),2),h=p[0],f=p[1];if(r.numPrescripts){var d=this.addScripts(h,-f,!0,r.psub,r.psup,this.firstPrescript,r.numPrescripts);"right"!==s&&this.adaptor.setAttribute(d,"script-align",s)}if(this.childNodes[0].toCHTML(e),r.numScripts){d=this.addScripts(h,-f,!1,r.sub,r.sup,1,r.numScripts);"left"!==a&&this.adaptor.setAttribute(d,"script-align",a)}},e.prototype.addScripts=function(t,e,r,n,o,i,s){for(var a=this.adaptor,l=t-o.d+(e-n.h),c=t<0&&0===e?n.h+t:t,u=l>0?{style:{height:this.em(l)}}:{},p=c?{style:{"vertical-align":this.em(c)}}:{},h=this.html("mjx-row"),f=this.html("mjx-row",u),d=this.html("mjx-row"),m="mjx-"+(r?"pre":"")+"scripts",y=i+2*s;i mjx-row > mjx-cell":{"text-align":"right"},'[script-align="left"] > mjx-row > mjx-cell':{"text-align":"left"},'[script-align="center"] > mjx-row > mjx-cell':{"text-align":"center"},'[script-align="right"] > mjx-row > mjx-cell':{"text-align":"right"}},e}((0,a.CommonMmultiscriptsMixin)(s.CHTMLmsubsup));e.CHTMLmmultiscripts=u},6460:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLmn=void 0;var i=r(5355),s=r(5023),a=r(3050),l=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.kind=a.MmlMn.prototype.kind,e}((0,s.CommonMnMixin)(i.CHTMLWrapper));e.CHTMLmn=l},6287:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLmo=void 0;var s=r(5355),a=r(7096),l=r(2756),c=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.toCHTML=function(t){var e,r,n=this.node.attributes,o=n.get("symmetric")&&2!==this.stretch.dir,s=0!==this.stretch.dir;s&&null===this.size&&this.getStretchedVariant([]);var a=this.standardCHTMLnode(t);if(s&&this.size<0)this.stretchHTML(a);else{if(o||n.get("largeop")){var l=this.em(this.getCenterOffset());"0"!==l&&this.adaptor.setStyle(a,"verticalAlign",l)}this.node.getProperty("mathaccent")&&(this.adaptor.setStyle(a,"width","0"),this.adaptor.setStyle(a,"margin-left",this.em(this.getAccentOffset())));try{for(var c=i(this.childNodes),u=c.next();!u.done;u=c.next()){u.value.toCHTML(a)}}catch(t){e={error:t}}finally{try{u&&!u.done&&(r=c.return)&&r.call(c)}finally{if(e)throw e.error}}}},e.prototype.stretchHTML=function(t){var e=this.getText().codePointAt(0);this.font.delimUsage.add(e),this.childNodes[0].markUsed();var r=this.stretch,n=r.stretch,o=[];n[0]&&o.push(this.html("mjx-beg",{},[this.html("mjx-c")])),o.push(this.html("mjx-ext",{},[this.html("mjx-c")])),4===n.length&&o.push(this.html("mjx-mid",{},[this.html("mjx-c")]),this.html("mjx-ext",{},[this.html("mjx-c")])),n[2]&&o.push(this.html("mjx-end",{},[this.html("mjx-c")]));var i={},s=this.bbox,l=s.h,c=s.d,u=s.w;1===r.dir?(o.push(this.html("mjx-mark")),i.height=this.em(l+c),i.verticalAlign=this.em(-c)):i.width=this.em(u);var p=a.DirectionVH[r.dir],h={class:this.char(r.c||e),style:i},f=this.html("mjx-stretchy-"+p,h,o);this.adaptor.append(t,f)},e.kind=l.MmlMo.prototype.kind,e.styles={"mjx-stretchy-h":{display:"inline-table",width:"100%"},"mjx-stretchy-h > *":{display:"table-cell",width:0},"mjx-stretchy-h > * > mjx-c":{display:"inline-block",transform:"scalex(1.0000001)"},"mjx-stretchy-h > * > mjx-c::before":{display:"inline-block",width:"initial"},"mjx-stretchy-h > mjx-ext":{"/* IE */ overflow":"hidden","/* others */ overflow":"clip visible",width:"100%"},"mjx-stretchy-h > mjx-ext > mjx-c::before":{transform:"scalex(500)"},"mjx-stretchy-h > mjx-ext > mjx-c":{width:0},"mjx-stretchy-h > mjx-beg > mjx-c":{"margin-right":"-.1em"},"mjx-stretchy-h > mjx-end > mjx-c":{"margin-left":"-.1em"},"mjx-stretchy-v":{display:"inline-block"},"mjx-stretchy-v > *":{display:"block"},"mjx-stretchy-v > mjx-beg":{height:0},"mjx-stretchy-v > mjx-end > mjx-c":{display:"block"},"mjx-stretchy-v > * > mjx-c":{transform:"scaley(1.0000001)","transform-origin":"left center",overflow:"hidden"},"mjx-stretchy-v > mjx-ext":{display:"block",height:"100%","box-sizing":"border-box",border:"0px solid transparent","/* IE */ overflow":"hidden","/* others */ overflow":"visible clip"},"mjx-stretchy-v > mjx-ext > mjx-c::before":{width:"initial","box-sizing":"border-box"},"mjx-stretchy-v > mjx-ext > mjx-c":{transform:"scaleY(500) translateY(.075em)",overflow:"visible"},"mjx-mark":{display:"inline-block",height:"0px"}},e}((0,a.CommonMoMixin)(s.CHTMLWrapper));e.CHTMLmo=c},5964:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},s=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLmpadded=void 0;var a=r(5355),l=r(6898),c=r(7238),u=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.toCHTML=function(t){var e,r,n=this.standardCHTMLnode(t),o=[],a={},l=i(this.getDimens(),9),c=l[2],u=l[3],p=l[4],h=l[5],f=l[6],d=l[7],m=l[8];if(h&&(a.width=this.em(c+h)),(u||p)&&(a.margin=this.em(u)+" 0 "+this.em(p)),f+m||d){a.position="relative";var y=this.html("mjx-rbox",{style:{left:this.em(f+m),top:this.em(-d),"max-width":a.width}});f+m&&this.childNodes[0].getBBox().pwidth&&(this.adaptor.setAttribute(y,"width","full"),this.adaptor.setStyle(y,"left",this.em(f))),o.push(y)}n=this.adaptor.append(n,this.html("mjx-block",{style:a},o));try{for(var g=s(this.childNodes),b=g.next();!b.done;b=g.next()){b.value.toCHTML(o[0]||n)}}catch(t){e={error:t}}finally{try{b&&!b.done&&(r=g.return)&&r.call(g)}finally{if(e)throw e.error}}},e.kind=c.MmlMpadded.prototype.kind,e.styles={"mjx-mpadded":{display:"inline-block"},"mjx-rbox":{display:"inline-block",position:"relative"}},e}((0,l.CommonMpaddedMixin)(a.CHTMLWrapper));e.CHTMLmpadded=u},8776:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s};Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLmroot=void 0;var s=r(5610),a=r(6991),l=r(6145),c=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.addRoot=function(t,e,r,n){e.toCHTML(t);var o=i(this.getRootDimens(r,n),3),s=o[0],a=o[1],l=o[2];this.adaptor.setStyle(t,"verticalAlign",this.em(a)),this.adaptor.setStyle(t,"width",this.em(s)),l&&this.adaptor.setStyle(this.adaptor.firstChild(t),"paddingLeft",this.em(l))},e.kind=l.MmlMroot.prototype.kind,e}((0,a.CommonMrootMixin)(s.CHTMLmsqrt));e.CHTMLmroot=c},4798:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLinferredMrow=e.CHTMLmrow=void 0;var s=r(5355),a=r(8411),l=r(8411),c=r(9878),u=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.toCHTML=function(t){var e,r,n=this.node.isInferred?this.chtml=t:this.standardCHTMLnode(t),o=!1;try{for(var s=i(this.childNodes),a=s.next();!a.done;a=s.next()){var l=a.value;l.toCHTML(n),l.bbox.w<0&&(o=!0)}}catch(t){e={error:t}}finally{try{a&&!a.done&&(r=s.return)&&r.call(s)}finally{if(e)throw e.error}}if(o){var c=this.getBBox().w;c&&(this.adaptor.setStyle(n,"width",this.em(Math.max(0,c))),c<0&&this.adaptor.setStyle(n,"marginRight",this.em(c)))}},e.kind=c.MmlMrow.prototype.kind,e}((0,a.CommonMrowMixin)(s.CHTMLWrapper));e.CHTMLmrow=u;var p=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.kind=c.MmlInferredMrow.prototype.kind,e}((0,l.CommonInferredMrowMixin)(u));e.CHTMLinferredMrow=p},4597:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLms=void 0;var i=r(5355),s=r(4126),a=r(7265),l=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.kind=a.MmlMs.prototype.kind,e}((0,s.CommonMsMixin)(i.CHTMLWrapper));e.CHTMLms=l},2970:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLmspace=void 0;var i=r(5355),s=r(258),a=r(6030),l=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.toCHTML=function(t){var e=this.standardCHTMLnode(t),r=this.getBBox(),n=r.w,o=r.h,i=r.d;n<0&&(this.adaptor.setStyle(e,"marginRight",this.em(n)),n=0),n&&this.adaptor.setStyle(e,"width",this.em(n)),(o=Math.max(0,o+i))&&this.adaptor.setStyle(e,"height",this.em(Math.max(0,o))),i&&this.adaptor.setStyle(e,"verticalAlign",this.em(-i))},e.kind=a.MmlMspace.prototype.kind,e}((0,s.CommonMspaceMixin)(i.CHTMLWrapper));e.CHTMLmspace=l},5610:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s};Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLmsqrt=void 0;var s=r(5355),a=r(4093),l=r(7131),c=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.toCHTML=function(t){var e,r,n,o,s=this.childNodes[this.surd],a=this.childNodes[this.base],l=s.getBBox(),c=a.getOuterBBox(),u=i(this.getPQ(l),2)[1],p=this.font.params.rule_thickness,h=c.h+u+p,f=this.standardCHTMLnode(t);null!=this.root&&(n=this.adaptor.append(f,this.html("mjx-root")),o=this.childNodes[this.root]);var d=this.adaptor.append(f,this.html("mjx-sqrt",{},[e=this.html("mjx-surd"),r=this.html("mjx-box",{style:{paddingTop:this.em(u)}})]));this.addRoot(n,o,l,h),s.toCHTML(e),a.toCHTML(r),s.size<0&&this.adaptor.addClass(d,"mjx-tall")},e.prototype.addRoot=function(t,e,r,n){},e.kind=l.MmlMsqrt.prototype.kind,e.styles={"mjx-root":{display:"inline-block","white-space":"nowrap"},"mjx-surd":{display:"inline-block","vertical-align":"top"},"mjx-sqrt":{display:"inline-block","padding-top":".07em"},"mjx-sqrt > mjx-box":{"border-top":".07em solid"},"mjx-sqrt.mjx-tall > mjx-box":{"padding-left":".3em","margin-left":"-.3em"}},e}((0,a.CommonMsqrtMixin)(s.CHTMLWrapper));e.CHTMLmsqrt=c},4300:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s};Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLmsubsup=e.CHTMLmsup=e.CHTMLmsub=void 0;var s=r(8650),a=r(905),l=r(905),c=r(905),u=r(4461),p=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.kind=u.MmlMsub.prototype.kind,e}((0,a.CommonMsubMixin)(s.CHTMLscriptbase));e.CHTMLmsub=p;var h=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.kind=u.MmlMsup.prototype.kind,e}((0,l.CommonMsupMixin)(s.CHTMLscriptbase));e.CHTMLmsup=h;var f=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.toCHTML=function(t){var e=this.adaptor,r=this.standardCHTMLnode(t),n=i([this.baseChild,this.supChild,this.subChild],3),o=n[0],s=n[1],a=n[2],l=i(this.getUVQ(),3),c=l[1],u=l[2],p={"vertical-align":this.em(c)};o.toCHTML(r);var h=e.append(r,this.html("mjx-script",{style:p}));s.toCHTML(h),e.append(h,this.html("mjx-spacer",{style:{"margin-top":this.em(u)}})),a.toCHTML(h);var f=this.getAdjustedIc();f&&e.setStyle(s.chtml,"marginLeft",this.em(f/s.bbox.rscale)),this.baseRemoveIc&&e.setStyle(h,"marginLeft",this.em(-this.baseIc))},e.kind=u.MmlMsubsup.prototype.kind,e.styles={"mjx-script":{display:"inline-block","padding-right":".05em","padding-left":".033em"},"mjx-script > mjx-spacer":{display:"block"}},e}((0,c.CommonMsubsupMixin)(s.CHTMLscriptbase));e.CHTMLmsubsup=f},8002:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},s=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s};Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLmtable=void 0;var a=r(5355),l=r(6237),c=r(1349),u=r(505),p=function(t){function e(e,r,n){void 0===n&&(n=null);var o=t.call(this,e,r,n)||this;return o.itable=o.html("mjx-itable"),o.labels=o.html("mjx-itable"),o}return o(e,t),e.prototype.getAlignShift=function(){var e=t.prototype.getAlignShift.call(this);return this.isTop||(e[1]=0),e},e.prototype.toCHTML=function(t){var e,r,n=this.standardCHTMLnode(t);this.adaptor.append(n,this.html("mjx-table",{},[this.itable]));try{for(var o=i(this.childNodes),s=o.next();!s.done;s=o.next()){s.value.toCHTML(this.itable)}}catch(t){e={error:t}}finally{try{s&&!s.done&&(r=o.return)&&r.call(o)}finally{if(e)throw e.error}}this.padRows(),this.handleColumnSpacing(),this.handleColumnLines(),this.handleColumnWidths(),this.handleRowSpacing(),this.handleRowLines(),this.handleRowHeights(),this.handleFrame(),this.handleWidth(),this.handleLabels(),this.handleAlign(),this.handleJustify(),this.shiftColor()},e.prototype.shiftColor=function(){var t=this.adaptor,e=t.getStyle(this.chtml,"backgroundColor");e&&(t.setStyle(this.chtml,"backgroundColor",""),t.setStyle(this.itable,"backgroundColor",e))},e.prototype.padRows=function(){var t,e,r=this.adaptor;try{for(var n=i(r.childNodes(this.itable)),o=n.next();!o.done;o=n.next())for(var s=o.value;r.childNodes(s).length1&&"0.4em"!==m||a&&1===p)&&this.adaptor.setStyle(g,"paddingLeft",m),(p1&&"0.215em"!==h||a&&1===l)&&this.adaptor.setStyle(y.chtml,"paddingTop",h),(l mjx-itable":{"vertical-align":"middle","text-align":"left","box-sizing":"border-box"},"mjx-labels > mjx-itable":{position:"absolute",top:0},'mjx-mtable[justify="left"]':{"text-align":"left"},'mjx-mtable[justify="right"]':{"text-align":"right"},'mjx-mtable[justify="left"][side="left"]':{"padding-right":"0 ! important"},'mjx-mtable[justify="left"][side="right"]':{"padding-left":"0 ! important"},'mjx-mtable[justify="right"][side="left"]':{"padding-right":"0 ! important"},'mjx-mtable[justify="right"][side="right"]':{"padding-left":"0 ! important"},"mjx-mtable[align]":{"vertical-align":"baseline"},'mjx-mtable[align="top"] > mjx-table':{"vertical-align":"top"},'mjx-mtable[align="bottom"] > mjx-table':{"vertical-align":"bottom"},'mjx-mtable[side="right"] mjx-labels':{"min-width":"100%"}},e}((0,l.CommonMtableMixin)(a.CHTMLWrapper));e.CHTMLmtable=p},7056:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLmtd=void 0;var i=r(5355),s=r(5164),a=r(4359),l=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.toCHTML=function(e){t.prototype.toCHTML.call(this,e);var r=this.node.attributes.get("rowalign"),n=this.node.attributes.get("columnalign");r!==this.parent.node.attributes.get("rowalign")&&this.adaptor.setAttribute(this.chtml,"rowalign",r),"center"===n||"mlabeledtr"===this.parent.kind&&this===this.parent.childNodes[0]&&n===this.parent.parent.node.attributes.get("side")||this.adaptor.setStyle(this.chtml,"textAlign",n),this.parent.parent.node.getProperty("useHeight")&&this.adaptor.append(this.chtml,this.html("mjx-tstrut"))},e.kind=a.MmlMtd.prototype.kind,e.styles={"mjx-mtd":{display:"table-cell","text-align":"center",padding:".215em .4em"},"mjx-mtd:first-child":{"padding-left":0},"mjx-mtd:last-child":{"padding-right":0},"mjx-mtable > * > mjx-itable > *:first-child > mjx-mtd":{"padding-top":0},"mjx-mtable > * > mjx-itable > *:last-child > mjx-mtd":{"padding-bottom":0},"mjx-tstrut":{display:"inline-block",height:"1em","vertical-align":"-.25em"},'mjx-labels[align="left"] > mjx-mtr > mjx-mtd':{"text-align":"left"},'mjx-labels[align="right"] > mjx-mtr > mjx-mtd':{"text-align":"right"},"mjx-mtd[extra]":{padding:0},'mjx-mtd[rowalign="top"]':{"vertical-align":"top"},'mjx-mtd[rowalign="center"]':{"vertical-align":"middle"},'mjx-mtd[rowalign="bottom"]':{"vertical-align":"bottom"},'mjx-mtd[rowalign="baseline"]':{"vertical-align":"baseline"},'mjx-mtd[rowalign="axis"]':{"vertical-align":".25em"}},e}((0,s.CommonMtdMixin)(i.CHTMLWrapper));e.CHTMLmtd=l},1259:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLmtext=void 0;var i=r(5355),s=r(6319),a=r(4770),l=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.kind=a.MmlMtext.prototype.kind,e}((0,s.CommonMtextMixin)(i.CHTMLWrapper));e.CHTMLmtext=l},3571:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLmlabeledtr=e.CHTMLmtr=void 0;var i=r(5355),s=r(5766),a=r(5766),l=r(5022),c=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.toCHTML=function(e){t.prototype.toCHTML.call(this,e);var r=this.node.attributes.get("rowalign");"baseline"!==r&&this.adaptor.setAttribute(this.chtml,"rowalign",r)},e.kind=l.MmlMtr.prototype.kind,e.styles={"mjx-mtr":{display:"table-row"},'mjx-mtr[rowalign="top"] > mjx-mtd':{"vertical-align":"top"},'mjx-mtr[rowalign="center"] > mjx-mtd':{"vertical-align":"middle"},'mjx-mtr[rowalign="bottom"] > mjx-mtd':{"vertical-align":"bottom"},'mjx-mtr[rowalign="baseline"] > mjx-mtd':{"vertical-align":"baseline"},'mjx-mtr[rowalign="axis"] > mjx-mtd':{"vertical-align":".25em"}},e}((0,s.CommonMtrMixin)(i.CHTMLWrapper));e.CHTMLmtr=c;var u=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.toCHTML=function(e){t.prototype.toCHTML.call(this,e);var r=this.adaptor.firstChild(this.chtml);if(r){this.adaptor.remove(r);var n=this.node.attributes.get("rowalign"),o="baseline"!==n&&"axis"!==n?{rowalign:n}:{},i=this.html("mjx-mtr",o,[r]);this.adaptor.append(this.parent.labels,i)}},e.prototype.markUsed=function(){t.prototype.markUsed.call(this),this.jax.wrapperUsage.add(c.kind)},e.kind=l.MmlMlabeledtr.prototype.kind,e.styles={"mjx-mlabeledtr":{display:"table-row"},'mjx-mlabeledtr[rowalign="top"] > mjx-mtd':{"vertical-align":"top"},'mjx-mlabeledtr[rowalign="center"] > mjx-mtd':{"vertical-align":"middle"},'mjx-mlabeledtr[rowalign="bottom"] > mjx-mtd':{"vertical-align":"bottom"},'mjx-mlabeledtr[rowalign="baseline"] > mjx-mtd':{"vertical-align":"baseline"},'mjx-mlabeledtr[rowalign="axis"] > mjx-mtd':{"vertical-align":".25em"}},e}((0,a.CommonMlabeledtrMixin)(c));e.CHTMLmlabeledtr=u},6590:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLmunderover=e.CHTMLmover=e.CHTMLmunder=void 0;var i=r(4300),s=r(1971),a=r(1971),l=r(1971),c=r(5184),u=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.toCHTML=function(e){if(this.hasMovableLimits())return t.prototype.toCHTML.call(this,e),void this.adaptor.setAttribute(this.chtml,"limits","false");this.chtml=this.standardCHTMLnode(e);var r=this.adaptor.append(this.adaptor.append(this.chtml,this.html("mjx-row")),this.html("mjx-base")),n=this.adaptor.append(this.adaptor.append(this.chtml,this.html("mjx-row")),this.html("mjx-under"));this.baseChild.toCHTML(r),this.scriptChild.toCHTML(n);var o=this.baseChild.getOuterBBox(),i=this.scriptChild.getOuterBBox(),s=this.getUnderKV(o,i)[0],a=this.isLineBelow?0:this.getDelta(!0);this.adaptor.setStyle(n,"paddingTop",this.em(s)),this.setDeltaW([r,n],this.getDeltaW([o,i],[0,-a])),this.adjustUnderDepth(n,i)},e.kind=c.MmlMunder.prototype.kind,e.styles={"mjx-over":{"text-align":"left"},'mjx-munder:not([limits="false"])':{display:"inline-table"},"mjx-munder > mjx-row":{"text-align":"left"},"mjx-under":{"padding-bottom":".1em"}},e}((0,s.CommonMunderMixin)(i.CHTMLmsub));e.CHTMLmunder=u;var p=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.toCHTML=function(e){if(this.hasMovableLimits())return t.prototype.toCHTML.call(this,e),void this.adaptor.setAttribute(this.chtml,"limits","false");this.chtml=this.standardCHTMLnode(e);var r=this.adaptor.append(this.chtml,this.html("mjx-over")),n=this.adaptor.append(this.chtml,this.html("mjx-base"));this.scriptChild.toCHTML(r),this.baseChild.toCHTML(n);var o=this.scriptChild.getOuterBBox(),i=this.baseChild.getOuterBBox();this.adjustBaseHeight(n,i);var s=this.getOverKU(i,o)[0],a=this.isLineAbove?0:this.getDelta();this.adaptor.setStyle(r,"paddingBottom",this.em(s)),this.setDeltaW([n,r],this.getDeltaW([i,o],[0,a])),this.adjustOverDepth(r,o)},e.kind=c.MmlMover.prototype.kind,e.styles={'mjx-mover:not([limits="false"])':{"padding-top":".1em"},'mjx-mover:not([limits="false"]) > *':{display:"block","text-align":"left"}},e}((0,a.CommonMoverMixin)(i.CHTMLmsup));e.CHTMLmover=p;var h=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.toCHTML=function(e){if(this.hasMovableLimits())return t.prototype.toCHTML.call(this,e),void this.adaptor.setAttribute(this.chtml,"limits","false");this.chtml=this.standardCHTMLnode(e);var r=this.adaptor.append(this.chtml,this.html("mjx-over")),n=this.adaptor.append(this.adaptor.append(this.chtml,this.html("mjx-box")),this.html("mjx-munder")),o=this.adaptor.append(this.adaptor.append(n,this.html("mjx-row")),this.html("mjx-base")),i=this.adaptor.append(this.adaptor.append(n,this.html("mjx-row")),this.html("mjx-under"));this.overChild.toCHTML(r),this.baseChild.toCHTML(o),this.underChild.toCHTML(i);var s=this.overChild.getOuterBBox(),a=this.baseChild.getOuterBBox(),l=this.underChild.getOuterBBox();this.adjustBaseHeight(o,a);var c=this.getOverKU(a,s)[0],u=this.getUnderKV(a,l)[0],p=this.getDelta();this.adaptor.setStyle(r,"paddingBottom",this.em(c)),this.adaptor.setStyle(i,"paddingTop",this.em(u)),this.setDeltaW([o,i,r],this.getDeltaW([a,l,s],[0,this.isLineBelow?0:-p,this.isLineAbove?0:p])),this.adjustOverDepth(r,s),this.adjustUnderDepth(i,l)},e.prototype.markUsed=function(){t.prototype.markUsed.call(this),this.jax.wrapperUsage.add(i.CHTMLmsubsup.kind)},e.kind=c.MmlMunderover.prototype.kind,e.styles={'mjx-munderover:not([limits="false"])':{"padding-top":".1em"},'mjx-munderover:not([limits="false"]) > *':{display:"block"}},e}((0,l.CommonMunderoverMixin)(i.CHTMLmsubsup));e.CHTMLmunderover=h},8650:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},s=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.CHTMLscriptbase=void 0;var a=r(5355),l=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.toCHTML=function(t){this.chtml=this.standardCHTMLnode(t);var e=i(this.getOffset(),2),r=e[0],n=e[1],o=r-(this.baseRemoveIc?this.baseIc:0),s={"vertical-align":this.em(n)};o&&(s["margin-left"]=this.em(o)),this.baseChild.toCHTML(this.chtml),this.scriptChild.toCHTML(this.adaptor.append(this.chtml,this.html("mjx-script",{style:s})))},e.prototype.setDeltaW=function(t,e){for(var r=0;r=0||this.adaptor.setStyle(t,"marginBottom",this.em(e.d*e.rscale))},e.prototype.adjustUnderDepth=function(t,e){var r,n;if(!(e.d>=0)){var o=this.adaptor,i=this.em(e.d),a=this.html("mjx-box",{style:{"margin-bottom":i,"vertical-align":i}});try{for(var l=s(o.childNodes(o.firstChild(t))),c=l.next();!c.done;c=l.next()){var u=c.value;o.append(a,u)}}catch(t){r={error:t}}finally{try{c&&!c.done&&(n=l.return)&&n.call(l)}finally{if(r)throw r.error}}o.append(o.firstChild(t),a)}},e.prototype.adjustBaseHeight=function(t,e){if(this.node.attributes.get("accent")){var r=this.font.params.x_height*e.scale;e.h\\338"},8816:{c:"\\2264\\338"},8817:{c:"\\2265\\338"},8832:{c:"\\227A\\338"},8833:{c:"\\227B\\338"},8836:{c:"\\2282\\338"},8837:{c:"\\2283\\338"},8840:{c:"\\2286\\338"},8841:{c:"\\2287\\338"},8876:{c:"\\22A2\\338"},8877:{c:"\\22A8\\338"},8930:{c:"\\2291\\338"},8931:{c:"\\2292\\338"},9001:{c:"\\27E8"},9002:{c:"\\27E9"},9653:{c:"\\25B3"},9663:{c:"\\25BD"},10072:{c:"\\2223"},10744:{c:"/",f:"BI"},10799:{c:"\\D7"},12296:{c:"\\27E8"},12297:{c:"\\27E9"}})},4515:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.doubleStruck=void 0;var n=r(6001);Object.defineProperty(e,"doubleStruck",{enumerable:!0,get:function(){return n.doubleStruck}})},6555:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.frakturBold=void 0;var n=r(8042),o=r(3696);e.frakturBold=(0,n.AddCSS)(o.frakturBold,{8260:{c:"/"}})},2183:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.fraktur=void 0;var n=r(8042),o=r(9587);e.fraktur=(0,n.AddCSS)(o.fraktur,{8260:{c:"/"}})},3490:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.italic=void 0;var n=r(8042),o=r(8348);e.italic=(0,n.AddCSS)(o.italic,{47:{f:"I"},989:{c:"\\E008",f:"A"},8213:{c:"\\2014"},8215:{c:"_"},8260:{c:"/",f:"I"},8710:{c:"\\394",f:"I"},10744:{c:"/",f:"I"}})},9056:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.largeop=void 0;var n=r(8042),o=r(1376);e.largeop=(0,n.AddCSS)(o.largeop,{8214:{f:"S1"},8260:{c:"/"},8593:{f:"S1"},8595:{f:"S1"},8657:{f:"S1"},8659:{f:"S1"},8739:{f:"S1"},8741:{f:"S1"},9001:{c:"\\27E8"},9002:{c:"\\27E9"},9168:{f:"S1"},10072:{c:"\\2223",f:"S1"},10764:{c:"\\222C\\222C"},12296:{c:"\\27E8"},12297:{c:"\\27E9"}})},3019:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.monospace=void 0;var n=r(8042),o=r(1439);e.monospace=(0,n.AddCSS)(o.monospace,{697:{c:"\\2032"},913:{c:"A"},914:{c:"B"},917:{c:"E"},918:{c:"Z"},919:{c:"H"},921:{c:"I"},922:{c:"K"},924:{c:"M"},925:{c:"N"},927:{c:"O"},929:{c:"P"},932:{c:"T"},935:{c:"X"},8215:{c:"_"},8243:{c:"\\2032\\2032"},8244:{c:"\\2032\\2032\\2032"},8260:{c:"/"},8279:{c:"\\2032\\2032\\2032\\2032"},8710:{c:"\\394"}})},2713:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.normal=void 0;var n=r(8042),o=r(331);e.normal=(0,n.AddCSS)(o.normal,{163:{f:"MI"},165:{f:"A"},174:{f:"A"},183:{c:"\\22C5"},240:{f:"A"},697:{c:"\\2032"},913:{c:"A"},914:{c:"B"},917:{c:"E"},918:{c:"Z"},919:{c:"H"},921:{c:"I"},922:{c:"K"},924:{c:"M"},925:{c:"N"},927:{c:"O"},929:{c:"P"},932:{c:"T"},935:{c:"X"},8192:{c:""},8193:{c:""},8194:{c:""},8195:{c:""},8196:{c:""},8197:{c:""},8198:{c:""},8201:{c:""},8202:{c:""},8203:{c:""},8204:{c:""},8213:{c:"\\2014"},8214:{c:"\\2225"},8215:{c:"_"},8226:{c:"\\2219"},8243:{c:"\\2032\\2032"},8244:{c:"\\2032\\2032\\2032"},8245:{f:"A"},8246:{c:"\\2035\\2035",f:"A"},8247:{c:"\\2035\\2035\\2035",f:"A"},8254:{c:"\\2C9"},8260:{c:"/"},8279:{c:"\\2032\\2032\\2032\\2032"},8288:{c:""},8289:{c:""},8290:{c:""},8291:{c:""},8292:{c:""},8407:{c:"\\2192",f:"V"},8450:{c:"C",f:"A"},8459:{c:"H",f:"SC"},8460:{c:"H",f:"FR"},8461:{c:"H",f:"A"},8462:{c:"h",f:"I"},8463:{f:"A"},8464:{c:"I",f:"SC"},8465:{c:"I",f:"FR"},8466:{c:"L",f:"SC"},8469:{c:"N",f:"A"},8473:{c:"P",f:"A"},8474:{c:"Q",f:"A"},8475:{c:"R",f:"SC"},8476:{c:"R",f:"FR"},8477:{c:"R",f:"A"},8484:{c:"Z",f:"A"},8486:{c:"\\3A9"},8487:{f:"A"},8488:{c:"Z",f:"FR"},8492:{c:"B",f:"SC"},8493:{c:"C",f:"FR"},8496:{c:"E",f:"SC"},8497:{c:"F",f:"SC"},8498:{f:"A"},8499:{c:"M",f:"SC"},8502:{f:"A"},8503:{f:"A"},8504:{f:"A"},8513:{f:"A"},8602:{f:"A"},8603:{f:"A"},8606:{f:"A"},8608:{f:"A"},8610:{f:"A"},8611:{f:"A"},8619:{f:"A"},8620:{f:"A"},8621:{f:"A"},8622:{f:"A"},8624:{f:"A"},8625:{f:"A"},8630:{f:"A"},8631:{f:"A"},8634:{f:"A"},8635:{f:"A"},8638:{f:"A"},8639:{f:"A"},8642:{f:"A"},8643:{f:"A"},8644:{f:"A"},8646:{f:"A"},8647:{f:"A"},8648:{f:"A"},8649:{f:"A"},8650:{f:"A"},8651:{f:"A"},8653:{f:"A"},8654:{f:"A"},8655:{f:"A"},8666:{f:"A"},8667:{f:"A"},8669:{f:"A"},8672:{f:"A"},8674:{f:"A"},8705:{f:"A"},8708:{c:"\\2203\\338"},8710:{c:"\\394"},8716:{c:"\\220B\\338"},8717:{f:"A"},8719:{f:"S1"},8720:{f:"S1"},8721:{f:"S1"},8724:{f:"A"},8737:{f:"A"},8738:{f:"A"},8740:{f:"A"},8742:{f:"A"},8748:{f:"S1"},8749:{f:"S1"},8750:{f:"S1"},8756:{f:"A"},8757:{f:"A"},8765:{f:"A"},8769:{f:"A"},8770:{f:"A"},8772:{c:"\\2243\\338"},8775:{c:"\\2246",f:"A"},8777:{c:"\\2248\\338"},8778:{f:"A"},8782:{f:"A"},8783:{f:"A"},8785:{f:"A"},8786:{f:"A"},8787:{f:"A"},8790:{f:"A"},8791:{f:"A"},8796:{f:"A"},8802:{c:"\\2261\\338"},8806:{f:"A"},8807:{f:"A"},8808:{f:"A"},8809:{f:"A"},8812:{f:"A"},8813:{c:"\\224D\\338"},8814:{f:"A"},8815:{f:"A"},8816:{f:"A"},8817:{f:"A"},8818:{f:"A"},8819:{f:"A"},8820:{c:"\\2272\\338"},8821:{c:"\\2273\\338"},8822:{f:"A"},8823:{f:"A"},8824:{c:"\\2276\\338"},8825:{c:"\\2277\\338"},8828:{f:"A"},8829:{f:"A"},8830:{f:"A"},8831:{f:"A"},8832:{f:"A"},8833:{f:"A"},8836:{c:"\\2282\\338"},8837:{c:"\\2283\\338"},8840:{f:"A"},8841:{f:"A"},8842:{f:"A"},8843:{f:"A"},8847:{f:"A"},8848:{f:"A"},8858:{f:"A"},8859:{f:"A"},8861:{f:"A"},8862:{f:"A"},8863:{f:"A"},8864:{f:"A"},8865:{f:"A"},8873:{f:"A"},8874:{f:"A"},8876:{f:"A"},8877:{f:"A"},8878:{f:"A"},8879:{f:"A"},8882:{f:"A"},8883:{f:"A"},8884:{f:"A"},8885:{f:"A"},8888:{f:"A"},8890:{f:"A"},8891:{f:"A"},8892:{f:"A"},8896:{f:"S1"},8897:{f:"S1"},8898:{f:"S1"},8899:{f:"S1"},8903:{f:"A"},8905:{f:"A"},8906:{f:"A"},8907:{f:"A"},8908:{f:"A"},8909:{f:"A"},8910:{f:"A"},8911:{f:"A"},8912:{f:"A"},8913:{f:"A"},8914:{f:"A"},8915:{f:"A"},8916:{f:"A"},8918:{f:"A"},8919:{f:"A"},8920:{f:"A"},8921:{f:"A"},8922:{f:"A"},8923:{f:"A"},8926:{f:"A"},8927:{f:"A"},8928:{f:"A"},8929:{f:"A"},8930:{c:"\\2291\\338"},8931:{c:"\\2292\\338"},8934:{f:"A"},8935:{f:"A"},8936:{f:"A"},8937:{f:"A"},8938:{f:"A"},8939:{f:"A"},8940:{f:"A"},8941:{f:"A"},8965:{c:"\\22BC",f:"A"},8966:{c:"\\2A5E",f:"A"},8988:{c:"\\250C",f:"A"},8989:{c:"\\2510",f:"A"},8990:{c:"\\2514",f:"A"},8991:{c:"\\2518",f:"A"},9001:{c:"\\27E8"},9002:{c:"\\27E9"},9168:{f:"S1"},9416:{f:"A"},9484:{f:"A"},9488:{f:"A"},9492:{f:"A"},9496:{f:"A"},9585:{f:"A"},9586:{f:"A"},9632:{f:"A"},9633:{f:"A"},9642:{c:"\\25A0",f:"A"},9650:{f:"A"},9652:{c:"\\25B2",f:"A"},9653:{c:"\\25B3"},9654:{f:"A"},9656:{c:"\\25B6",f:"A"},9660:{f:"A"},9662:{c:"\\25BC",f:"A"},9663:{c:"\\25BD"},9664:{f:"A"},9666:{c:"\\25C0",f:"A"},9674:{f:"A"},9723:{c:"\\25A1",f:"A"},9724:{c:"\\25A0",f:"A"},9733:{f:"A"},10003:{f:"A"},10016:{f:"A"},10072:{c:"\\2223"},10731:{f:"A"},10744:{c:"/",f:"I"},10752:{f:"S1"},10753:{f:"S1"},10754:{f:"S1"},10756:{f:"S1"},10758:{f:"S1"},10764:{c:"\\222C\\222C",f:"S1"},10799:{c:"\\D7"},10846:{f:"A"},10877:{f:"A"},10878:{f:"A"},10885:{f:"A"},10886:{f:"A"},10887:{f:"A"},10888:{f:"A"},10889:{f:"A"},10890:{f:"A"},10891:{f:"A"},10892:{f:"A"},10901:{f:"A"},10902:{f:"A"},10933:{f:"A"},10934:{f:"A"},10935:{f:"A"},10936:{f:"A"},10937:{f:"A"},10938:{f:"A"},10949:{f:"A"},10950:{f:"A"},10955:{f:"A"},10956:{f:"A"},12296:{c:"\\27E8"},12297:{c:"\\27E9"},57350:{f:"A"},57351:{f:"A"},57352:{f:"A"},57353:{f:"A"},57356:{f:"A"},57357:{f:"A"},57358:{f:"A"},57359:{f:"A"},57360:{f:"A"},57361:{f:"A"},57366:{f:"A"},57367:{f:"A"},57368:{f:"A"},57369:{f:"A"},57370:{f:"A"},57371:{f:"A"},119808:{c:"A",f:"B"},119809:{c:"B",f:"B"},119810:{c:"C",f:"B"},119811:{c:"D",f:"B"},119812:{c:"E",f:"B"},119813:{c:"F",f:"B"},119814:{c:"G",f:"B"},119815:{c:"H",f:"B"},119816:{c:"I",f:"B"},119817:{c:"J",f:"B"},119818:{c:"K",f:"B"},119819:{c:"L",f:"B"},119820:{c:"M",f:"B"},119821:{c:"N",f:"B"},119822:{c:"O",f:"B"},119823:{c:"P",f:"B"},119824:{c:"Q",f:"B"},119825:{c:"R",f:"B"},119826:{c:"S",f:"B"},119827:{c:"T",f:"B"},119828:{c:"U",f:"B"},119829:{c:"V",f:"B"},119830:{c:"W",f:"B"},119831:{c:"X",f:"B"},119832:{c:"Y",f:"B"},119833:{c:"Z",f:"B"},119834:{c:"a",f:"B"},119835:{c:"b",f:"B"},119836:{c:"c",f:"B"},119837:{c:"d",f:"B"},119838:{c:"e",f:"B"},119839:{c:"f",f:"B"},119840:{c:"g",f:"B"},119841:{c:"h",f:"B"},119842:{c:"i",f:"B"},119843:{c:"j",f:"B"},119844:{c:"k",f:"B"},119845:{c:"l",f:"B"},119846:{c:"m",f:"B"},119847:{c:"n",f:"B"},119848:{c:"o",f:"B"},119849:{c:"p",f:"B"},119850:{c:"q",f:"B"},119851:{c:"r",f:"B"},119852:{c:"s",f:"B"},119853:{c:"t",f:"B"},119854:{c:"u",f:"B"},119855:{c:"v",f:"B"},119856:{c:"w",f:"B"},119857:{c:"x",f:"B"},119858:{c:"y",f:"B"},119859:{c:"z",f:"B"},119860:{c:"A",f:"I"},119861:{c:"B",f:"I"},119862:{c:"C",f:"I"},119863:{c:"D",f:"I"},119864:{c:"E",f:"I"},119865:{c:"F",f:"I"},119866:{c:"G",f:"I"},119867:{c:"H",f:"I"},119868:{c:"I",f:"I"},119869:{c:"J",f:"I"},119870:{c:"K",f:"I"},119871:{c:"L",f:"I"},119872:{c:"M",f:"I"},119873:{c:"N",f:"I"},119874:{c:"O",f:"I"},119875:{c:"P",f:"I"},119876:{c:"Q",f:"I"},119877:{c:"R",f:"I"},119878:{c:"S",f:"I"},119879:{c:"T",f:"I"},119880:{c:"U",f:"I"},119881:{c:"V",f:"I"},119882:{c:"W",f:"I"},119883:{c:"X",f:"I"},119884:{c:"Y",f:"I"},119885:{c:"Z",f:"I"},119886:{c:"a",f:"I"},119887:{c:"b",f:"I"},119888:{c:"c",f:"I"},119889:{c:"d",f:"I"},119890:{c:"e",f:"I"},119891:{c:"f",f:"I"},119892:{c:"g",f:"I"},119894:{c:"i",f:"I"},119895:{c:"j",f:"I"},119896:{c:"k",f:"I"},119897:{c:"l",f:"I"},119898:{c:"m",f:"I"},119899:{c:"n",f:"I"},119900:{c:"o",f:"I"},119901:{c:"p",f:"I"},119902:{c:"q",f:"I"},119903:{c:"r",f:"I"},119904:{c:"s",f:"I"},119905:{c:"t",f:"I"},119906:{c:"u",f:"I"},119907:{c:"v",f:"I"},119908:{c:"w",f:"I"},119909:{c:"x",f:"I"},119910:{c:"y",f:"I"},119911:{c:"z",f:"I"},119912:{c:"A",f:"BI"},119913:{c:"B",f:"BI"},119914:{c:"C",f:"BI"},119915:{c:"D",f:"BI"},119916:{c:"E",f:"BI"},119917:{c:"F",f:"BI"},119918:{c:"G",f:"BI"},119919:{c:"H",f:"BI"},119920:{c:"I",f:"BI"},119921:{c:"J",f:"BI"},119922:{c:"K",f:"BI"},119923:{c:"L",f:"BI"},119924:{c:"M",f:"BI"},119925:{c:"N",f:"BI"},119926:{c:"O",f:"BI"},119927:{c:"P",f:"BI"},119928:{c:"Q",f:"BI"},119929:{c:"R",f:"BI"},119930:{c:"S",f:"BI"},119931:{c:"T",f:"BI"},119932:{c:"U",f:"BI"},119933:{c:"V",f:"BI"},119934:{c:"W",f:"BI"},119935:{c:"X",f:"BI"},119936:{c:"Y",f:"BI"},119937:{c:"Z",f:"BI"},119938:{c:"a",f:"BI"},119939:{c:"b",f:"BI"},119940:{c:"c",f:"BI"},119941:{c:"d",f:"BI"},119942:{c:"e",f:"BI"},119943:{c:"f",f:"BI"},119944:{c:"g",f:"BI"},119945:{c:"h",f:"BI"},119946:{c:"i",f:"BI"},119947:{c:"j",f:"BI"},119948:{c:"k",f:"BI"},119949:{c:"l",f:"BI"},119950:{c:"m",f:"BI"},119951:{c:"n",f:"BI"},119952:{c:"o",f:"BI"},119953:{c:"p",f:"BI"},119954:{c:"q",f:"BI"},119955:{c:"r",f:"BI"},119956:{c:"s",f:"BI"},119957:{c:"t",f:"BI"},119958:{c:"u",f:"BI"},119959:{c:"v",f:"BI"},119960:{c:"w",f:"BI"},119961:{c:"x",f:"BI"},119962:{c:"y",f:"BI"},119963:{c:"z",f:"BI"},119964:{c:"A",f:"SC"},119966:{c:"C",f:"SC"},119967:{c:"D",f:"SC"},119970:{c:"G",f:"SC"},119973:{c:"J",f:"SC"},119974:{c:"K",f:"SC"},119977:{c:"N",f:"SC"},119978:{c:"O",f:"SC"},119979:{c:"P",f:"SC"},119980:{c:"Q",f:"SC"},119982:{c:"S",f:"SC"},119983:{c:"T",f:"SC"},119984:{c:"U",f:"SC"},119985:{c:"V",f:"SC"},119986:{c:"W",f:"SC"},119987:{c:"X",f:"SC"},119988:{c:"Y",f:"SC"},119989:{c:"Z",f:"SC"},120068:{c:"A",f:"FR"},120069:{c:"B",f:"FR"},120071:{c:"D",f:"FR"},120072:{c:"E",f:"FR"},120073:{c:"F",f:"FR"},120074:{c:"G",f:"FR"},120077:{c:"J",f:"FR"},120078:{c:"K",f:"FR"},120079:{c:"L",f:"FR"},120080:{c:"M",f:"FR"},120081:{c:"N",f:"FR"},120082:{c:"O",f:"FR"},120083:{c:"P",f:"FR"},120084:{c:"Q",f:"FR"},120086:{c:"S",f:"FR"},120087:{c:"T",f:"FR"},120088:{c:"U",f:"FR"},120089:{c:"V",f:"FR"},120090:{c:"W",f:"FR"},120091:{c:"X",f:"FR"},120092:{c:"Y",f:"FR"},120094:{c:"a",f:"FR"},120095:{c:"b",f:"FR"},120096:{c:"c",f:"FR"},120097:{c:"d",f:"FR"},120098:{c:"e",f:"FR"},120099:{c:"f",f:"FR"},120100:{c:"g",f:"FR"},120101:{c:"h",f:"FR"},120102:{c:"i",f:"FR"},120103:{c:"j",f:"FR"},120104:{c:"k",f:"FR"},120105:{c:"l",f:"FR"},120106:{c:"m",f:"FR"},120107:{c:"n",f:"FR"},120108:{c:"o",f:"FR"},120109:{c:"p",f:"FR"},120110:{c:"q",f:"FR"},120111:{c:"r",f:"FR"},120112:{c:"s",f:"FR"},120113:{c:"t",f:"FR"},120114:{c:"u",f:"FR"},120115:{c:"v",f:"FR"},120116:{c:"w",f:"FR"},120117:{c:"x",f:"FR"},120118:{c:"y",f:"FR"},120119:{c:"z",f:"FR"},120120:{c:"A",f:"A"},120121:{c:"B",f:"A"},120123:{c:"D",f:"A"},120124:{c:"E",f:"A"},120125:{c:"F",f:"A"},120126:{c:"G",f:"A"},120128:{c:"I",f:"A"},120129:{c:"J",f:"A"},120130:{c:"K",f:"A"},120131:{c:"L",f:"A"},120132:{c:"M",f:"A"},120134:{c:"O",f:"A"},120138:{c:"S",f:"A"},120139:{c:"T",f:"A"},120140:{c:"U",f:"A"},120141:{c:"V",f:"A"},120142:{c:"W",f:"A"},120143:{c:"X",f:"A"},120144:{c:"Y",f:"A"},120172:{c:"A",f:"FRB"},120173:{c:"B",f:"FRB"},120174:{c:"C",f:"FRB"},120175:{c:"D",f:"FRB"},120176:{c:"E",f:"FRB"},120177:{c:"F",f:"FRB"},120178:{c:"G",f:"FRB"},120179:{c:"H",f:"FRB"},120180:{c:"I",f:"FRB"},120181:{c:"J",f:"FRB"},120182:{c:"K",f:"FRB"},120183:{c:"L",f:"FRB"},120184:{c:"M",f:"FRB"},120185:{c:"N",f:"FRB"},120186:{c:"O",f:"FRB"},120187:{c:"P",f:"FRB"},120188:{c:"Q",f:"FRB"},120189:{c:"R",f:"FRB"},120190:{c:"S",f:"FRB"},120191:{c:"T",f:"FRB"},120192:{c:"U",f:"FRB"},120193:{c:"V",f:"FRB"},120194:{c:"W",f:"FRB"},120195:{c:"X",f:"FRB"},120196:{c:"Y",f:"FRB"},120197:{c:"Z",f:"FRB"},120198:{c:"a",f:"FRB"},120199:{c:"b",f:"FRB"},120200:{c:"c",f:"FRB"},120201:{c:"d",f:"FRB"},120202:{c:"e",f:"FRB"},120203:{c:"f",f:"FRB"},120204:{c:"g",f:"FRB"},120205:{c:"h",f:"FRB"},120206:{c:"i",f:"FRB"},120207:{c:"j",f:"FRB"},120208:{c:"k",f:"FRB"},120209:{c:"l",f:"FRB"},120210:{c:"m",f:"FRB"},120211:{c:"n",f:"FRB"},120212:{c:"o",f:"FRB"},120213:{c:"p",f:"FRB"},120214:{c:"q",f:"FRB"},120215:{c:"r",f:"FRB"},120216:{c:"s",f:"FRB"},120217:{c:"t",f:"FRB"},120218:{c:"u",f:"FRB"},120219:{c:"v",f:"FRB"},120220:{c:"w",f:"FRB"},120221:{c:"x",f:"FRB"},120222:{c:"y",f:"FRB"},120223:{c:"z",f:"FRB"},120224:{c:"A",f:"SS"},120225:{c:"B",f:"SS"},120226:{c:"C",f:"SS"},120227:{c:"D",f:"SS"},120228:{c:"E",f:"SS"},120229:{c:"F",f:"SS"},120230:{c:"G",f:"SS"},120231:{c:"H",f:"SS"},120232:{c:"I",f:"SS"},120233:{c:"J",f:"SS"},120234:{c:"K",f:"SS"},120235:{c:"L",f:"SS"},120236:{c:"M",f:"SS"},120237:{c:"N",f:"SS"},120238:{c:"O",f:"SS"},120239:{c:"P",f:"SS"},120240:{c:"Q",f:"SS"},120241:{c:"R",f:"SS"},120242:{c:"S",f:"SS"},120243:{c:"T",f:"SS"},120244:{c:"U",f:"SS"},120245:{c:"V",f:"SS"},120246:{c:"W",f:"SS"},120247:{c:"X",f:"SS"},120248:{c:"Y",f:"SS"},120249:{c:"Z",f:"SS"},120250:{c:"a",f:"SS"},120251:{c:"b",f:"SS"},120252:{c:"c",f:"SS"},120253:{c:"d",f:"SS"},120254:{c:"e",f:"SS"},120255:{c:"f",f:"SS"},120256:{c:"g",f:"SS"},120257:{c:"h",f:"SS"},120258:{c:"i",f:"SS"},120259:{c:"j",f:"SS"},120260:{c:"k",f:"SS"},120261:{c:"l",f:"SS"},120262:{c:"m",f:"SS"},120263:{c:"n",f:"SS"},120264:{c:"o",f:"SS"},120265:{c:"p",f:"SS"},120266:{c:"q",f:"SS"},120267:{c:"r",f:"SS"},120268:{c:"s",f:"SS"},120269:{c:"t",f:"SS"},120270:{c:"u",f:"SS"},120271:{c:"v",f:"SS"},120272:{c:"w",f:"SS"},120273:{c:"x",f:"SS"},120274:{c:"y",f:"SS"},120275:{c:"z",f:"SS"},120276:{c:"A",f:"SSB"},120277:{c:"B",f:"SSB"},120278:{c:"C",f:"SSB"},120279:{c:"D",f:"SSB"},120280:{c:"E",f:"SSB"},120281:{c:"F",f:"SSB"},120282:{c:"G",f:"SSB"},120283:{c:"H",f:"SSB"},120284:{c:"I",f:"SSB"},120285:{c:"J",f:"SSB"},120286:{c:"K",f:"SSB"},120287:{c:"L",f:"SSB"},120288:{c:"M",f:"SSB"},120289:{c:"N",f:"SSB"},120290:{c:"O",f:"SSB"},120291:{c:"P",f:"SSB"},120292:{c:"Q",f:"SSB"},120293:{c:"R",f:"SSB"},120294:{c:"S",f:"SSB"},120295:{c:"T",f:"SSB"},120296:{c:"U",f:"SSB"},120297:{c:"V",f:"SSB"},120298:{c:"W",f:"SSB"},120299:{c:"X",f:"SSB"},120300:{c:"Y",f:"SSB"},120301:{c:"Z",f:"SSB"},120302:{c:"a",f:"SSB"},120303:{c:"b",f:"SSB"},120304:{c:"c",f:"SSB"},120305:{c:"d",f:"SSB"},120306:{c:"e",f:"SSB"},120307:{c:"f",f:"SSB"},120308:{c:"g",f:"SSB"},120309:{c:"h",f:"SSB"},120310:{c:"i",f:"SSB"},120311:{c:"j",f:"SSB"},120312:{c:"k",f:"SSB"},120313:{c:"l",f:"SSB"},120314:{c:"m",f:"SSB"},120315:{c:"n",f:"SSB"},120316:{c:"o",f:"SSB"},120317:{c:"p",f:"SSB"},120318:{c:"q",f:"SSB"},120319:{c:"r",f:"SSB"},120320:{c:"s",f:"SSB"},120321:{c:"t",f:"SSB"},120322:{c:"u",f:"SSB"},120323:{c:"v",f:"SSB"},120324:{c:"w",f:"SSB"},120325:{c:"x",f:"SSB"},120326:{c:"y",f:"SSB"},120327:{c:"z",f:"SSB"},120328:{c:"A",f:"SSI"},120329:{c:"B",f:"SSI"},120330:{c:"C",f:"SSI"},120331:{c:"D",f:"SSI"},120332:{c:"E",f:"SSI"},120333:{c:"F",f:"SSI"},120334:{c:"G",f:"SSI"},120335:{c:"H",f:"SSI"},120336:{c:"I",f:"SSI"},120337:{c:"J",f:"SSI"},120338:{c:"K",f:"SSI"},120339:{c:"L",f:"SSI"},120340:{c:"M",f:"SSI"},120341:{c:"N",f:"SSI"},120342:{c:"O",f:"SSI"},120343:{c:"P",f:"SSI"},120344:{c:"Q",f:"SSI"},120345:{c:"R",f:"SSI"},120346:{c:"S",f:"SSI"},120347:{c:"T",f:"SSI"},120348:{c:"U",f:"SSI"},120349:{c:"V",f:"SSI"},120350:{c:"W",f:"SSI"},120351:{c:"X",f:"SSI"},120352:{c:"Y",f:"SSI"},120353:{c:"Z",f:"SSI"},120354:{c:"a",f:"SSI"},120355:{c:"b",f:"SSI"},120356:{c:"c",f:"SSI"},120357:{c:"d",f:"SSI"},120358:{c:"e",f:"SSI"},120359:{c:"f",f:"SSI"},120360:{c:"g",f:"SSI"},120361:{c:"h",f:"SSI"},120362:{c:"i",f:"SSI"},120363:{c:"j",f:"SSI"},120364:{c:"k",f:"SSI"},120365:{c:"l",f:"SSI"},120366:{c:"m",f:"SSI"},120367:{c:"n",f:"SSI"},120368:{c:"o",f:"SSI"},120369:{c:"p",f:"SSI"},120370:{c:"q",f:"SSI"},120371:{c:"r",f:"SSI"},120372:{c:"s",f:"SSI"},120373:{c:"t",f:"SSI"},120374:{c:"u",f:"SSI"},120375:{c:"v",f:"SSI"},120376:{c:"w",f:"SSI"},120377:{c:"x",f:"SSI"},120378:{c:"y",f:"SSI"},120379:{c:"z",f:"SSI"},120432:{c:"A",f:"T"},120433:{c:"B",f:"T"},120434:{c:"C",f:"T"},120435:{c:"D",f:"T"},120436:{c:"E",f:"T"},120437:{c:"F",f:"T"},120438:{c:"G",f:"T"},120439:{c:"H",f:"T"},120440:{c:"I",f:"T"},120441:{c:"J",f:"T"},120442:{c:"K",f:"T"},120443:{c:"L",f:"T"},120444:{c:"M",f:"T"},120445:{c:"N",f:"T"},120446:{c:"O",f:"T"},120447:{c:"P",f:"T"},120448:{c:"Q",f:"T"},120449:{c:"R",f:"T"},120450:{c:"S",f:"T"},120451:{c:"T",f:"T"},120452:{c:"U",f:"T"},120453:{c:"V",f:"T"},120454:{c:"W",f:"T"},120455:{c:"X",f:"T"},120456:{c:"Y",f:"T"},120457:{c:"Z",f:"T"},120458:{c:"a",f:"T"},120459:{c:"b",f:"T"},120460:{c:"c",f:"T"},120461:{c:"d",f:"T"},120462:{c:"e",f:"T"},120463:{c:"f",f:"T"},120464:{c:"g",f:"T"},120465:{c:"h",f:"T"},120466:{c:"i",f:"T"},120467:{c:"j",f:"T"},120468:{c:"k",f:"T"},120469:{c:"l",f:"T"},120470:{c:"m",f:"T"},120471:{c:"n",f:"T"},120472:{c:"o",f:"T"},120473:{c:"p",f:"T"},120474:{c:"q",f:"T"},120475:{c:"r",f:"T"},120476:{c:"s",f:"T"},120477:{c:"t",f:"T"},120478:{c:"u",f:"T"},120479:{c:"v",f:"T"},120480:{c:"w",f:"T"},120481:{c:"x",f:"T"},120482:{c:"y",f:"T"},120483:{c:"z",f:"T"},120488:{c:"A",f:"B"},120489:{c:"B",f:"B"},120490:{c:"\\393",f:"B"},120491:{c:"\\394",f:"B"},120492:{c:"E",f:"B"},120493:{c:"Z",f:"B"},120494:{c:"H",f:"B"},120495:{c:"\\398",f:"B"},120496:{c:"I",f:"B"},120497:{c:"K",f:"B"},120498:{c:"\\39B",f:"B"},120499:{c:"M",f:"B"},120500:{c:"N",f:"B"},120501:{c:"\\39E",f:"B"},120502:{c:"O",f:"B"},120503:{c:"\\3A0",f:"B"},120504:{c:"P",f:"B"},120506:{c:"\\3A3",f:"B"},120507:{c:"T",f:"B"},120508:{c:"\\3A5",f:"B"},120509:{c:"\\3A6",f:"B"},120510:{c:"X",f:"B"},120511:{c:"\\3A8",f:"B"},120512:{c:"\\3A9",f:"B"},120513:{c:"\\2207",f:"B"},120546:{c:"A",f:"I"},120547:{c:"B",f:"I"},120548:{c:"\\393",f:"I"},120549:{c:"\\394",f:"I"},120550:{c:"E",f:"I"},120551:{c:"Z",f:"I"},120552:{c:"H",f:"I"},120553:{c:"\\398",f:"I"},120554:{c:"I",f:"I"},120555:{c:"K",f:"I"},120556:{c:"\\39B",f:"I"},120557:{c:"M",f:"I"},120558:{c:"N",f:"I"},120559:{c:"\\39E",f:"I"},120560:{c:"O",f:"I"},120561:{c:"\\3A0",f:"I"},120562:{c:"P",f:"I"},120564:{c:"\\3A3",f:"I"},120565:{c:"T",f:"I"},120566:{c:"\\3A5",f:"I"},120567:{c:"\\3A6",f:"I"},120568:{c:"X",f:"I"},120569:{c:"\\3A8",f:"I"},120570:{c:"\\3A9",f:"I"},120572:{c:"\\3B1",f:"I"},120573:{c:"\\3B2",f:"I"},120574:{c:"\\3B3",f:"I"},120575:{c:"\\3B4",f:"I"},120576:{c:"\\3B5",f:"I"},120577:{c:"\\3B6",f:"I"},120578:{c:"\\3B7",f:"I"},120579:{c:"\\3B8",f:"I"},120580:{c:"\\3B9",f:"I"},120581:{c:"\\3BA",f:"I"},120582:{c:"\\3BB",f:"I"},120583:{c:"\\3BC",f:"I"},120584:{c:"\\3BD",f:"I"},120585:{c:"\\3BE",f:"I"},120586:{c:"\\3BF",f:"I"},120587:{c:"\\3C0",f:"I"},120588:{c:"\\3C1",f:"I"},120589:{c:"\\3C2",f:"I"},120590:{c:"\\3C3",f:"I"},120591:{c:"\\3C4",f:"I"},120592:{c:"\\3C5",f:"I"},120593:{c:"\\3C6",f:"I"},120594:{c:"\\3C7",f:"I"},120595:{c:"\\3C8",f:"I"},120596:{c:"\\3C9",f:"I"},120597:{c:"\\2202"},120598:{c:"\\3F5",f:"I"},120599:{c:"\\3D1",f:"I"},120600:{c:"\\E009",f:"A"},120601:{c:"\\3D5",f:"I"},120602:{c:"\\3F1",f:"I"},120603:{c:"\\3D6",f:"I"},120604:{c:"A",f:"BI"},120605:{c:"B",f:"BI"},120606:{c:"\\393",f:"BI"},120607:{c:"\\394",f:"BI"},120608:{c:"E",f:"BI"},120609:{c:"Z",f:"BI"},120610:{c:"H",f:"BI"},120611:{c:"\\398",f:"BI"},120612:{c:"I",f:"BI"},120613:{c:"K",f:"BI"},120614:{c:"\\39B",f:"BI"},120615:{c:"M",f:"BI"},120616:{c:"N",f:"BI"},120617:{c:"\\39E",f:"BI"},120618:{c:"O",f:"BI"},120619:{c:"\\3A0",f:"BI"},120620:{c:"P",f:"BI"},120622:{c:"\\3A3",f:"BI"},120623:{c:"T",f:"BI"},120624:{c:"\\3A5",f:"BI"},120625:{c:"\\3A6",f:"BI"},120626:{c:"X",f:"BI"},120627:{c:"\\3A8",f:"BI"},120628:{c:"\\3A9",f:"BI"},120630:{c:"\\3B1",f:"BI"},120631:{c:"\\3B2",f:"BI"},120632:{c:"\\3B3",f:"BI"},120633:{c:"\\3B4",f:"BI"},120634:{c:"\\3B5",f:"BI"},120635:{c:"\\3B6",f:"BI"},120636:{c:"\\3B7",f:"BI"},120637:{c:"\\3B8",f:"BI"},120638:{c:"\\3B9",f:"BI"},120639:{c:"\\3BA",f:"BI"},120640:{c:"\\3BB",f:"BI"},120641:{c:"\\3BC",f:"BI"},120642:{c:"\\3BD",f:"BI"},120643:{c:"\\3BE",f:"BI"},120644:{c:"\\3BF",f:"BI"},120645:{c:"\\3C0",f:"BI"},120646:{c:"\\3C1",f:"BI"},120647:{c:"\\3C2",f:"BI"},120648:{c:"\\3C3",f:"BI"},120649:{c:"\\3C4",f:"BI"},120650:{c:"\\3C5",f:"BI"},120651:{c:"\\3C6",f:"BI"},120652:{c:"\\3C7",f:"BI"},120653:{c:"\\3C8",f:"BI"},120654:{c:"\\3C9",f:"BI"},120655:{c:"\\2202",f:"B"},120656:{c:"\\3F5",f:"BI"},120657:{c:"\\3D1",f:"BI"},120658:{c:"\\E009",f:"A"},120659:{c:"\\3D5",f:"BI"},120660:{c:"\\3F1",f:"BI"},120661:{c:"\\3D6",f:"BI"},120662:{c:"A",f:"SSB"},120663:{c:"B",f:"SSB"},120664:{c:"\\393",f:"SSB"},120665:{c:"\\394",f:"SSB"},120666:{c:"E",f:"SSB"},120667:{c:"Z",f:"SSB"},120668:{c:"H",f:"SSB"},120669:{c:"\\398",f:"SSB"},120670:{c:"I",f:"SSB"},120671:{c:"K",f:"SSB"},120672:{c:"\\39B",f:"SSB"},120673:{c:"M",f:"SSB"},120674:{c:"N",f:"SSB"},120675:{c:"\\39E",f:"SSB"},120676:{c:"O",f:"SSB"},120677:{c:"\\3A0",f:"SSB"},120678:{c:"P",f:"SSB"},120680:{c:"\\3A3",f:"SSB"},120681:{c:"T",f:"SSB"},120682:{c:"\\3A5",f:"SSB"},120683:{c:"\\3A6",f:"SSB"},120684:{c:"X",f:"SSB"},120685:{c:"\\3A8",f:"SSB"},120686:{c:"\\3A9",f:"SSB"},120782:{c:"0",f:"B"},120783:{c:"1",f:"B"},120784:{c:"2",f:"B"},120785:{c:"3",f:"B"},120786:{c:"4",f:"B"},120787:{c:"5",f:"B"},120788:{c:"6",f:"B"},120789:{c:"7",f:"B"},120790:{c:"8",f:"B"},120791:{c:"9",f:"B"},120802:{c:"0",f:"SS"},120803:{c:"1",f:"SS"},120804:{c:"2",f:"SS"},120805:{c:"3",f:"SS"},120806:{c:"4",f:"SS"},120807:{c:"5",f:"SS"},120808:{c:"6",f:"SS"},120809:{c:"7",f:"SS"},120810:{c:"8",f:"SS"},120811:{c:"9",f:"SS"},120812:{c:"0",f:"SSB"},120813:{c:"1",f:"SSB"},120814:{c:"2",f:"SSB"},120815:{c:"3",f:"SSB"},120816:{c:"4",f:"SSB"},120817:{c:"5",f:"SSB"},120818:{c:"6",f:"SSB"},120819:{c:"7",f:"SSB"},120820:{c:"8",f:"SSB"},120821:{c:"9",f:"SSB"},120822:{c:"0",f:"T"},120823:{c:"1",f:"T"},120824:{c:"2",f:"T"},120825:{c:"3",f:"T"},120826:{c:"4",f:"T"},120827:{c:"5",f:"T"},120828:{c:"6",f:"T"},120829:{c:"7",f:"T"},120830:{c:"8",f:"T"},120831:{c:"9",f:"T"}})},7517:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.sansSerifBoldItalic=void 0;var n=r(8042),o=r(4886);e.sansSerifBoldItalic=(0,n.AddCSS)(o.sansSerifBoldItalic,{305:{f:"SSB"},567:{f:"SSB"}})},4182:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.sansSerifBold=void 0;var n=r(8042),o=r(4471);e.sansSerifBold=(0,n.AddCSS)(o.sansSerifBold,{8213:{c:"\\2014"},8215:{c:"_"},8260:{c:"/"},8710:{c:"\\394"}})},2679:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.sansSerifItalic=void 0;var n=r(8042),o=r(5181);e.sansSerifItalic=(0,n.AddCSS)(o.sansSerifItalic,{913:{c:"A"},914:{c:"B"},917:{c:"E"},918:{c:"Z"},919:{c:"H"},921:{c:"I"},922:{c:"K"},924:{c:"M"},925:{c:"N"},927:{c:"O"},929:{c:"P"},932:{c:"T"},935:{c:"X"},8213:{c:"\\2014"},8215:{c:"_"},8260:{c:"/"},8710:{c:"\\394"}})},5469:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.sansSerif=void 0;var n=r(8042),o=r(3526);e.sansSerif=(0,n.AddCSS)(o.sansSerif,{913:{c:"A"},914:{c:"B"},917:{c:"E"},918:{c:"Z"},919:{c:"H"},921:{c:"I"},922:{c:"K"},924:{c:"M"},925:{c:"N"},927:{c:"O"},929:{c:"P"},932:{c:"T"},935:{c:"X"},8213:{c:"\\2014"},8215:{c:"_"},8260:{c:"/"},8710:{c:"\\394"}})},7563:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.scriptBold=void 0;var n=r(5649);Object.defineProperty(e,"scriptBold",{enumerable:!0,get:function(){return n.scriptBold}})},9409:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.script=void 0;var n=r(7153);Object.defineProperty(e,"script",{enumerable:!0,get:function(){return n.script}})},775:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.smallop=void 0;var n=r(8042),o=r(5745);e.smallop=(0,n.AddCSS)(o.smallop,{8260:{c:"/"},9001:{c:"\\27E8"},9002:{c:"\\27E9"},10072:{c:"\\2223"},10764:{c:"\\222C\\222C"},12296:{c:"\\27E8"},12297:{c:"\\27E9"}})},9551:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.texCalligraphicBold=void 0;var n=r(8042),o=r(1411);e.texCalligraphicBold=(0,n.AddCSS)(o.texCalligraphicBold,{305:{f:"B"},567:{f:"B"}})},7907:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.texCalligraphic=void 0;var n=r(6384);Object.defineProperty(e,"texCalligraphic",{enumerable:!0,get:function(){return n.texCalligraphic}})},9659:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.texMathit=void 0;var n=r(6041);Object.defineProperty(e,"texMathit",{enumerable:!0,get:function(){return n.texMathit}})},98:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.texOldstyleBold=void 0;var n=r(8199);Object.defineProperty(e,"texOldstyleBold",{enumerable:!0,get:function(){return n.texOldstyleBold}})},6275:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.texOldstyle=void 0;var n=r(9848);Object.defineProperty(e,"texOldstyle",{enumerable:!0,get:function(){return n.texOldstyle}})},6530:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.texSize3=void 0;var n=r(8042),o=r(7906);e.texSize3=(0,n.AddCSS)(o.texSize3,{8260:{c:"/"},9001:{c:"\\27E8"},9002:{c:"\\27E9"},12296:{c:"\\27E8"},12297:{c:"\\27E9"}})},4409:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.texSize4=void 0;var n=r(8042),o=r(2644);e.texSize4=(0,n.AddCSS)(o.texSize4,{8260:{c:"/"},9001:{c:"\\27E8"},9002:{c:"\\27E9"},12296:{c:"\\27E8"},12297:{c:"\\27E9"},57685:{c:"\\E153\\E152"},57686:{c:"\\E151\\E150"}})},5292:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.texVariant=void 0;var n=r(8042),o=r(4926);e.texVariant=(0,n.AddCSS)(o.texVariant,{1008:{c:"\\E009"},8463:{f:""},8740:{c:"\\E006"},8742:{c:"\\E007"},8808:{c:"\\E00C"},8809:{c:"\\E00D"},8816:{c:"\\E011"},8817:{c:"\\E00E"},8840:{c:"\\E016"},8841:{c:"\\E018"},8842:{c:"\\E01A"},8843:{c:"\\E01B"},10887:{c:"\\E010"},10888:{c:"\\E00F"},10955:{c:"\\E017"},10956:{c:"\\E019"}})},5884:function(t,e,r){var n=this&&this.__assign||function(){return n=Object.assign||function(t){for(var e,r=1,n=arguments.length;r0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},i=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.FontData=e.NOSTRETCH=e.H=e.V=void 0;var a=r(7233);e.V=1,e.H=2,e.NOSTRETCH={dir:0};var l=function(){function t(t){var e,r,l,c;void 0===t&&(t=null),this.variant={},this.delimiters={},this.cssFontMap={},this.remapChars={},this.skewIcFactor=.75;var u=this.constructor;this.options=(0,a.userOptions)((0,a.defaultOptions)({},u.OPTIONS),t),this.params=n({},u.defaultParams),this.sizeVariants=i([],o(u.defaultSizeVariants),!1),this.stretchVariants=i([],o(u.defaultStretchVariants),!1),this.cssFontMap=n({},u.defaultCssFonts);try{for(var p=s(Object.keys(this.cssFontMap)),h=p.next();!h.done;h=p.next()){var f=h.value;"unknown"===this.cssFontMap[f][0]&&(this.cssFontMap[f][0]=this.options.unknownFamily)}}catch(t){e={error:t}}finally{try{h&&!h.done&&(r=p.return)&&r.call(p)}finally{if(e)throw e.error}}this.cssFamilyPrefix=u.defaultCssFamilyPrefix,this.createVariants(u.defaultVariants),this.defineDelimiters(u.defaultDelimiters);try{for(var d=s(Object.keys(u.defaultChars)),m=d.next();!m.done;m=d.next()){var y=m.value;this.defineChars(y,u.defaultChars[y])}}catch(t){l={error:t}}finally{try{m&&!m.done&&(c=d.return)&&c.call(d)}finally{if(l)throw l.error}}this.defineRemap("accent",u.defaultAccentMap),this.defineRemap("mo",u.defaultMoMap),this.defineRemap("mn",u.defaultMnMap)}return t.charOptions=function(t,e){var r=t[e];return 3===r.length&&(r[3]={}),r[3]},Object.defineProperty(t.prototype,"styles",{get:function(){return this._styles},set:function(t){this._styles=t},enumerable:!1,configurable:!0}),t.prototype.createVariant=function(t,e,r){void 0===e&&(e=null),void 0===r&&(r=null);var n={linked:[],chars:e?Object.create(this.variant[e].chars):{}};r&&this.variant[r]&&(Object.assign(n.chars,this.variant[r].chars),this.variant[r].linked.push(n.chars),n.chars=Object.create(n.chars)),this.remapSmpChars(n.chars,t),this.variant[t]=n},t.prototype.remapSmpChars=function(t,e){var r,n,i,a,l=this.constructor;if(l.VariantSmp[e]){var c=l.SmpRemap,u=[null,null,l.SmpRemapGreekU,l.SmpRemapGreekL];try{for(var p=s(l.SmpRanges),h=p.next();!h.done;h=p.next()){var f=o(h.value,3),d=f[0],m=f[1],y=f[2],g=l.VariantSmp[e][d];if(g){for(var b=m;b<=y;b++)if(930!==b){var v=g+b-m;t[b]=this.smpChar(c[v]||v)}if(u[d])try{for(var _=(i=void 0,s(Object.keys(u[d]).map((function(t){return parseInt(t)})))),S=_.next();!S.done;S=_.next()){t[b=S.value]=this.smpChar(g+u[d][b])}}catch(t){i={error:t}}finally{try{S&&!S.done&&(a=_.return)&&a.call(_)}finally{if(i)throw i.error}}}}}catch(t){r={error:t}}finally{try{h&&!h.done&&(n=p.return)&&n.call(p)}finally{if(r)throw r.error}}}"bold"===e&&(t[988]=this.smpChar(120778),t[989]=this.smpChar(120779))},t.prototype.smpChar=function(t){return[,,,{smp:t}]},t.prototype.createVariants=function(t){var e,r;try{for(var n=s(t),o=n.next();!o.done;o=n.next()){var i=o.value;this.createVariant(i[0],i[1],i[2])}}catch(t){e={error:t}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(e)throw e.error}}},t.prototype.defineChars=function(t,e){var r,n,o=this.variant[t];Object.assign(o.chars,e);try{for(var i=s(o.linked),a=i.next();!a.done;a=i.next()){var l=a.value;Object.assign(l,e)}}catch(t){r={error:t}}finally{try{a&&!a.done&&(n=i.return)&&n.call(i)}finally{if(r)throw r.error}}},t.prototype.defineDelimiters=function(t){Object.assign(this.delimiters,t)},t.prototype.defineRemap=function(t,e){this.remapChars.hasOwnProperty(t)||(this.remapChars[t]={}),Object.assign(this.remapChars[t],e)},t.prototype.getDelimiter=function(t){return this.delimiters[t]},t.prototype.getSizeVariant=function(t,e){return this.delimiters[t].variants&&(e=this.delimiters[t].variants[e]),this.sizeVariants[e]},t.prototype.getStretchVariant=function(t,e){return this.stretchVariants[this.delimiters[t].stretchv?this.delimiters[t].stretchv[e]:0]},t.prototype.getChar=function(t,e){return this.variant[t].chars[e]},t.prototype.getVariant=function(t){return this.variant[t]},t.prototype.getCssFont=function(t){return this.cssFontMap[t]||["serif",!1,!1]},t.prototype.getFamily=function(t){return this.cssFamilyPrefix?this.cssFamilyPrefix+", "+t:t},t.prototype.getRemappedChar=function(t,e){return(this.remapChars[t]||{})[e]},t.OPTIONS={unknownFamily:"serif"},t.JAX="common",t.NAME="",t.defaultVariants=[["normal"],["bold","normal"],["italic","normal"],["bold-italic","italic","bold"],["double-struck","bold"],["fraktur","normal"],["bold-fraktur","bold","fraktur"],["script","italic"],["bold-script","bold-italic","script"],["sans-serif","normal"],["bold-sans-serif","bold","sans-serif"],["sans-serif-italic","italic","sans-serif"],["sans-serif-bold-italic","bold-italic","bold-sans-serif"],["monospace","normal"]],t.defaultCssFonts={normal:["unknown",!1,!1],bold:["unknown",!1,!0],italic:["unknown",!0,!1],"bold-italic":["unknown",!0,!0],"double-struck":["unknown",!1,!0],fraktur:["unknown",!1,!1],"bold-fraktur":["unknown",!1,!0],script:["cursive",!1,!1],"bold-script":["cursive",!1,!0],"sans-serif":["sans-serif",!1,!1],"bold-sans-serif":["sans-serif",!1,!0],"sans-serif-italic":["sans-serif",!0,!1],"sans-serif-bold-italic":["sans-serif",!0,!0],monospace:["monospace",!1,!1]},t.defaultCssFamilyPrefix="",t.VariantSmp={bold:[119808,119834,120488,120514,120782],italic:[119860,119886,120546,120572],"bold-italic":[119912,119938,120604,120630],script:[119964,119990],"bold-script":[120016,120042],fraktur:[120068,120094],"double-struck":[120120,120146,,,120792],"bold-fraktur":[120172,120198],"sans-serif":[120224,120250,,,120802],"bold-sans-serif":[120276,120302,120662,120688,120812],"sans-serif-italic":[120328,120354],"sans-serif-bold-italic":[120380,120406,120720,120746],monospace:[120432,120458,,,120822]},t.SmpRanges=[[0,65,90],[1,97,122],[2,913,937],[3,945,969],[4,48,57]],t.SmpRemap={119893:8462,119965:8492,119968:8496,119969:8497,119971:8459,119972:8464,119975:8466,119976:8499,119981:8475,119994:8495,119996:8458,120004:8500,120070:8493,120075:8460,120076:8465,120085:8476,120093:8488,120122:8450,120127:8461,120133:8469,120135:8473,120136:8474,120137:8477,120145:8484},t.SmpRemapGreekU={8711:25,1012:17},t.SmpRemapGreekL={977:27,981:29,982:31,1008:28,1009:30,1013:26,8706:25},t.defaultAccentMap={768:"\u02cb",769:"\u02ca",770:"\u02c6",771:"\u02dc",772:"\u02c9",774:"\u02d8",775:"\u02d9",776:"\xa8",778:"\u02da",780:"\u02c7",8594:"\u20d7",8242:"'",8243:"''",8244:"'''",8245:"`",8246:"``",8247:"```",8279:"''''",8400:"\u21bc",8401:"\u21c0",8406:"\u2190",8417:"\u2194",8432:"*",8411:"...",8412:"....",8428:"\u21c1",8429:"\u21bd",8430:"\u2190",8431:"\u2192"},t.defaultMoMap={45:"\u2212"},t.defaultMnMap={45:"\u2212"},t.defaultParams={x_height:.442,quad:1,num1:.676,num2:.394,num3:.444,denom1:.686,denom2:.345,sup1:.413,sup2:.363,sup3:.289,sub1:.15,sub2:.247,sup_drop:.386,sub_drop:.05,delim1:2.39,delim2:1,axis_height:.25,rule_thickness:.06,big_op_spacing1:.111,big_op_spacing2:.167,big_op_spacing3:.2,big_op_spacing4:.6,big_op_spacing5:.1,surd_height:.075,scriptspace:.05,nulldelimiterspace:.12,delimiterfactor:901,delimitershortfall:.3,min_rule_thickness:1.25,separation_factor:1.75,extra_ic:.033},t.defaultDelimiters={},t.defaultChars={},t.defaultSizeVariants=[],t.defaultStretchVariants=[],t}();e.FontData=l},5552:function(t,e){var r=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s};Object.defineProperty(e,"__esModule",{value:!0}),e.CommonArrow=e.CommonDiagonalArrow=e.CommonDiagonalStrike=e.CommonBorder2=e.CommonBorder=e.arrowBBox=e.diagonalArrowDef=e.arrowDef=e.arrowBBoxW=e.arrowBBoxHD=e.arrowHead=e.fullBorder=e.fullPadding=e.fullBBox=e.sideNames=e.sideIndex=e.SOLID=e.PADDING=e.THICKNESS=e.ARROWY=e.ARROWDX=e.ARROWX=void 0,e.ARROWX=4,e.ARROWDX=1,e.ARROWY=2,e.THICKNESS=.067,e.PADDING=.2,e.SOLID=e.THICKNESS+"em solid",e.sideIndex={top:0,right:1,bottom:2,left:3},e.sideNames=Object.keys(e.sideIndex),e.fullBBox=function(t){return new Array(4).fill(t.thickness+t.padding)},e.fullPadding=function(t){return new Array(4).fill(t.padding)},e.fullBorder=function(t){return new Array(4).fill(t.thickness)};e.arrowHead=function(t){return Math.max(t.padding,t.thickness*(t.arrowhead.x+t.arrowhead.dx+1))};e.arrowBBoxHD=function(t,e){if(t.childNodes[0]){var r=t.childNodes[0].getBBox(),n=r.h,o=r.d;e[0]=e[2]=Math.max(0,t.thickness*t.arrowhead.y-(n+o)/2)}return e};e.arrowBBoxW=function(t,e){if(t.childNodes[0]){var r=t.childNodes[0].getBBox().w;e[1]=e[3]=Math.max(0,t.thickness*t.arrowhead.y-r/2)}return e},e.arrowDef={up:[-Math.PI/2,!1,!0,"verticalstrike"],down:[Math.PI/2,!1,!0,"verticakstrike"],right:[0,!1,!1,"horizontalstrike"],left:[Math.PI,!1,!1,"horizontalstrike"],updown:[Math.PI/2,!0,!0,"verticalstrike uparrow downarrow"],leftright:[0,!0,!1,"horizontalstrike leftarrow rightarrow"]},e.diagonalArrowDef={updiagonal:[-1,0,!1,"updiagonalstrike northeastarrow"],northeast:[-1,0,!1,"updiagonalstrike updiagonalarrow"],southeast:[1,0,!1,"downdiagonalstrike"],northwest:[1,Math.PI,!1,"downdiagonalstrike"],southwest:[-1,Math.PI,!1,"updiagonalstrike"],northeastsouthwest:[-1,0,!0,"updiagonalstrike northeastarrow updiagonalarrow southwestarrow"],northwestsoutheast:[1,0,!0,"downdiagonalstrike northwestarrow southeastarrow"]},e.arrowBBox={up:function(t){return(0,e.arrowBBoxW)(t,[(0,e.arrowHead)(t),0,t.padding,0])},down:function(t){return(0,e.arrowBBoxW)(t,[t.padding,0,(0,e.arrowHead)(t),0])},right:function(t){return(0,e.arrowBBoxHD)(t,[0,(0,e.arrowHead)(t),0,t.padding])},left:function(t){return(0,e.arrowBBoxHD)(t,[0,t.padding,0,(0,e.arrowHead)(t)])},updown:function(t){return(0,e.arrowBBoxW)(t,[(0,e.arrowHead)(t),0,(0,e.arrowHead)(t),0])},leftright:function(t){return(0,e.arrowBBoxHD)(t,[0,(0,e.arrowHead)(t),0,(0,e.arrowHead)(t)])}};e.CommonBorder=function(t){return function(r){var n=e.sideIndex[r];return[r,{renderer:t,bbox:function(t){var e=[0,0,0,0];return e[n]=t.thickness+t.padding,e},border:function(t){var e=[0,0,0,0];return e[n]=t.thickness,e}}]}};e.CommonBorder2=function(t){return function(r,n,o){var i=e.sideIndex[n],s=e.sideIndex[o];return[r,{renderer:t,bbox:function(t){var e=t.thickness+t.padding,r=[0,0,0,0];return r[i]=r[s]=e,r},border:function(t){var e=[0,0,0,0];return e[i]=e[s]=t.thickness,e},remove:n+" "+o}]}};e.CommonDiagonalStrike=function(t){return function(r){var n="mjx-"+r.charAt(0)+"strike";return[r+"diagonalstrike",{renderer:t(n),bbox:e.fullBBox}]}};e.CommonDiagonalArrow=function(t){return function(n){var o=r(e.diagonalArrowDef[n],4),i=o[0],s=o[1],a=o[2];return[n+"arrow",{renderer:function(e,n){var o=r(e.arrowAW(),2),l=o[0],c=o[1],u=e.arrow(c,i*(l-s),a);t(e,u)},bbox:function(t){var e=t.arrowData(),n=e.a,o=e.x,i=e.y,s=r([t.arrowhead.x,t.arrowhead.y,t.arrowhead.dx],3),a=s[0],l=s[1],c=s[2],u=r(t.getArgMod(a+c,l),2),p=u[0],h=u[1],f=i+(p>n?t.thickness*h*Math.sin(p-n):0),d=o+(p>Math.PI/2-n?t.thickness*h*Math.sin(p+n-Math.PI/2):0);return[f,d,f,d]},remove:o[3]}]}};e.CommonArrow=function(t){return function(n){var o=r(e.arrowDef[n],4),i=o[0],s=o[1],a=o[2],l=o[3];return[n+"arrow",{renderer:function(e,n){var o=e.getBBox(),l=o.w,c=o.h,u=o.d,p=r(a?[c+u,"X"]:[l,"Y"],2),h=p[0],f=p[1],d=e.getOffset(f),m=e.arrow(h,i,s,f,d);t(e,m)},bbox:e.arrowBBox[n],remove:l}]}}},3055:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__assign||function(){return i=Object.assign||function(t){for(var e,r=1,n=arguments.length;r0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},a=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.CommonOutputJax=void 0;var l=r(2975),c=r(4474),u=r(7233),p=r(6010),h=r(8054),f=r(4139),d=function(t){function e(e,r,n){void 0===e&&(e=null),void 0===r&&(r=null),void 0===n&&(n=null);var o=this,i=s((0,u.separateOptions)(e,n.OPTIONS),2),a=i[0],l=i[1];return(o=t.call(this,a)||this).factory=o.options.wrapperFactory||new r,o.factory.jax=o,o.cssStyles=o.options.cssStyles||new f.CssStyles,o.font=o.options.font||new n(l),o.unknownCache=new Map,o}return o(e,t),e.prototype.typeset=function(t,e){this.setDocument(e);var r=this.createNode();return this.toDOM(t,r,e),r},e.prototype.createNode=function(){var t=this.constructor.NAME;return this.html("mjx-container",{class:"MathJax",jax:t})},e.prototype.setScale=function(t){var e=this.math.metrics.scale*this.options.scale;1!==e&&this.adaptor.setStyle(t,"fontSize",(0,p.percent)(e))},e.prototype.toDOM=function(t,e,r){void 0===r&&(r=null),this.setDocument(r),this.math=t,this.pxPerEm=t.metrics.ex/this.font.params.x_height,t.root.setTeXclass(null),this.setScale(e),this.nodeMap=new Map,this.container=e,this.processMath(t.root,e),this.nodeMap=null,this.executeFilters(this.postFilters,t,r,e)},e.prototype.getBBox=function(t,e){this.setDocument(e),this.math=t,t.root.setTeXclass(null),this.nodeMap=new Map;var r=this.factory.wrap(t.root).getOuterBBox();return this.nodeMap=null,r},e.prototype.getMetrics=function(t){var e,r;this.setDocument(t);var n=this.adaptor,o=this.getMetricMaps(t);try{for(var i=a(t.math),s=i.next();!s.done;s=i.next()){var l=s.value,u=n.parent(l.start.node);if(l.state()=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},c=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},u=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o600?"bold":"normal"),n.family?r=this.explicitVariant(n.family,n.weight,n.style):(this.node.getProperty("variantForm")&&(r="-tex-variant"),r=(e.BOLDVARIANTS[n.weight]||{})[r]||r,r=(e.ITALICVARIANTS[n.style]||{})[r]||r)}this.variant=r}},e.prototype.explicitVariant=function(t,e,r){var n=this.styles;return n||(n=this.styles=new m.Styles),n.set("fontFamily",t),e&&n.set("fontWeight",e),r&&n.set("fontStyle",r),"-explicitFont"},e.prototype.getScale=function(){var t=1,e=this.parent,r=e?e.bbox.scale:1,n=this.node.attributes,o=Math.min(n.get("scriptlevel"),2),i=n.get("fontsize"),s=this.node.isToken||this.node.isKind("mstyle")?n.get("mathsize"):n.getInherited("mathsize");if(0!==o){t=Math.pow(n.get("scriptsizemultiplier"),o);var a=this.length2em(n.get("scriptminsize"),.8,1);t0;this.bbox.L=n.isSet("lspace")?Math.max(0,this.length2em(n.get("lspace"))):v(o,t.lspace),this.bbox.R=n.isSet("rspace")?Math.max(0,this.length2em(n.get("rspace"))):v(o,t.rspace);var i=r.childIndex(e);if(0!==i){var s=r.childNodes[i-1];if(s.isEmbellished){var a=this.jax.nodeMap.get(s).getBBox();a.R&&(this.bbox.L=Math.max(0,this.bbox.L-a.R))}}}},e.prototype.getTeXSpacing=function(t,e){if(!e){var r=this.node.texSpacing();r&&(this.bbox.L=this.length2em(r))}if(t||e){var n=this.node.coreMO().attributes;n.isSet("lspace")&&(this.bbox.L=Math.max(0,this.length2em(n.get("lspace")))),n.isSet("rspace")&&(this.bbox.R=Math.max(0,this.length2em(n.get("rspace"))))}},e.prototype.isTopEmbellished=function(){return this.node.isEmbellished&&!(this.node.parent&&this.node.parent.isEmbellished)},e.prototype.core=function(){return this.jax.nodeMap.get(this.node.core())},e.prototype.coreMO=function(){return this.jax.nodeMap.get(this.node.coreMO())},e.prototype.getText=function(){var t,e,r="";if(this.node.isToken)try{for(var n=l(this.node.childNodes),o=n.next();!o.done;o=n.next()){var i=o.value;i instanceof h.TextNode&&(r+=i.getText())}}catch(e){t={error:e}}finally{try{o&&!o.done&&(e=n.return)&&e.call(n)}finally{if(t)throw t.error}}return r},e.prototype.canStretch=function(t){if(this.stretch=g.NOSTRETCH,this.node.isEmbellished){var e=this.core();e&&e.node!==this.node&&e.canStretch(t)&&(this.stretch=e.stretch)}return 0!==this.stretch.dir},e.prototype.getAlignShift=function(){var t,e=(t=this.node.attributes).getList.apply(t,u([],c(h.indentAttributes),!1)),r=e.indentalign,n=e.indentshift,o=e.indentalignfirst,i=e.indentshiftfirst;return"indentalign"!==o&&(r=o),"auto"===r&&(r=this.jax.options.displayAlign),"indentshift"!==i&&(n=i),"auto"===n&&(n=this.jax.options.displayIndent,"right"!==r||n.match(/^\s*0[a-z]*\s*$/)||(n=("-"+n.trim()).replace(/^--/,""))),[r,this.length2em(n,this.metrics.containerWidth)]},e.prototype.getAlignX=function(t,e,r){return"right"===r?t-(e.w+e.R)*e.rscale:"left"===r?e.L*e.rscale:(t-e.w*e.rscale)/2},e.prototype.getAlignY=function(t,e,r,n,o){return"top"===o?t-r:"bottom"===o?n-e:"center"===o?(t-r-(e-n))/2:0},e.prototype.getWrapWidth=function(t){return this.childNodes[t].getBBox().w},e.prototype.getChildAlign=function(t){return"left"},e.prototype.percent=function(t){return d.percent(t)},e.prototype.em=function(t){return d.em(t)},e.prototype.px=function(t,e){return void 0===e&&(e=-d.BIGDIMEN),d.px(t,e,this.metrics.em)},e.prototype.length2em=function(t,e,r){return void 0===e&&(e=1),void 0===r&&(r=null),null===r&&(r=this.bbox.scale),d.length2em(t,e,r,this.jax.pxPerEm)},e.prototype.unicodeChars=function(t,e){void 0===e&&(e=this.variant);var r=(0,f.unicodeChars)(t),n=this.font.getVariant(e);if(n&&n.chars){var o=n.chars;r=r.map((function(t){return((o[t]||[])[3]||{}).smp||t}))}return r},e.prototype.remapChars=function(t){return t},e.prototype.mmlText=function(t){return this.node.factory.create("text").setText(t)},e.prototype.mmlNode=function(t,e,r){return void 0===e&&(e={}),void 0===r&&(r=[]),this.node.factory.create(t,e,r)},e.prototype.createMo=function(t){var e=this.node.factory,r=e.create("text").setText(t),n=e.create("mo",{stretchy:!0},[r]);n.inheritAttributesFrom(this.node);var o=this.wrap(n);return o.parent=this,o},e.prototype.getVariantChar=function(t,e){var r=this.font.getChar(t,e)||[0,0,0,{unknown:!0}];return 3===r.length&&(r[3]={}),r},e.kind="unknown",e.styles={},e.removeStyles=["fontSize","fontFamily","fontWeight","fontStyle","fontVariant","font"],e.skipAttributes={fontfamily:!0,fontsize:!0,fontweight:!0,fontstyle:!0,color:!0,background:!0,class:!0,href:!0,style:!0,xmlns:!0},e.BOLDVARIANTS={bold:{normal:"bold",italic:"bold-italic",fraktur:"bold-fraktur",script:"bold-script","sans-serif":"bold-sans-serif","sans-serif-italic":"sans-serif-bold-italic"},normal:{bold:"normal","bold-italic":"italic","bold-fraktur":"fraktur","bold-script":"script","bold-sans-serif":"sans-serif","sans-serif-bold-italic":"sans-serif-italic"}},e.ITALICVARIANTS={italic:{normal:"italic",bold:"bold-italic","sans-serif":"sans-serif-italic","bold-sans-serif":"sans-serif-bold-italic"},normal:{italic:"normal","bold-italic":"bold","sans-serif-italic":"sans-serif","sans-serif-bold-italic":"bold-sans-serif"}},e}(p.AbstractWrapper);e.CommonWrapper=_},4420:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.CommonWrapperFactory=void 0;var i=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.jax=null,e}return o(e,t),Object.defineProperty(e.prototype,"Wrappers",{get:function(){return this.node},enumerable:!1,configurable:!0}),e.defaultNodes={},e}(r(3811).AbstractWrapperFactory);e.CommonWrapperFactory=i},9800:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.CommonTeXAtomMixin=void 0;var i=r(9007);e.CommonTeXAtomMixin=function(t){return function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.computeBBox=function(e,r){if(void 0===r&&(r=!1),t.prototype.computeBBox.call(this,e,r),this.childNodes[0]&&this.childNodes[0].bbox.ic&&(e.ic=this.childNodes[0].bbox.ic),this.node.texClass===i.TEXCLASS.VCENTER){var n=e.h,o=(n+e.d)/2+this.font.params.axis_height-n;e.h+=o,e.d-=o}},e}(t)}},1160:function(t,e){var r,n=this&&this.__extends||(r=function(t,e){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},r(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}r(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)}),o=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},i=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s};Object.defineProperty(e,"__esModule",{value:!0}),e.CommonTextNodeMixin=void 0,e.CommonTextNodeMixin=function(t){return function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.computeBBox=function(t,e){var r,n;void 0===e&&(e=!1);var s=this.parent.variant,a=this.node.getText();if("-explicitFont"===s){var l=this.jax.getFontData(this.parent.styles),c=this.jax.measureText(a,s,l),u=c.w,p=c.h,h=c.d;t.h=p,t.d=h,t.w=u}else{var f=this.remappedText(a,s);t.empty();try{for(var d=o(f),m=d.next();!m.done;m=d.next()){var y=m.value,g=i(this.getVariantChar(s,y),4),b=(p=g[0],h=g[1],u=g[2],g[3]);if(b.unknown){var v=this.jax.measureText(String.fromCodePoint(y),s);u=v.w,p=v.h,h=v.d}t.w+=u,p>t.h&&(t.h=p),h>t.d&&(t.d=h),t.ic=b.ic||0,t.sk=b.sk||0,t.dx=b.dx||0}}catch(t){r={error:t}}finally{try{m&&!m.done&&(n=d.return)&&n.call(d)}finally{if(r)throw r.error}}f.length>1&&(t.sk=0),t.clean()}},e.prototype.remappedText=function(t,e){var r=this.parent.stretch.c;return r?[r]:this.parent.remapChars(this.unicodeChars(t,e))},e.prototype.getStyles=function(){},e.prototype.getVariant=function(){},e.prototype.getScale=function(){},e.prototype.getSpace=function(){},e}(t)}},1956:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},s=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},c=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.CommonMencloseMixin=void 0;var p=a(r(5552)),h=r(505);e.CommonMencloseMixin=function(t){return function(t){function e(){for(var e=[],r=0;r.001?s:0},e.prototype.getArgMod=function(t,e){return[Math.atan2(e,t),Math.sqrt(t*t+e*e)]},e.prototype.arrow=function(t,e,r,n,o){return void 0===n&&(n=""),void 0===o&&(o=0),null},e.prototype.arrowData=function(){var t=l([this.padding,this.thickness],2),e=t[0],r=t[1]*(this.arrowhead.x+Math.max(1,this.arrowhead.dx)),n=this.childNodes[0].getBBox(),o=n.h,i=n.d,s=n.w,a=o+i,c=Math.sqrt(a*a+s*s),u=Math.max(e,r*s/c),p=Math.max(e,r*a/c),h=l(this.getArgMod(s+2*u,a+2*p),2);return{a:h[0],W:h[1],x:u,y:p}},e.prototype.arrowAW=function(){var t=this.childNodes[0].getBBox(),e=t.h,r=t.d,n=t.w,o=l(this.TRBL,4),i=o[0],s=o[1],a=o[2],c=o[3];return this.getArgMod(c+n+s,i+e+r+a)},e.prototype.createMsqrt=function(t){var e=this.node.factory.create("msqrt");e.inheritAttributesFrom(this.node),e.childNodes[0]=t.node;var r=this.wrap(e);return r.parent=this,r},e.prototype.sqrtTRBL=function(){var t=this.msqrt.getBBox(),e=this.msqrt.childNodes[0].getBBox();return[t.h-e.h,0,t.d-e.d,t.w-e.w]},e}(t)}},7555:function(t,e){var r,n=this&&this.__extends||(r=function(t,e){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},r(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}r(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)}),o=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},i=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.CommonMfencedMixin=void 0,e.CommonMfencedMixin=function(t){return function(t){function e(){for(var e=[],r=0;r0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},i=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},i=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},s=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.CommonMmultiscriptsMixin=e.ScriptNames=e.NextScript=void 0;var l=r(6469);e.NextScript={base:"subList",subList:"supList",supList:"subList",psubList:"psupList",psupList:"psubList"},e.ScriptNames=["sup","sup","psup","psub"],e.CommonMmultiscriptsMixin=function(t){return function(t){function r(){for(var e=[],r=0;re.length&&e.push(l.BBox.empty())},r.prototype.combineBBoxLists=function(t,e,r,n){for(var o=0;ot.h&&(t.h=l),c>t.d&&(t.d=c),h>e.h&&(e.h=h),f>e.d&&(e.d=f)}},r.prototype.getScaledWHD=function(t){var e=t.w,r=t.h,n=t.d,o=t.rscale;return[e*o,r*o,n*o]},r.prototype.getUVQ=function(e,r){var n;if(!this.UVQ){var o=i([0,0,0],3),s=o[0],a=o[1],l=o[2];0===e.h&&0===e.d?s=this.getU():0===r.h&&0===r.d?s=-this.getV():(s=(n=i(t.prototype.getUVQ.call(this,e,r),3))[0],a=n[1],l=n[2]),this.UVQ=[s,a,l]}return this.UVQ},r}(t)}},5023:function(t,e){var r,n=this&&this.__extends||(r=function(t,e){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},r(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}r(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0}),e.CommonMnMixin=void 0,e.CommonMnMixin=function(t){return function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.remapChars=function(t){if(t.length){var e=this.font.getRemappedChar("mn",t[0]);if(e){var r=this.unicodeChars(e,this.variant);1===r.length?t[0]=r[0]:t=r.concat(t.slice(1))}}return t},e}(t)}},7096:function(t,e,r){var n,o,i=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),s=this&&this.__assign||function(){return s=Object.assign||function(t){for(var e,r=1,n=arguments.length;r0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},l=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.CommonMoMixin=e.DirectionVH=void 0;var u=r(6469),p=r(505),h=r(5884);e.DirectionVH=((o={})[1]="v",o[2]="h",o),e.CommonMoMixin=function(t){return function(t){function e(){for(var e=[],r=0;r=0)&&(t.w=0)},e.prototype.protoBBox=function(e){var r=0!==this.stretch.dir;r&&null===this.size&&this.getStretchedVariant([0]),r&&this.size<0||(t.prototype.computeBBox.call(this,e),this.copySkewIC(e))},e.prototype.getAccentOffset=function(){var t=u.BBox.empty();return this.protoBBox(t),-t.w/2},e.prototype.getCenterOffset=function(e){return void 0===e&&(e=null),e||(e=u.BBox.empty(),t.prototype.computeBBox.call(this,e)),(e.h+e.d)/2+this.font.params.axis_height-e.h},e.prototype.getVariant=function(){this.node.attributes.get("largeop")?this.variant=this.node.attributes.get("displaystyle")?"-largeop":"-smallop":this.node.attributes.getExplicit("mathvariant")||!1!==this.node.getProperty("pseudoscript")?t.prototype.getVariant.call(this):this.variant="-tex-variant"},e.prototype.canStretch=function(t){if(0!==this.stretch.dir)return this.stretch.dir===t;if(!this.node.attributes.get("stretchy"))return!1;var e=this.getText();if(1!==Array.from(e).length)return!1;var r=this.font.getDelimiter(e.codePointAt(0));return this.stretch=r&&r.dir===t?r:h.NOSTRETCH,0!==this.stretch.dir},e.prototype.getStretchedVariant=function(t,e){var r,n;if(void 0===e&&(e=!1),0!==this.stretch.dir){var o=this.getWH(t),i=this.getSize("minsize",0),a=this.getSize("maxsize",1/0),l=this.node.getProperty("mathaccent");o=Math.max(i,Math.min(a,o));var u=this.font.params.delimiterfactor/1e3,p=this.font.params.delimitershortfall,h=i||e?o:l?Math.min(o/u,o+p):Math.max(o*u,o-p),f=this.stretch,d=f.c||this.getText().codePointAt(0),m=0;if(f.sizes)try{for(var y=c(f.sizes),g=y.next();!g.done;g=y.next()){if(g.value>=h)return l&&m&&m--,this.variant=this.font.getSizeVariant(d,m),this.size=m,void(f.schar&&f.schar[m]&&(this.stretch=s(s({},this.stretch),{c:f.schar[m]})));m++}}catch(t){r={error:t}}finally{try{g&&!g.done&&(n=y.return)&&n.call(y)}finally{if(r)throw r.error}}f.stretch?(this.size=-1,this.invalidateBBox(),this.getStretchBBox(t,this.checkExtendedHeight(o,f),f)):(this.variant=this.font.getSizeVariant(d,m-1),this.size=m-1)}},e.prototype.getSize=function(t,e){var r=this.node.attributes;return r.isSet(t)&&(e=this.length2em(r.get(t),1,1)),e},e.prototype.getWH=function(t){if(0===t.length)return 0;if(1===t.length)return t[0];var e=a(t,2),r=e[0],n=e[1],o=this.font.params.axis_height;return this.node.attributes.get("symmetric")?2*Math.max(r-o,n+o):r+n},e.prototype.getStretchBBox=function(t,e,r){var n;r.hasOwnProperty("min")&&r.min>e&&(e=r.min);var o=a(r.HDW,3),i=o[0],s=o[1],l=o[2];1===this.stretch.dir?(i=(n=a(this.getBaseline(t,e,r),2))[0],s=n[1]):l=e,this.bbox.h=i,this.bbox.d=s,this.bbox.w=l},e.prototype.getBaseline=function(t,e,r){var n=2===t.length&&t[0]+t[1]===e,o=this.node.attributes.get("symmetric"),i=a(n?t:[e,0],2),s=i[0],l=i[1],c=a([s+l,0],2),u=c[0],p=c[1];if(o){var h=this.font.params.axis_height;n&&(u=2*Math.max(s-h,l+h)),p=u/2-h}else if(n)p=l;else{var f=a(r.HDW||[.75,.25],2),d=f[0],m=f[1];p=m*(u/(d+m))}return[u-p,p]},e.prototype.checkExtendedHeight=function(t,e){if(e.fullExt){var r=a(e.fullExt,2),n=r[0],o=r[1];t=o+Math.ceil(Math.max(0,t-o)/n)*n}return t},e.prototype.remapChars=function(t){var e=this.node.getProperty("primes");if(e)return(0,p.unicodeChars)(e);if(1===t.length){var r=this.node.coreParent().parent,n=this.isAccent&&!r.isKind("mrow")?"accent":"mo",o=this.font.getRemappedChar(n,t[0]);o&&(t=this.unicodeChars(o,this.variant))}return t},e}(t)}},6898:function(t,e){var r,n=this&&this.__extends||(r=function(t,e){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},r(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}r(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)}),o=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s};Object.defineProperty(e,"__esModule",{value:!0}),e.CommonMpaddedMixin=void 0,e.CommonMpaddedMixin=function(t){return function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.getDimens=function(){var t=this.node.attributes.getList("width","height","depth","lspace","voffset"),e=this.childNodes[0].getBBox(),r=e.w,n=e.h,o=e.d,i=r,s=n,a=o,l=0,c=0,u=0;""!==t.width&&(r=this.dimen(t.width,e,"w",0)),""!==t.height&&(n=this.dimen(t.height,e,"h",0)),""!==t.depth&&(o=this.dimen(t.depth,e,"d",0)),""!==t.voffset&&(c=this.dimen(t.voffset,e)),""!==t.lspace&&(l=this.dimen(t.lspace,e));var p=this.node.attributes.get("data-align");return p&&(u=this.getAlignX(r,e,p)),[s,a,i,n-s,o-a,r-i,l,c,u]},e.prototype.dimen=function(t,e,r,n){void 0===r&&(r=""),void 0===n&&(n=null);var o=(t=String(t)).match(/width|height|depth/),i=o?e[o[0].charAt(0)]:r?e[r]:0,s=this.length2em(t,i)||0;return t.match(/^[-+]/)&&r&&(s+=i),null!=n&&(s=Math.max(n,s)),s},e.prototype.computeBBox=function(t,e){void 0===e&&(e=!1);var r=o(this.getDimens(),6),n=r[0],i=r[1],s=r[2],a=r[3],l=r[4],c=r[5];t.w=s+c,t.h=n+a,t.d=i+l,this.setChildPWidths(e,t.w)},e.prototype.getWrapWidth=function(t){return this.getBBox().w},e.prototype.getChildAlign=function(t){return this.node.attributes.get("data-align")||"left"},e}(t)}},6991:function(t,e){var r,n=this&&this.__extends||(r=function(t,e){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},r(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}r(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0}),e.CommonMrootMixin=void 0,e.CommonMrootMixin=function(t){return function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),Object.defineProperty(e.prototype,"surd",{get:function(){return 2},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"root",{get:function(){return 1},enumerable:!1,configurable:!0}),e.prototype.combineRootBBox=function(t,e,r){var n=this.childNodes[this.root].getOuterBBox(),o=this.getRootDimens(e,r)[1];t.combine(n,0,o)},e.prototype.getRootDimens=function(t,e){var r=this.childNodes[this.surd],n=this.childNodes[this.root].getOuterBBox(),o=(r.size<0?.5:.6)*t.w,i=n.w,s=n.rscale,a=Math.max(i,o/s),l=Math.max(0,a-i);return[a*s-o,this.rootHeight(n,t,r.size,e),l]},e.prototype.rootHeight=function(t,e,r,n){var o=e.h+e.d;return(r<0?1.9:.55*o)-(o-n)+Math.max(0,t.d*t.rscale)},e}(t)}},8411:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},s=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.CommonInferredMrowMixin=e.CommonMrowMixin=void 0;var l=r(6469);e.CommonMrowMixin=function(t){return function(t){function e(){for(var e,r,n=[],o=0;o1){var h=0,f=0,d=u>1&&u===p;try{for(var m=a(this.childNodes),y=m.next();!y.done;y=m.next()){var g=0===(x=y.value).stretch.dir;if(d||g){var b=x.getOuterBBox(g),v=b.h,_=b.d,S=b.rscale;(v*=S)>h&&(h=v),(_*=S)>f&&(f=_)}}}catch(t){r={error:t}}finally{try{y&&!y.done&&(n=m.return)&&n.call(m)}finally{if(r)throw r.error}}try{for(var M=a(s),O=M.next();!O.done;O=M.next()){var x;(x=O.value).coreMO().getStretchedVariant([h,f])}}catch(t){o={error:t}}finally{try{O&&!O.done&&(i=M.return)&&i.call(M)}finally{if(o)throw o.error}}}},e}(t)},e.CommonInferredMrowMixin=function(t){return function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.getScale=function(){this.bbox.scale=this.parent.bbox.scale,this.bbox.rscale=1},e}(t)}},4126:function(t,e){var r,n=this&&this.__extends||(r=function(t,e){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},r(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}r(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)}),o=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},i=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},s=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;othis.surdH?(t.h+t.d-(this.surdH-2*e-r/2))/2:e+r/4]},e.prototype.getRootDimens=function(t,e){return[0,0,0,0]},e}(t)}},905:function(t,e){var r,n=this&&this.__extends||(r=function(t,e){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},r(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}r(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)}),o=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s};Object.defineProperty(e,"__esModule",{value:!0}),e.CommonMsubsupMixin=e.CommonMsupMixin=e.CommonMsubMixin=void 0,e.CommonMsubMixin=function(t){var e;return e=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),Object.defineProperty(e.prototype,"scriptChild",{get:function(){return this.childNodes[this.node.sub]},enumerable:!1,configurable:!0}),e.prototype.getOffset=function(){return[0,-this.getV()]},e}(t),e.useIC=!1,e},e.CommonMsupMixin=function(t){return function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),Object.defineProperty(e.prototype,"scriptChild",{get:function(){return this.childNodes[this.node.sup]},enumerable:!1,configurable:!0}),e.prototype.getOffset=function(){return[this.getAdjustedIc()-(this.baseRemoveIc?0:this.baseIc),this.getU()]},e}(t)},e.CommonMsubsupMixin=function(t){var e;return e=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.UVQ=null,e}return n(e,t),Object.defineProperty(e.prototype,"subChild",{get:function(){return this.childNodes[this.node.sub]},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"supChild",{get:function(){return this.childNodes[this.node.sup]},enumerable:!1,configurable:!0}),e.prototype.computeBBox=function(t,e){void 0===e&&(e=!1);var r=this.baseChild.getOuterBBox(),n=o([this.subChild.getOuterBBox(),this.supChild.getOuterBBox()],2),i=n[0],s=n[1];t.empty(),t.append(r);var a=this.getBaseWidth(),l=this.getAdjustedIc(),c=o(this.getUVQ(),2),u=c[0],p=c[1];t.combine(i,a,p),t.combine(s,a+l,u),t.w+=this.font.params.scriptspace,t.clean(),this.setChildPWidths(e)},e.prototype.getUVQ=function(t,e){void 0===t&&(t=this.subChild.getOuterBBox()),void 0===e&&(e=this.supChild.getOuterBBox());var r=this.baseCore.getOuterBBox();if(this.UVQ)return this.UVQ;var n=this.font.params,i=3*n.rule_thickness,s=this.length2em(this.node.attributes.get("subscriptshift"),n.sub2),a=this.baseCharZero(r.d*this.baseScale+n.sub_drop*t.rscale),l=o([this.getU(),Math.max(a,s)],2),c=l[0],u=l[1],p=c-e.d*e.rscale-(t.h*t.rscale-u);if(p0&&(c+=h,u-=h)}return c=Math.max(this.length2em(this.node.attributes.get("superscriptshift"),c),c),u=Math.max(this.length2em(this.node.attributes.get("subscriptshift"),u),u),p=c-e.d*e.rscale-(t.h*t.rscale-u),this.UVQ=[c,-u,p],this.UVQ},e}(t),e.useIC=!1,e}},6237:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},s=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.CommonMtableMixin=void 0;var l=r(6469),c=r(505),u=r(7875);e.CommonMtableMixin=function(t){return function(t){function e(){for(var e=[],r=0;r1){if(null===e){e=0;var d=h>1&&h===f;try{for(var m=a(this.tableRows),y=m.next();!y.done;y=m.next()){var g;if(g=y.value.getChild(t)){var b=0===(M=g.childNodes[0]).stretch.dir;if(d||b){var v=M.getBBox(b).w;v>e&&(e=v)}}}}catch(t){o={error:t}}finally{try{y&&!y.done&&(i=m.return)&&i.call(m)}finally{if(o)throw o.error}}}try{for(var _=a(c),S=_.next();!S.done;S=_.next()){var M;(M=S.value).coreMO().getStretchedVariant([e])}}catch(t){s={error:t}}finally{try{S&&!S.done&&(l=_.return)&&l.call(_)}finally{if(s)throw s.error}}}},e.prototype.getTableData=function(){if(this.data)return this.data;for(var t=new Array(this.numRows).fill(0),e=new Array(this.numRows).fill(0),r=new Array(this.numCols).fill(0),n=new Array(this.numRows),o=new Array(this.numRows),i=[0],s=this.tableRows,a=0;ao[r]&&(o[r]=c),u>i[r]&&(i[r]=u),f>a&&(a=f),s&&p>s[e]&&(s[e]=p),a},e.prototype.extendHD=function(t,e,r,n){var o=(n-(e[t]+r[t]))/2;o<1e-5||(e[t]+=o,r[t]+=o)},e.prototype.recordPWidthCell=function(t,e){t.childNodes[0]&&t.childNodes[0].getBBox().pwidth&&this.pwidthCells.push([t,e])},e.prototype.computeBBox=function(t,e){void 0===e&&(e=!1);var r,n,o=this.getTableData(),s=o.H,a=o.D;if(this.node.attributes.get("equalrows")){var l=this.getEqualRowHeight();r=(0,u.sum)([].concat(this.rLines,this.rSpace))+l*this.numRows}else r=(0,u.sum)(s.concat(a,this.rLines,this.rSpace));r+=2*(this.fLine+this.fSpace[1]);var p=this.getComputedWidths();n=(0,u.sum)(p.concat(this.cLines,this.cSpace))+2*(this.fLine+this.fSpace[0]);var h=this.node.attributes.get("width");"auto"!==h&&(n=Math.max(this.length2em(h,0)+2*this.fLine,n));var f=i(this.getBBoxHD(r),2),d=f[0],m=f[1];t.h=d,t.d=m,t.w=n;var y=i(this.getBBoxLR(),2),g=y[0],b=y[1];t.L=g,t.R=b,(0,c.isPercent)(h)||this.setColumnPWidths()},e.prototype.setChildPWidths=function(t,e,r){var n=this.node.attributes.get("width");if(!(0,c.isPercent)(n))return!1;this.hasLabels||(this.bbox.pwidth="",this.container.bbox.pwidth="");var o=this.bbox,i=o.w,s=o.L,a=o.R,l=this.node.attributes.get("data-width-includes-label"),p=Math.max(i,this.length2em(n,Math.max(e,s+i+a)))-(l?s+a:0),h=this.node.attributes.get("equalcolumns")?Array(this.numCols).fill(this.percent(1/Math.max(1,this.numCols))):this.getColumnAttributes("columnwidth",0);this.cWidths=this.getColumnWidthsFixed(h,p);var f=this.getComputedWidths();return this.pWidth=(0,u.sum)(f.concat(this.cLines,this.cSpace))+2*(this.fLine+this.fSpace[0]),this.isTop&&(this.bbox.w=this.pWidth),this.setColumnPWidths(),this.pWidth!==i&&this.parent.invalidateBBox(),this.pWidth!==i},e.prototype.setColumnPWidths=function(){var t,e,r=this.cWidths;try{for(var n=a(this.pwidthCells),o=n.next();!o.done;o=n.next()){var s=i(o.value,2),l=s[0],c=s[1];l.setChildPWidths(!1,r[c])&&(l.invalidateBBox(),l.getBBox())}}catch(e){t={error:e}}finally{try{o&&!o.done&&(e=n.return)&&e.call(n)}finally{if(t)throw t.error}}},e.prototype.getBBoxHD=function(t){var e=i(this.getAlignmentRow(),2),r=e[0],n=e[1];if(null===n){var o=this.font.params.axis_height,s=t/2;return{top:[0,t],center:[s,s],bottom:[t,0],baseline:[s,s],axis:[s+o,s-o]}[r]||[s,s]}var a=this.getVerticalPosition(n,r);return[a,t-a]},e.prototype.getBBoxLR=function(){if(this.hasLabels){var t=this.node.attributes,e=t.get("side"),r=i(this.getPadAlignShift(e),2),n=r[0],o=r[1],s=this.hasLabels&&!!t.get("data-width-includes-label");return s&&this.frame&&this.fSpace[0]&&(n-=this.fSpace[0]),"center"!==o||s?"left"===e?[n,0]:[0,n]:[n,n]}return[0,0]},e.prototype.getPadAlignShift=function(t){var e=this.getTableData().L+this.length2em(this.node.attributes.get("minlabelspacing")),r=i(null==this.styles?["",""]:[this.styles.get("padding-left"),this.styles.get("padding-right")],2),n=r[0],o=r[1];(n||o)&&(e=Math.max(e,this.length2em(n||"0"),this.length2em(o||"0")));var s=i(this.getAlignShift(),2),a=s[0],l=s[1];return a===t&&(l="left"===t?Math.max(e,l)-e:Math.min(-e,l)+e),[e,a,l]},e.prototype.getAlignShift=function(){return this.isTop?t.prototype.getAlignShift.call(this):[this.container.getChildAlign(this.containerI),0]},e.prototype.getWidth=function(){return this.pWidth||this.getBBox().w},e.prototype.getEqualRowHeight=function(){var t=this.getTableData(),e=t.H,r=t.D,n=Array.from(e.keys()).map((function(t){return e[t]+r[t]}));return Math.max.apply(Math,n)},e.prototype.getComputedWidths=function(){var t=this,e=this.getTableData().W,r=Array.from(e.keys()).map((function(r){return"number"==typeof t.cWidths[r]?t.cWidths[r]:e[r]}));return this.node.attributes.get("equalcolumns")&&(r=Array(r.length).fill((0,u.max)(r))),r},e.prototype.getColumnWidths=function(){var t=this.node.attributes.get("width");if(this.node.attributes.get("equalcolumns"))return this.getEqualColumns(t);var e=this.getColumnAttributes("columnwidth",0);return"auto"===t?this.getColumnWidthsAuto(e):(0,c.isPercent)(t)?this.getColumnWidthsPercent(e):this.getColumnWidthsFixed(e,this.length2em(t))},e.prototype.getEqualColumns=function(t){var e,r=Math.max(1,this.numCols);if("auto"===t){var n=this.getTableData().W;e=(0,u.max)(n)}else if((0,c.isPercent)(t))e=this.percent(1/r);else{var o=(0,u.sum)([].concat(this.cLines,this.cSpace))+2*this.fSpace[0];e=Math.max(0,this.length2em(t)-o)/r}return Array(this.numCols).fill(e)},e.prototype.getColumnWidthsAuto=function(t){var e=this;return t.map((function(t){return"auto"===t||"fit"===t?null:(0,c.isPercent)(t)?t:e.length2em(t)}))},e.prototype.getColumnWidthsPercent=function(t){var e=this,r=t.indexOf("fit")>=0,n=(r?this.getTableData():{W:null}).W;return Array.from(t.keys()).map((function(o){var i=t[o];return"fit"===i?null:"auto"===i?r?n[o]:null:(0,c.isPercent)(i)?i:e.length2em(i)}))},e.prototype.getColumnWidthsFixed=function(t,e){var r=this,n=Array.from(t.keys()),o=n.filter((function(e){return"fit"===t[e]})),i=n.filter((function(e){return"auto"===t[e]})),s=o.length||i.length,a=(s?this.getTableData():{W:null}).W,l=e-(0,u.sum)([].concat(this.cLines,this.cSpace))-2*this.fSpace[0],c=l;n.forEach((function(e){var n=t[e];c-="fit"===n||"auto"===n?a[e]:r.length2em(n,l)}));var p=s&&c>0?c/s:0;return n.map((function(e){var n=t[e];return"fit"===n?a[e]+p:"auto"===n?a[e]+(0===o.length?p:0):r.length2em(n,l)}))},e.prototype.getVerticalPosition=function(t,e){for(var r=this.node.attributes.get("equalrows"),n=this.getTableData(),o=n.H,s=n.D,a=r?this.getEqualRowHeight():0,l=this.getRowHalfSpacing(),c=this.fLine,u=0;uthis.numRows?null:n-1]},e.prototype.getColumnAttributes=function(t,e){void 0===e&&(e=1);var r=this.numCols-e,n=this.getAttributeArray(t);if(0===n.length)return null;for(;n.lengthr&&n.splice(r),n},e.prototype.getRowAttributes=function(t,e){void 0===e&&(e=1);var r=this.numRows-e,n=this.getAttributeArray(t);if(0===n.length)return null;for(;n.lengthr&&n.splice(r),n},e.prototype.getAttributeArray=function(t){var e=this.node.attributes.get(t);return e?(0,c.split)(e):[this.node.attributes.getDefault(t)]},e.prototype.addEm=function(t,e){var r=this;return void 0===e&&(e=1),t?t.map((function(t){return r.em(t/e)})):null},e.prototype.convertLengths=function(t){var e=this;return t?t.map((function(t){return e.length2em(t)})):null},e}(t)}},5164:function(t,e){var r,n=this&&this.__extends||(r=function(t,e){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},r(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}r(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0}),e.CommonMtdMixin=void 0,e.CommonMtdMixin=function(t){return function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),Object.defineProperty(e.prototype,"fixesPWidth",{get:function(){return!1},enumerable:!1,configurable:!0}),e.prototype.invalidateBBox=function(){this.bboxComputed=!1},e.prototype.getWrapWidth=function(t){var e=this.parent.parent,r=this.parent,n=this.node.childPosition()-(r.labeled?1:0);return"number"==typeof e.cWidths[n]?e.cWidths[n]:e.getTableData().W[n]},e.prototype.getChildAlign=function(t){return this.node.attributes.get("columnalign")},e}(t)}},6319:function(t,e){var r,n=this&&this.__extends||(r=function(t,e){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},r(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}r(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0}),e.CommonMtextMixin=void 0,e.CommonMtextMixin=function(t){var e;return e=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.getVariant=function(){var e=this.jax.options,r=this.jax.math.outputData,n=(!!r.merrorFamily||!!e.merrorFont)&&this.node.Parent.isKind("merror");if(r.mtextFamily||e.mtextFont||n){var o=this.node.attributes.get("mathvariant"),i=this.constructor.INHERITFONTS[o]||this.jax.font.getCssFont(o),s=i[0]||(n?r.merrorFamily||e.merrorFont:r.mtextFamily||e.mtextFont);this.variant=this.explicitVariant(s,i[2]?"bold":"",i[1]?"italic":"")}else t.prototype.getVariant.call(this)},e}(t),e.INHERITFONTS={normal:["",!1,!1],bold:["",!1,!0],italic:["",!0,!1],"bold-italic":["",!0,!0]},e}},5766:function(t,e){var r,n=this&&this.__extends||(r=function(t,e){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},r(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}r(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)}),o=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.CommonMlabeledtrMixin=e.CommonMtrMixin=void 0,e.CommonMtrMixin=function(t){return function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),Object.defineProperty(e.prototype,"fixesPWidth",{get:function(){return!1},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"numCells",{get:function(){return this.childNodes.length},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"labeled",{get:function(){return!1},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"tableCells",{get:function(){return this.childNodes},enumerable:!1,configurable:!0}),e.prototype.getChild=function(t){return this.childNodes[t]},e.prototype.getChildBBoxes=function(){return this.childNodes.map((function(t){return t.getBBox()}))},e.prototype.stretchChildren=function(t){var e,r,n,i,s,a;void 0===t&&(t=null);var l=[],c=this.labeled?this.childNodes.slice(1):this.childNodes;try{for(var u=o(c),p=u.next();!p.done;p=u.next()){(E=p.value.childNodes[0]).canStretch(1)&&l.push(E)}}catch(t){e={error:t}}finally{try{p&&!p.done&&(r=u.return)&&r.call(u)}finally{if(e)throw e.error}}var h=l.length,f=this.childNodes.length;if(h&&f>1){if(null===t){var d=0,m=0,y=h>1&&h===f;try{for(var g=o(c),b=g.next();!b.done;b=g.next()){var v=0===(E=b.value.childNodes[0]).stretch.dir;if(y||v){var _=E.getBBox(v),S=_.h,M=_.d;S>d&&(d=S),M>m&&(m=M)}}}catch(t){n={error:t}}finally{try{b&&!b.done&&(i=g.return)&&i.call(g)}finally{if(n)throw n.error}}t=[d,m]}try{for(var O=o(l),x=O.next();!x.done;x=O.next()){var E;(E=x.value).coreMO().getStretchedVariant(t)}}catch(t){s={error:t}}finally{try{x&&!x.done&&(a=O.return)&&a.call(O)}finally{if(s)throw s.error}}}},e}(t)},e.CommonMlabeledtrMixin=function(t){return function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),Object.defineProperty(e.prototype,"numCells",{get:function(){return Math.max(0,this.childNodes.length-1)},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"labeled",{get:function(){return!0},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"tableCells",{get:function(){return this.childNodes.slice(1)},enumerable:!1,configurable:!0}),e.prototype.getChild=function(t){return this.childNodes[t+1]},e.prototype.getChildBBoxes=function(){return this.childNodes.slice(1).map((function(t){return t.getBBox()}))},e}(t)}},1971:function(t,e){var r,n=this&&this.__extends||(r=function(t,e){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},r(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}r(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)}),o=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},i=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},s=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.CommonScriptbaseMixin=void 0;var l=r(9007);e.CommonScriptbaseMixin=function(t){var e;return e=function(t){function e(){for(var e=[],r=0;r1){var h=0,f=u>1&&u===p;try{for(var d=a(this.childNodes),m=d.next();!m.done;m=d.next()){var y=0===(M=m.value).stretch.dir;if(f||y){var g=M.getOuterBBox(y),b=g.w,v=g.rscale;b*v>h&&(h=b*v)}}}catch(t){r={error:t}}finally{try{m&&!m.done&&(n=d.return)&&n.call(d)}finally{if(r)throw r.error}}try{for(var _=a(s),S=_.next();!S.done;S=_.next()){var M;(M=S.value).coreMO().getStretchedVariant([h/M.bbox.rscale])}}catch(t){o={error:t}}finally{try{S&&!S.done&&(i=_.return)&&i.call(_)}finally{if(o)throw o.error}}}},e}(t),e.useIC=!0,e}},5806:function(t,e){var r,n=this&&this.__extends||(r=function(t,e){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},r(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}r(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0}),e.CommonSemanticsMixin=void 0,e.CommonSemanticsMixin=function(t){return function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.computeBBox=function(t,e){if(void 0===e&&(e=!1),this.childNodes.length){var r=this.childNodes[0].getBBox(),n=r.w,o=r.h,i=r.d;t.w=n,t.h=o,t.d=i}},e}(t)}},5920:function(t,e){var r,n=this&&this.__extends||(r=function(t,e){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},r(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}r(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)}),o=this&&this.__assign||function(){return o=Object.assign||function(t){for(var e,r=1,n=arguments.length;r0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},s=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},s=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s};Object.defineProperty(e,"__esModule",{value:!0}),e.MJContextMenu=void 0;var a=r(5073),l=r(6186),c=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.mathItem=null,e.annotation="",e.annotationTypes={},e}return o(e,t),e.prototype.post=function(e,r){if(this.mathItem){if(void 0!==r){var n=this.mathItem.inputJax.name,o=this.findID("Show","Original");o.content="MathML"===n?"Original MathML":n+" Commands",this.findID("Copy","Original").content=o.content;var i=this.findID("Settings","semantics");"MathML"===n?i.disable():i.enable(),this.getAnnotationMenu(),this.dynamicSubmenus()}t.prototype.post.call(this,e,r)}},e.prototype.unpost=function(){t.prototype.unpost.call(this),this.mathItem=null},e.prototype.findID=function(){for(var t,e,r=[],n=0;n=0)return a}}catch(t){e={error:t}}finally{try{s&&!s.done&&(r=o.return)&&r.call(o)}finally{if(e)throw e.error}}return null},e.prototype.createAnnotationMenu=function(t,e,r){var n=this,o=this.findID(t,"Annotation");o.submenu=this.factory.get("subMenu")(this.factory,{items:e.map((function(t){var e=s(t,2),o=e[0],i=e[1];return{type:"command",id:o,content:o,action:function(){n.annotation=i,r()}}})),id:"annotations"},o),e.length?o.enable():o.disable()},e.prototype.dynamicSubmenus=function(){var t,r;try{for(var n=i(e.DynamicSubmenus),o=n.next();!o.done;o=n.next()){var a=s(o.value,2),l=a[0],c=a[1],u=this.find(l);if(u){var p=c(this,u);u.submenu=p,p.items.length?u.enable():u.disable()}}}catch(e){t={error:e}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(t)throw t.error}}},e.DynamicSubmenus=new Map,e}(a.ContextMenu);e.MJContextMenu=c},8310:function(t,e,r){var n=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},o=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},i=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0}),e.Menu=void 0;var s=r(5713),a=r(4474),l=r(9515),c=r(7233),u=r(5865),p=r(473),h=r(4414),f=r(4922),d=r(6914),m=r(3463),y=r(7309),g=i(r(5445)),b=l.MathJax,v="undefined"!=typeof window&&window.navigator&&"Mac"===window.navigator.platform.substr(0,3),_=function(){function t(t,e){void 0===e&&(e={});var r=this;this.settings=null,this.defaultSettings=null,this.menu=null,this.MmlVisitor=new p.MmlVisitor,this.jax={CHTML:null,SVG:null},this.rerenderStart=a.STATE.LAST,this.about=new f.Info('MathJax v'+s.mathjax.version,(function(){var t=[];return t.push("Input Jax: "+r.document.inputJax.map((function(t){return t.name})).join(", ")),t.push("Output Jax: "+r.document.outputJax.name),t.push("Document Type: "+r.document.kind),t.join("
")}),'www.mathjax.org'),this.help=new f.Info("MathJax Help",(function(){return["

MathJax is a JavaScript library that allows page"," authors to include mathematics within their web pages."," As a reader, you don't need to do anything to make that happen.

","

Browsers: MathJax works with all modern browsers including"," Edge, Firefox, Chrome, Safari, Opera, and most mobile browsers.

","

Math Menu: MathJax adds a contextual menu to equations."," Right-click or CTRL-click on any mathematics to access the menu.

",'
',"

Show Math As: These options allow you to view the formula's"," source markup (as MathML or in its original format).

","

Copy to Clipboard: These options copy the formula's source markup,"," as MathML or in its original format, to the clipboard"," (in browsers that support that).

","

Math Settings: These give you control over features of MathJax,"," such the size of the mathematics, and the mechanism used"," to display equations.

","

Accessibility: MathJax can work with screen"," readers to make mathematics accessible to the visually impaired."," Turn on the explorer to enable generation of speech strings"," and the ability to investigate expressions interactively.

","

Language: This menu lets you select the language used by MathJax"," for its menus and warning messages. (Not yet implemented in version 3.)

","
","

Math Zoom: If you are having difficulty reading an"," equation, MathJax can enlarge it to help you see it better, or"," you can scall all the math on the page to make it larger."," Turn these features on in the Math Settings menu.

","

Preferences: MathJax uses your browser's localStorage database"," to save the preferences set via this menu locally in your browser. These"," are not used to track you, and are not transferred or used remotely by"," MathJax in any way.

"].join("\n")}),'www.mathjax.org'),this.mathmlCode=new h.SelectableInfo("MathJax MathML Expression",(function(){if(!r.menu.mathItem)return"";var t=r.toMML(r.menu.mathItem);return"
"+r.formatSource(t)+"
"}),""),this.originalText=new h.SelectableInfo("MathJax Original Source",(function(){if(!r.menu.mathItem)return"";var t=r.menu.mathItem.math;return'
'+r.formatSource(t)+"
"}),""),this.annotationText=new h.SelectableInfo("MathJax Annotation Text",(function(){if(!r.menu.mathItem)return"";var t=r.menu.annotation;return'
'+r.formatSource(t)+"
"}),""),this.zoomBox=new f.Info("MathJax Zoomed Expression",(function(){if(!r.menu.mathItem)return"";var t=r.menu.mathItem.typesetRoot.cloneNode(!0);return t.style.margin="0",'
'+t.outerHTML+"
"}),""),this.document=t,this.options=(0,c.userOptions)((0,c.defaultOptions)({},this.constructor.OPTIONS),e),this.initSettings(),this.mergeUserSettings(),this.initMenu(),this.applySettings()}return Object.defineProperty(t.prototype,"isLoading",{get:function(){return t.loading>0},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"loadingPromise",{get:function(){return this.isLoading?(t._loadingPromise||(t._loadingPromise=new Promise((function(e,r){t._loadingOK=e,t._loadingFailed=r}))),t._loadingPromise):Promise.resolve()},enumerable:!1,configurable:!0}),t.prototype.initSettings=function(){this.settings=this.options.settings,this.jax=this.options.jax;var t=this.document.outputJax;this.jax[t.name]=t,this.settings.renderer=t.name,b._.a11y&&b._.a11y.explorer&&Object.assign(this.settings,this.document.options.a11y),this.settings.scale=t.options.scale,this.defaultSettings=Object.assign({},this.settings)},t.prototype.initMenu=function(){var t=this,e=new d.Parser([["contextMenu",u.MJContextMenu.fromJson.bind(u.MJContextMenu)]]);this.menu=e.parse({type:"contextMenu",id:"MathJax_Menu",pool:[this.variable("texHints"),this.variable("semantics"),this.variable("zoom"),this.variable("zscale"),this.variable("renderer",(function(e){return t.setRenderer(e)})),this.variable("alt"),this.variable("cmd"),this.variable("ctrl"),this.variable("shift"),this.variable("scale",(function(e){return t.setScale(e)})),this.variable("explorer",(function(e){return t.setExplorer(e)})),this.a11yVar("highlight"),this.a11yVar("backgroundColor"),this.a11yVar("backgroundOpacity"),this.a11yVar("foregroundColor"),this.a11yVar("foregroundOpacity"),this.a11yVar("speech"),this.a11yVar("subtitles"),this.a11yVar("braille"),this.a11yVar("viewBraille"),this.a11yVar("locale",(function(t){return g.default.setupEngine({locale:t})})),this.a11yVar("speechRules",(function(e){var r=n(e.split("-"),2),o=r[0],i=r[1];t.document.options.sre.domain=o,t.document.options.sre.style=i})),this.a11yVar("magnification"),this.a11yVar("magnify"),this.a11yVar("treeColoring"),this.a11yVar("infoType"),this.a11yVar("infoRole"),this.a11yVar("infoPrefix"),this.variable("autocollapse"),this.variable("collapsible",(function(e){return t.setCollapsible(e)})),this.variable("inTabOrder",(function(e){return t.setTabOrder(e)})),this.variable("assistiveMml",(function(e){return t.setAssistiveMml(e)}))],items:[this.submenu("Show","Show Math As",[this.command("MathMLcode","MathML Code",(function(){return t.mathmlCode.post()})),this.command("Original","Original Form",(function(){return t.originalText.post()})),this.submenu("Annotation","Annotation")]),this.submenu("Copy","Copy to Clipboard",[this.command("MathMLcode","MathML Code",(function(){return t.copyMathML()})),this.command("Original","Original Form",(function(){return t.copyOriginal()})),this.submenu("Annotation","Annotation")]),this.rule(),this.submenu("Settings","Math Settings",[this.submenu("Renderer","Math Renderer",this.radioGroup("renderer",[["CHTML"],["SVG"]])),this.rule(),this.submenu("ZoomTrigger","Zoom Trigger",[this.command("ZoomNow","Zoom Once Now",(function(){return t.zoom(null,"",t.menu.mathItem)})),this.rule(),this.radioGroup("zoom",[["Click"],["DoubleClick","Double-Click"],["NoZoom","No Zoom"]]),this.rule(),this.label("TriggerRequires","Trigger Requires:"),this.checkbox(v?"Option":"Alt",v?"Option":"Alt","alt"),this.checkbox("Command","Command","cmd",{hidden:!v}),this.checkbox("Control","Control","ctrl",{hiddne:v}),this.checkbox("Shift","Shift","shift")]),this.submenu("ZoomFactor","Zoom Factor",this.radioGroup("zscale",[["150%"],["175%"],["200%"],["250%"],["300%"],["400%"]])),this.rule(),this.command("Scale","Scale All Math...",(function(){return t.scaleAllMath()})),this.rule(),this.checkbox("texHints","Add TeX hints to MathML","texHints"),this.checkbox("semantics","Add original as annotation","semantics"),this.rule(),this.command("Reset","Reset to defaults",(function(){return t.resetDefaults()}))]),this.submenu("Accessibility","Accessibility",[this.checkbox("Activate","Activate","explorer"),this.submenu("Speech","Speech",[this.checkbox("Speech","Speech Output","speech"),this.checkbox("Subtitles","Speech Subtitles","subtitles"),this.checkbox("Braille","Braille Output","braille"),this.checkbox("View Braille","Braille Subtitles","viewBraille"),this.rule(),this.submenu("A11yLanguage","Language"),this.rule(),this.submenu("Mathspeak","Mathspeak Rules",this.radioGroup("speechRules",[["mathspeak-default","Verbose"],["mathspeak-brief","Brief"],["mathspeak-sbrief","Superbrief"]])),this.submenu("Clearspeak","Clearspeak Rules",this.radioGroup("speechRules",[["clearspeak-default","Auto"]])),this.submenu("ChromeVox","ChromeVox Rules",this.radioGroup("speechRules",[["chromevox-default","Standard"],["chromevox-alternative","Alternative"]]))]),this.submenu("Highlight","Highlight",[this.submenu("Background","Background",this.radioGroup("backgroundColor",[["Blue"],["Red"],["Green"],["Yellow"],["Cyan"],["Magenta"],["White"],["Black"]])),{type:"slider",variable:"backgroundOpacity",content:" "},this.submenu("Foreground","Foreground",this.radioGroup("foregroundColor",[["Black"],["White"],["Magenta"],["Cyan"],["Yellow"],["Green"],["Red"],["Blue"]])),{type:"slider",variable:"foregroundOpacity",content:" "},this.rule(),this.radioGroup("highlight",[["None"],["Hover"],["Flame"]]),this.rule(),this.checkbox("TreeColoring","Tree Coloring","treeColoring")]),this.submenu("Magnification","Magnification",[this.radioGroup("magnification",[["None"],["Keyboard"],["Mouse"]]),this.rule(),this.radioGroup("magnify",[["200%"],["300%"],["400%"],["500%"]])]),this.submenu("Semantic Info","Semantic Info",[this.checkbox("Type","Type","infoType"),this.checkbox("Role","Role","infoRole"),this.checkbox("Prefix","Prefix","infoPrefix")],!0),this.rule(),this.checkbox("Collapsible","Collapsible Math","collapsible"),this.checkbox("AutoCollapse","Auto Collapse","autocollapse",{disabled:!0}),this.rule(),this.checkbox("InTabOrder","Include in Tab Order","inTabOrder"),this.checkbox("AssistiveMml","Include Hidden MathML","assistiveMml")]),this.submenu("Language","Language"),this.rule(),this.command("About","About MathJax",(function(){return t.about.post()})),this.command("Help","MathJax Help",(function(){return t.help.post()}))]});var r=this.menu;this.about.attachMenu(r),this.help.attachMenu(r),this.originalText.attachMenu(r),this.annotationText.attachMenu(r),this.mathmlCode.attachMenu(r),this.zoomBox.attachMenu(r),this.checkLoadableItems(),this.enableExplorerItems(this.settings.explorer),r.showAnnotation=this.annotationText,r.copyAnnotation=this.copyAnnotation.bind(this),r.annotationTypes=this.options.annotationTypes,y.CssStyles.addInfoStyles(this.document.document),y.CssStyles.addMenuStyles(this.document.document)},t.prototype.checkLoadableItems=function(){var t,e;if(b&&b._&&b.loader&&b.startup)!this.settings.collapsible||b._.a11y&&b._.a11y.complexity||this.loadA11y("complexity"),!this.settings.explorer||b._.a11y&&b._.a11y.explorer||this.loadA11y("explorer"),!this.settings.assistiveMml||b._.a11y&&b._.a11y["assistive-mml"]||this.loadA11y("assistive-mml");else{var r=this.menu;try{for(var n=o(Object.keys(this.jax)),i=n.next();!i.done;i=n.next()){var s=i.value;this.jax[s]||r.findID("Settings","Renderer",s).disable()}}catch(e){t={error:e}}finally{try{i&&!i.done&&(e=n.return)&&e.call(n)}finally{if(t)throw t.error}}r.findID("Accessibility","Activate").disable(),r.findID("Accessibility","AutoCollapse").disable(),r.findID("Accessibility","Collapsible").disable()}},t.prototype.enableExplorerItems=function(t){var e,r,n=this.menu.findID("Accessibility","Activate").menu;try{for(var i=o(n.items.slice(1)),s=i.next();!s.done;s=i.next()){var a=s.value;if(a instanceof m.Rule)break;t?a.enable():a.disable()}}catch(t){e={error:t}}finally{try{s&&!s.done&&(r=i.return)&&r.call(i)}finally{if(e)throw e.error}}},t.prototype.mergeUserSettings=function(){try{var e=localStorage.getItem(t.MENU_STORAGE);if(!e)return;Object.assign(this.settings,JSON.parse(e)),this.setA11y(this.settings)}catch(t){console.log("MathJax localStorage error: "+t.message)}},t.prototype.saveUserSettings=function(){var e,r,n={};try{for(var i=o(Object.keys(this.settings)),s=i.next();!s.done;s=i.next()){var a=s.value;this.settings[a]!==this.defaultSettings[a]&&(n[a]=this.settings[a])}}catch(t){e={error:t}}finally{try{s&&!s.done&&(r=i.return)&&r.call(i)}finally{if(e)throw e.error}}try{Object.keys(n).length?localStorage.setItem(t.MENU_STORAGE,JSON.stringify(n)):localStorage.removeItem(t.MENU_STORAGE)}catch(t){console.log("MathJax localStorage error: "+t.message)}},t.prototype.setA11y=function(t){b._.a11y&&b._.a11y.explorer&&b._.a11y.explorer_ts.setA11yOptions(this.document,t)},t.prototype.getA11y=function(t){if(b._.a11y&&b._.a11y.explorer)return void 0!==this.document.options.a11y[t]?this.document.options.a11y[t]:this.document.options.sre[t]},t.prototype.applySettings=function(){this.setTabOrder(this.settings.inTabOrder),this.document.options.enableAssistiveMml=this.settings.assistiveMml,this.document.outputJax.options.scale=parseFloat(this.settings.scale),this.settings.renderer!==this.defaultSettings.renderer&&this.setRenderer(this.settings.renderer)},t.prototype.setScale=function(t){this.document.outputJax.options.scale=parseFloat(t),this.document.rerender()},t.prototype.setRenderer=function(t){var e=this;if(this.jax[t])this.setOutputJax(t);else{var r=t.toLowerCase();this.loadComponent("output/"+r,(function(){var n=b.startup;r in n.constructors&&(n.useOutput(r,!0),n.output=n.getOutputJax(),e.jax[t]=n.output,e.setOutputJax(t))}))}},t.prototype.setOutputJax=function(t){this.jax[t].setAdaptor(this.document.adaptor),this.document.outputJax=this.jax[t],this.rerender()},t.prototype.setTabOrder=function(t){this.menu.store.inTaborder(t)},t.prototype.setAssistiveMml=function(t){this.document.options.enableAssistiveMml=t,!t||b._.a11y&&b._.a11y["assistive-mml"]?this.rerender():this.loadA11y("assistive-mml")},t.prototype.setExplorer=function(t){this.enableExplorerItems(t),this.document.options.enableExplorer=t,!t||b._.a11y&&b._.a11y.explorer?this.rerender(this.settings.collapsible?a.STATE.RERENDER:a.STATE.COMPILED):this.loadA11y("explorer")},t.prototype.setCollapsible=function(t){this.document.options.enableComplexity=t,!t||b._.a11y&&b._.a11y.complexity?this.rerender(a.STATE.COMPILED):this.loadA11y("complexity")},t.prototype.scaleAllMath=function(){var t=(100*parseFloat(this.settings.scale)).toFixed(1).replace(/.0$/,""),e=prompt("Scale all mathematics (compared to surrounding text) by",t+"%");if(e)if(e.match(/^\s*\d+(\.\d*)?\s*%?\s*$/)){var r=parseFloat(e)/100;r?this.menu.pool.lookup("scale").setValue(String(r)):alert("The scale should not be zero")}else alert("The scale should be a percentage (e.g., 120%)")},t.prototype.resetDefaults=function(){var e,r;t.loading++;var n=this.menu.pool,i=this.defaultSettings;try{for(var s=o(Object.keys(this.settings)),l=s.next();!l.done;l=s.next()){var c=l.value,u=n.lookup(c);if(u){u.setValue(i[c]);var p=u.items[0];p&&p.executeCallbacks_()}else this.settings[c]=i[c]}}catch(t){e={error:t}}finally{try{l&&!l.done&&(r=s.return)&&r.call(s)}finally{if(e)throw e.error}}t.loading--,this.rerender(a.STATE.COMPILED)},t.prototype.checkComponent=function(e){var r=t.loadingPromises.get(e);r&&s.mathjax.retryAfter(r)},t.prototype.loadComponent=function(e,r){if(!t.loadingPromises.has(e)){var n=b.loader;if(n){t.loading++;var o=n.load(e).then((function(){t.loading--,t.loadingPromises.delete(e),r(),0===t.loading&&t._loadingPromise&&(t._loadingPromise=null,t._loadingOK())})).catch((function(e){t._loadingPromise?(t._loadingPromise=null,t._loadingFailed(e)):console.log(e)}));t.loadingPromises.set(e,o)}}},t.prototype.loadA11y=function(e){var r=this,n=!a.STATE.ENRICHED;this.loadComponent("a11y/"+e,(function(){var o=b.startup;s.mathjax.handlers.unregister(o.handler),o.handler=o.getHandler(),s.mathjax.handlers.register(o.handler);var i=r.document;r.document=o.document=o.getDocument(),r.document.menu=r,r.document.outputJax.reset(),r.transferMathList(i),r.document.processed=i.processed,t._loadingPromise||(r.document.outputJax.reset(),r.rerender("complexity"===e||n?a.STATE.COMPILED:a.STATE.TYPESET))}))},t.prototype.transferMathList=function(t){var e,r,n=this.document.options.MathItem;try{for(var i=o(t.math),s=i.next();!s.done;s=i.next()){var a=s.value,l=new n;Object.assign(l,a),this.document.math.push(l)}}catch(t){e={error:t}}finally{try{s&&!s.done&&(r=i.return)&&r.call(i)}finally{if(e)throw e.error}}},t.prototype.formatSource=function(t){return t.trim().replace(/&/g,"&").replace(//g,">")},t.prototype.toMML=function(t){return this.MmlVisitor.visitTree(t.root,t,{texHints:this.settings.texHints,semantics:this.settings.semantics&&"MathML"!==t.inputJax.name})},t.prototype.zoom=function(t,e,r){t&&!this.isZoomEvent(t,e)||(this.menu.mathItem=r,t&&this.menu.post(t),this.zoomBox.post())},t.prototype.isZoomEvent=function(t,e){return this.settings.zoom===e&&(!this.settings.alt||t.altKey)&&(!this.settings.ctrl||t.ctrlKey)&&(!this.settings.cmd||t.metaKey)&&(!this.settings.shift||t.shiftKey)},t.prototype.rerender=function(e){void 0===e&&(e=a.STATE.TYPESET),this.rerenderStart=Math.min(e,this.rerenderStart),t.loading||(this.rerenderStart<=a.STATE.COMPILED&&this.document.reset({inputJax:[]}),this.document.rerender(this.rerenderStart),this.rerenderStart=a.STATE.LAST)},t.prototype.copyMathML=function(){this.copyToClipboard(this.toMML(this.menu.mathItem))},t.prototype.copyOriginal=function(){this.copyToClipboard(this.menu.mathItem.math.trim())},t.prototype.copyAnnotation=function(){this.copyToClipboard(this.menu.annotation.trim())},t.prototype.copyToClipboard=function(t){var e=document.createElement("textarea");e.value=t,e.setAttribute("readonly",""),e.style.cssText="height: 1px; width: 1px; padding: 1px; position: absolute; left: -10px",document.body.appendChild(e),e.select();try{document.execCommand("copy")}catch(t){alert("Can't copy to clipboard: "+t.message)}document.body.removeChild(e)},t.prototype.addMenu=function(t){var e=this,r=t.typesetRoot;r.addEventListener("contextmenu",(function(){return e.menu.mathItem=t}),!0),r.addEventListener("keydown",(function(){return e.menu.mathItem=t}),!0),r.addEventListener("click",(function(r){return e.zoom(r,"Click",t)}),!0),r.addEventListener("dblclick",(function(r){return e.zoom(r,"DoubleClick",t)}),!0),this.menu.store.insert(r)},t.prototype.clear=function(){this.menu.store.clear()},t.prototype.variable=function(t,e){var r=this;return{name:t,getter:function(){return r.settings[t]},setter:function(n){r.settings[t]=n,e&&e(n),r.saveUserSettings()}}},t.prototype.a11yVar=function(t,e){var r=this;return{name:t,getter:function(){return r.getA11y(t)},setter:function(n){r.settings[t]=n;var o={};o[t]=n,r.setA11y(o),e&&e(n),r.saveUserSettings()}}},t.prototype.submenu=function(t,e,r,n){var i,s;void 0===r&&(r=[]),void 0===n&&(n=!1);var a=[];try{for(var l=o(r),c=l.next();!c.done;c=l.next()){var u=c.value;Array.isArray(u)?a=a.concat(u):a.push(u)}}catch(t){i={error:t}}finally{try{c&&!c.done&&(s=l.return)&&s.call(l)}finally{if(i)throw i.error}}return{type:"submenu",id:t,content:e,menu:{items:a},disabled:0===a.length||n}},t.prototype.command=function(t,e,r,n){return void 0===n&&(n={}),Object.assign({type:"command",id:t,content:e,action:r},n)},t.prototype.checkbox=function(t,e,r,n){return void 0===n&&(n={}),Object.assign({type:"checkbox",id:t,content:e,variable:r},n)},t.prototype.radioGroup=function(t,e){var r=this;return e.map((function(e){return r.radio(e[0],e[1]||e[0],t)}))},t.prototype.radio=function(t,e,r,n){return void 0===n&&(n={}),Object.assign({type:"radio",id:t,content:e,variable:r},n)},t.prototype.label=function(t,e){return{type:"label",id:t,content:e}},t.prototype.rule=function(){return{type:"rule"}},t.MENU_STORAGE="MathJax-Menu-Settings",t.OPTIONS={settings:{texHints:!0,semantics:!1,zoom:"NoZoom",zscale:"200%",renderer:"CHTML",alt:!1,cmd:!1,ctrl:!1,shift:!1,scale:1,autocollapse:!1,collapsible:!1,inTabOrder:!0,assistiveMml:!0,explorer:!1},jax:{CHTML:null,SVG:null},annotationTypes:(0,c.expandable)({TeX:["TeX","LaTeX","application/x-tex"],StarMath:["StarMath 5.0"],Maple:["Maple"],ContentMathML:["MathML-Content","application/mathml-content+xml"],OpenMath:["OpenMath"]})},t.loading=0,t.loadingPromises=new Map,t._loadingPromise=null,t._loadingOK=null,t._loadingFailed=null,t}();e.Menu=_},4001:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__assign||function(){return i=Object.assign||function(t){for(var e,r=1,n=arguments.length;r0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},a=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.MenuHandler=e.MenuMathDocumentMixin=e.MenuMathItemMixin=void 0;var c=r(5713),u=r(4474),p=r(7233),h=r(8310);function f(t){return function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.addMenu=function(t,e){void 0===e&&(e=!1),this.state()>=u.STATE.CONTEXT_MENU||(this.isEscaped||!t.options.enableMenu&&!e||t.menu.addMenu(this),this.state(u.STATE.CONTEXT_MENU))},e.prototype.checkLoading=function(t){t.checkLoading()},e}(t)}function d(t){var e;return e=function(t){function e(){for(var e=[],r=0;r\n"+this.childNodeMml(e,r+" ","\n")+r+""},e.prototype.visitMathNode=function(e,r){if(!this.options.semantics||"TeX"!==this.mathItem.inputJax.name)return t.prototype.visitDefault.call(this,e,r);var n=e.childNodes.length&&e.childNodes[0].childNodes.length>1;return r+"\n"+r+" \n"+(n?r+" \n":"")+this.childNodeMml(e,r+(n?" ":" "),"\n")+(n?r+" \n":"")+r+' '+this.mathItem.math+"\n"+r+" \n"+r+""},e}(i.SerializedMmlVisitor);e.MmlVisitor=a},4414:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.SelectableInfo=void 0;var i=r(4922),s=r(2165),a=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.addEvents=function(t){var e=this;t.addEventListener("keypress",(function(t){"a"===t.key&&(t.ctrlKey||t.metaKey)&&(e.selectAll(),e.stop(t))}))},e.prototype.selectAll=function(){document.getSelection().selectAllChildren(this.html.querySelector("pre"))},e.prototype.copyToClipboard=function(){this.selectAll();try{document.execCommand("copy")}catch(t){alert("Can't copy to clipboard: "+t.message)}document.getSelection().removeAllRanges()},e.prototype.generateHtml=function(){var e=this;t.prototype.generateHtml.call(this);var r=this.html.querySelector("span."+s.HtmlClasses.INFOSIGNATURE).appendChild(document.createElement("input"));r.type="button",r.value="Copy to Clipboard",r.addEventListener("click",(function(t){return e.copyToClipboard()}))},e}(i.Info);e.SelectableInfo=a},9923:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.asyncLoad=void 0;var n=r(5713);e.asyncLoad=function(t){return n.mathjax.asyncLoad?new Promise((function(e,r){var o=n.mathjax.asyncLoad(t);o instanceof Promise?o.then((function(t){return e(t)})).catch((function(t){return r(t)})):e(o)})):Promise.reject("Can't load '".concat(t,"': No asyncLoad method specified"))}},6469:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.BBox=void 0;var n=r(6010),o=function(){function t(t){void 0===t&&(t={w:0,h:-n.BIGDIMEN,d:-n.BIGDIMEN}),this.w=t.w||0,this.h="h"in t?t.h:-n.BIGDIMEN,this.d="d"in t?t.d:-n.BIGDIMEN,this.L=this.R=this.ic=this.sk=this.dx=0,this.scale=this.rscale=1,this.pwidth=""}return t.zero=function(){return new t({h:0,d:0,w:0})},t.empty=function(){return new t},t.prototype.empty=function(){return this.w=0,this.h=this.d=-n.BIGDIMEN,this},t.prototype.clean=function(){this.w===-n.BIGDIMEN&&(this.w=0),this.h===-n.BIGDIMEN&&(this.h=0),this.d===-n.BIGDIMEN&&(this.d=0)},t.prototype.rescale=function(t){this.w*=t,this.h*=t,this.d*=t},t.prototype.combine=function(t,e,r){void 0===e&&(e=0),void 0===r&&(r=0);var n=t.rscale,o=e+n*(t.w+t.L+t.R),i=r+n*t.h,s=n*t.d-r;o>this.w&&(this.w=o),i>this.h&&(this.h=i),s>this.d&&(this.d=s)},t.prototype.append=function(t){var e=t.rscale;this.w+=e*(t.w+t.L+t.R),e*t.h>this.h&&(this.h=e*t.h),e*t.d>this.d&&(this.d=e*t.d)},t.prototype.updateFrom=function(t){this.h=t.h,this.d=t.d,this.w=t.w,t.pwidth&&(this.pwidth=t.pwidth)},t.fullWidth="100%",t.StyleAdjust=[["borderTopWidth","h"],["borderRightWidth","w"],["borderBottomWidth","d"],["borderLeftWidth","w",0],["paddingTop","h"],["paddingRight","w"],["paddingBottom","d"],["paddingLeft","w",0]],t}();e.BBox=o},6751:function(t,e){var r,n=this&&this.__extends||(r=function(t,e){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},r(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}r(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)}),o=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},i=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},s=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o",gtdot:"\u22d7",harrw:"\u21ad",hbar:"\u210f",hellip:"\u2026",hookleftarrow:"\u21a9",hookrightarrow:"\u21aa",imath:"\u0131",infin:"\u221e",intcal:"\u22ba",iota:"\u03b9",jmath:"\u0237",kappa:"\u03ba",kappav:"\u03f0",lEg:"\u2a8b",lambda:"\u03bb",lap:"\u2a85",larrlp:"\u21ab",larrtl:"\u21a2",lbrace:"{",lbrack:"[",le:"\u2264",leftleftarrows:"\u21c7",leftthreetimes:"\u22cb",lessdot:"\u22d6",lmoust:"\u23b0",lnE:"\u2268",lnap:"\u2a89",lne:"\u2a87",lnsim:"\u22e6",longmapsto:"\u27fc",looparrowright:"\u21ac",lowast:"\u2217",loz:"\u25ca",lt:"<",ltimes:"\u22c9",ltri:"\u25c3",macr:"\xaf",malt:"\u2720",mho:"\u2127",mu:"\u03bc",multimap:"\u22b8",nLeftarrow:"\u21cd",nLeftrightarrow:"\u21ce",nRightarrow:"\u21cf",nVDash:"\u22af",nVdash:"\u22ae",natur:"\u266e",nearr:"\u2197",nharr:"\u21ae",nlarr:"\u219a",not:"\xac",nrarr:"\u219b",nu:"\u03bd",nvDash:"\u22ad",nvdash:"\u22ac",nwarr:"\u2196",omega:"\u03c9",omicron:"\u03bf",or:"\u2228",osol:"\u2298",period:".",phi:"\u03c6",phiv:"\u03d5",pi:"\u03c0",piv:"\u03d6",prap:"\u2ab7",precnapprox:"\u2ab9",precneqq:"\u2ab5",precnsim:"\u22e8",prime:"\u2032",psi:"\u03c8",quot:'"',rarrtl:"\u21a3",rbrace:"}",rbrack:"]",rho:"\u03c1",rhov:"\u03f1",rightrightarrows:"\u21c9",rightthreetimes:"\u22cc",ring:"\u02da",rmoust:"\u23b1",rtimes:"\u22ca",rtri:"\u25b9",scap:"\u2ab8",scnE:"\u2ab6",scnap:"\u2aba",scnsim:"\u22e9",sdot:"\u22c5",searr:"\u2198",sect:"\xa7",sharp:"\u266f",sigma:"\u03c3",sigmav:"\u03c2",simne:"\u2246",smile:"\u2323",spades:"\u2660",sub:"\u2282",subE:"\u2ac5",subnE:"\u2acb",subne:"\u228a",supE:"\u2ac6",supnE:"\u2acc",supne:"\u228b",swarr:"\u2199",tau:"\u03c4",theta:"\u03b8",thetav:"\u03d1",tilde:"\u02dc",times:"\xd7",triangle:"\u25b5",triangleq:"\u225c",upsi:"\u03c5",upuparrows:"\u21c8",veebar:"\u22bb",vellip:"\u22ee",weierp:"\u2118",xi:"\u03be",yen:"\xa5",zeta:"\u03b6",zigrarr:"\u21dd",nbsp:"\xa0",rsquo:"\u2019",lsquo:"\u2018"};var i={};function s(t,r){if("#"===r.charAt(0))return a(r.slice(1));if(e.entities[r])return e.entities[r];if(e.options.loadMissingEntities){var s=r.match(/^[a-zA-Z](fr|scr|opf)$/)?RegExp.$1:r.charAt(0).toLowerCase();i[s]||(i[s]=!0,(0,n.retryAfter)((0,o.asyncLoad)("./util/entities/"+s+".js")))}return t}function a(t){var e="x"===t.charAt(0)?parseInt(t.slice(1),16):parseInt(t);return String.fromCodePoint(e)}e.add=function(t,r){Object.assign(e.entities,t),i[r]=!0},e.remove=function(t){delete e.entities[t]},e.translate=function(t){return t.replace(/&([a-z][a-z0-9]*|#(?:[0-9]+|x[0-9a-f]+));/gi,s)},e.numeric=a},7525:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])},n(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},s=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},a=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o0&&o[o.length-1])||6!==i[0]&&2!==i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},o=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.LinkedList=e.ListItem=e.END=void 0,e.END=Symbol();var s=function(t){void 0===t&&(t=null),this.next=null,this.prev=null,this.data=t};e.ListItem=s;var a=function(){function t(){for(var t=[],r=0;r1;){var c=o.shift(),u=o.shift();c.merge(u,e),o.push(c)}return o.length&&(this.list=o[0].list),this},t.prototype.merge=function(t,r){var o,i,s,a,l;void 0===r&&(r=null),null===r&&(r=this.isBefore.bind(this));for(var c=this.list.next,u=t.list.next;c.data!==e.END&&u.data!==e.END;)r(u.data,c.data)?(o=n([c,u],2),u.prev.next=o[0],c.prev.next=o[1],i=n([c.prev,u.prev],2),u.prev=i[0],c.prev=i[1],s=n([t.list,this.list],2),this.list.prev.next=s[0],t.list.prev.next=s[1],a=n([t.list.prev,this.list.prev],2),this.list.prev=a[0],t.list.prev=a[1],c=(l=n([u.next,c],2))[0],u=l[1]):c=c.next;return u.data!==e.END&&(this.list.prev.next=t.list.next,t.list.next.prev=this.list.prev,t.list.prev.next=this.list,this.list.prev=t.list.prev,t.list.next=t.list.prev=t.list),this},t}();e.LinkedList=a},7233:function(t,e){var r=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},n=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},o=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;oe.length}}}},t.prototype.add=function(e,r){void 0===r&&(r=t.DEFAULTPRIORITY);var n=this.items.length;do{n--}while(n>=0&&r=0&&this.items[e].item!==t);e>=0&&this.items.splice(e,1)},t.DEFAULTPRIORITY=5,t}();e.PrioritizedList=r},4542:function(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.retryAfter=e.handleRetriesFor=void 0,e.handleRetriesFor=function(t){return new Promise((function e(r,n){try{r(t())}catch(t){t.retry&&t.retry instanceof Promise?t.retry.then((function(){return e(r,n)})).catch((function(t){return n(t)})):t.restart&&t.restart.isCallback?MathJax.Callback.After((function(){return e(r,n)}),t.restart):n(t)}}))},e.retryAfter=function(t){var e=new Error("MathJax retry");throw e.retry=t,e}},4139:function(t,e){var r=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.CssStyles=void 0;var n=function(){function t(t){void 0===t&&(t=null),this.styles={},this.addStyles(t)}return Object.defineProperty(t.prototype,"cssText",{get:function(){return this.getStyleString()},enumerable:!1,configurable:!0}),t.prototype.addStyles=function(t){var e,n;if(t)try{for(var o=r(Object.keys(t)),i=o.next();!i.done;i=o.next()){var s=i.value;this.styles[s]||(this.styles[s]={}),Object.assign(this.styles[s],t[s])}}catch(t){e={error:t}}finally{try{i&&!i.done&&(n=o.return)&&n.call(o)}finally{if(e)throw e.error}}},t.prototype.removeStyles=function(){for(var t,e,n=[],o=0;o=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},n=this&&this.__read||function(t,e){var r="function"==typeof Symbol&&t[Symbol.iterator];if(!r)return t;var n,o,i=r.call(t),s=[];try{for(;(void 0===e||e-- >0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},o=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o1;)e.shift(),r.push(e.shift());return r}function l(t){var e,n,o=a(this.styles[t]);0===o.length&&o.push(""),1===o.length&&o.push(o[0]),2===o.length&&o.push(o[0]),3===o.length&&o.push(o[1]);try{for(var i=r(v.connect[t].children),s=i.next();!s.done;s=i.next()){var l=s.value;this.setStyle(this.childName(t,l),o.shift())}}catch(t){e={error:t}}finally{try{s&&!s.done&&(n=i.return)&&n.call(i)}finally{if(e)throw e.error}}}function c(t){var e,n,o=v.connect[t].children,i=[];try{for(var s=r(o),a=s.next();!a.done;a=s.next()){var l=a.value,c=this.styles[t+"-"+l];if(!c)return void delete this.styles[t];i.push(c)}}catch(t){e={error:t}}finally{try{a&&!a.done&&(n=s.return)&&n.call(s)}finally{if(e)throw e.error}}i[3]===i[1]&&(i.pop(),i[2]===i[0]&&(i.pop(),i[1]===i[0]&&i.pop())),this.styles[t]=i.join(" ")}function u(t){var e,n;try{for(var o=r(v.connect[t].children),i=o.next();!i.done;i=o.next()){var s=i.value;this.setStyle(this.childName(t,s),this.styles[t])}}catch(t){e={error:t}}finally{try{i&&!i.done&&(n=o.return)&&n.call(o)}finally{if(e)throw e.error}}}function p(t){var e,i,s=o([],n(v.connect[t].children),!1),a=this.styles[this.childName(t,s.shift())];try{for(var l=r(s),c=l.next();!c.done;c=l.next()){var u=c.value;if(this.styles[this.childName(t,u)]!==a)return void delete this.styles[t]}}catch(t){e={error:t}}finally{try{c&&!c.done&&(i=l.return)&&i.call(l)}finally{if(e)throw e.error}}this.styles[t]=a}var h=/^(?:[\d.]+(?:[a-z]+)|thin|medium|thick|inherit|initial|unset)$/,f=/^(?:none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset|inherit|initial|unset)$/;function d(t){var e,n,o,i,s={width:"",style:"",color:""};try{for(var l=r(a(this.styles[t])),c=l.next();!c.done;c=l.next()){var u=c.value;u.match(h)&&""===s.width?s.width=u:u.match(f)&&""===s.style?s.style=u:s.color=u}}catch(t){e={error:t}}finally{try{c&&!c.done&&(n=l.return)&&n.call(l)}finally{if(e)throw e.error}}try{for(var p=r(v.connect[t].children),d=p.next();!d.done;d=p.next()){var m=d.value;this.setStyle(this.childName(t,m),s[m])}}catch(t){o={error:t}}finally{try{d&&!d.done&&(i=p.return)&&i.call(p)}finally{if(o)throw o.error}}}function m(t){var e,n,o=[];try{for(var i=r(v.connect[t].children),s=i.next();!s.done;s=i.next()){var a=s.value,l=this.styles[this.childName(t,a)];l&&o.push(l)}}catch(t){e={error:t}}finally{try{s&&!s.done&&(n=i.return)&&n.call(i)}finally{if(e)throw e.error}}o.length?this.styles[t]=o.join(" "):delete this.styles[t]}var y={style:/^(?:normal|italic|oblique|inherit|initial|unset)$/,variant:new RegExp("^(?:"+["normal|none","inherit|initial|unset","common-ligatures|no-common-ligatures","discretionary-ligatures|no-discretionary-ligatures","historical-ligatures|no-historical-ligatures","contextual|no-contextual","(?:stylistic|character-variant|swash|ornaments|annotation)\\([^)]*\\)","small-caps|all-small-caps|petite-caps|all-petite-caps|unicase|titling-caps","lining-nums|oldstyle-nums|proportional-nums|tabular-nums","diagonal-fractions|stacked-fractions","ordinal|slashed-zero","jis78|jis83|jis90|jis04|simplified|traditional","full-width|proportional-width","ruby"].join("|")+")$"),weight:/^(?:normal|bold|bolder|lighter|[1-9]00|inherit|initial|unset)$/,stretch:new RegExp("^(?:"+["normal","(?:(?:ultra|extra|semi)-)?condensed","(?:(?:semi|extra|ulta)-)?expanded","inherit|initial|unset"].join("|")+")$"),size:new RegExp("^(?:"+["xx-small|x-small|small|medium|large|x-large|xx-large|larger|smaller","[d.]+%|[d.]+[a-z]+","inherit|initial|unset"].join("|")+")(?:/(?:normal|[d.+](?:%|[a-z]+)?))?$")};function g(t){var e,o,i,s,l=a(this.styles[t]),c={style:"",variant:[],weight:"",stretch:"",size:"",family:"","line-height":""};try{for(var u=r(l),p=u.next();!p.done;p=u.next()){var h=p.value;c.family=h;try{for(var f=(i=void 0,r(Object.keys(y))),d=f.next();!d.done;d=f.next()){var m=d.value;if((Array.isArray(c[m])||""===c[m])&&h.match(y[m]))if("size"===m){var g=n(h.split(/\//),2),b=g[0],_=g[1];c[m]=b,_&&(c["line-height"]=_)}else""===c.size&&(Array.isArray(c[m])?c[m].push(h):c[m]=h)}}catch(t){i={error:t}}finally{try{d&&!d.done&&(s=f.return)&&s.call(f)}finally{if(i)throw i.error}}}}catch(t){e={error:t}}finally{try{p&&!p.done&&(o=u.return)&&o.call(u)}finally{if(e)throw e.error}}!function(t,e){var n,o;try{for(var i=r(v.connect[t].children),s=i.next();!s.done;s=i.next()){var a=s.value,l=this.childName(t,a);if(Array.isArray(e[a])){var c=e[a];c.length&&(this.styles[l]=c.join(" "))}else""!==e[a]&&(this.styles[l]=e[a])}}catch(t){n={error:t}}finally{try{s&&!s.done&&(o=i.return)&&o.call(i)}finally{if(n)throw n.error}}}(t,c),delete this.styles[t]}function b(t){}var v=function(){function t(t){void 0===t&&(t=""),this.parse(t)}return Object.defineProperty(t.prototype,"cssText",{get:function(){var t,e,n=[];try{for(var o=r(Object.keys(this.styles)),i=o.next();!i.done;i=o.next()){var s=i.value,a=this.parentName(s);this.styles[a]||n.push(s+": "+this.styles[s]+";")}}catch(e){t={error:e}}finally{try{i&&!i.done&&(e=o.return)&&e.call(o)}finally{if(t)throw t.error}}return n.join(" ")},enumerable:!1,configurable:!0}),t.prototype.set=function(e,r){for(e=this.normalizeName(e),this.setStyle(e,r),t.connect[e]&&!t.connect[e].combine&&(this.combineChildren(e),delete this.styles[e]);e.match(/-/)&&(e=this.parentName(e),t.connect[e]);)t.connect[e].combine.call(this,e)},t.prototype.get=function(t){return t=this.normalizeName(t),this.styles.hasOwnProperty(t)?this.styles[t]:""},t.prototype.setStyle=function(e,r){this.styles[e]=r,t.connect[e]&&t.connect[e].children&&t.connect[e].split.call(this,e),""===r&&delete this.styles[e]},t.prototype.combineChildren=function(e){var n,o,i=this.parentName(e);try{for(var s=r(t.connect[e].children),a=s.next();!a.done;a=s.next()){var l=a.value,c=this.childName(i,l);t.connect[c].combine.call(this,c)}}catch(t){n={error:t}}finally{try{a&&!a.done&&(o=s.return)&&o.call(s)}finally{if(n)throw n.error}}},t.prototype.parentName=function(t){var e=t.replace(/-[^-]*$/,"");return t===e?"":e},t.prototype.childName=function(e,r){return r.match(/-/)?r:(t.connect[e]&&!t.connect[e].combine&&(r+=e.replace(/.*-/,"-"),e=this.parentName(e)),e+"-"+r)},t.prototype.normalizeName=function(t){return t.replace(/[A-Z]/g,(function(t){return"-"+t.toLowerCase()}))},t.prototype.parse=function(t){void 0===t&&(t="");var e=this.constructor.pattern;this.styles={};for(var r=t.replace(e.comment,"").split(e.style);r.length>1;){var o=n(r.splice(0,3),3),i=o[0],s=o[1],a=o[2];if(i.match(/[^\s\n]/))return;this.set(s,a)}},t.pattern={style:/([-a-z]+)[\s\n]*:[\s\n]*((?:'[^']*'|"[^"]*"|\n|.)*?)[\s\n]*(?:;|$)/g,comment:/\/\*[^]*?\*\//g},t.connect={padding:{children:i,split:l,combine:c},border:{children:i,split:u,combine:p},"border-top":{children:s,split:d,combine:m},"border-right":{children:s,split:d,combine:m},"border-bottom":{children:s,split:d,combine:m},"border-left":{children:s,split:d,combine:m},"border-width":{children:i,split:l,combine:null},"border-style":{children:i,split:l,combine:null},"border-color":{children:i,split:l,combine:null},font:{children:["style","variant","weight","stretch","line-height","size","family"],split:g,combine:b}},t}();e.Styles=v},6010:function(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.px=e.emRounded=e.em=e.percent=e.length2em=e.MATHSPACE=e.RELUNITS=e.UNITS=e.BIGDIMEN=void 0,e.BIGDIMEN=1e6,e.UNITS={px:1,in:96,cm:96/2.54,mm:96/25.4},e.RELUNITS={em:1,ex:.431,pt:.1,pc:1.2,mu:1/18},e.MATHSPACE={veryverythinmathspace:1/18,verythinmathspace:2/18,thinmathspace:3/18,mediummathspace:4/18,thickmathspace:5/18,verythickmathspace:6/18,veryverythickmathspace:7/18,negativeveryverythinmathspace:-1/18,negativeverythinmathspace:-2/18,negativethinmathspace:-3/18,negativemediummathspace:-4/18,negativethickmathspace:-5/18,negativeverythickmathspace:-6/18,negativeveryverythickmathspace:-7/18,thin:.04,medium:.06,thick:.1,normal:1,big:2,small:1/Math.sqrt(2),infinity:e.BIGDIMEN},e.length2em=function(t,r,n,o){if(void 0===r&&(r=0),void 0===n&&(n=1),void 0===o&&(o=16),"string"!=typeof t&&(t=String(t)),""===t||null==t)return r;if(e.MATHSPACE[t])return e.MATHSPACE[t];var i=t.match(/^\s*([-+]?(?:\.\d+|\d+(?:\.\d*)?))?(pt|em|ex|mu|px|pc|in|mm|cm|%)?/);if(!i)return r;var s=parseFloat(i[1]||"1"),a=i[2];return e.UNITS.hasOwnProperty(a)?s*e.UNITS[a]/o/n:e.RELUNITS.hasOwnProperty(a)?s*e.RELUNITS[a]:"%"===a?s/100*r:s*r},e.percent=function(t){return(100*t).toFixed(1).replace(/\.?0+$/,"")+"%"},e.em=function(t){return Math.abs(t)<.001?"0":t.toFixed(3).replace(/\.?0+$/,"")+"em"},e.emRounded=function(t,e){return void 0===e&&(e=16),t=(Math.round(t*e)+.05)/e,Math.abs(t)<.001?"0em":t.toFixed(3).replace(/\.?0+$/,"")+"em"},e.px=function(t,r,n){return void 0===r&&(r=-e.BIGDIMEN),void 0===n&&(n=16),t*=n,r&&t0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},n=this&&this.__spreadArray||function(t,e,r){if(r||2===arguments.length)for(var n,o=0,i=e.length;o=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.AbstractItem=void 0;var s=r(9329),a=r(2556),l=r(2165),c=function(t){function e(e,r,n,o){var i=t.call(this,e,r)||this;return i._content=n,i.disabled=!1,i.callbacks=[],i._id=o||n,i}return o(e,t),Object.defineProperty(e.prototype,"content",{get:function(){return this._content},set:function(t){this._content=t,this.generateHtml(),this.menu&&this.menu.generateHtml()},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"id",{get:function(){return this._id},enumerable:!1,configurable:!0}),e.prototype.press=function(){this.disabled||(this.executeAction(),this.executeCallbacks_())},e.prototype.executeAction=function(){},e.prototype.registerCallback=function(t){-1===this.callbacks.indexOf(t)&&this.callbacks.push(t)},e.prototype.unregisterCallback=function(t){var e=this.callbacks.indexOf(t);-1!==e&&this.callbacks.splice(e,1)},e.prototype.mousedown=function(t){this.press(),this.stop(t)},e.prototype.mouseover=function(t){this.focus(),this.stop(t)},e.prototype.mouseout=function(t){this.deactivate(),this.stop(t)},e.prototype.generateHtml=function(){t.prototype.generateHtml.call(this);var e=this.html;e.setAttribute("aria-disabled","false"),e.textContent=this.content},e.prototype.activate=function(){this.disabled||this.html.classList.add(l.HtmlClasses.MENUACTIVE)},e.prototype.deactivate=function(){this.html.classList.remove(l.HtmlClasses.MENUACTIVE)},e.prototype.focus=function(){this.menu.focused=this,t.prototype.focus.call(this),this.activate()},e.prototype.unfocus=function(){this.deactivate(),t.prototype.unfocus.call(this)},e.prototype.escape=function(t){a.MenuUtil.close(this)},e.prototype.up=function(t){this.menu.up(t)},e.prototype.down=function(t){this.menu.down(t)},e.prototype.left=function(t){this.menu.left(t)},e.prototype.right=function(t){this.menu.right(t)},e.prototype.space=function(t){this.press()},e.prototype.disable=function(){this.disabled=!0;var t=this.html;t.classList.add(l.HtmlClasses.MENUDISABLED),t.setAttribute("aria-disabled","true")},e.prototype.enable=function(){this.disabled=!1;var t=this.html;t.classList.remove(l.HtmlClasses.MENUDISABLED),t.removeAttribute("aria-disabled")},e.prototype.executeCallbacks_=function(){var t,e;try{for(var r=i(this.callbacks),n=r.next();!n.done;n=r.next()){var o=n.value;try{o(this)}catch(t){a.MenuUtil.error(t,"Callback for menu entry "+this.id+" failed.")}}}catch(e){t={error:e}}finally{try{n&&!n.done&&(e=r.return)&&e.call(r)}finally{if(t)throw t.error}}},e}(s.AbstractEntry);e.AbstractItem=c},1484:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r])},n(t,e)},function(t,e){function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)}),i=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.AbstractMenu=void 0;var s=r(8372),a=r(1340),l=r(2165),c=r(6186),u=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.className=l.HtmlClasses.CONTEXTMENU,e.role="menu",e._items=[],e._baseMenu=null,e}return o(e,t),Object.defineProperty(e.prototype,"baseMenu",{get:function(){return this._baseMenu},set:function(t){this._baseMenu=t},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"items",{get:function(){return this._items},set:function(t){this._items=t},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"pool",{get:function(){return this.variablePool},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"focused",{get:function(){return this._focused},set:function(t){if(this._focused!==t){this._focused||this.unfocus();var e=this._focused;this._focused=t,e&&e.unfocus()}},enumerable:!1,configurable:!0}),e.prototype.up=function(t){var e=this.items.filter((function(t){return t instanceof a.AbstractItem&&!t.isHidden()}));if(0!==e.length)if(this.focused){var r=e.indexOf(this.focused);-1!==r&&e[r=r?--r:e.length-1].focus()}else e[e.length-1].focus()},e.prototype.down=function(t){var e=this.items.filter((function(t){return t instanceof a.AbstractItem&&!t.isHidden()}));if(0!==e.length)if(this.focused){var r=e.indexOf(this.focused);-1!==r&&e[r=++r===e.length?0:r].focus()}else e[0].focus()},e.prototype.generateHtml=function(){t.prototype.generateHtml.call(this),this.generateMenu()},e.prototype.generateMenu=function(){var t,e,r=this.html;r.classList.add(l.HtmlClasses.MENU);try{for(var n=i(this.items),o=n.next();!o.done;o=n.next()){var s=o.value;if(s.isHidden()){var a=s.html;a.parentNode&&a.parentNode.removeChild(a)}else r.appendChild(s.html)}}catch(e){t={error:e}}finally{try{o&&!o.done&&(e=n.return)&&e.call(n)}finally{if(t)throw t.error}}},e.prototype.post=function(e,r){this.variablePool.update(),t.prototype.post.call(this,e,r)},e.prototype.unpostSubmenus=function(){var t,e,r=this.items.filter((function(t){return t instanceof c.Submenu}));try{for(var n=i(r),o=n.next();!o.done;o=n.next()){var s=o.value;s.submenu.unpost(),s!==this.focused&&s.unfocus()}}catch(e){t={error:e}}finally{try{o&&!o.done&&(e=n.return)&&e.call(n)}finally{if(t)throw t.error}}},e.prototype.unpost=function(){t.prototype.unpost.call(this),this.unpostSubmenus(),this.focused=null},e.prototype.find=function(t){var e,r;try{for(var n=i(this.items),o=n.next();!o.done;o=n.next()){var s=o.value;if("rule"!==s.type){if(s.id===t)return s;if("submenu"===s.type){var a=s.submenu.find(t);if(a)return a}}}}catch(t){e={error:t}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(e)throw e.error}}return null},e}(s.AbstractPostable);e.AbstractMenu=u},2868:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.AbstractNavigatable=void 0;var n=r(3205),o=r(8853),i=function(){function t(){this.bubble=!1}return t.prototype.bubbleKey=function(){this.bubble=!0},t.prototype.keydown=function(t){switch(t.keyCode){case n.KEY.ESCAPE:this.escape(t);break;case n.KEY.RIGHT:this.right(t);break;case n.KEY.LEFT:this.left(t);break;case n.KEY.UP:this.up(t);break;case n.KEY.DOWN:this.down(t);break;case n.KEY.RETURN:case n.KEY.SPACE:this.space(t);break;default:return}this.bubble?this.bubble=!1:this.stop(t)},t.prototype.escape=function(t){},t.prototype.space=function(t){},t.prototype.left=function(t){},t.prototype.right=function(t){},t.prototype.up=function(t){},t.prototype.down=function(t){},t.prototype.stop=function(t){t&&(t.stopPropagation(),t.preventDefault(),t.cancelBubble=!0)},t.prototype.mousedown=function(t){return this.stop(t)},t.prototype.mouseup=function(t){return this.stop(t)},t.prototype.mouseover=function(t){return this.stop(t)},t.prototype.mouseout=function(t){return this.stop(t)},t.prototype.click=function(t){return this.stop(t)},t.prototype.addEvents=function(t){t.addEventListener(o.MOUSE.DOWN,this.mousedown.bind(this)),t.addEventListener(o.MOUSE.UP,this.mouseup.bind(this)),t.addEventListener(o.MOUSE.OVER,this.mouseover.bind(this)),t.addEventListener(o.MOUSE.OUT,this.mouseout.bind(this)),t.addEventListener(o.MOUSE.CLICK,this.click.bind(this)),t.addEventListener("keydown",this.keydown.bind(this)),t.addEventListener("dragstart",this.stop.bind(this)),t.addEventListener(o.MOUSE.SELECTSTART,this.stop.bind(this)),t.addEventListener("contextmenu",this.stop.bind(this)),t.addEventListener(o.MOUSE.DBLCLICK,this.stop.bind(this))},t}();e.AbstractNavigatable=i},8372:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r])},n(t,e)},function(t,e){function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.AbstractPostable=void 0;var i=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.posted=!1,e}return o(e,t),e.prototype.isPosted=function(){return this.posted},e.prototype.post=function(t,e){this.posted||(void 0!==t&&void 0!==e&&this.html.setAttribute("style","left: "+t+"px; top: "+e+"px;"),this.display(),this.posted=!0)},e.prototype.unpost=function(){if(this.posted){var t=this.html;t.parentNode&&t.parentNode.removeChild(t),this.posted=!1}},e}(r(9328).MenuElement);e.AbstractPostable=i},6765:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r])},n(t,e)},function(t,e){function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.AbstractVariableItem=void 0;var i=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.generateHtml=function(){t.prototype.generateHtml.call(this);var e=this.html;this.span||this.generateSpan(),e.appendChild(this.span),this.update()},e.prototype.register=function(){this.variable.register(this)},e.prototype.unregister=function(){this.variable.unregister(this)},e.prototype.update=function(){this.updateAria(),this.span&&this.updateSpan()},e}(r(1340).AbstractItem);e.AbstractVariableItem=i},5179:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r])},n(t,e)},function(t,e){function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.CloseButton=void 0;var i=r(8372),s=r(2165),a=function(t){function e(e){var r=t.call(this)||this;return r.element=e,r.className=s.HtmlClasses.MENUCLOSE,r.role="button",r}return o(e,t),e.prototype.generateHtml=function(){var t=document.createElement("span");t.classList.add(this.className),t.setAttribute("role",this.role),t.setAttribute("tabindex","0");var e=document.createElement("span");e.textContent="\xd7",t.appendChild(e),this.html=t},e.prototype.display=function(){},e.prototype.unpost=function(){t.prototype.unpost.call(this),this.element.unpost()},e.prototype.keydown=function(e){this.bubbleKey(),t.prototype.keydown.call(this,e)},e.prototype.space=function(t){this.unpost(),this.stop(t)},e.prototype.mousedown=function(t){this.unpost(),this.stop(t)},e}(i.AbstractPostable);e.CloseButton=a},5073:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r])},n(t,e)},function(t,e){function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.ContextMenu=void 0;var i=r(1484),s=r(2165),a=r(1932),l=r(2358),c=function(t){function e(e){var r=t.call(this)||this;return r.factory=e,r.id="",r.moving=!1,r._store=new a.MenuStore(r),r.widgets=[],r.variablePool=new l.VariablePool,r}return o(e,t),e.fromJson=function(t,e){var r=e.pool,n=e.items,o=e.id,i=void 0===o?"":o,s=new this(t);s.id=i;var a=t.get("variable");r.forEach((function(e){return a(t,e,s.pool)}));var l=t.get("items")(t,n,s);return s.items=l,s},e.prototype.generateHtml=function(){this.isPosted()&&this.unpost(),t.prototype.generateHtml.call(this),this._frame=document.createElement("div"),this._frame.classList.add(s.HtmlClasses.MENUFRAME);var e="left: 0px; top: 0px; z-index: 200; width: 100%; height: 100%; border: 0px; padding: 0px; margin: 0px;";this._frame.setAttribute("style","position: absolute; "+e);var r=document.createElement("div");r.setAttribute("style","position: fixed; "+e),this._frame.appendChild(r),r.addEventListener("mousedown",function(t){this.unpost(),this.unpostWidgets(),this.stop(t)}.bind(this))},e.prototype.display=function(){document.body.appendChild(this.frame),this.frame.appendChild(this.html),this.focus()},e.prototype.escape=function(t){this.unpost(),this.unpostWidgets()},e.prototype.unpost=function(){if(t.prototype.unpost.call(this),!(this.widgets.length>0)){this.frame.parentNode.removeChild(this.frame);var e=this.store;this.moving||e.insertTaborder(),e.active.focus()}},e.prototype.left=function(t){this.move_(this.store.previous())},e.prototype.right=function(t){this.move_(this.store.next())},Object.defineProperty(e.prototype,"frame",{get:function(){return this._frame},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"store",{get:function(){return this._store},enumerable:!1,configurable:!0}),e.prototype.post=function(e,r){if(void 0!==r)return this.moving||this.store.removeTaborder(),void t.prototype.post.call(this,e,r);var n,o,i,s=e;if(s instanceof Event?(n=s.target,this.stop(s)):n=s,s instanceof MouseEvent&&(o=s.pageX,i=s.pageY,o||i||!s.clientX||(o=s.clientX+document.body.scrollLeft+document.documentElement.scrollLeft,i=s.clientY+document.body.scrollTop+document.documentElement.scrollTop)),!o&&!i&&n){var a=window.pageXOffset||document.documentElement.scrollLeft,l=window.pageYOffset||document.documentElement.scrollTop,c=n.getBoundingClientRect();o=(c.right+c.left)/2+a,i=(c.bottom+c.top)/2+l}this.store.active=n,this.anchor=this.store.active;var u=this.html;o+u.offsetWidth>document.body.offsetWidth-5&&(o=document.body.offsetWidth-u.offsetWidth-5),this.post(o,i)},e.prototype.registerWidget=function(t){this.widgets.push(t)},e.prototype.unregisterWidget=function(t){var e=this.widgets.indexOf(t);e>-1&&this.widgets.splice(e,1),0===this.widgets.length&&this.unpost()},e.prototype.unpostWidgets=function(){this.widgets.forEach((function(t){return t.unpost()}))},e.prototype.toJson=function(){return{type:""}},e.prototype.move_=function(t){this.anchor&&t!==this.anchor&&(this.moving=!0,this.unpost(),this.post(t),this.moving=!1)},e}(i.AbstractMenu);e.ContextMenu=c},7309:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.CssStyles=void 0;var n=r(2165);!function(t){function e(t){return"."+(n.HtmlClasses[t]||t)}var r={};r[e("INFOCLOSE")]="{ top:.2em; right:.2em;}",r[e("INFOCONTENT")]="{ overflow:auto; text-align:left; font-size:80%; padding:.4em .6em; border:1px inset; margin:1em 0px; max-height:20em; max-width:30em; background-color:#EEEEEE; white-space:normal;}",r[e("INFO")+e("MOUSEPOST")]="{outline:none;}",r[e("INFO")]='{ position:fixed; left:50%; width:auto; text-align:center; border:3px outset; padding:1em 2em; background-color:#DDDDDD; color:black; cursor:default; font-family:message-box; font-size:120%; font-style:normal; text-indent:0; text-transform:none; line-height:normal; letter-spacing:normal; word-spacing:normal; word-wrap:normal; white-space:nowrap; float:none; z-index:201; border-radius: 15px; /* Opera 10.5 and IE9 */ -webkit-border-radius:15px; /* Safari and Chrome */ -moz-border-radius:15px; /* Firefox */ -khtml-border-radius:15px; /* Konqueror */ box-shadow:0px 10px 20px #808080; /* Opera 10.5 and IE9 */ -webkit-box-shadow:0px 10px 20px #808080; /* Safari 3 & Chrome */ -moz-box-shadow:0px 10px 20px #808080; /* Forefox 3.5 */ -khtml-box-shadow:0px 10px 20px #808080; /* Konqueror */ filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=2, OffY=2, Color="gray", Positive="true"); /* IE */}';var o={};o[e("MENU")]="{ position:absolute; background-color:white; color:black; width:auto; padding:5px 0px; border:1px solid #CCCCCC; margin:0; cursor:default; font: menu; text-align:left; text-indent:0; text-transform:none; line-height:normal; letter-spacing:normal; word-spacing:normal; word-wrap:normal; white-space:nowrap; float:none; z-index:201; border-radius: 5px; /* Opera 10.5 and IE9 */ -webkit-border-radius: 5px; /* Safari and Chrome */ -moz-border-radius: 5px; /* Firefox */ -khtml-border-radius: 5px; /* Konqueror */ box-shadow:0px 10px 20px #808080; /* Opera 10.5 and IE9 */ -webkit-box-shadow:0px 10px 20px #808080; /* Safari 3 & Chrome */ -moz-box-shadow:0px 10px 20px #808080; /* Forefox 3.5 */ -khtml-box-shadow:0px 10px 20px #808080; /* Konqueror */}",o[e("MENUITEM")]="{ padding: 1px 2em; background:transparent;}",o[e("MENUARROW")]="{ position:absolute; right:.5em; padding-top:.25em; color:#666666; font-family: null; font-size: .75em}",o[e("MENUACTIVE")+" "+e("MENUARROW")]="{color:white}",o[e("MENUARROW")+e("RTL")]="{left:.5em; right:auto}",o[e("MENUCHECK")]="{ position:absolute; left:.7em; font-family: null}",o[e("MENUCHECK")+e("RTL")]="{ right:.7em; left:auto }",o[e("MENURADIOCHECK")]="{ position:absolute; left: .7em;}",o[e("MENURADIOCHECK")+e("RTL")]="{ right: .7em; left:auto}",o[e("MENUINPUTBOX")]="{ padding-left: 1em; right:.5em; color:#666666; font-family: null;}",o[e("MENUINPUTBOX")+e("RTL")]="{ left: .1em;}",o[e("MENUCOMBOBOX")]="{ left:.1em; padding-bottom:.5em;}",o[e("MENUSLIDER")]="{ left: .1em;}",o[e("SLIDERVALUE")]="{ position:absolute; right:.1em; padding-top:.25em; color:#333333; font-size: .75em}",o[e("SLIDERBAR")]="{ outline: none; background: #d3d3d3}",o[e("MENULABEL")]="{ padding: 1px 2em 3px 1.33em; font-style:italic}",o[e("MENURULE")]="{ border-top: 1px solid #DDDDDD; margin: 4px 3px;}",o[e("MENUDISABLED")]="{ color:GrayText}",o[e("MENUACTIVE")]="{ background-color: #606872; color: white;}",o[e("MENUDISABLED")+":focus"]="{ background-color: #E8E8E8}",o[e("MENULABEL")+":focus"]="{ background-color: #E8E8E8}",o[e("CONTEXTMENU")+":focus"]="{ outline:none}",o[e("CONTEXTMENU")+" "+e("MENUITEM")+":focus"]="{ outline:none}",o[e("SELECTIONMENU")]="{ position:relative; float:left; border-bottom: none; -webkit-box-shadow:none; -webkit-border-radius:0px; }",o[e("SELECTIONITEM")]="{ padding-right: 1em;}",o[e("SELECTION")]="{ right: 40%; width:50%; }",o[e("SELECTIONBOX")]="{ padding: 0em; max-height:20em; max-width: none; background-color:#FFFFFF;}",o[e("SELECTIONDIVIDER")]="{ clear: both; border-top: 2px solid #000000;}",o[e("MENU")+" "+e("MENUCLOSE")]="{ top:-10px; left:-10px}";var i={};i[e("MENUCLOSE")]='{ position:absolute; cursor:pointer; display:inline-block; border:2px solid #AAA; border-radius:18px; -webkit-border-radius: 18px; /* Safari and Chrome */ -moz-border-radius: 18px; /* Firefox */ -khtml-border-radius: 18px; /* Konqueror */ font-family: "Courier New", Courier; font-size:24px; color:#F0F0F0}',i[e("MENUCLOSE")+" span"]="{ display:block; background-color:#AAA; border:1.5px solid; border-radius:18px; -webkit-border-radius: 18px; /* Safari and Chrome */ -moz-border-radius: 18px; /* Firefox */ -khtml-border-radius: 18px; /* Konqueror */ line-height:0; padding:8px 0 6px /* may need to be browser-specific */}",i[e("MENUCLOSE")+":hover"]="{ color:white!important; border:2px solid #CCC!important}",i[e("MENUCLOSE")+":hover span"]="{ background-color:#CCC!important}",i[e("MENUCLOSE")+":hover:focus"]="{ outline:none}";var s=!1,a=!1,l=!1;function c(t){l||(u(i,t),l=!0)}function u(t,e){var r=e||document,n=r.createElement("style");n.type="text/css";var o="";for(var i in t)o+=i,o+=" ",o+=t[i],o+="\n";n.innerHTML=o,r.head.appendChild(n)}t.addMenuStyles=function(t){a||(u(o,t),a=!0,c(t))},t.addInfoStyles=function(t){s||(u(r,t),s=!0,c(t))}}(e.CssStyles||(e.CssStyles={}))},2165:function(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.HtmlAttrs=e.HtmlClasses=void 0;function r(t){return"CtxtMenu_"+t}function n(t){return r(t)}function o(t){return r(t)}e.HtmlClasses={ATTACHED:n("Attached"),CONTEXTMENU:n("ContextMenu"),MENU:n("Menu"),MENUARROW:n("MenuArrow"),MENUACTIVE:n("MenuActive"),MENUCHECK:n("MenuCheck"),MENUCLOSE:n("MenuClose"),MENUCOMBOBOX:n("MenuComboBox"),MENUDISABLED:n("MenuDisabled"),MENUFRAME:n("MenuFrame"),MENUITEM:n("MenuItem"),MENULABEL:n("MenuLabel"),MENURADIOCHECK:n("MenuRadioCheck"),MENUINPUTBOX:n("MenuInputBox"),MENURULE:n("MenuRule"),MENUSLIDER:n("MenuSlider"),MOUSEPOST:n("MousePost"),RTL:n("RTL"),INFO:n("Info"),INFOCLOSE:n("InfoClose"),INFOCONTENT:n("InfoContent"),INFOSIGNATURE:n("InfoSignature"),INFOTITLE:n("InfoTitle"),SLIDERVALUE:n("SliderValue"),SLIDERBAR:n("SliderBar"),SELECTION:n("Selection"),SELECTIONBOX:n("SelectionBox"),SELECTIONMENU:n("SelectionMenu"),SELECTIONDIVIDER:n("SelectionDivider"),SELECTIONITEM:n("SelectionItem")},e.HtmlAttrs={COUNTER:o("Counter"),KEYDOWNFUNC:o("keydownFunc"),CONTEXTMENUFUNC:o("contextmenuFunc"),OLDTAB:o("Oldtabindex"),TOUCHFUNC:o("TouchFunc")}},4922:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r])},n(t,e)},function(t,e){function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.Info=void 0;var i=r(5179),s=r(2165),a=function(t){function e(e,r,n){var o=t.call(this)||this;return o.title=e,o.signature=n,o.className=s.HtmlClasses.INFO,o.role="dialog",o.contentDiv=o.generateContent(),o.close=o.generateClose(),o.content=r||function(){return""},o}return o(e,t),e.prototype.attachMenu=function(t){this.menu=t},e.prototype.generateHtml=function(){t.prototype.generateHtml.call(this);var e=this.html;e.appendChild(this.generateTitle()),e.appendChild(this.contentDiv),e.appendChild(this.generateSignature()),e.appendChild(this.close.html),e.setAttribute("tabindex","0")},e.prototype.post=function(){t.prototype.post.call(this);var e=document.documentElement,r=this.html,n=window.innerHeight||e.clientHeight||e.scrollHeight||0,o=Math.floor(-r.offsetWidth/2),i=Math.floor((n-r.offsetHeight)/3);r.setAttribute("style","margin-left: "+o+"px; top: "+i+"px;"),window.event instanceof MouseEvent&&r.classList.add(s.HtmlClasses.MOUSEPOST),r.focus()},e.prototype.display=function(){this.menu.registerWidget(this),this.contentDiv.innerHTML=this.content();var t=this.menu.html;t.parentNode&&t.parentNode.removeChild(t),this.menu.frame.appendChild(this.html)},e.prototype.click=function(t){},e.prototype.keydown=function(e){this.bubbleKey(),t.prototype.keydown.call(this,e)},e.prototype.escape=function(t){this.unpost()},e.prototype.unpost=function(){t.prototype.unpost.call(this),this.html.classList.remove(s.HtmlClasses.MOUSEPOST),this.menu.unregisterWidget(this)},e.prototype.generateClose=function(){var t=new i.CloseButton(this),e=t.html;return e.classList.add(s.HtmlClasses.INFOCLOSE),e.setAttribute("aria-label","Close Dialog Box"),t},e.prototype.generateTitle=function(){var t=document.createElement("span");return t.innerHTML=this.title,t.classList.add(s.HtmlClasses.INFOTITLE),t},e.prototype.generateContent=function(){var t=document.createElement("div");return t.classList.add(s.HtmlClasses.INFOCONTENT),t.setAttribute("tabindex","0"),t},e.prototype.generateSignature=function(){var t=document.createElement("span");return t.innerHTML=this.signature,t.classList.add(s.HtmlClasses.INFOSIGNATURE),t},e.prototype.toJson=function(){return{type:""}},e}(r(8372).AbstractPostable);e.Info=a},1409:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r])},n(t,e)},function(t,e){function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.Checkbox=void 0;var i=r(6765),s=r(2556),a=r(2165),l=function(t){function e(e,r,n,o){var i=t.call(this,e,"checkbox",r,o)||this;return i.role="menuitemcheckbox",i.variable=e.pool.lookup(n),i.register(),i}return o(e,t),e.fromJson=function(t,e,r){return new this(r,e.content,e.variable,e.id)},e.prototype.executeAction=function(){this.variable.setValue(!this.variable.getValue()),s.MenuUtil.close(this)},e.prototype.generateSpan=function(){this.span=document.createElement("span"),this.span.textContent="\u2713",this.span.classList.add(a.HtmlClasses.MENUCHECK)},e.prototype.updateAria=function(){this.html.setAttribute("aria-checked",this.variable.getValue()?"true":"false")},e.prototype.updateSpan=function(){this.span.style.display=this.variable.getValue()?"":"none"},e.prototype.toJson=function(){return{type:""}},e}(i.AbstractVariableItem);e.Checkbox=l},9886:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r])},n(t,e)},function(t,e){function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.Combo=void 0;var i=r(6765),s=r(2556),a=r(2165),l=r(3205),c=function(t){function e(e,r,n,o){var i=t.call(this,e,"combobox",r,o)||this;return i.role="combobox",i.inputEvent=!1,i.variable=e.pool.lookup(n),i.register(),i}return o(e,t),e.fromJson=function(t,e,r){return new this(r,e.content,e.variable,e.id)},e.prototype.executeAction=function(){this.variable.setValue(this.input.value,s.MenuUtil.getActiveElement(this))},e.prototype.space=function(e){t.prototype.space.call(this,e),s.MenuUtil.close(this)},e.prototype.focus=function(){t.prototype.focus.call(this),this.input.focus()},e.prototype.unfocus=function(){t.prototype.unfocus.call(this),this.updateSpan()},e.prototype.generateHtml=function(){t.prototype.generateHtml.call(this),this.html.classList.add(a.HtmlClasses.MENUCOMBOBOX)},e.prototype.generateSpan=function(){this.span=document.createElement("span"),this.span.classList.add(a.HtmlClasses.MENUINPUTBOX),this.input=document.createElement("input"),this.input.addEventListener("keydown",this.inputKey.bind(this)),this.input.setAttribute("size","10em"),this.input.setAttribute("type","text"),this.input.setAttribute("tabindex","-1"),this.span.appendChild(this.input)},e.prototype.inputKey=function(t){this.bubbleKey(),this.inputEvent=!0},e.prototype.keydown=function(e){if(this.inputEvent&&e.keyCode!==l.KEY.ESCAPE&&e.keyCode!==l.KEY.RETURN)return this.inputEvent=!1,void e.stopPropagation();t.prototype.keydown.call(this,e),e.stopPropagation()},e.prototype.updateAria=function(){},e.prototype.updateSpan=function(){var t;try{t=this.variable.getValue(s.MenuUtil.getActiveElement(this))}catch(e){t=""}this.input.value=t},e.prototype.toJson=function(){return{type:""}},e}(i.AbstractVariableItem);e.Combo=c},3467:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r])},n(t,e)},function(t,e){function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.Command=void 0;var i=r(1340),s=r(2556),a=function(t){function e(e,r,n,o){var i=t.call(this,e,"command",r,o)||this;return i.command=n,i}return o(e,t),e.fromJson=function(t,e,r){return new this(r,e.content,e.action,e.id)},e.prototype.executeAction=function(){try{this.command(s.MenuUtil.getActiveElement(this))}catch(t){s.MenuUtil.error(t,"Illegal command callback.")}s.MenuUtil.close(this)},e.prototype.toJson=function(){return{type:""}},e}(i.AbstractItem);e.Command=a},2965:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r])},n(t,e)},function(t,e){function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.Label=void 0;var i=r(1340),s=r(2165),a=function(t){function e(e,r,n){return t.call(this,e,"label",r,n)||this}return o(e,t),e.fromJson=function(t,e,r){return new this(r,e.content,e.id)},e.prototype.generateHtml=function(){t.prototype.generateHtml.call(this),this.html.classList.add(s.HtmlClasses.MENULABEL)},e.prototype.toJson=function(){return{type:""}},e}(i.AbstractItem);e.Label=a},385:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r])},n(t,e)},function(t,e){function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.Radio=void 0;var i=r(6765),s=r(2556),a=r(2165),l=function(t){function e(e,r,n,o){var i=t.call(this,e,"radio",r,o)||this;return i.role="menuitemradio",i.variable=e.pool.lookup(n),i.register(),i}return o(e,t),e.fromJson=function(t,e,r){return new this(r,e.content,e.variable,e.id)},e.prototype.executeAction=function(){this.variable.setValue(this.id),s.MenuUtil.close(this)},e.prototype.generateSpan=function(){this.span=document.createElement("span"),this.span.textContent="\u2713",this.span.classList.add(a.HtmlClasses.MENURADIOCHECK)},e.prototype.updateAria=function(){this.html.setAttribute("aria-checked",this.variable.getValue()===this.id?"true":"false")},e.prototype.updateSpan=function(){this.span.style.display=this.variable.getValue()===this.id?"":"none"},e.prototype.toJson=function(){return{type:""}},e}(i.AbstractVariableItem);e.Radio=l},3463:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r])},n(t,e)},function(t,e){function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.Rule=void 0;var i=r(9329),s=r(2165),a=function(t){function e(e){var r=t.call(this,e,"rule")||this;return r.className=s.HtmlClasses.MENUITEM,r.role="separator",r}return o(e,t),e.fromJson=function(t,e,r){return new this(r)},e.prototype.generateHtml=function(){t.prototype.generateHtml.call(this);var e=this.html;e.classList.add(s.HtmlClasses.MENURULE),e.setAttribute("aria-orientation","vertical")},e.prototype.addEvents=function(t){},e.prototype.toJson=function(){return{type:"rule"}},e}(i.AbstractEntry);e.Rule=a},7625:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r])},n(t,e)},function(t,e){function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.Slider=void 0;var i=r(6765),s=r(2556),a=r(2165),l=r(3205),c=function(t){function e(e,r,n,o){var i=t.call(this,e,"slider",r,o)||this;return i.role="slider",i.labelId="ctx_slideLabel"+s.MenuUtil.counter(),i.valueId="ctx_slideValue"+s.MenuUtil.counter(),i.inputEvent=!1,i.variable=e.pool.lookup(n),i.register(),i}return o(e,t),e.fromJson=function(t,e,r){return new this(r,e.content,e.variable,e.id)},e.prototype.executeAction=function(){this.variable.setValue(this.input.value,s.MenuUtil.getActiveElement(this)),this.update()},e.prototype.space=function(e){t.prototype.space.call(this,e),s.MenuUtil.close(this)},e.prototype.focus=function(){t.prototype.focus.call(this),this.input.focus()},e.prototype.unfocus=function(){t.prototype.unfocus.call(this),this.updateSpan()},e.prototype.generateHtml=function(){t.prototype.generateHtml.call(this),this.html.classList.add(a.HtmlClasses.MENUSLIDER),this.valueSpan=document.createElement("span"),this.valueSpan.setAttribute("id",this.valueId),this.valueSpan.classList.add(a.HtmlClasses.SLIDERVALUE),this.html.appendChild(this.valueSpan)},e.prototype.generateSpan=function(){this.span=document.createElement("span"),this.labelSpan=document.createElement("span"),this.labelSpan.setAttribute("id",this.labelId),this.labelSpan.appendChild(this.html.childNodes[0]),this.html.appendChild(this.labelSpan),this.input=document.createElement("input"),this.input.setAttribute("type","range"),this.input.setAttribute("min","0"),this.input.setAttribute("max","100"),this.input.setAttribute("aria-valuemin","0"),this.input.setAttribute("aria-valuemax","100"),this.input.setAttribute("aria-labelledby",this.labelId),this.input.addEventListener("keydown",this.inputKey.bind(this)),this.input.addEventListener("input",this.executeAction.bind(this)),this.input.classList.add(a.HtmlClasses.SLIDERBAR),this.span.appendChild(this.input)},e.prototype.inputKey=function(t){this.inputEvent=!0},e.prototype.mousedown=function(t){t.stopPropagation()},e.prototype.mouseup=function(t){event.stopPropagation()},e.prototype.keydown=function(e){var r=e.keyCode;return r===l.KEY.UP||r===l.KEY.DOWN?(e.preventDefault(),void t.prototype.keydown.call(this,e)):this.inputEvent&&r!==l.KEY.ESCAPE&&r!==l.KEY.RETURN?(this.inputEvent=!1,void e.stopPropagation()):(t.prototype.keydown.call(this,e),void e.stopPropagation())},e.prototype.updateAria=function(){var t=this.variable.getValue();t&&this.input&&(this.input.setAttribute("aria-valuenow",t),this.input.setAttribute("aria-valuetext",t+"%"))},e.prototype.updateSpan=function(){var t;try{t=this.variable.getValue(s.MenuUtil.getActiveElement(this)),this.valueSpan.innerHTML=t+"%"}catch(e){t=""}this.input.value=t},e.prototype.toJson=function(){return{type:""}},e}(i.AbstractVariableItem);e.Slider=c},6186:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r])},n(t,e)},function(t,e){function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.Submenu=void 0;var i=r(1340),s=r(2165),a=function(t){function e(e,r,n){var o=t.call(this,e,"submenu",r,n)||this;return o._submenu=null,o}return o(e,t),e.fromJson=function(t,e,r){var n=e.content,o=e.menu,i=new this(r,n,e.id),s=t.get("subMenu")(t,o,i);return i.submenu=s,i},Object.defineProperty(e.prototype,"submenu",{get:function(){return this._submenu},set:function(t){this._submenu=t},enumerable:!1,configurable:!0}),e.prototype.mouseover=function(t){this.focus(),this.stop(t)},e.prototype.mouseout=function(t){this.stop(t)},e.prototype.unfocus=function(){if(this.submenu.isPosted()){if(this.menu.focused!==this)return t.prototype.unfocus.call(this),void this.menu.unpostSubmenus();this.html.setAttribute("tabindex","-1"),this.html.blur()}else t.prototype.unfocus.call(this)},e.prototype.focus=function(){t.prototype.focus.call(this),this.submenu.isPosted()||this.disabled||this.submenu.post()},e.prototype.executeAction=function(){this.submenu.isPosted()?this.submenu.unpost():this.submenu.post()},e.prototype.generateHtml=function(){t.prototype.generateHtml.call(this);var e=this.html;this.span=document.createElement("span"),this.span.textContent="\u25ba",this.span.classList.add(s.HtmlClasses.MENUARROW),e.appendChild(this.span),e.setAttribute("aria-haspopup","true")},e.prototype.left=function(e){this.submenu.isPosted()?this.submenu.unpost():t.prototype.left.call(this,e)},e.prototype.right=function(t){this.submenu.isPosted()?this.submenu.down(t):this.submenu.post()},e.prototype.toJson=function(){return{type:""}},e}(i.AbstractItem);e.Submenu=a},3205:function(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.KEY=void 0,function(t){t[t.RETURN=13]="RETURN",t[t.ESCAPE=27]="ESCAPE",t[t.SPACE=32]="SPACE",t[t.LEFT=37]="LEFT",t[t.UP=38]="UP",t[t.RIGHT=39]="RIGHT",t[t.DOWN=40]="DOWN"}(e.KEY||(e.KEY={}))},9328:function(t,e,r){var n,o=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r])},n(t,e)},function(t,e){function r(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)});Object.defineProperty(e,"__esModule",{value:!0}),e.MenuElement=void 0;var i=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.prototype.addAttributes=function(t){for(var e in t)this.html.setAttribute(e,t[e])},Object.defineProperty(e.prototype,"html",{get:function(){return this._html||this.generateHtml(),this._html},set:function(t){this._html=t,this.addEvents(t)},enumerable:!1,configurable:!0}),e.prototype.generateHtml=function(){var t=document.createElement("div");t.classList.add(this.className),t.setAttribute("role",this.role),this.html=t},e.prototype.focus=function(){var t=this.html;t.setAttribute("tabindex","0"),t.focus()},e.prototype.unfocus=function(){var t=this.html;t.hasAttribute("tabindex")&&t.setAttribute("tabindex","-1");try{t.blur()}catch(t){}t.blur()},e}(r(2868).AbstractNavigatable);e.MenuElement=i},1932:function(t,e,r){var n=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.MenuStore=void 0;var o=r(2556),i=r(2165),s=r(3205),a=function(){function t(t){this.menu=t,this.store=[],this._active=null,this.counter=0,this.attachedClass=i.HtmlClasses.ATTACHED+"_"+o.MenuUtil.counter(),this.taborder=!0,this.attrMap={}}return Object.defineProperty(t.prototype,"active",{get:function(){return this._active},set:function(t){do{if(-1!==this.store.indexOf(t)){this._active=t;break}t=t.parentNode}while(t)},enumerable:!1,configurable:!0}),t.prototype.next=function(){var t=this.store.length;if(0===t)return this.active=null,null;var e=this.store.indexOf(this.active);return e=-1===e?0:e0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s},i=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,r=e&&t[e],n=0;if(r)return r.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&n>=t.length&&(t=void 0),{value:t&&t[n++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},s=this&&this.__spread||function(){for(var t=[],e=0;e0)&&!(n=i.next()).done;)s.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(r=i.return)&&r.call(i)}finally{if(o)throw o.error}}return s};Object.defineProperty(e,"__esModule",{value:!0}),e.SelectionBox=e.SelectionMenu=void 0;var s=r(2556),a=r(2165),l=r(1484),c=r(4922),u=function(t){function e(e){var r=t.call(this)||this;return r.anchor=e,r.className=a.HtmlClasses.SELECTIONMENU,r.variablePool=r.anchor.menu.pool,r.baseMenu=r.anchor.menu,r}return o(e,t),e.fromJson=function(t,e,r){var n=e.title,o=e.values,i=e.variable,s=new this(r),a=t.get("label")(t,{content:n||"",id:n||"id"},s),l=t.get("rule")(t,{},s),c=o.map((function(e){return t.get("radio")(t,{content:e,variable:i,id:e},s)})),u=[a,l].concat(c);return s.items=u,s},e.prototype.generateHtml=function(){t.prototype.generateHtml.call(this),this.items.forEach((function(t){return t.html.classList.add(a.HtmlClasses.SELECTIONITEM)}))},e.prototype.display=function(){},e.prototype.right=function(t){this.anchor.right(t)},e.prototype.left=function(t){this.anchor.left(t)},e}(l.AbstractMenu);e.SelectionMenu=u;var p=function(t){function e(e,r,n,o){void 0===n&&(n="none"),void 0===o&&(o="vertical");var i=t.call(this,e,null,r)||this;return i.style=n,i.grid=o,i._selections=[],i.prefix="ctxt-selection",i._balanced=!0,i}return o(e,t),e.fromJson=function(t,e,r){var n=e.title,o=e.signature,i=e.selections,s=new this(n,o,e.order,e.grid);s.attachMenu(r);var a=i.map((function(e){return t.get("selectionMenu")(t,e,s)}));return s.selections=a,s},e.prototype.attachMenu=function(t){this.menu=t},Object.defineProperty(e.prototype,"selections",{get:function(){return this._selections},set:function(t){var e=this;this._selections=[],t.forEach((function(t){return e.addSelection(t)}))},enumerable:!1,configurable:!0}),e.prototype.addSelection=function(t){t.anchor=this,this._selections.push(t)},e.prototype.rowDiv=function(t){var e=this,r=document.createElement("div");this.contentDiv.appendChild(r);var n=t.map((function(t){return r.appendChild(t.html),t.html.id||(t.html.id=e.prefix+s.MenuUtil.counter()),t.html.getBoundingClientRect()})),o=n.map((function(t){return t.width})),i=o.reduce((function(t,e){return t+e}),0),l=n.reduce((function(t,e){return Math.max(t,e.height)}),0);return r.classList.add(a.HtmlClasses.SELECTIONDIVIDER),r.setAttribute("style","height: "+l+"px;"),[r,i,l,o]},e.prototype.display=function(){if(t.prototype.display.call(this),this.order(),this.selections.length){for(var e=[],r=0,n=[],o=this.getChunkSize(this.selections.length),s=function(t){var s=a.selections.slice(t,t+o),l=i(a.rowDiv(s),4),c=l[0],u=l[1],p=l[2],h=l[3];e.push(c),r=Math.max(r,u),s.forEach((function(t){return t.html.style.height=p+"px"})),n=a.combineColumn(n,h)},a=this,l=0;ldocument.body.offsetWidth-5&&(i=Math.max(5,i-o-r.offsetWidth+6)),t.prototype.post.call(this,i,s)}},e.prototype.display=function(){this.baseMenu.frame.appendChild(this.html)},e.prototype.setBaseMenu=function(){var t=this;do{t=t.anchor.menu}while(t instanceof e);this.baseMenu=t},e.prototype.left=function(t){this.focused=null,this.anchor.focus()},e.prototype.toJson=function(){return{type:""}},e}(r(1484).AbstractMenu);e.SubMenu=i},3737:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.Variable=void 0;var n=r(2556),o=function(){function t(t,e,r){this._name=t,this.getter=e,this.setter=r,this.items=[]}return t.fromJson=function(t,e,r){var n=new this(e.name,e.getter,e.setter);r.insert(n)},Object.defineProperty(t.prototype,"name",{get:function(){return this._name},enumerable:!1,configurable:!0}),t.prototype.getValue=function(t){try{return this.getter(t)}catch(t){return n.MenuUtil.error(t,"Command of variable "+this.name+" failed."),null}},t.prototype.setValue=function(t,e){try{this.setter(t,e)}catch(t){n.MenuUtil.error(t,"Command of variable "+this.name+" failed.")}this.update()},t.prototype.register=function(t){-1===this.items.indexOf(t)&&this.items.push(t)},t.prototype.unregister=function(t){var e=this.items.indexOf(t);-1!==e&&this.items.splice(e,1)},t.prototype.update=function(){this.items.forEach((function(t){return t.update()}))},t.prototype.registerCallback=function(t){this.items.forEach((function(e){return e.registerCallback(t)}))},t.prototype.unregisterCallback=function(t){this.items.forEach((function(e){return e.unregisterCallback(t)}))},t.prototype.toJson=function(){return{type:"variable",name:this.name,getter:this.getter.toString(),setter:this.setter.toString()}},t}();e.Variable=o},2358:function(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.VariablePool=void 0;var r=function(){function t(){this.pool={}}return t.prototype.insert=function(t){this.pool[t.name]=t},t.prototype.lookup=function(t){return this.pool[t]},t.prototype.remove=function(t){delete this.pool[t]},t.prototype.update=function(){for(var t in this.pool)this.pool[t].update()},t}();e.VariablePool=r},3921:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.AbstractAudioRenderer=void 0;const n=r(5897);e.AbstractAudioRenderer=class{constructor(){this.separator_=" "}setSeparator(t){this.separator_=t}getSeparator(){return"braille"===n.default.getInstance().modality?"":this.separator_}error(t){return null}merge(t){let e="";const r=t.length-1;for(let n,o=0;n=t[o];o++)if(e+=n.speech,odelete t[e])),e.open.forEach((r=>t[r]=e[r]));const r=Object.keys(t);t.open=r},e.sortClose=function(t,e){if(t.length<=1)return t;const r=[];for(let n,o=0;n=e[o],t.length;o++)n.close&&n.close.length&&n.close.forEach((function(e){const n=t.indexOf(e);-1!==n&&(r.unshift(e),t.splice(n,1))}));return r};let a={},l=[];function c(t,e){const r=t[t.length-1];if(r){if(f(e)&&f(r)){if(void 0===r.join)return void(r.span=r.span.concat(e.span));const t=r.span.pop(),n=e.span.shift();return r.span.push(t+r.join+n),r.span=r.span.concat(e.span),void(r.join=e.join)}h(e)&&h(r)?r.pause=s(r.pause,e.pause):t.push(e)}else t.push(e)}function u(t,e){t.rate&&(e.rate=t.rate),t.pitch&&(e.pitch=t.pitch),t.volume&&(e.volume=t.volume)}function p(t){return"object"==typeof t&&t.open}function h(t){return"object"==typeof t&&1===Object.keys(t).length&&Object.keys(t)[0]===o.personalityProps.PAUSE}function f(t){const e=Object.keys(t);return"object"==typeof t&&(1===e.length&&"span"===e[0]||2===e.length&&("span"===e[0]&&"join"===e[1]||"span"===e[1]&&"join"===e[0]))}function d(t,e,r,n,a,l=!1){if(l){const l=t[t.length-1];let c;if(l&&(c=l[o.personalityProps.JOIN]),l&&!e.speech&&a&&h(l)){const t=o.personalityProps.PAUSE;l[t]=s(l[t],a[t]),a=null}if(l&&e.speech&&0===Object.keys(r).length&&f(l)){if(void 0!==c){const t=l.span.pop();e=new i.Span(t.speech+c+e.speech,t.attributes)}l.span.push(e),e=new i.Span("",{}),l[o.personalityProps.JOIN]=n}}0!==Object.keys(r).length&&t.push(r),e.speech&&t.push({span:[e],join:n}),a&&t.push(a)}function m(t,e){if(!e)return t;const r={};for(const n of o.personalityPropList){const o=t[n],i=e[n];if(!o&&!i||o&&i&&o===i)continue;const s=o||0;p(r)||(r.open=[],r.close=[]),o||r.close.push(n),i||r.open.push(n),i&&o&&(r.close.push(n),r.open.push(n)),e[n]=s,r[n]=s,a[n]?a[n].push(s):a[n]=[s]}if(p(r)){let t=r.close.slice();for(;t.length>0;){let o=l.pop();const i=(0,n.setdifference)(o,t);if(t=(0,n.setdifference)(t,o),o=i,0!==t.length){if(0!==o.length){r.close=r.close.concat(o),r.open=r.open.concat(o);for(let t,n=0;t=o[n];n++)r[t]=e[t]}}else 0!==o.length&&l.push(o)}l.push(r.open)}return r}e.personalityMarkup=function(t){a={},l=[];let e=[];const r={};for(let n,i=0;n=t[i];i++){let t=null;const i=n.descriptionSpan(),s=n.personality,a=s[o.personalityProps.JOIN];delete s[o.personalityProps.JOIN],void 0!==s[o.personalityProps.PAUSE]&&(t={[o.personalityProps.PAUSE]:s[o.personalityProps.PAUSE]},delete s[o.personalityProps.PAUSE]);d(e,i,m(s,r),a,t,!0)}return e=e.concat(function(){const t=[];for(let e=l.length-1;e>=0;e--){const r=l[e];if(r.length){const e={open:[],close:[]};for(let t=0;t"string"==typeof t?new c.Span(t,{}):t)),r=m.get(n.default.getInstance().markup);return r?r.merge(e):t.join()},e.finalize=function(t){const e=m.get(n.default.getInstance().markup);return e?e.finalize(t):t},e.error=function(t){const e=m.get(n.default.getInstance().markup);return e?e.error(t):""},e.registerRenderer=function(t,e){m.set(t,e)},e.isXml=function(){return m.get(n.default.getInstance().markup)instanceof f.XmlRenderer}},8639:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.LayoutRenderer=void 0;const n=r(2057),o=r(5740),i=r(4440),s=r(3706),a=r(2456);class l extends a.XmlRenderer{finalize(t){return function(t){c="";const e=o.parseInput(`${t}`);return n.Debugger.getInstance().output(o.formatXml(e.toString())),c=f(e),c}(t)}pause(t){return""}prosodyElement(t,e){return t===i.personalityProps.LAYOUT?`<${e}>`:""}closeTag(t){return``}markup(t){const e=[];let r=[];for(const n of t){if(!n.layout){r.push(n);continue}e.push(this.processContent(r)),r=[];const t=n.layout;t.match(/^begin/)?e.push("<"+t.replace(/^begin/,"")+">"):t.match(/^end/)?e.push(""):console.warn("Something went wrong with layout markup: "+t)}return e.push(this.processContent(r)),e.join("")}processContent(t){const e=[],r=s.personalityMarkup(t);for(let t,n=0;t=r[n];n++)t.span?e.push(this.merge(t.span)):s.isPauseElement(t);return e.join("")}}e.LayoutRenderer=l;let c="";const u={TABLE:function(t){let e=g(t);e.forEach((t=>{t.cells=t.cells.slice(1).slice(0,-1),t.width=t.width.slice(1).slice(0,-1)}));const[r,n]=b(e);return e=v(e,n),_(e,r)},CASES:function(t){let e=g(t);e.forEach((t=>{t.cells=t.cells.slice(0,-1),t.width=t.width.slice(0,-1)}));const[r,n]=b(e);return e=v(e,n),_(e,r)},CAYLEY:function(t){let e=g(t);e.forEach((t=>{t.cells=t.cells.slice(1).slice(0,-1),t.width=t.width.slice(1).slice(0,-1),t.sep=t.sep+t.sep}));const[r,n]=b(e),o={lfence:"",rfence:"",cells:n.map((t=>"\u2810"+new Array(t).join("\u2812"))),width:n,height:1,sep:e[0].sep};return e.splice(1,0,o),e=v(e,n),_(e,r)},MATRIX:function(t){let e=g(t);const[r,n]=b(e);return e=v(e,n),_(e,r)},CELL:f,FENCE:f,ROW:f,FRACTION:function(t){const[e,r,,n,o]=Array.from(t.childNodes),i=p(r),s=p(n),a=m(i),l=m(s);let c=Math.max(a,l);const u=e+new Array(c+1).join("\u2812")+o;return c=u.length,`${x(i,c)}\n${u}\n${x(s,c)}`},NUMERATOR:E,DENOMINATOR:E};function p(t){const e=o.tagName(t),r=u[e];return r?r(t):t.textContent}function h(t,e){if(!t||!e)return t+e;const r=d(t),n=d(e),o=r-n;t=o<0?y(t,n,m(t)):t,e=o>0?y(e,r,m(e)):e;const i=t.split(/\r\n|\r|\n/),s=e.split(/\r\n|\r|\n/),a=[];for(let t=0;tMath.max(e.length,t)),0)}function y(t,e,r){return t=function(t,e){const r=e-d(t);return t+(r>0?new Array(r+1).join("\n"):"")}(t,e),function(t,e){const r=t.split(/\r\n|\r|\n/),n=[];for(const t of r){const r=e-t.length;n.push(t+(r>0?new Array(r+1).join("\u2800"):""))}return n.join("\n")}(t,r)}function g(t){const e=Array.from(t.childNodes),r=[];for(const t of e)t.nodeType===o.NodeType.ELEMENT_NODE&&r.push(O(t));return r}function b(t){const e=t.reduce(((t,e)=>Math.max(e.height,t)),0),r=[];for(let e=0;et.width[e])).reduce(((t,e)=>Math.max(t,e)),0));return[e,r]}function v(t,e){const r=[];for(const n of t){if(0===n.height)continue;const t=[];for(let r=0;rt.lfence+t.cells.join(t.sep)+t.rfence)).join("\n");const r=[];for(const e of t){const t=S(e.sep,e.height);let n=e.cells.shift();for(;e.cells.length;)n=h(n,t),n=h(n,e.cells.shift());n=h(S(e.lfence,e.height),n),n=h(n,S(e.rfence,e.height)),r.push(n),r.push(e.lfence+new Array(m(n)-3).join(e.sep)+e.rfence)}return r.slice(0,-1).join("\n")}function S(t,e){let r="";for(;e;)r+=t+"\n",e--;return r.slice(0,-1)}function M(t){return t.nodeType===o.NodeType.ELEMENT_NODE&&"FENCE"===o.tagName(t)?p(t):""}function O(t){const e=Array.from(t.childNodes),r=M(e[0]),n=M(e[e.length-1]);r&&e.shift(),n&&e.pop();let i="";const s=[];for(const t of e){if(t.nodeType===o.NodeType.TEXT_NODE){i=t.textContent;continue}const e=p(t);s.push(e)}return{lfence:r,rfence:n,sep:i,cells:s,height:s.reduce(((t,e)=>Math.max(d(e),t)),0),width:s.map(m)}}function x(t,e){const r=(e-m(t))/2,[n,o]=Math.floor(r)===r?[r,r]:[Math.floor(r),Math.ceil(r)],i=t.split(/\r\n|\r|\n/),s=[],[a,l]=[new Array(n+1).join("\u2800"),new Array(o+1).join("\u2800")];for(const t of i)s.push(a+t+l);return s.join("\n")}function E(t){const e=t.firstChild,r=f(t);if(e&&e.nodeType===o.NodeType.ELEMENT_NODE){if("ENGLISH"===o.tagName(e))return"\u2830"+r;if("NUMBER"===o.tagName(e))return"\u283c"+r}return r}},182:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.MarkupRenderer=void 0;const n=r(4440),o=r(3921);class i extends o.AbstractAudioRenderer{constructor(){super(...arguments),this.ignoreElements=[n.personalityProps.LAYOUT],this.scaleFunction=null}setScaleFunction(t,e,r,n,o=0){this.scaleFunction=i=>{const s=(i-t)/(e-t),a=r*(1-s)+n*s;return+(Math.round(a+"e+"+o)+"e-"+o)}}applyScaleFunction(t){return this.scaleFunction?this.scaleFunction(t):t}ignoreElement(t){return-1!==this.ignoreElements.indexOf(t)}}e.MarkupRenderer=i},8990:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.PunctuationRenderer=void 0;const n=r(4440),o=r(3921),i=r(3706);class s extends o.AbstractAudioRenderer{markup(t){const e=i.personalityMarkup(t);let r="",o=null,s=!1;for(let t,a=0;t=e[a];a++)i.isMarkupElement(t)||(i.isPauseElement(t)?s&&(o=i.mergePause(o,t,Math.max)):(o&&(r+=this.pause(o[n.personalityProps.PAUSE]),o=null),r+=(s?this.getSeparator():"")+this.merge(t.span),s=!0));return r}pause(t){let e;return e="number"==typeof t?t<=250?"short":t<=500?"medium":"long":t,s.PAUSE_PUNCTUATION.get(e)||""}}e.PunctuationRenderer=s,s.PAUSE_PUNCTUATION=new Map([["short",","],["medium",";"],["long","."]])},6660:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.SableRenderer=void 0;const n=r(4440),o=r(2456);class i extends o.XmlRenderer{finalize(t){return''+this.getSeparator()+t+this.getSeparator()+""}pause(t){return''}prosodyElement(t,e){switch(e=this.applyScaleFunction(e),t){case n.personalityProps.PITCH:return'';case n.personalityProps.RATE:return'';case n.personalityProps.VOLUME:return'';default:return"<"+t.toUpperCase()+' VALUE="'+e+'">'}}closeTag(t){return""}}e.SableRenderer=i},9536:function(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.Span=void 0;e.Span=class{constructor(t,e){this.speech=t,this.attributes=e}}},7504:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.SsmlRenderer=void 0;const n=r(5897),o=r(4440),i=r(2456);class s extends i.XmlRenderer{finalize(t){return''+this.getSeparator()+t+this.getSeparator()+""}pause(t){return''}prosodyElement(t,e){const r=(e=Math.floor(this.applyScaleFunction(e)))<0?e.toString():"+"+e.toString();return"":'%">')}closeTag(t){return""}}e.SsmlRenderer=s},3757:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.SsmlStepRenderer=void 0;const n=r(7504);class o extends n.SsmlRenderer{markup(t){return o.MARKS={},super.markup(t)}merge(t){const e=[];for(let r=0;r'),o.MARKS[i]=!0),1===n.speech.length&&n.speech.match(/[a-zA-Z]/)?e.push(''+n.speech+""):e.push(n.speech)}return e.join(this.getSeparator())}}e.SsmlStepRenderer=o,o.CHARACTER_ATTR="character",o.MARKS={}},4032:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.StringRenderer=void 0;const n=r(3921),o=r(3706);class i extends n.AbstractAudioRenderer{markup(t){let e="";const r=(0,o.personalityMarkup)(t).filter((t=>t.span));if(!r.length)return e;const n=r.length-1;for(let t,o=0;t=r[o];o++){if(t.span&&(e+=this.merge(t.span)),o>=n)continue;const r=t.join;e+=void 0===r?this.getSeparator():r}return e}}e.StringRenderer=i},2456:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.XmlRenderer=void 0;const n=r(5897),o=r(3706),i=r(182);class s extends i.MarkupRenderer{markup(t){this.setScaleFunction(-2,2,-100,100,2);const e=o.personalityMarkup(t),r=[],i=[];for(let t,s=0;t=e[s];s++)if(t.span)r.push(this.merge(t.span));else if(o.isPauseElement(t))r.push(this.pause(t));else{if(t.close.length)for(let e=0;e{r.push(this.prosodyElement(e,t[e])),i.push(e)}))}}return r.join(" ")}}e.XmlRenderer=s},707:function(t,e){function r(t,e){return t?e?t.filter((t=>e.indexOf(t)<0)):t:[]}Object.defineProperty(e,"__esModule",{value:!0}),e.union=e.setdifference=e.interleaveLists=e.removeEmpty=void 0,e.removeEmpty=function(t){return t.filter((t=>t))},e.interleaveLists=function(t,e){const r=[];for(;t.length||e.length;)t.length&&r.push(t.shift()),e.length&&r.push(e.shift());return r},e.setdifference=r,e.union=function(t,e){return t&&e?t.concat(r(e,t)):t||e||[]}},2139:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.loadScript=e.loadMapsForIE_=e.installWGXpath_=e.loadWGXpath_=e.mapsForIE=e.detectEdge=e.detectIE=void 0;const n=r(2315),o=r(5274);function i(t){l(n.default.WGXpath),s(t)}function s(t,e){let r=e||1;"undefined"==typeof wgxpath&&r<10?setTimeout((function(){s(t,r++)}),200):r>=10||(n.default.wgxpath=wgxpath,t?n.default.wgxpath.install({document:document}):n.default.wgxpath.install(),o.xpath.evaluate=document.evaluate,o.xpath.result=XPathResult,o.xpath.createNSResolver=document.createNSResolver)}function a(){l(n.default.mathmapsIePath)}function l(t){const e=n.default.document.createElement("script");e.type="text/javascript",e.src=t,n.default.document.head?n.default.document.head.appendChild(e):n.default.document.body.appendChild(e)}e.detectIE=function(){return"undefined"!=typeof window&&"ActiveXObject"in window&&"clipboardData"in window&&(a(),i(),!0)},e.detectEdge=function(){var t;return"undefined"!=typeof window&&"MSGestureEvent"in window&&null===(null===(t=window.chrome)||void 0===t?void 0:t.loadTimes)&&(document.evaluate=null,i(!0),!0)},e.mapsForIE=null,e.loadWGXpath_=i,e.installWGXpath_=s,e.loadMapsForIE_=a,e.loadScript=l},2057:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.Debugger=void 0;const n=r(2315);class o{constructor(){this.isActive_=!1,this.outputFunction_=console.info,this.stream_=null}static getInstance(){return o.instance=o.instance||new o,o.instance}init(t){t&&this.startDebugFile_(t),this.isActive_=!0}output(...t){this.isActive_&&this.output_(t)}generateOutput(t){this.isActive_&&this.output_(t.apply(t,[]))}exit(t=(()=>{})){this.isActive_&&this.stream_&&this.stream_.end("","",t)}startDebugFile_(t){this.stream_=n.default.fs.createWriteStream(t),this.outputFunction_=function(...t){this.stream_.write(t.join(" ")),this.stream_.write("\n")}.bind(this),this.stream_.on("error",function(t){console.info("Invalid log file. Debug information sent to console."),this.outputFunction_=console.info}.bind(this)),this.stream_.on("finish",(function(){console.info("Finalizing debug file.")}))}output_(t){this.outputFunction_.apply(console.info===this.outputFunction_?console:this.outputFunction_,["Speech Rule Engine Debugger:"].concat(t))}}e.Debugger=o},5740:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.serializeXml=e.cloneNode=e.tagName=e.querySelectorAll=e.querySelectorAllByAttrValue=e.querySelectorAllByAttr=e.formatXml=e.createTextNode=e.createElementNS=e.createElement=e.replaceNode=e.NodeType=e.parseInput=e.XML_ENTITIES=e.trimInput_=e.toArray=void 0;const n=r(5897),o=r(4440),i=r(2315),s=r(5274);function a(t){const e=[];for(let r=0,n=t.length;r[ \f\n\r\t\v\u200b]+<").trim()}function c(t,e){if(!e)return[!1,""];const r=t.match(/^<([^> ]+).*>/),n=e.match(/^<\/([^>]+)>(.*)/);return r&&n&&r[1]===n[1]?[!0,n[2]]:[!1,""]}e.toArray=a,e.trimInput_=l,e.XML_ENTITIES={"<":!0,">":!0,"&":!0,""":!0,"'":!0},e.parseInput=function(t){const e=new i.default.xmldom.DOMParser,r=l(t),a=!!r.match(/&(?!lt|gt|amp|quot|apos)\w+;/g);if(!r)throw new Error("Empty input!");try{const t=e.parseFromString(r,a?"text/html":"text/xml");return n.default.getInstance().mode===o.Mode.HTTP?(s.xpath.currentDocument=t,a?t.body.childNodes[0]:t.documentElement):t.documentElement}catch(t){throw new n.SREError("Illegal input: "+t.message)}},function(t){t[t.ELEMENT_NODE=1]="ELEMENT_NODE",t[t.ATTRIBUTE_NODE=2]="ATTRIBUTE_NODE",t[t.TEXT_NODE=3]="TEXT_NODE",t[t.CDATA_SECTION_NODE=4]="CDATA_SECTION_NODE",t[t.ENTITY_REFERENCE_NODE=5]="ENTITY_REFERENCE_NODE",t[t.ENTITY_NODE=6]="ENTITY_NODE",t[t.PROCESSING_INSTRUCTION_NODE=7]="PROCESSING_INSTRUCTION_NODE",t[t.COMMENT_NODE=8]="COMMENT_NODE",t[t.DOCUMENT_NODE=9]="DOCUMENT_NODE",t[t.DOCUMENT_TYPE_NODE=10]="DOCUMENT_TYPE_NODE",t[t.DOCUMENT_FRAGMENT_NODE=11]="DOCUMENT_FRAGMENT_NODE",t[t.NOTATION_NODE=12]="NOTATION_NODE"}(e.NodeType||(e.NodeType={})),e.replaceNode=function(t,e){t.parentNode&&(t.parentNode.insertBefore(e,t),t.parentNode.removeChild(t))},e.createElement=function(t){return i.default.document.createElement(t)},e.createElementNS=function(t,e){return i.default.document.createElementNS(t,e)},e.createTextNode=function(t){return i.default.document.createTextNode(t)},e.formatXml=function(t){let e="",r=/(>)(<)(\/*)/g,n=0,o=(t=t.replace(r,"$1\r\n$2$3")).split("\r\n");for(r=/(\.)*(<)(\/*)/g,o=o.map((t=>t.replace(r,"$1\r\n$2$3").split("\r\n"))).reduce(((t,e)=>t.concat(e)),[]);o.length;){let t=o.shift();if(!t)continue;let r=0;if(t.match(/^<\w[^>/]*>[^>]+$/)){const e=c(t,o[0]);e[0]?e[1]?(t+=o.shift().slice(0,-e[1].length),e[1].trim()&&o.unshift(e[1])):t+=o.shift():r=1}else if(t.match(/^<\/\w/))0!==n&&(n-=1);else if(t.match(/^<\w[^>]*[^/]>.*$/))r=1;else if(t.match(/^<\w[^>]*\/>.+$/)){const e=t.indexOf(">")+1;t.slice(e).trim()&&o.unshift(),t=t.slice(0,e)}else r=0;e+=new Array(n+1).join(" ")+t+"\r\n",n+=r}return e},e.querySelectorAllByAttr=function(t,e){return t.querySelectorAll?a(t.querySelectorAll(`[${e}]`)):s.evalXPath(`.//*[@${e}]`,t)},e.querySelectorAllByAttrValue=function(t,e,r){return t.querySelectorAll?a(t.querySelectorAll(`[${e}="${r}"]`)):s.evalXPath(`.//*[@${e}="${r}"]`,t)},e.querySelectorAll=function(t,e){return t.querySelectorAll?a(t.querySelectorAll(e)):s.evalXPath(`.//${e}`,t)},e.tagName=function(t){return t.tagName.toUpperCase()},e.cloneNode=function(t){return t.cloneNode(!0)},e.serializeXml=function(t){return(new i.default.xmldom.XMLSerializer).serializeToString(t)}},5897:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.EnginePromise=e.SREError=void 0;const n=r(1676),o=r(4440),i=r(2057),s=r(1377);class a extends Error{constructor(t=""){super(),this.message=t,this.name="SRE Error"}}e.SREError=a;class l{constructor(){this.customLoader=null,this.parsers={},this.comparator=null,this.mode=o.Mode.SYNC,this.init=!0,this.delay=!1,this.comparators={},this.domain="mathspeak",this.style=n.DynamicCstr.DEFAULT_VALUES[n.Axis.STYLE],this._defaultLocale=n.DynamicCstr.DEFAULT_VALUES[n.Axis.LOCALE],this.locale=this.defaultLocale,this.subiso="",this.modality=n.DynamicCstr.DEFAULT_VALUES[n.Axis.MODALITY],this.speech=o.Speech.NONE,this.markup=o.Markup.NONE,this.walker="Table",this.structure=!1,this.ruleSets=[],this.strict=!1,this.isIE=!1,this.isEdge=!1,this.rate="100",this.pprint=!1,this.config=!1,this.rules="",this.prune="",this.evaluator=l.defaultEvaluator,this.defaultParser=new n.DynamicCstrParser(n.DynamicCstr.DEFAULT_ORDER),this.parser=this.defaultParser,this.dynamicCstr=n.DynamicCstr.defaultCstr()}set defaultLocale(t){this._defaultLocale=s.Variables.ensureLocale(t,this._defaultLocale)}get defaultLocale(){return this._defaultLocale}static getInstance(){return l.instance=l.instance||new l,l.instance}static defaultEvaluator(t,e){return t}static evaluateNode(t){return l.nodeEvaluator(t)}getRate(){const t=parseInt(this.rate,10);return isNaN(t)?100:t}setDynamicCstr(t){if(this.defaultLocale&&(n.DynamicCstr.DEFAULT_VALUES[n.Axis.LOCALE]=this.defaultLocale),t){const e=Object.keys(t);for(let r=0;r{void 0!==t[r]&&(e[r]=t[r])};return r("mode"),e.configurate(t),a.default.BINARY_FEATURES.forEach((r=>{void 0!==t[r]&&(e[r]=!!t[r])})),a.default.STRING_FEATURES.forEach(r),t.json&&(c.default.jsonPath=l.makePath(t.json)),t.xpath&&(c.default.WGXpath=t.xpath),e.setCustomLoader(t.custom),function(t){t.isIE=s.detectIE(),t.isEdge=s.detectEdge()}(e),o.setLocale(),e.setDynamicCstr(),e.init?(a.EnginePromise.promises.init=new Promise(((t,e)=>{setTimeout((()=>{t("init")}),10)})),e.init=!1,a.EnginePromise.get()):e.delay?(e.delay=!1,a.EnginePromise.get()):i.loadLocale()}))}},8496:function(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.Event=e.EventType=e.Move=e.KeyCode=void 0,function(t){t[t.ENTER=13]="ENTER",t[t.ESC=27]="ESC",t[t.SPACE=32]="SPACE",t[t.PAGE_UP=33]="PAGE_UP",t[t.PAGE_DOWN=34]="PAGE_DOWN",t[t.END=35]="END",t[t.HOME=36]="HOME",t[t.LEFT=37]="LEFT",t[t.UP=38]="UP",t[t.RIGHT=39]="RIGHT",t[t.DOWN=40]="DOWN",t[t.TAB=9]="TAB",t[t.LESS=188]="LESS",t[t.GREATER=190]="GREATER",t[t.DASH=189]="DASH",t[t.ZERO=48]="ZERO",t[t.ONE=49]="ONE",t[t.TWO=50]="TWO",t[t.THREE=51]="THREE",t[t.FOUR=52]="FOUR",t[t.FIVE=53]="FIVE",t[t.SIX=54]="SIX",t[t.SEVEN=55]="SEVEN",t[t.EIGHT=56]="EIGHT",t[t.NINE=57]="NINE",t[t.A=65]="A",t[t.B=66]="B",t[t.C=67]="C",t[t.D=68]="D",t[t.E=69]="E",t[t.F=70]="F",t[t.G=71]="G",t[t.H=72]="H",t[t.I=73]="I",t[t.J=74]="J",t[t.K=75]="K",t[t.L=76]="L",t[t.M=77]="M",t[t.N=78]="N",t[t.O=79]="O",t[t.P=80]="P",t[t.Q=81]="Q",t[t.R=82]="R",t[t.S=83]="S",t[t.T=84]="T",t[t.U=85]="U",t[t.V=86]="V",t[t.W=87]="W",t[t.X=88]="X",t[t.Y=89]="Y",t[t.Z=90]="Z"}(e.KeyCode||(e.KeyCode={})),e.Move=new Map([[13,"ENTER"],[27,"ESC"],[32,"SPACE"],[33,"PAGE_UP"],[34,"PAGE_DOWN"],[35,"END"],[36,"HOME"],[37,"LEFT"],[38,"UP"],[39,"RIGHT"],[40,"DOWN"],[9,"TAB"],[188,"LESS"],[190,"GREATER"],[189,"DASH"],[48,"ZERO"],[49,"ONE"],[50,"TWO"],[51,"THREE"],[52,"FOUR"],[53,"FIVE"],[54,"SIX"],[55,"SEVEN"],[56,"EIGHT"],[57,"NINE"],[65,"A"],[66,"B"],[67,"C"],[68,"D"],[69,"E"],[70,"F"],[71,"G"],[72,"H"],[73,"I"],[74,"J"],[75,"K"],[76,"L"],[77,"M"],[78,"N"],[79,"O"],[80,"P"],[81,"Q"],[82,"R"],[83,"S"],[84,"T"],[85,"U"],[86,"V"],[87,"W"],[88,"X"],[89,"Y"],[90,"Z"]]),function(t){t.CLICK="click",t.DBLCLICK="dblclick",t.MOUSEDOWN="mousedown",t.MOUSEUP="mouseup",t.MOUSEOVER="mouseover",t.MOUSEOUT="mouseout",t.MOUSEMOVE="mousemove",t.SELECTSTART="selectstart",t.KEYPRESS="keypress",t.KEYDOWN="keydown",t.KEYUP="keyup",t.TOUCHSTART="touchstart",t.TOUCHMOVE="touchmove",t.TOUCHEND="touchend",t.TOUCHCANCEL="touchcancel"}(e.EventType||(e.EventType={}));e.Event=class{constructor(t,e,r){this.src=t,this.type=e,this.callback=r}add(){this.src.addEventListener(this.type,this.callback)}remove(){this.src.removeEventListener(this.type,this.callback)}}},7248:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.localePath=e.makePath=void 0;const n=r(2315);function o(t){return t.match("/$")?t:t+"/"}e.makePath=o,e.localePath=function(t,e="json"){return o(n.default.jsonPath)+t+(e.match(/^\./)?e:"."+e)}},3769:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.KeyProcessor=e.Processor=void 0;const n=r(8496);class o{constructor(t,e){this.name=t,this.process=e.processor,this.postprocess=e.postprocessor||((t,e)=>t),this.processor=this.postprocess?function(t){return this.postprocess(this.process(t),t)}:this.process,this.print=e.print||o.stringify_,this.pprint=e.pprint||this.print}static stringify_(t){return t?t.toString():t}}e.Processor=o,o.LocalState={walker:null,speechGenerator:null,highlighter:null};class i extends o{constructor(t,e){super(t,e),this.key=e.key||i.getKey_}static getKey_(t){return"string"==typeof t?n.KeyCode[t.toUpperCase()]:t}}e.KeyProcessor=i},6499:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.keypress=e.output=e.print=e.process=e.set=void 0;const n=r(8290),o=r(5714),i=r(3090),s=r(4356),a=r(1414),l=r(9552),c=r(9543),u=r(3362),p=r(1204),h=r(5740),f=r(5897),d=r(4440),m=r(3769),y=r(5274),g=new Map;function b(t){g.set(t.name,t)}function v(t){const e=g.get(t);if(!e)throw new f.SREError("Unknown processor "+t);return e}function _(t,e){const r=v(t);try{return r.processor(e)}catch(t){throw new f.SREError("Processing error for expression "+e)}}function S(t,e){const r=v(t);return f.default.getInstance().pprint?r.pprint(e):r.print(e)}e.set=b,e.process=_,e.print=S,e.output=function(t,e){const r=v(t);try{const t=r.processor(e);return f.default.getInstance().pprint?r.pprint(t):r.print(t)}catch(t){throw new f.SREError("Processing error for expression "+e)}},e.keypress=function(t,e){const r=v(t),n=r instanceof m.KeyProcessor?r.key(e):e,o=r.processor(n);return f.default.getInstance().pprint?r.pprint(o):r.print(o)},b(new m.Processor("semantic",{processor:function(t){const e=h.parseInput(t);return a.xmlTree(e)},postprocessor:function(t,e){const r=f.default.getInstance().speech;if(r===d.Speech.NONE)return t;const o=h.cloneNode(t);let i=c.computeMarkup(o);if(r===d.Speech.SHALLOW)return t.setAttribute("speech",n.finalize(i)),t;const s=y.evalXPath(".//*[@id]",t),a=y.evalXPath(".//*[@id]",o);for(let t,e,r=0;t=s[r],e=a[r];r++)i=c.computeMarkup(e),t.setAttribute("speech",n.finalize(i));return t},pprint:function(t){return h.formatXml(t.toString())}})),b(new m.Processor("speech",{processor:function(t){const e=h.parseInput(t),r=a.xmlTree(e),o=c.computeSpeech(r);return n.finalize(n.markup(o))},pprint:function(t){const e=t.toString();return n.isXml()?h.formatXml(e):e}})),b(new m.Processor("json",{processor:function(t){const e=h.parseInput(t);return a.getTree(e).toJson()},postprocessor:function(t,e){const r=f.default.getInstance().speech;if(r===d.Speech.NONE)return t;const o=h.parseInput(e),i=a.xmlTree(o),s=c.computeMarkup(i);if(r===d.Speech.SHALLOW)return t.stree.speech=n.finalize(s),t;const l=t=>{const e=y.evalXPath(`.//*[@id=${t.id}]`,i)[0],r=c.computeMarkup(e);t.speech=n.finalize(r),t.children&&t.children.forEach(l)};return l(t.stree),t},print:function(t){return JSON.stringify(t)},pprint:function(t){return JSON.stringify(t,null,2)}})),b(new m.Processor("description",{processor:function(t){const e=h.parseInput(t),r=a.xmlTree(e);return c.computeSpeech(r)},print:function(t){return JSON.stringify(t)},pprint:function(t){return JSON.stringify(t,null,2)}})),b(new m.Processor("enriched",{processor:function(t){return o.semanticMathmlSync(t)},postprocessor:function(t,e){const r=p.getSemanticRoot(t);let n;switch(f.default.getInstance().speech){case d.Speech.NONE:break;case d.Speech.SHALLOW:n=l.generator("Adhoc"),n.getSpeech(r,t);break;case d.Speech.DEEP:n=l.generator("Tree"),n.getSpeech(t,t)}return t},pprint:function(t){return h.formatXml(t.toString())}})),b(new m.Processor("walker",{processor:function(t){const e=l.generator("Node");m.Processor.LocalState.speechGenerator=e,e.setOptions({modality:f.default.getInstance().modality,locale:f.default.getInstance().locale,domain:f.default.getInstance().domain,style:f.default.getInstance().style}),m.Processor.LocalState.highlighter=i.highlighter({color:"black"},{color:"white"},{renderer:"NativeMML"});const r=_("enriched",t),n=S("enriched",r);return m.Processor.LocalState.walker=u.walker(f.default.getInstance().walker,r,e,m.Processor.LocalState.highlighter,n),m.Processor.LocalState.walker},print:function(t){return m.Processor.LocalState.walker.speech()}})),b(new m.KeyProcessor("move",{processor:function(t){if(!m.Processor.LocalState.walker)return null;return!1===m.Processor.LocalState.walker.move(t)?n.error(t):m.Processor.LocalState.walker.speech()}})),b(new m.Processor("number",{processor:function(t){const e=parseInt(t,10);return isNaN(e)?"":s.LOCALE.NUMBERS.numberToWords(e)}})),b(new m.Processor("ordinal",{processor:function(t){const e=parseInt(t,10);return isNaN(e)?"":s.LOCALE.NUMBERS.wordOrdinal(e)}})),b(new m.Processor("numericOrdinal",{processor:function(t){const e=parseInt(t,10);return isNaN(e)?"":s.LOCALE.NUMBERS.numericOrdinal(e)}})),b(new m.Processor("vulgar",{processor:function(t){const[e,r]=t.split("/").map((t=>parseInt(t,10)));return isNaN(e)||isNaN(r)?"":_("speech",`${e}${r}`)}}))},2998:function(t,e,r){var n=this&&this.__awaiter||function(t,e,r,n){return new(r||(r=Promise))((function(o,i){function s(t){try{l(n.next(t))}catch(t){i(t)}}function a(t){try{l(n.throw(t))}catch(t){i(t)}}function l(t){var e;t.done?o(t.value):(e=t.value,e instanceof r?e:new r((function(t){t(e)}))).then(s,a)}l((n=n.apply(t,e||[])).next())}))};Object.defineProperty(e,"__esModule",{value:!0}),e.localePath=e.exit=e.move=e.walk=e.processFile=e.file=e.vulgar=e.numericOrdinal=e.ordinal=e.number=e.toEnriched=e.toDescription=e.toJson=e.toSemantic=e.toSpeech=e.localeLoader=e.engineReady=e.engineSetup=e.setupEngine=e.version=void 0;const o=r(5897),i=r(6828),s=r(4440),a=r(7248),l=r(6499),c=r(2315),u=r(1377),p=r(6141);function h(t){return n(this,void 0,void 0,(function*(){return(0,i.setup)(t)}))}function f(t,e){return l.process(t,e)}function d(t,e,r){switch(o.default.getInstance().mode){case s.Mode.ASYNC:return function(t,e,r){return n(this,void 0,void 0,(function*(){const n=yield c.default.fs.promises.readFile(e,{encoding:"utf8"}),i=l.output(t,n);if(r)try{c.default.fs.promises.writeFile(r,i)}catch(t){throw new o.SREError("Can not write to file: "+r)}return i}))}(t,e,r);case s.Mode.SYNC:return function(t,e,r){const n=function(t){let e;try{e=c.default.fs.readFileSync(t,{encoding:"utf8"})}catch(e){throw new o.SREError("Can not open file: "+t)}return e}(e),i=l.output(t,n);if(r)try{c.default.fs.writeFileSync(r,i)}catch(t){throw new o.SREError("Can not write to file: "+r)}return i}(t,e,r);default:throw new o.SREError(`Can process files in ${o.default.getInstance().mode} mode`)}}e.version=u.Variables.VERSION,e.setupEngine=h,e.engineSetup=function(){const t=["mode"].concat(o.default.STRING_FEATURES,o.default.BINARY_FEATURES),e=o.default.getInstance(),r={};return t.forEach((function(t){r[t]=e[t]})),r.json=c.default.jsonPath,r.xpath=c.default.WGXpath,r.rules=e.ruleSets.slice(),r},e.engineReady=function(){return n(this,void 0,void 0,(function*(){return h({}).then((()=>o.EnginePromise.getall()))}))},e.localeLoader=p.standardLoader,e.toSpeech=function(t){return f("speech",t)},e.toSemantic=function(t){return f("semantic",t)},e.toJson=function(t){return f("json",t)},e.toDescription=function(t){return f("description",t)},e.toEnriched=function(t){return f("enriched",t)},e.number=function(t){return f("number",t)},e.ordinal=function(t){return f("ordinal",t)},e.numericOrdinal=function(t){return f("numericOrdinal",t)},e.vulgar=function(t){return f("vulgar",t)},e.file={},e.file.toSpeech=function(t,e){return d("speech",t,e)},e.file.toSemantic=function(t,e){return d("semantic",t,e)},e.file.toJson=function(t,e){return d("json",t,e)},e.file.toDescription=function(t,e){return d("description",t,e)},e.file.toEnriched=function(t,e){return d("enriched",t,e)},e.processFile=d,e.walk=function(t){return l.output("walker",t)},e.move=function(t){return l.keypress("move",t)},e.exit=function(t){const e=t||0;o.EnginePromise.getall().then((()=>process.exit(e)))},e.localePath=a.localePath,c.default.documentSupported?h({mode:s.Mode.HTTP}).then((()=>h({}))):h({mode:s.Mode.SYNC}).then((()=>h({mode:s.Mode.ASYNC})))},2315:function(__unused_webpack_module,exports,__webpack_require__){var __dirname="/";Object.defineProperty(exports,"__esModule",{value:!0});const variables_1=__webpack_require__(1377);class SystemExternal{static extRequire(library){if("undefined"!=typeof process){const nodeRequire=eval("require");return nodeRequire(library)}return null}}exports.default=SystemExternal,SystemExternal.windowSupported=!("undefined"==typeof window),SystemExternal.documentSupported=SystemExternal.windowSupported&&!(void 0===window.document),SystemExternal.xmldom=SystemExternal.documentSupported?window:SystemExternal.extRequire("xmldom-sre"),SystemExternal.document=SystemExternal.documentSupported?window.document:(new SystemExternal.xmldom.DOMImplementation).createDocument("","",0),SystemExternal.xpath=SystemExternal.documentSupported?document:function(){const t={document:{},XPathResult:{}};return SystemExternal.extRequire("wicked-good-xpath").install(t),t.document.XPathResult=t.XPathResult,t.document}(),SystemExternal.mathmapsIePath="https://cdn.jsdelivr.net/npm/sre-mathmaps-ie@"+variables_1.Variables.VERSION+"mathmaps_ie.js",SystemExternal.commander=SystemExternal.documentSupported?null:SystemExternal.extRequire("commander"),SystemExternal.fs=SystemExternal.documentSupported?null:SystemExternal.extRequire("fs"),SystemExternal.url=variables_1.Variables.url,SystemExternal.jsonPath=(SystemExternal.documentSupported?SystemExternal.url:process.env.SRE_JSON_PATH||__webpack_require__.g.SRE_JSON_PATH||__dirname+"/mathmaps")+"/",SystemExternal.WGXpath=variables_1.Variables.WGXpath,SystemExternal.wgxpath=null},1377:function(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.Variables=void 0;class r{static ensureLocale(t,e){return r.LOCALES.get(t)?t:(console.error(`Locale ${t} does not exist! Using ${r.LOCALES.get(e)} instead.`),e)}}e.Variables=r,r.VERSION="4.0.6",r.LOCALES=new Map([["ca","Catalan"],["da","Danish"],["de","German"],["en","English"],["es","Spanish"],["fr","French"],["hi","Hindi"],["it","Italian"],["nb","Bokm\xe5l"],["nn","Nynorsk"],["sv","Swedish"],["nemeth","Nemeth"]]),r.mathjaxVersion="3.2.1",r.url="https://cdn.jsdelivr.net/npm/speech-rule-engine@"+r.VERSION+"/lib/mathmaps",r.WGXpath="https://cdn.jsdelivr.net/npm/wicked-good-xpath@1.3.0/dist/wgxpath.install.js"},5274:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.updateEvaluator=e.evaluateString=e.evaluateBoolean=e.getLeafNodes=e.evalXPath=e.resolveNameSpace=e.xpath=void 0;const n=r(5897),o=r(4440),i=r(2315);function s(){return"undefined"!=typeof XPathResult}e.xpath={currentDocument:null,evaluate:s()?document.evaluate:i.default.xpath.evaluate,result:s()?XPathResult:i.default.xpath.XPathResult,createNSResolver:s()?document.createNSResolver:i.default.xpath.createNSResolver};const a={xhtml:"http://www.w3.org/1999/xhtml",mathml:"http://www.w3.org/1998/Math/MathML",mml:"http://www.w3.org/1998/Math/MathML",svg:"http://www.w3.org/2000/svg"};function l(t){return a[t]||null}e.resolveNameSpace=l;class c{constructor(){this.lookupNamespaceURI=l}}function u(t,r,i){return n.default.getInstance().mode!==o.Mode.HTTP||n.default.getInstance().isIE||n.default.getInstance().isEdge?e.xpath.evaluate(t,r,new c,i,null):e.xpath.currentDocument.evaluate(t,r,l,i,null)}function p(t,r){let n;try{n=u(t,r,e.xpath.result.ORDERED_NODE_ITERATOR_TYPE)}catch(t){return[]}const o=[];for(let t=n.iterateNext();t;t=n.iterateNext())o.push(t);return o}e.evalXPath=p,e.getLeafNodes=function(t){return p(".//*[count(*)=0]",t)},e.evaluateBoolean=function(t,r){let n;try{n=u(t,r,e.xpath.result.BOOLEAN_TYPE)}catch(t){return!1}return n.booleanValue},e.evaluateString=function(t,r){let n;try{n=u(t,r,e.xpath.result.STRING_TYPE)}catch(t){return""}return n.stringValue},e.updateEvaluator=function(t){if(n.default.getInstance().mode!==o.Mode.HTTP)return;let r=t;for(;r&&!r.evaluate;)r=r.parentNode;r&&r.evaluate?e.xpath.currentDocument=r:t.ownerDocument&&(e.xpath.currentDocument=t.ownerDocument)}},9268:function(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.AbstractEnrichCase=void 0;e.AbstractEnrichCase=class{constructor(t){this.semantic=t}}},6061:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.CaseBinomial=void 0;const n=r(5740),o=r(9268),i=r(5452),s=r(2298);class a extends o.AbstractEnrichCase{constructor(t){super(t),this.mml=t.mathmlTree}static test(t){return!t.mathmlTree&&"line"===t.type&&"binomial"===t.role}getMathml(){if(!this.semantic.childNodes.length)return this.mml;const t=this.semantic.childNodes[0];if(this.mml=(0,i.walkTree)(t),this.mml.hasAttribute(s.Attribute.TYPE)){const t=n.createElement("mrow");t.setAttribute(s.Attribute.ADDED,"true"),n.replaceNode(this.mml,t),t.appendChild(this.mml),this.mml=t}return(0,s.setAttributes)(this.mml,this.semantic),this.mml}}e.CaseBinomial=a},5765:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.CaseDoubleScript=void 0;const n=r(5740),o=r(9268),i=r(5452),s=r(2298);class a extends o.AbstractEnrichCase{constructor(t){super(t),this.mml=t.mathmlTree}static test(t){if(!t.mathmlTree||!t.childNodes.length)return!1;const e=n.tagName(t.mathmlTree),r=t.childNodes[0].role;return"MSUBSUP"===e&&"subsup"===r||"MUNDEROVER"===e&&"underover"===r}getMathml(){const t=this.semantic.childNodes[0],e=t.childNodes[0],r=this.semantic.childNodes[1],n=t.childNodes[1],o=i.walkTree(r),a=i.walkTree(e),l=i.walkTree(n);return(0,s.setAttributes)(this.mml,this.semantic),this.mml.setAttribute(s.Attribute.CHILDREN,(0,s.makeIdList)([e,n,r])),[a,l,o].forEach((t=>i.getInnerNode(t).setAttribute(s.Attribute.PARENT,this.mml.getAttribute(s.Attribute.ID)))),this.mml.setAttribute(s.Attribute.TYPE,t.role),i.addCollapsedAttribute(this.mml,[this.semantic.id,[t.id,e.id,n.id],r.id]),this.mml}}e.CaseDoubleScript=a},7251:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.CaseEmbellished=void 0;const n=r(5740),o=r(5952),i=r(9268),s=r(5765),a=r(7014),l=r(6887),c=r(5452),u=r(2298);class p extends i.AbstractEnrichCase{constructor(t){super(t),this.fenced=null,this.fencedMml=null,this.fencedMmlNodes=[],this.ofence=null,this.ofenceMml=null,this.ofenceMap={},this.cfence=null,this.cfenceMml=null,this.cfenceMap={},this.parentCleanup=[]}static test(t){return!(!t.mathmlTree||!t.fencePointer||t.mathmlTree.getAttribute("data-semantic-type"))}static makeEmptyNode_(t){const e=n.createElement("mrow"),r=new o.SemanticNode(t);return r.type="empty",r.mathmlTree=e,r}static fencedMap_(t,e){e[t.id]=t.mathmlTree,t.embellished&&p.fencedMap_(t.childNodes[0],e)}getMathml(){this.getFenced_(),this.fencedMml=c.walkTree(this.fenced),this.getFencesMml_(),"empty"!==this.fenced.type||this.fencedMml.parentNode||(this.fencedMml.setAttribute(u.Attribute.ADDED,"true"),this.cfenceMml.parentNode.insertBefore(this.fencedMml,this.cfenceMml)),this.getFencedMml_();return this.rewrite_()}fencedElement(t){return"fenced"===t.type||"matrix"===t.type||"vector"===t.type}getFenced_(){let t=this.semantic;for(;!this.fencedElement(t);)t=t.childNodes[0];this.fenced=t.childNodes[0],this.ofence=t.contentNodes[0],this.cfence=t.contentNodes[1],p.fencedMap_(this.ofence,this.ofenceMap),p.fencedMap_(this.cfence,this.cfenceMap)}getFencedMml_(){let t=this.ofenceMml.nextSibling;for(t=t===this.fencedMml?t:this.fencedMml;t&&t!==this.cfenceMml;)this.fencedMmlNodes.push(t),t=t.nextSibling}getFencesMml_(){let t=this.semantic;const e=Object.keys(this.ofenceMap),r=Object.keys(this.cfenceMap);for(;!(this.ofenceMml&&this.cfenceMml||t===this.fenced);)-1===e.indexOf(t.fencePointer)||this.ofenceMml||(this.ofenceMml=t.mathmlTree),-1===r.indexOf(t.fencePointer)||this.cfenceMml||(this.cfenceMml=t.mathmlTree),t=t.childNodes[0];this.ofenceMml||(this.ofenceMml=this.ofence.mathmlTree),this.cfenceMml||(this.cfenceMml=this.cfence.mathmlTree),this.ofenceMml&&(this.ofenceMml=c.ascendNewNode(this.ofenceMml)),this.cfenceMml&&(this.cfenceMml=c.ascendNewNode(this.cfenceMml))}rewrite_(){let t=this.semantic,e=null;const r=this.introduceNewLayer_();for((0,u.setAttributes)(r,this.fenced.parent);!this.fencedElement(t);){const o=t.mathmlTree,i=this.specialCase_(t,o);if(i)t=i;else{(0,u.setAttributes)(o,t);const e=[];for(let r,n=1;r=t.childNodes[n];n++)e.push(c.walkTree(r));t=t.childNodes[0]}const s=n.createElement("dummy"),a=o.childNodes[0];n.replaceNode(o,s),n.replaceNode(r,o),n.replaceNode(o.childNodes[0],r),n.replaceNode(s,a),e||(e=o)}return c.walkTree(this.ofence),c.walkTree(this.cfence),this.cleanupParents_(),e||r}specialCase_(t,e){const r=n.tagName(e);let o,i=null;if("MSUBSUP"===r?(i=t.childNodes[0],o=s.CaseDoubleScript):"MMULTISCRIPTS"===r&&("superscript"===t.type||"subscript"===t.type?o=a.CaseMultiscripts:"tensor"===t.type&&(o=l.CaseTensor),i=o&&t.childNodes[0]&&"subsup"===t.childNodes[0].role?t.childNodes[0]:t),!i)return null;const c=i.childNodes[0],u=p.makeEmptyNode_(c.id);return i.childNodes[0]=u,e=new o(t).getMathml(),i.childNodes[0]=c,this.parentCleanup.push(e),i.childNodes[0]}introduceNewLayer_(){const t=this.fullFence(this.ofenceMml),e=this.fullFence(this.cfenceMml);let r=n.createElement("mrow");if(n.replaceNode(this.fencedMml,r),this.fencedMmlNodes.forEach((t=>r.appendChild(t))),r.insertBefore(t,this.fencedMml),r.appendChild(e),!r.parentNode){const t=n.createElement("mrow");for(;r.childNodes.length>0;)t.appendChild(r.childNodes[0]);r.appendChild(t),r=t}return r}fullFence(t){const e=this.fencedMml.parentNode;let r=t;for(;r.parentNode&&r.parentNode!==e;)r=r.parentNode;return r}cleanupParents_(){this.parentCleanup.forEach((function(t){const e=t.childNodes[1].getAttribute(u.Attribute.PARENT);t.childNodes[0].setAttribute(u.Attribute.PARENT,e)}))}}e.CaseEmbellished=p},6265:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.CaseLimit=void 0;const n=r(5740),o=r(9268),i=r(5452),s=r(2298);class a extends o.AbstractEnrichCase{constructor(t){super(t),this.mml=t.mathmlTree}static test(t){if(!t.mathmlTree||!t.childNodes.length)return!1;const e=n.tagName(t.mathmlTree),r=t.type;return("limupper"===r||"limlower"===r)&&("MSUBSUP"===e||"MUNDEROVER"===e)||"limboth"===r&&("MSUB"===e||"MUNDER"===e||"MSUP"===e||"MOVER"===e)}static walkTree_(t){t&&i.walkTree(t)}getMathml(){const t=this.semantic.childNodes;return"limboth"!==this.semantic.type&&this.mml.childNodes.length>=3&&(this.mml=i.introduceNewLayer([this.mml],this.semantic)),(0,s.setAttributes)(this.mml,this.semantic),t[0].mathmlTree||(t[0].mathmlTree=this.semantic.mathmlTree),t.forEach(a.walkTree_),this.mml}}e.CaseLimit=a},6514:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.CaseLine=void 0;const n=r(9268),o=r(5452),i=r(2298);class s extends n.AbstractEnrichCase{constructor(t){super(t),this.mml=t.mathmlTree}static test(t){return!!t.mathmlTree&&"line"===t.type}getMathml(){return this.semantic.contentNodes.length&&o.walkTree(this.semantic.contentNodes[0]),this.semantic.childNodes.length&&o.walkTree(this.semantic.childNodes[0]),(0,i.setAttributes)(this.mml,this.semantic),this.mml}}e.CaseLine=s},6839:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.CaseMultiindex=void 0;const n=r(5740),o=r(9268),i=r(5452),s=r(2298);class a extends o.AbstractEnrichCase{constructor(t){super(t),this.mml=t.mathmlTree}static multiscriptIndex(t){return"punctuated"===t.type&&"dummy"===t.contentNodes[0].role?i.collapsePunctuated(t):(i.walkTree(t),t.id)}static createNone_(t){const e=n.createElement("none");return t&&(0,s.setAttributes)(e,t),e.setAttribute(s.Attribute.ADDED,"true"),e}completeMultiscript(t,e){const r=n.toArray(this.mml.childNodes).slice(1);let o=0;const l=t=>{for(let e,n=0;e=t[n];n++){const t=r[o];if(t&&e===parseInt(i.getInnerNode(t).getAttribute(s.Attribute.ID)))i.getInnerNode(t).setAttribute(s.Attribute.PARENT,this.semantic.id.toString()),o++;else{const r=this.semantic.querySelectorAll((t=>t.id===e));this.mml.insertBefore(a.createNone_(r[0]),t||null)}}};l(t),r[o]&&"MPRESCRIPTS"!==n.tagName(r[o])?this.mml.insertBefore(r[o],n.createElement("mprescripts")):o++,l(e)}}e.CaseMultiindex=a},7014:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.CaseMultiscripts=void 0;const n=r(5740),o=r(5656),i=r(6839),s=r(5452),a=r(2298);class l extends i.CaseMultiindex{static test(t){if(!t.mathmlTree)return!1;return"MMULTISCRIPTS"===n.tagName(t.mathmlTree)&&("superscript"===t.type||"subscript"===t.type)}constructor(t){super(t)}getMathml(){let t,e,r;if((0,a.setAttributes)(this.mml,this.semantic),this.semantic.childNodes[0]&&"subsup"===this.semantic.childNodes[0].role){const n=this.semantic.childNodes[0];t=n.childNodes[0],e=i.CaseMultiindex.multiscriptIndex(this.semantic.childNodes[1]),r=i.CaseMultiindex.multiscriptIndex(n.childNodes[1]);const l=[this.semantic.id,[n.id,t.id,r],e];s.addCollapsedAttribute(this.mml,l),this.mml.setAttribute(a.Attribute.TYPE,n.role),this.completeMultiscript(o.SemanticSkeleton.interleaveIds(r,e),[])}else{t=this.semantic.childNodes[0],e=i.CaseMultiindex.multiscriptIndex(this.semantic.childNodes[1]);const r=[this.semantic.id,t.id,e];s.addCollapsedAttribute(this.mml,r)}const n=o.SemanticSkeleton.collapsedLeafs(r||[],e),l=s.walkTree(t);return s.getInnerNode(l).setAttribute(a.Attribute.PARENT,this.semantic.id.toString()),n.unshift(t.id),this.mml.setAttribute(a.Attribute.CHILDREN,n.join(",")),this.mml}}e.CaseMultiscripts=l},3416:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.CaseProof=void 0;const n=r(9268),o=r(5452),i=r(2298);class s extends n.AbstractEnrichCase{constructor(t){super(t),this.mml=t.mathmlTree}static test(t){return!!t.mathmlTree&&("inference"===t.type||"premises"===t.type)}getMathml(){return this.semantic.childNodes.length?(this.semantic.contentNodes.forEach((function(t){o.walkTree(t),(0,i.setAttributes)(t.mathmlTree,t)})),this.semantic.childNodes.forEach((function(t){o.walkTree(t)})),(0,i.setAttributes)(this.mml,this.semantic),this.mml.getAttribute("data-semantic-id")===this.mml.getAttribute("data-semantic-parent")&&this.mml.removeAttribute("data-semantic-parent"),this.mml):this.mml}}e.CaseProof=s},5699:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.CaseTable=void 0;const n=r(5740),o=r(9268),i=r(5452),s=r(2298);class a extends o.AbstractEnrichCase{constructor(t){super(t),this.inner=[],this.mml=t.mathmlTree}static test(t){return"matrix"===t.type||"vector"===t.type||"cases"===t.type}getMathml(){const t=i.cloneContentNode(this.semantic.contentNodes[0]),e=this.semantic.contentNodes[1]?i.cloneContentNode(this.semantic.contentNodes[1]):null;if(this.inner=this.semantic.childNodes.map(i.walkTree),this.mml)if("MFENCED"===n.tagName(this.mml)){const r=this.mml.childNodes;this.mml.insertBefore(t,r[0]||null),e&&this.mml.appendChild(e),this.mml=i.rewriteMfenced(this.mml)}else{const r=[t,this.mml];e&&r.push(e),this.mml=i.introduceNewLayer(r,this.semantic)}else this.mml=i.introduceNewLayer([t].concat(this.inner,[e]),this.semantic);return(0,s.setAttributes)(this.mml,this.semantic),this.mml}}e.CaseTable=a},6887:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.CaseTensor=void 0;const n=r(5656),o=r(6839),i=r(5452),s=r(2298);class a extends o.CaseMultiindex{static test(t){return!!t.mathmlTree&&"tensor"===t.type}constructor(t){super(t)}getMathml(){i.walkTree(this.semantic.childNodes[0]);const t=o.CaseMultiindex.multiscriptIndex(this.semantic.childNodes[1]),e=o.CaseMultiindex.multiscriptIndex(this.semantic.childNodes[2]),r=o.CaseMultiindex.multiscriptIndex(this.semantic.childNodes[3]),a=o.CaseMultiindex.multiscriptIndex(this.semantic.childNodes[4]);(0,s.setAttributes)(this.mml,this.semantic);const l=[this.semantic.id,this.semantic.childNodes[0].id,t,e,r,a];i.addCollapsedAttribute(this.mml,l);const c=n.SemanticSkeleton.collapsedLeafs(t,e,r,a);return c.unshift(this.semantic.childNodes[0].id),this.mml.setAttribute(s.Attribute.CHILDREN,c.join(",")),this.completeMultiscript(n.SemanticSkeleton.interleaveIds(r,a),n.SemanticSkeleton.interleaveIds(t,e)),this.mml}}e.CaseTensor=a},9236:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.CaseText=void 0;const n=r(9268),o=r(5452),i=r(2298);class s extends n.AbstractEnrichCase{constructor(t){super(t),this.mml=t.mathmlTree}static test(t){return"punctuated"===t.type&&("text"===t.role||t.contentNodes.every((t=>"dummy"===t.role)))}getMathml(){const t=[],e=o.collapsePunctuated(this.semantic,t);return this.mml=o.introduceNewLayer(t,this.semantic),(0,i.setAttributes)(this.mml,this.semantic),this.mml.removeAttribute(i.Attribute.CONTENT),o.addCollapsedAttribute(this.mml,e),this.mml}}e.CaseText=s},5714:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.prepareMmlString=e.testTranslation__=e.semanticMathml=e.semanticMathmlSync=e.semanticMathmlNode=void 0;const n=r(2057),o=r(5740),i=r(5897),s=r(1414),a=r(5452),l=r(2298);function c(t){const e=o.cloneNode(t),r=s.getTree(e);return a.enrich(e,r)}function u(t){return c(o.parseInput(t))}function p(t){return t.match(/^$/)||(t+=""),t}r(1513),e.semanticMathmlNode=c,e.semanticMathmlSync=u,e.semanticMathml=function(t,e){i.EnginePromise.getall().then((()=>{const r=o.parseInput(t);e(c(r))}))},e.testTranslation__=function(t){n.Debugger.getInstance().init();const e=u(p(t)).toString();return(0,l.removeAttributePrefix)(e),n.Debugger.getInstance().exit(),e},e.prepareMmlString=p},2298:function(t,e){var r;function n(t){return t.map((function(t){return t.id})).join(",")}function o(t,e){const n=[];"mglyph"===e.role&&n.push("image"),e.attributes.href&&n.push("link"),n.length&&t.setAttribute(r.POSTFIX,n.join(" "))}Object.defineProperty(e,"__esModule",{value:!0}),e.addPrefix=e.removeAttributePrefix=e.setPostfix=e.setAttributes=e.makeIdList=e.EnrichAttributes=e.Attribute=e.Prefix=void 0,e.Prefix="data-semantic-",function(t){t.ADDED="data-semantic-added",t.ALTERNATIVE="data-semantic-alternative",t.CHILDREN="data-semantic-children",t.COLLAPSED="data-semantic-collapsed",t.CONTENT="data-semantic-content",t.EMBELLISHED="data-semantic-embellished",t.FENCEPOINTER="data-semantic-fencepointer",t.FONT="data-semantic-font",t.ID="data-semantic-id",t.ANNOTATION="data-semantic-annotation",t.OPERATOR="data-semantic-operator",t.OWNS="data-semantic-owns",t.PARENT="data-semantic-parent",t.POSTFIX="data-semantic-postfix",t.PREFIX="data-semantic-prefix",t.ROLE="data-semantic-role",t.SPEECH="data-semantic-speech",t.STRUCTURE="data-semantic-structure",t.TYPE="data-semantic-type"}(r=e.Attribute||(e.Attribute={})),e.EnrichAttributes=[r.ADDED,r.ALTERNATIVE,r.CHILDREN,r.COLLAPSED,r.CONTENT,r.EMBELLISHED,r.FENCEPOINTER,r.FONT,r.ID,r.ANNOTATION,r.OPERATOR,r.OWNS,r.PARENT,r.POSTFIX,r.PREFIX,r.ROLE,r.SPEECH,r.STRUCTURE,r.TYPE],e.makeIdList=n,e.setAttributes=function(t,i){t.setAttribute(r.TYPE,i.type);const s=i.allAttributes();for(let r,n=0;r=s[n];n++)t.setAttribute(e.Prefix+r[0].toLowerCase(),r[1]);i.childNodes.length&&t.setAttribute(r.CHILDREN,n(i.childNodes)),i.contentNodes.length&&t.setAttribute(r.CONTENT,n(i.contentNodes)),i.parent&&t.setAttribute(r.PARENT,i.parent.id.toString()),o(t,i)},e.setPostfix=o,e.removeAttributePrefix=function(t){return t.toString().replace(new RegExp(e.Prefix,"g"),"")},e.addPrefix=function(t){return e.Prefix+t}},3532:function(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.factory=e.getCase=void 0,e.getCase=function(t){for(let r,n=0;r=e.factory[n];n++)if(r.test(t))return r.constr(t);return null},e.factory=[]},1513:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0});const n=r(6061),o=r(5765),i=r(7251),s=r(6265),a=r(6514),l=r(7014),c=r(3416),u=r(5699),p=r(6887),h=r(9236);r(3532).factory.push({test:s.CaseLimit.test,constr:t=>new s.CaseLimit(t)},{test:i.CaseEmbellished.test,constr:t=>new i.CaseEmbellished(t)},{test:o.CaseDoubleScript.test,constr:t=>new o.CaseDoubleScript(t)},{test:p.CaseTensor.test,constr:t=>new p.CaseTensor(t)},{test:l.CaseMultiscripts.test,constr:t=>new l.CaseMultiscripts(t)},{test:a.CaseLine.test,constr:t=>new a.CaseLine(t)},{test:n.CaseBinomial.test,constr:t=>new n.CaseBinomial(t)},{test:c.CaseProof.test,constr:t=>new c.CaseProof(t)},{test:u.CaseTable.test,constr:t=>new u.CaseTable(t)},{test:h.CaseText.test,constr:t=>new h.CaseText(t)})},5452:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.printNodeList__=e.collapsePunctuated=e.formattedOutput_=e.formattedOutput=e.getInnerNode=e.setOperatorAttribute_=e.createInvisibleOperator_=e.rewriteMfenced=e.cloneContentNode=e.addCollapsedAttribute=e.parentNode_=e.isIgnorable_=e.unitChild_=e.descendNode_=e.ascendNewNode=e.validLca_=e.pathToRoot_=e.attachedElement_=e.prunePath_=e.mathmlLca_=e.lcaType=e.functionApplication_=e.isDescendant_=e.insertNewChild_=e.mergeChildren_=e.collectChildNodes_=e.collateChildNodes_=e.childrenSubset_=e.moveSemanticAttributes_=e.introduceLayerAboveLca=e.introduceNewLayer=e.walkTree=e.enrich=e.SETTINGS=void 0;const n=r(2057),o=r(5740),i=r(5897),s=r(3588),a=r(7516),l=r(5656),c=r(4795),u=r(2298),p=r(3532);function h(t){const e=(0,p.getCase)(t);let r;if(e)return r=e.getMathml(),N(r);if(1===t.mathml.length)return n.Debugger.getInstance().output("Walktree Case 0"),r=t.mathml[0],u.setAttributes(r,t),t.childNodes.length&&(n.Debugger.getInstance().output("Walktree Case 0.1"),t.childNodes.forEach((function(t){"empty"===t.type&&r.appendChild(h(t))}))),N(r);const o=t.contentNodes.map(R);B(t,o);const i=t.childNodes.map(h),s=l.SemanticSkeleton.combineContentChildren(t,o,i);if(r=t.mathmlTree,null===r)n.Debugger.getInstance().output("Walktree Case 1"),r=f(s,t);else{const t=A(s);n.Debugger.getInstance().output("Walktree Case 2"),t?(n.Debugger.getInstance().output("Walktree Case 2.1"),r=t.parentNode):(n.Debugger.getInstance().output("Walktree Case 2.2"),r=D(r))}return r=k(r),v(r,s,t),u.setAttributes(r,t),N(r)}function f(t,e){const r=x(t);let i=r.node;const s=r.type;if(s!==O.VALID||!c.hasEmptyTag(i))if(n.Debugger.getInstance().output("Walktree Case 1.1"),i=o.createElement("mrow"),s===O.PRUNED)n.Debugger.getInstance().output("Walktree Case 1.1.0"),i=d(i,r.node,t);else if(t[0]){n.Debugger.getInstance().output("Walktree Case 1.1.1");const e=A(t),r=y(e.parentNode,t);o.replaceNode(e,i),r.forEach((function(t){i.appendChild(t)}))}return e.mathmlTree||(e.mathmlTree=i),i}function d(t,e,r){let i=w(e);if(c.hasMathTag(i)){n.Debugger.getInstance().output("Walktree Case 1.1.0.0"),m(i,t),o.toArray(i.childNodes).forEach((function(e){t.appendChild(e)}));const e=t;t=i,i=e}const s=r.indexOf(e);return r[s]=i,o.replaceNode(i,t),t.appendChild(i),r.forEach((function(e){t.appendChild(e)})),t}function m(t,e){for(const r of u.EnrichAttributes)t.hasAttribute(r)&&(e.setAttribute(r,t.getAttribute(r)),t.removeAttribute(r))}function y(t,e){const r=o.toArray(t.childNodes);let n=1/0,i=-1/0;return e.forEach((function(t){const e=r.indexOf(t);-1!==e&&(n=Math.min(n,e),i=Math.max(i,e))})),r.slice(n,i+1)}function g(t,e,r){const n=[];let i=o.toArray(t.childNodes),s=!1;for(;i.length;){const t=i.shift();if(t.hasAttribute(u.Attribute.TYPE)){n.push(t);continue}const e=b(t);0!==e.length&&(1!==e.length?(s?t.setAttribute("AuxiliaryImplicit",!0):s=!0,i=e.concat(i)):n.push(t))}const a=[],l=r.childNodes.map((function(t){return t.mathmlTree}));for(;l.length;){const t=l.pop();if(t){if(-1!==n.indexOf(t))break;-1!==e.indexOf(t)&&a.unshift(t)}}return n.concat(a)}function b(t){const e=[];let r=o.toArray(t.childNodes);for(;r.length;){const t=r.shift();t.nodeType===o.NodeType.ELEMENT_NODE&&(t.hasAttribute(u.Attribute.TYPE)?e.push(t):r=o.toArray(t.childNodes).concat(r))}return e}function v(t,e,r){const n="implicit"===r.role&&a.flags.combine_juxtaposition?g(t,e,r):o.toArray(t.childNodes);if(!n.length)return void e.forEach((function(e){t.appendChild(e)}));let i=0;for(;e.length;){const r=e[0];n[i]===r||M(n[i],r)?(e.shift(),i++):n[i]&&-1===e.indexOf(n[i])?i++:(S(r,t)||_(t,n[i],r),e.shift())}}function _(t,e,r){if(!e)return void t.insertBefore(r,null);let n=e,o=P(n);for(;o&&o.firstChild===n&&!n.hasAttribute("AuxiliaryImplicit")&&o!==t;)n=o,o=P(n);o&&(o.insertBefore(r,n),n.removeAttribute("AuxiliaryImplicit"))}function S(t,e){if(!t)return!1;do{if((t=t.parentNode)===e)return!0}while(t);return!1}function M(t,e){const r=s.functionApplication();if(t&&e&&t.textContent&&e.textContent&&t.textContent===r&&e.textContent===r&&"true"===e.getAttribute(u.Attribute.ADDED)){for(let r,n=0;r=t.attributes[n];n++)e.hasAttribute(r.nodeName)||e.setAttribute(r.nodeName,r.nodeValue);return o.replaceNode(t,e),!0}return!1}var O;function x(t){const e=A(t);if(!e)return{type:O.INVALID,node:null};const r=A(t.slice().reverse());if(e===r)return{type:O.VALID,node:e};const n=C(e),o=E(n,t),i=C(r,(function(t){return-1!==o.indexOf(t)})),s=i[0],a=o.indexOf(s);return-1===a?{type:O.INVALID,node:null}:{type:o.length!==n.length?O.PRUNED:T(o[a+1],i[1])?O.VALID:O.INVALID,node:s}}function E(t,e){let r=0;for(;t[r]&&-1===e.indexOf(t[r]);)r++;return t.slice(0,r+1)}function A(t){let e=0,r=null;for(;!r&&e!1),n=[t];for(;!r(t)&&!c.hasMathTag(t)&&t.parentNode;)t=P(t),n.unshift(t);return n}function T(t,e){return!(!t||!e||t.previousSibling||e.nextSibling)}function N(t){for(;!c.hasMathTag(t)&&L(t);)t=P(t);return t}function w(t){const e=o.toArray(t.childNodes);if(!e)return t;const r=e.filter((function(t){return t.nodeType===o.NodeType.ELEMENT_NODE&&!c.hasIgnoreTag(t)}));return 1===r.length&&c.hasEmptyTag(r[0])&&!r[0].hasAttribute(u.Attribute.TYPE)?w(r[0]):t}function L(t){const e=P(t);return!(!e||!c.hasEmptyTag(e))&&o.toArray(e.childNodes).every((function(e){return e===t||I(e)}))}function I(t){if(t.nodeType!==o.NodeType.ELEMENT_NODE)return!0;if(!t||c.hasIgnoreTag(t))return!0;const e=o.toArray(t.childNodes);return!(!c.hasEmptyTag(t)&&e.length||c.hasDisplayTag(t)||t.hasAttribute(u.Attribute.TYPE)||c.isOrphanedGlyph(t))&&o.toArray(t.childNodes).every(I)}function P(t){return t.parentNode}function R(t){if(t.mathml.length)return h(t);const r=e.SETTINGS.implicit?j(t):o.createElement("mrow");return t.mathml=[r],r}function k(t){if("MFENCED"!==o.tagName(t))return t;const e=o.createElement("mrow");for(let r,n=0;r=t.attributes[n];n++)-1===["open","close","separators"].indexOf(r.name)&&e.setAttribute(r.name,r.value);return o.toArray(t.childNodes).forEach((function(t){e.appendChild(t)})),o.replaceNode(t,e),e}function j(t){const e=o.createElement("mo"),r=o.createTextNode(t.textContent);return e.appendChild(r),u.setAttributes(e,t),e.setAttribute(u.Attribute.ADDED,"true"),e}function B(t,e){const r=t.type+(t.textContent?","+t.textContent:"");e.forEach((function(t){D(t).setAttribute(u.Attribute.OPERATOR,r)}))}function D(t){const e=o.toArray(t.childNodes);if(!e)return t;const r=e.filter((function(t){return!I(t)})),n=[];for(let t,e=0;t=r[e];e++)if(c.hasEmptyTag(t)){const e=D(t);e&&e!==t&&n.push(e)}else n.push(t);return 1===n.length?n[0]:t}function F(t,e,r,n){const o=n||!1;H(t,"Original MathML",o),H(r,"Semantic Tree",o),H(e,"Semantically enriched MathML",o)}function H(t,e,r){const n=o.formatXml(t.toString());r?console.info(e+":\n```html\n"+u.removeAttributePrefix(n)+"\n```\n"):console.info(n)}e.SETTINGS={collapsed:!0,implicit:!0},e.enrich=function(t,e){const r=o.cloneNode(t);return h(e.root),i.default.getInstance().structure&&t.setAttribute(u.Attribute.STRUCTURE,l.SemanticSkeleton.fromStructure(t,e).toString()),n.Debugger.getInstance().generateOutput((function(){return F(r,t,e,!0),[]})),t},e.walkTree=h,e.introduceNewLayer=f,e.introduceLayerAboveLca=d,e.moveSemanticAttributes_=m,e.childrenSubset_=y,e.collateChildNodes_=g,e.collectChildNodes_=b,e.mergeChildren_=v,e.insertNewChild_=_,e.isDescendant_=S,e.functionApplication_=M,function(t){t.VALID="valid",t.INVALID="invalid",t.PRUNED="pruned"}(O=e.lcaType||(e.lcaType={})),e.mathmlLca_=x,e.prunePath_=E,e.attachedElement_=A,e.pathToRoot_=C,e.validLca_=T,e.ascendNewNode=N,e.descendNode_=w,e.unitChild_=L,e.isIgnorable_=I,e.parentNode_=P,e.addCollapsedAttribute=function(t,e){const r=new l.SemanticSkeleton(e);t.setAttribute(u.Attribute.COLLAPSED,r.toString())},e.cloneContentNode=R,e.rewriteMfenced=k,e.createInvisibleOperator_=j,e.setOperatorAttribute_=B,e.getInnerNode=D,e.formattedOutput=F,e.formattedOutput_=H,e.collapsePunctuated=function(t,e){const r=!!e,n=e||[],o=t.parent,i=t.contentNodes.map((function(t){return t.id}));i.unshift("c");const s=[t.id,i];for(let e,i=0;e=t.childNodes[i];i++){const t=h(e);n.push(t);const i=D(t);o&&!r&&i.setAttribute(u.Attribute.PARENT,o.id.toString()),s.push(e.id)}return s},e.printNodeList__=function(t,e){console.info(t),o.toArray(e).forEach((function(t){console.info(t.toString())})),console.info("<<<<<<<<<<<<<<<<<")}},5105:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.AbstractHighlighter=void 0;const n=r(5274),o=r(2298);class i{constructor(){this.color=null,this.mactionName="",this.currentHighlights=[]}highlight(t){this.currentHighlights.push(t.map((t=>{const e=this.highlightNode(t);return this.setHighlighted(t),e})))}highlightAll(t){const e=this.getMactionNodes(t);for(let t,r=0;t=e[r];r++)this.highlight([t])}unhighlight(){const t=this.currentHighlights.pop();t&&t.forEach((t=>{this.isHighlighted(t.node)&&(this.unhighlightNode(t),this.unsetHighlighted(t.node))}))}unhighlightAll(){for(;this.currentHighlights.length>0;)this.unhighlight()}setColor(t){this.color=t}colorString(){return this.color.rgba()}addEvents(t,e){const r=this.getMactionNodes(t);for(let t,n=0;t=r[n];n++)for(const r in e)t.addEventListener(r,e[r])}getMactionNodes(t){return Array.from(t.getElementsByClassName(this.mactionName))}isMactionNode(t){const e=t.className||t.getAttribute("class");return!!e&&!!e.match(new RegExp(this.mactionName))}isHighlighted(t){return t.hasAttribute(i.ATTR)}setHighlighted(t){t.setAttribute(i.ATTR,"true")}unsetHighlighted(t){t.removeAttribute(i.ATTR)}colorizeAll(t){n.evalXPath(`.//*[@${o.Attribute.ID}]`,t).forEach((t=>this.colorize(t)))}uncolorizeAll(t){n.evalXPath(`.//*[@${o.Attribute.ID}]`,t).forEach((t=>this.uncolorize(t)))}colorize(t){const e=(0,o.addPrefix)("foreground");t.hasAttribute(e)&&(t.setAttribute(e+"-old",t.style.color),t.style.color=t.getAttribute(e))}uncolorize(t){const e=(0,o.addPrefix)("foreground")+"-old";t.hasAttribute(e)&&(t.style.color=t.getAttribute(e))}}e.AbstractHighlighter=i,i.ATTR="sre-highlight"},6937:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.ChtmlHighlighter=void 0;const n=r(933);class o extends n.CssHighlighter{constructor(){super()}isMactionNode(t){return t.tagName.toUpperCase()===this.mactionName.toUpperCase()}getMactionNodes(t){return Array.from(t.getElementsByTagName(this.mactionName))}}e.ChtmlHighlighter=o},8396:function(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.ContrastPicker=e.ColorPicker=void 0;const r={red:{red:255,green:0,blue:0},green:{red:0,green:255,blue:0},blue:{red:0,green:0,blue:255},yellow:{red:255,green:255,blue:0},cyan:{red:0,green:255,blue:255},magenta:{red:255,green:0,blue:255},white:{red:255,green:255,blue:255},black:{red:0,green:0,blue:0}};function n(t,e){const n=t||{color:e};let o=Object.prototype.hasOwnProperty.call(n,"color")?r[n.color]:n;return o||(o=r[e]),o.alpha=Object.prototype.hasOwnProperty.call(n,"alpha")?n.alpha:1,function(t){const e=t=>(t=Math.max(t,0),t=Math.min(255,t),Math.round(t));return t.red=e(t.red),t.green=e(t.green),t.blue=e(t.blue),t.alpha=Math.max(t.alpha,0),t.alpha=Math.min(1,t.alpha),t}(o)}class o{constructor(t,e){this.foreground=n(e,o.DEFAULT_FOREGROUND_),this.background=n(t,o.DEFAULT_BACKGROUND_)}static toHex(t){const e=t.toString(16);return 1===e.length?"0"+e:e}rgba(){const t=function(t){return"rgba("+t.red+","+t.green+","+t.blue+","+t.alpha+")"};return{background:t(this.background),foreground:t(this.foreground)}}rgb(){const t=function(t){return"rgb("+t.red+","+t.green+","+t.blue+")"};return{background:t(this.background),alphaback:this.background.alpha.toString(),foreground:t(this.foreground),alphafore:this.foreground.alpha.toString()}}hex(){const t=function(t){return"#"+o.toHex(t.red)+o.toHex(t.green)+o.toHex(t.blue)};return{background:t(this.background),alphaback:this.background.alpha.toString(),foreground:t(this.foreground),alphafore:this.foreground.alpha.toString()}}}e.ColorPicker=o,o.DEFAULT_BACKGROUND_="blue",o.DEFAULT_FOREGROUND_="black";e.ContrastPicker=class{constructor(){this.hue=10,this.sat=100,this.light=50,this.incr=50}generate(){return e=function(t,e,r){e=e>1?e/100:e,r=r>1?r/100:r;const n=(1-Math.abs(2*r-1))*e,o=n*(1-Math.abs(t/60%2-1)),i=r-n/2;let s=0,a=0,l=0;return 0<=t&&t<60?[s,a,l]=[n,o,0]:60<=t&&t<120?[s,a,l]=[o,n,0]:120<=t&&t<180?[s,a,l]=[0,n,o]:180<=t&&t<240?[s,a,l]=[0,o,n]:240<=t&&t<300?[s,a,l]=[o,0,n]:300<=t&&t<360&&([s,a,l]=[n,0,o]),{red:s+i,green:a+i,blue:l+i}}(this.hue,this.sat,this.light),"rgb("+(t={red:Math.round(255*e.red),green:Math.round(255*e.green),blue:Math.round(255*e.blue)}).red+","+t.green+","+t.blue+")";var t,e}increment(){this.hue=(this.hue+this.incr)%360}}},933:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.CssHighlighter=void 0;const n=r(5105);class o extends n.AbstractHighlighter{constructor(){super(),this.mactionName="mjx-maction"}highlightNode(t){const e={node:t,background:t.style.backgroundColor,foreground:t.style.color},r=this.colorString();return t.style.backgroundColor=r.background,t.style.color=r.foreground,e}unhighlightNode(t){t.node.style.backgroundColor=t.background,t.node.style.color=t.foreground}}e.CssHighlighter=o},3090:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.highlighterMapping_=e.addEvents=e.highlighter=void 0;const n=r(6937),o=r(8396),i=r(933),s=r(2598),a=r(4500),l=r(7071),c=r(4346),u=r(2222);e.highlighter=function(t,r,n){const i=new o.ColorPicker(t,r),s="NativeMML"===n.renderer&&"Safari"===n.browser?"MML-CSS":"SVG"===n.renderer&&"v3"===n.browser?"SVG-V3":n.renderer,a=new(e.highlighterMapping_[s]||e.highlighterMapping_.NativeMML);return a.setColor(i),a},e.addEvents=function(t,r,n){const o=e.highlighterMapping_[n.renderer];o&&(new o).addEvents(t,r)},e.highlighterMapping_={SVG:c.SvgHighlighter,"SVG-V3":u.SvgV3Highlighter,NativeMML:l.MmlHighlighter,"HTML-CSS":s.HtmlHighlighter,"MML-CSS":a.MmlCssHighlighter,CommonHTML:i.CssHighlighter,CHTML:n.ChtmlHighlighter}},2598:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.HtmlHighlighter=void 0;const n=r(5740),o=r(5105);class i extends o.AbstractHighlighter{constructor(){super(),this.mactionName="maction"}highlightNode(t){const e={node:t,foreground:t.style.color,position:t.style.position},r=this.color.rgb();t.style.color=r.foreground,t.style.position="relative";const o=t.bbox;if(o&&o.w){const i=.05,s=0,a=n.createElement("span"),l=parseFloat(t.style.paddingLeft||"0");a.style.backgroundColor=r.background,a.style.opacity=r.alphaback.toString(),a.style.display="inline-block",a.style.height=o.h+o.d+2*i+"em",a.style.verticalAlign=-o.d+"em",a.style.marginTop=a.style.marginBottom=-i+"em",a.style.width=o.w+2*s+"em",a.style.marginLeft=l-s+"em",a.style.marginRight=-o.w-s-l+"em",t.parentNode.insertBefore(a,t),e.box=a}return e}unhighlightNode(t){const e=t.node;e.style.color=t.foreground,e.style.position=t.position,t.box&&t.box.parentNode.removeChild(t.box)}}e.HtmlHighlighter=i},4500:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.MmlCssHighlighter=void 0;const n=r(933);class o extends n.CssHighlighter{constructor(){super(),this.mactionName="maction"}getMactionNodes(t){return Array.from(t.getElementsByTagName(this.mactionName))}isMactionNode(t){return t.tagName===this.mactionName}}e.MmlCssHighlighter=o},7071:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.MmlHighlighter=void 0;const n=r(5105);class o extends n.AbstractHighlighter{constructor(){super(),this.mactionName="maction"}highlightNode(t){let e=t.getAttribute("style");return e+=";background-color: "+this.colorString().background,e+=";color: "+this.colorString().foreground,t.setAttribute("style",e),{node:t}}unhighlightNode(t){let e=t.node.getAttribute("style");e=e.replace(";background-color: "+this.colorString().background,""),e=e.replace(";color: "+this.colorString().foreground,""),t.node.setAttribute("style",e)}colorString(){return this.color.rgba()}getMactionNodes(t){return Array.from(t.getElementsByTagName(this.mactionName))}isMactionNode(t){return t.tagName===this.mactionName}}e.MmlHighlighter=o},4346:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.SvgHighlighter=void 0;const n=r(5740),o=r(5105);class i extends o.AbstractHighlighter{constructor(){super(),this.mactionName="mjx-svg-maction"}highlightNode(t){let e;if(this.isHighlighted(t))return e={node:t.previousSibling||t,background:t.style.backgroundColor,foreground:t.style.color},e;if("svg"===t.tagName){const e={node:t,background:t.style.backgroundColor,foreground:t.style.color};return t.style.backgroundColor=this.colorString().background,t.style.color=this.colorString().foreground,e}const r=n.createElementNS("http://www.w3.org/2000/svg","rect");let i;if("use"===t.nodeName){const e=n.createElementNS("http://www.w3.org/2000/svg","g");t.parentNode.insertBefore(e,t),e.appendChild(t),i=e.getBBox(),e.parentNode.replaceChild(t,e)}else i=t.getBBox();r.setAttribute("x",(i.x-40).toString()),r.setAttribute("y",(i.y-40).toString()),r.setAttribute("width",(i.width+80).toString()),r.setAttribute("height",(i.height+80).toString());const s=t.getAttribute("transform");return s&&r.setAttribute("transform",s),r.setAttribute("fill",this.colorString().background),r.setAttribute(o.AbstractHighlighter.ATTR,"true"),t.parentNode.insertBefore(r,t),e={node:r,foreground:t.getAttribute("fill")},t.setAttribute("fill",this.colorString().foreground),e}setHighlighted(t){"svg"===t.tagName&&super.setHighlighted(t)}unhighlightNode(t){if("background"in t)return t.node.style.backgroundColor=t.background,void(t.node.style.color=t.foreground);t.foreground?t.node.nextSibling.setAttribute("fill",t.foreground):t.node.nextSibling.removeAttribute("fill"),t.node.parentNode.removeChild(t.node)}isMactionNode(t){let e=t.className||t.getAttribute("class");return e=void 0!==e.baseVal?e.baseVal:e,!!e&&!!e.match(new RegExp(this.mactionName))}}e.SvgHighlighter=i},2222:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.SvgV3Highlighter=void 0;const n=r(5740),o=r(5274),i=r(5105),s=r(8396),a=r(4346);class l extends a.SvgHighlighter{constructor(){super(),this.mactionName="maction"}highlightNode(t){let e;if(this.isHighlighted(t))return e={node:t,background:this.colorString().background,foreground:this.colorString().foreground},e;if("svg"===t.tagName||"MJX-CONTAINER"===t.tagName)return e={node:t,background:t.style.backgroundColor,foreground:t.style.color},t.style.backgroundColor=this.colorString().background,t.style.color=this.colorString().foreground,e;const r=n.createElementNS("http://www.w3.org/2000/svg","rect");r.setAttribute("sre-highlighter-added","true");const o=t.getBBox();r.setAttribute("x",(o.x-40).toString()),r.setAttribute("y",(o.y-40).toString()),r.setAttribute("width",(o.width+80).toString()),r.setAttribute("height",(o.height+80).toString());const a=t.getAttribute("transform");if(a&&r.setAttribute("transform",a),r.setAttribute("fill",this.colorString().background),t.setAttribute(i.AbstractHighlighter.ATTR,"true"),t.parentNode.insertBefore(r,t),e={node:t,foreground:t.getAttribute("fill")},"rect"===t.nodeName){const e=new s.ColorPicker({alpha:0,color:"black"});t.setAttribute("fill",e.rgba().foreground)}else t.setAttribute("fill",this.colorString().foreground);return e}unhighlightNode(t){const e=t.node.previousSibling;if(e&&e.hasAttribute("sre-highlighter-added"))return t.foreground?t.node.setAttribute("fill",t.foreground):t.node.removeAttribute("fill"),void t.node.parentNode.removeChild(e);t.node.style.backgroundColor=t.background,t.node.style.color=t.foreground}isMactionNode(t){return t.getAttribute("data-mml-node")===this.mactionName}getMactionNodes(t){return Array.from(o.evalXPath(`.//*[@data-mml-node="${this.mactionName}"]`,t))}}e.SvgV3Highlighter=l},7222:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.StaticTrieNode=e.AbstractTrieNode=void 0;const n=r(2057),o=r(4391);class i{constructor(t,e){this.constraint=t,this.test=e,this.children_={},this.kind=o.TrieNodeKind.ROOT}getConstraint(){return this.constraint}getKind(){return this.kind}applyTest(t){return this.test(t)}addChild(t){const e=t.getConstraint(),r=this.children_[e];return this.children_[e]=t,r}getChild(t){return this.children_[t]}getChildren(){const t=[];for(const e in this.children_)t.push(this.children_[e]);return t}findChildren(t){const e=[];for(const r in this.children_){const n=this.children_[r];n.applyTest(t)&&e.push(n)}return e}removeChild(t){delete this.children_[t]}toString(){return this.constraint}}e.AbstractTrieNode=i;e.StaticTrieNode=class extends i{constructor(t,e){super(t,e),this.rule_=null,this.kind=o.TrieNodeKind.STATIC}getRule(){return this.rule_}setRule(t){this.rule_&&n.Debugger.getInstance().output("Replacing rule "+this.rule_+" with "+t),this.rule_=t}toString(){return this.getRule()?this.constraint+"\n==> "+this.getRule().action:this.constraint}}},4508:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.Trie=void 0;const n=r(4391),o=r(9701);class i{constructor(){this.root=(0,o.getNode)(n.TrieNodeKind.ROOT,"",null)}static collectRules_(t){const e=[];let r=[t];for(;r.length;){const t=r.shift();if(t.getKind()===n.TrieNodeKind.QUERY||t.getKind()===n.TrieNodeKind.BOOLEAN){const r=t.getRule();r&&e.unshift(r)}r=r.concat(t.getChildren())}return e}static printWithDepth_(t,e,r){r+=new Array(e+2).join(e.toString())+": "+t.toString()+"\n";const n=t.getChildren();for(let t,o=0;t=n[o];o++)r=i.printWithDepth_(t,e+1,r);return r}static order_(t){const e=t.getChildren();if(!e.length)return 0;const r=Math.max.apply(null,e.map(i.order_));return Math.max(e.length,r)}addRule(t){let e=this.root;const r=t.context,o=t.dynamicCstr.getValues();for(let t=0,i=o.length;t{e.getKind()===n.TrieNodeKind.DYNAMIC&&-1===t.indexOf(e.getConstraint())||o.push(e)}))}r=o.slice()}for(;r.length;){const e=r.shift();if(e.getRule){const t=e.getRule();t&&o.push(t)}const n=e.findChildren(t);r=r.concat(n)}return o}hasSubtrie(t){let e=this.root;for(let r=0,n=t.length;r!0)),this.kind=c.TrieNodeKind.ROOT}}e.RootTrieNode=u;class p extends a.AbstractTrieNode{constructor(t){super(t,(e=>e===t)),this.kind=c.TrieNodeKind.DYNAMIC}}e.DynamicTrieNode=p;const h={"=":(t,e)=>t===e,"!=":(t,e)=>t!==e,"<":(t,e)=>t":(t,e)=>t>e,"<=":(t,e)=>t<=e,">=":(t,e)=>t>=e};function f(t){if(t.match(/^self::\*$/))return t=>!0;if(t.match(/^self::\w+$/)){const e=t.slice(6).toUpperCase();return t=>t.tagName&&n.tagName(t)===e}if(t.match(/^self::\w+:\w+$/)){const e=t.split(":"),r=o.resolveNameSpace(e[2]);if(!r)return null;const n=e[3].toUpperCase();return t=>t.localName&&t.localName.toUpperCase()===n&&t.namespaceURI===r}if(t.match(/^@\w+$/)){const e=t.slice(1);return t=>t.hasAttribute&&t.hasAttribute(e)}if(t.match(/^@\w+="[\w\d ]+"$/)){const e=t.split("="),r=e[0].slice(1),n=e[1].slice(1,-1);return t=>t.hasAttribute&&t.hasAttribute(r)&&t.getAttribute(r)===n}if(t.match(/^@\w+!="[\w\d ]+"$/)){const e=t.split("!="),r=e[0].slice(1),n=e[1].slice(1,-1);return t=>!t.hasAttribute||!t.hasAttribute(r)||t.getAttribute(r)!==n}if(t.match(/^contains\(\s*@grammar\s*,\s*"[\w\d ]+"\s*\)$/)){const e=t.split('"')[1];return t=>!!i.Grammar.getInstance().getParameter(e)}if(t.match(/^not\(\s*contains\(\s*@grammar\s*,\s*"[\w\d ]+"\s*\)\s*\)$/)){const e=t.split('"')[1];return t=>!i.Grammar.getInstance().getParameter(e)}if(t.match(/^name\(\.\.\/\.\.\)="\w+"$/)){const e=t.split('"')[1].toUpperCase();return t=>{var r,o;return(null===(o=null===(r=t.parentNode)||void 0===r?void 0:r.parentNode)||void 0===o?void 0:o.tagName)&&n.tagName(t.parentNode.parentNode)===e}}if(t.match(/^count\(preceding-sibling::\*\)=\d+$/)){const e=t.split("="),r=parseInt(e[1],10);return t=>{var e;return(null===(e=t.parentNode)||void 0===e?void 0:e.childNodes[r])===t}}if(t.match(/^.+\[@category!?=".+"\]$/)){let[,e,r,n]=t.match(/^(.+)\[@category(!?=)"(.+)"\]$/);const i=n.match(/^unit:(.+)$/);let a="";return i&&(n=i[1],a=":unit"),t=>{const i=o.evalXPath(e,t)[0];if(i){const t=s.lookupCategory(i.textContent+a);return"="===r?t===n:t!==n}return!1}}if(t.match(/^string-length\(.+\)\W+\d+/)){const[,e,r,n]=t.match(/^string-length\((.+)\)(\W+)(\d+)/),i=h[r]||h["="],s=parseInt(n,10);return t=>{const r=o.evalXPath(e,t)[0];return!!r&&i(Array.from(r.textContent).length,s)}}return null}e.constraintTest_=f;class d extends l.StaticTrieNode{constructor(t,e){super(t,f(t)),this.context=e,this.kind=c.TrieNodeKind.QUERY}applyTest(t){return this.test?this.test(t):this.context.applyQuery(t,this.constraint)===t}}e.QueryTrieNode=d;class m extends l.StaticTrieNode{constructor(t,e){super(t,f(t)),this.context=e,this.kind=c.TrieNodeKind.BOOLEAN}applyTest(t){return this.test?this.test(t):this.context.applyConstraint(t,this.constraint)}}e.BooleanTrieNode=m},7491:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.completeLocale=e.getLocale=e.setLocale=e.locales=void 0;const n=r(5897),o=r(1377),i=r(2105),s=r(4249),a=r(8657),l=r(173),c=r(9393),u=r(7978),p=r(5540),h=r(5218),f=r(3887),d=r(8384),m=r(7206),y=r(7734),g=r(7264),b=r(4356);function v(){const t=o.Variables.ensureLocale(n.default.getInstance().locale,n.default.getInstance().defaultLocale);return n.default.getInstance().locale=t,e.locales[t]()}e.locales={ca:s.ca,da:a.da,de:l.de,en:c.en,es:u.es,fr:p.fr,hi:h.hi,it:f.it,nb:d.nb,nn:y.nn,sv:g.sv,nemeth:m.nemeth},e.setLocale=function(){const t=v();if(function(t){const e=n.default.getInstance().subiso;-1===t.SUBISO.all.indexOf(e)&&(n.default.getInstance().subiso=t.SUBISO.default);t.SUBISO.current=n.default.getInstance().subiso}(t),t){for(const e of Object.getOwnPropertyNames(t))b.LOCALE[e]=t[e];for(const[e,r]of Object.entries(t.CORRECTIONS))i.Grammar.getInstance().setCorrection(e,r)}},e.getLocale=v,e.completeLocale=function(t){const r=e.locales[t.locale];if(!r)return void console.error("Locale "+t.locale+" does not exist!");const n=t.kind.toUpperCase(),o=t.messages;if(!o)return;const i=r();for(const[t,e]of Object.entries(o))i[n][t]=e}},4356:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.createLocale=e.LOCALE=void 0;const n=r(7549);function o(){return{FUNCTIONS:(0,n.FUNCTIONS)(),MESSAGES:(0,n.MESSAGES)(),ALPHABETS:(0,n.ALPHABETS)(),NUMBERS:(0,n.NUMBERS)(),COMBINERS:{},CORRECTIONS:{},SUBISO:(0,n.SUBISO)()}}e.LOCALE=o(),e.createLocale=o},2536:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.localeFontCombiner=e.extractString=e.localEnclose=e.localRole=e.localFont=e.combinePostfixIndex=e.nestingToString=void 0;const n=r(4356),o=r(4977);function i(t,e){return void 0===t?e:"string"==typeof t?t:t[0]}e.nestingToString=function(t){switch(t){case 1:return n.LOCALE.MESSAGES.MS.ONCE||"";case 2:return n.LOCALE.MESSAGES.MS.TWICE;default:return t.toString()}},e.combinePostfixIndex=function(t,e){return t===n.LOCALE.MESSAGES.MS.ROOTINDEX||t===n.LOCALE.MESSAGES.MS.INDEX?t:t+" "+e},e.localFont=function(t){return i(n.LOCALE.MESSAGES.font[t],t)},e.localRole=function(t){return i(n.LOCALE.MESSAGES.role[t],t)},e.localEnclose=function(t){return i(n.LOCALE.MESSAGES.enclose[t],t)},e.extractString=i,e.localeFontCombiner=function(t){return"string"==typeof t?{font:t,combiner:n.LOCALE.ALPHABETS.combiner}:{font:t[0],combiner:n.LOCALE.COMBINERS[t[1]]||o.Combiners[t[1]]||n.LOCALE.ALPHABETS.combiner}}},4249:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.ca=void 0;const n=r(4356),o=r(2536),i=r(614),s=r(4977),a=function(t,e,r){return t="sans serif "+(r?r+" "+t:t),e?t+" "+e:t};let l=null;e.ca=function(){return l||(l=function(){const t=(0,n.createLocale)();return t.NUMBERS=i.default,t.COMBINERS.sansserif=a,t.FUNCTIONS.fracNestDepth=t=>!1,t.FUNCTIONS.combineRootIndex=o.combinePostfixIndex,t.FUNCTIONS.combineNestedRadical=(t,e,r)=>t+r,t.FUNCTIONS.fontRegexp=t=>RegExp("^"+t+" "),t.FUNCTIONS.plural=t=>/.*os$/.test(t)?t+"sos":/.*s$/.test(t)?t+"os":/.*ga$/.test(t)?t.slice(0,-2)+"gues":/.*\xe7a$/.test(t)?t.slice(0,-2)+"ces":/.*ca$/.test(t)?t.slice(0,-2)+"ques":/.*ja$/.test(t)?t.slice(0,-2)+"ges":/.*qua$/.test(t)?t.slice(0,-3)+"q\xfces":/.*a$/.test(t)?t.slice(0,-1)+"es":/.*(e|i)$/.test(t)?t+"ns":/.*\xed$/.test(t)?t.slice(0,-1)+"ins":t+"s",t.FUNCTIONS.si=(t,e)=>(e.match(/^metre/)&&(t=t.replace(/a$/,"\xe0").replace(/o$/,"\xf2").replace(/i$/,"\xed")),t+e),t.ALPHABETS.combiner=s.Combiners.prefixCombiner,t}()),l}},8657:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.da=void 0;const n=r(4356),o=r(2536),i=r(3866),s=r(4977);let a=null;e.da=function(){return a||(a=function(){const t=(0,n.createLocale)();return t.NUMBERS=i.default,t.FUNCTIONS.radicalNestDepth=o.nestingToString,t.FUNCTIONS.fontRegexp=e=>e===t.ALPHABETS.capPrefix.default?RegExp("^"+e+" "):RegExp(" "+e+"$"),t.ALPHABETS.combiner=s.Combiners.postfixCombiner,t.ALPHABETS.digitTrans.default=i.default.numberToWords,t}()),a}},173:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.de=void 0;const n=r(2105),o=r(2536),i=r(4356),s=r(1435),a=function(t,e,r){return"s"===r&&(e=e.split(" ").map((function(t){return t.replace(/s$/,"")})).join(" "),r=""),t=r?r+" "+t:t,e?e+" "+t:t},l=function(t,e,r){return t=r&&"s"!==r?r+" "+t:t,e?t+" "+e:t};let c=null;e.de=function(){return c||(c=function(){const t=(0,i.createLocale)();return t.NUMBERS=s.default,t.COMBINERS.germanPostfix=l,t.ALPHABETS.combiner=a,t.FUNCTIONS.radicalNestDepth=e=>e>1?t.NUMBERS.numberToWords(e)+"fach":"",t.FUNCTIONS.combineRootIndex=(t,e)=>{const r=e?e+"wurzel":"";return t.replace("Wurzel",r)},t.FUNCTIONS.combineNestedRadical=(t,e,r)=>{const n=(e?e+" ":"")+(t=r.match(/exponent$/)?t+"r":t);return r.match(/ /)?r.replace(/ /," "+n+" "):n+" "+r},t.FUNCTIONS.fontRegexp=function(t){return t=t.split(" ").map((function(t){return t.replace(/s$/,"(|s)")})).join(" "),new RegExp("((^"+t+" )|( "+t+"$))")},t.CORRECTIONS.correctOne=t=>t.replace(/^eins$/,"ein"),t.CORRECTIONS.localFontNumber=t=>(0,o.localFont)(t).split(" ").map((function(t){return t.replace(/s$/,"")})).join(" "),t.CORRECTIONS.lowercase=t=>t.toLowerCase(),t.CORRECTIONS.article=t=>{const e=n.Grammar.getInstance().getParameter("case"),r=n.Grammar.getInstance().getParameter("plural");return"dative"===e?{der:"dem",die:r?"den":"der",das:"dem"}[t]:t},t.CORRECTIONS.masculine=t=>"dative"===n.Grammar.getInstance().getParameter("case")?t+"n":t,t}()),c}},9393:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.en=void 0;const n=r(2105),o=r(4356),i=r(2536),s=r(310),a=r(4977);let l=null;e.en=function(){return l||(l=function(){const t=(0,o.createLocale)();return t.NUMBERS=s.default,t.FUNCTIONS.radicalNestDepth=i.nestingToString,t.FUNCTIONS.plural=t=>/.*s$/.test(t)?t:t+"s",t.ALPHABETS.combiner=a.Combiners.prefixCombiner,t.ALPHABETS.digitTrans.default=s.default.numberToWords,t.CORRECTIONS.article=t=>n.Grammar.getInstance().getParameter("noArticle")?"":t,t}()),l}},7978:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.es=void 0;const n=r(4356),o=r(2536),i=r(4634),s=r(4977),a=function(t,e,r){return t="sans serif "+(r?r+" "+t:t),e?t+" "+e:t};let l=null;e.es=function(){return l||(l=function(){const t=(0,n.createLocale)();return t.NUMBERS=i.default,t.COMBINERS.sansserif=a,t.FUNCTIONS.fracNestDepth=t=>!1,t.FUNCTIONS.combineRootIndex=o.combinePostfixIndex,t.FUNCTIONS.combineNestedRadical=(t,e,r)=>t+r,t.FUNCTIONS.fontRegexp=t=>RegExp("^"+t+" "),t.FUNCTIONS.plural=t=>/.*(a|e|i|o|u)$/.test(t)?t+"s":/.*z$/.test(t)?t.slice(0,-1)+"ces":/.*c$/.test(t)?t.slice(0,-1)+"ques":/.*g$/.test(t)?t+"ues":/.*\u00f3n$/.test(t)?t.slice(0,-2)+"ones":t+"es",t.FUNCTIONS.si=(t,e)=>(e.match(/^metro/)&&(t=t.replace(/a$/,"\xe1").replace(/o$/,"\xf3").replace(/i$/,"\xed")),t+e),t.ALPHABETS.combiner=s.Combiners.prefixCombiner,t}()),l}},5540:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.fr=void 0;const n=r(2105),o=r(4356),i=r(2536),s=r(2350),a=r(4977);let l=null;e.fr=function(){return l||(l=function(){const t=(0,o.createLocale)();return t.NUMBERS=s.default,t.FUNCTIONS.radicalNestDepth=i.nestingToString,t.FUNCTIONS.combineRootIndex=i.combinePostfixIndex,t.FUNCTIONS.combineNestedFraction=(t,e,r)=>r.replace(/ $/g,"")+e+t,t.FUNCTIONS.combineNestedRadical=(t,e,r)=>r+" "+t,t.FUNCTIONS.fontRegexp=t=>RegExp(" (en |)"+t+"$"),t.FUNCTIONS.plural=t=>/.*s$/.test(t)?t:t+"s",t.CORRECTIONS.article=t=>n.Grammar.getInstance().getParameter("noArticle")?"":t,t.ALPHABETS.combiner=a.Combiners.romanceCombiner,t.SUBISO={default:"fr",current:"fr",all:["fr","be","ch"]},t}()),l}},5218:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.hi=void 0;const n=r(4356),o=r(4438),i=r(4977),s=r(2536);let a=null;e.hi=function(){return a||(a=function(){const t=(0,n.createLocale)();return t.NUMBERS=o.default,t.ALPHABETS.combiner=i.Combiners.prefixCombiner,t.FUNCTIONS.radicalNestDepth=s.nestingToString,t}()),a}},3887:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.it=void 0;const n=r(2536),o=r(4356),i=r(8825),s=r(4977),a=function(t,e,r){return t.match(/^[a-zA-Z]$/)&&(e=e.replace("cerchiato","cerchiata")),t=r?t+" "+r:t,e?t+" "+e:t};let l=null;e.it=function(){return l||(l=function(){const t=(0,o.createLocale)();return t.NUMBERS=i.default,t.COMBINERS.italianPostfix=a,t.FUNCTIONS.radicalNestDepth=n.nestingToString,t.FUNCTIONS.combineRootIndex=n.combinePostfixIndex,t.FUNCTIONS.combineNestedFraction=(t,e,r)=>r.replace(/ $/g,"")+e+t,t.FUNCTIONS.combineNestedRadical=(t,e,r)=>r+" "+t,t.FUNCTIONS.fontRegexp=t=>RegExp(" (en |)"+t+"$"),t.ALPHABETS.combiner=s.Combiners.romanceCombiner,t}()),l}},8384:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.nb=void 0;const n=r(4356),o=r(2536),i=r(8274),s=r(4977);let a=null;e.nb=function(){return a||(a=function(){const t=(0,n.createLocale)();return t.NUMBERS=i.default,t.ALPHABETS.combiner=s.Combiners.prefixCombiner,t.ALPHABETS.digitTrans.default=i.default.numberToWords,t.FUNCTIONS.radicalNestDepth=o.nestingToString,t}()),a}},7206:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.nemeth=void 0;const n=r(4356),o=r(3720),i=r(4977),s=function(t){return t.match(RegExp("^"+h.ALPHABETS.languagePrefix.english))?t.slice(1):t},a=function(t,e,r){return t=s(t),e?t+e:t},l=function(t,e,r){return e+s(t)},c=function(t,e,r){return e+(r||"")+(t=s(t))+"\u283b"},u=function(t,e,r){return e+(r||"")+(t=s(t))+"\u283b\u283b"},p=function(t,e,r){return e+(t=s(t))+"\u283e"};let h=null;e.nemeth=function(){return h||(h=function(){const t=(0,n.createLocale)();return t.NUMBERS=o.default,t.COMBINERS={postfixCombiner:a,germanCombiner:l,embellishCombiner:c,doubleEmbellishCombiner:u,parensCombiner:p},t.FUNCTIONS.fracNestDepth=t=>!1,t.FUNCTIONS.fontRegexp=t=>RegExp("^"+t),t.FUNCTIONS.si=i.identityTransformer,t.ALPHABETS.combiner=(t,e,r)=>e?e+r+t:s(t),t.ALPHABETS.digitTrans={default:o.default.numberToWords},t}()),h}},7734:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.nn=void 0;const n=r(4356),o=r(2536),i=r(8274),s=r(4977);let a=null;e.nn=function(){return a||(a=function(){const t=(0,n.createLocale)();return t.NUMBERS=i.default,t.ALPHABETS.combiner=s.Combiners.prefixCombiner,t.ALPHABETS.digitTrans.default=i.default.numberToWords,t.FUNCTIONS.radicalNestDepth=o.nestingToString,t.SUBISO={default:"",current:"",all:["","alt"]},t}()),a}},7264:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.sv=void 0;const n=r(4356),o=r(2536),i=r(3898),s=r(4977);let a=null;e.sv=function(){return a||(a=function(){const t=(0,n.createLocale)();return t.NUMBERS=i.default,t.FUNCTIONS.radicalNestDepth=o.nestingToString,t.FUNCTIONS.fontRegexp=function(t){return new RegExp("((^"+t+" )|( "+t+"$))")},t.ALPHABETS.combiner=s.Combiners.prefixCombiner,t.ALPHABETS.digitTrans.default=i.default.numberToWords,t.CORRECTIONS.correctOne=t=>t.replace(/^ett$/,"en"),t}()),a}},7549:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.SUBISO=e.FUNCTIONS=e.ALPHABETS=e.NUMBERS=e.MESSAGES=void 0;const n=r(4977);e.MESSAGES=function(){return{MS:{},MSroots:{},font:{},embellish:{},role:{},enclose:{},navigate:{},regexp:{},unitTimes:""}},e.NUMBERS=function(){return{zero:"zero",ones:[],tens:[],large:[],special:{},wordOrdinal:n.identityTransformer,numericOrdinal:n.identityTransformer,numberToWords:n.identityTransformer,numberToOrdinal:n.pluralCase,vulgarSep:" ",numSep:" "}},e.ALPHABETS=function(){return{latinSmall:[],latinCap:[],greekSmall:[],greekCap:[],capPrefix:{default:""},smallPrefix:{default:""},digitPrefix:{default:""},languagePrefix:{},digitTrans:{default:n.identityTransformer,mathspeak:n.identityTransformer,clearspeak:n.identityTransformer},letterTrans:{default:n.identityTransformer},combiner:(t,e,r)=>t}},e.FUNCTIONS=function(){return{fracNestDepth:t=>n.vulgarFractionSmall(t,10,100),radicalNestDepth:t=>"",combineRootIndex:function(t,e){return t},combineNestedFraction:n.Combiners.identityCombiner,combineNestedRadical:n.Combiners.identityCombiner,fontRegexp:function(t){return new RegExp("^"+t.split(/ |-/).join("( |-)")+"( |-)")},si:n.siCombiner,plural:n.identityTransformer}},e.SUBISO=function(){return{default:"",current:"",all:[]}}},614:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0});const n=r(2105);function o(t){const e=t%1e3,r=Math.floor(e/100),n=r?1===r?"cent":a.ones[r]+"-cents":"",o=function(t){const e=t%100;if(e<20)return a.ones[e];const r=Math.floor(e/10),n=a.tens[r],o=a.ones[e%10];return n&&o?n+(2===r?"-i-":"-")+o:n||o}(e%100);return n&&o?n+a.numSep+o:n||o}function i(t){if(0===t)return a.zero;if(t>=Math.pow(10,36))return t.toString();let e=0,r="";for(;t>0;){const n=t%(e>1?1e6:1e3);if(n){let t=a.large[e];if(e)if(1===e)r=(1===n?"":o(n)+a.numSep)+t+(r?a.numSep+r:"");else{const e=i(n);t=1===n?t:t.replace(/\u00f3$/,"ons"),r=e+a.numSep+t+(r?a.numSep+r:"")}else r=o(n)}t=Math.floor(t/(e>1?1e6:1e3)),e++}return r}function s(t){const e=n.Grammar.getInstance().getParameter("gender");return t.toString()+("f"===e?"a":"n")}const a=(0,r(7549).NUMBERS)();a.numericOrdinal=s,a.numberToWords=i,a.numberToOrdinal=function(t,e){if(t>1999)return s(t);if(t<=10)return a.special.onesOrdinals[t-1];const r=i(t);return r.match(/mil$/)?r.replace(/mil$/,"mil\xb7l\xe8sima"):r.match(/u$/)?r.replace(/u$/,"vena"):r.match(/a$/)?r.replace(/a$/,"ena"):r+(r.match(/e$/)?"na":"ena")},e.default=a},3866:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0});function n(t,e=!1){return t===a.ones[1]?e?"et":"en":t}function o(t,e=!1){let r=t%1e3,o="",i=a.ones[Math.floor(r/100)];if(o+=i?n(i,!0)+" hundrede":"",r%=100,r)if(o+=o?" og ":"",i=e?a.special.smallOrdinals[r]:a.ones[r],i)o+=i;else{const t=e?a.special.tensOrdinals[Math.floor(r/10)]:a.tens[Math.floor(r/10)];i=a.ones[r%10],o+=i?n(i)+"og"+t:t}return o}function i(t,e=!1){if(0===t)return a.zero;if(t>=Math.pow(10,36))return t.toString();let r=0,i="";for(;t>0;){const s=t%1e3;if(s){const t=o(s,e&&!r);if(r){const e=a.large[r],o=s>1?"er":"";i=n(t,r<=1)+" "+e+o+(i?" og ":"")+i}else i=n(t)+i}t=Math.floor(t/1e3),r++}return i}function s(t){if(t%100)return i(t,!0);const e=i(t);return e.match(/e$/)?e:e+"e"}const a=(0,r(7549).NUMBERS)();a.wordOrdinal=s,a.numericOrdinal=function(t){return t.toString()+"."},a.numberToWords=i,a.numberToOrdinal=function(t,e){return 1===t?e?"hel":"hele":2===t?e?"halv":"halve":s(t)+(e?"dele":"del")},e.default=a},1435:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0});function n(t,e=!1){return t===a.ones[1]?e?"eine":"ein":t}function o(t){let e=t%1e3,r="",o=a.ones[Math.floor(e/100)];if(r+=o?n(o)+"hundert":"",e%=100,e)if(r+=r?a.numSep:"",o=a.ones[e],o)r+=o;else{const t=a.tens[Math.floor(e/10)];o=a.ones[e%10],r+=o?n(o)+"und"+t:t}return r}function i(t){if(0===t)return a.zero;if(t>=Math.pow(10,36))return t.toString();let e=0,r="";for(;t>0;){const i=t%1e3;if(i){const s=o(t%1e3);if(e){const t=a.large[e],o=e>1&&i>1?t.match(/e$/)?"n":"en":"";r=n(s,e>1)+t+o+r}else r=n(s,e>1)+r}t=Math.floor(t/1e3),e++}return r.replace(/ein$/,"eins")}function s(t){if(1===t)return"erste";if(3===t)return"dritte";if(7===t)return"siebte";if(8===t)return"achte";return i(t)+(t<19?"te":"ste")}const a=(0,r(7549).NUMBERS)();a.wordOrdinal=s,a.numericOrdinal=function(t){return t.toString()+"."},a.numberToWords=i,a.numberToOrdinal=function(t,e){return 1===t?"eintel":2===t?e?"halbe":"halb":s(t)+"l"},e.default=a},310:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0});function n(t){let e=t%1e3,r="";return r+=s.ones[Math.floor(e/100)]?s.ones[Math.floor(e/100)]+s.numSep+"hundred":"",e%=100,e&&(r+=r?s.numSep:"",r+=s.ones[e]||s.tens[Math.floor(e/10)]+(e%10?s.numSep+s.ones[e%10]:"")),r}function o(t){if(0===t)return s.zero;if(t>=Math.pow(10,36))return t.toString();let e=0,r="";for(;t>0;){t%1e3&&(r=n(t%1e3)+(e?"-"+s.large[e]+"-":"")+r),t=Math.floor(t/1e3),e++}return r.replace(/-$/,"")}function i(t){let e=o(t);return e.match(/one$/)?e=e.slice(0,-3)+"first":e.match(/two$/)?e=e.slice(0,-3)+"second":e.match(/three$/)?e=e.slice(0,-5)+"third":e.match(/five$/)?e=e.slice(0,-4)+"fifth":e.match(/eight$/)?e=e.slice(0,-5)+"eighth":e.match(/nine$/)?e=e.slice(0,-4)+"ninth":e.match(/twelve$/)?e=e.slice(0,-6)+"twelfth":e.match(/ty$/)?e=e.slice(0,-2)+"tieth":e+="th",e}const s=(0,r(7549).NUMBERS)();s.wordOrdinal=i,s.numericOrdinal=function(t){const e=t%100,r=t.toString();if(e>10&&e<20)return r+"th";switch(t%10){case 1:return r+"st";case 2:return r+"nd";case 3:return r+"rd";default:return r+"th"}},s.numberToWords=o,s.numberToOrdinal=function(t,e){if(1===t)return e?"oneths":"oneth";if(2===t)return e?"halves":"half";const r=i(t);return e?r+"s":r},e.default=s},4634:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0});const n=r(2105);function o(t){const e=t%1e3,r=Math.floor(e/100),n=i.special.hundreds[r],o=function(t){const e=t%100;if(e<30)return i.ones[e];const r=i.tens[Math.floor(e/10)],n=i.ones[e%10];return r&&n?r+" y "+n:r||n}(e%100);return 1===r?o?n+"to "+o:n:n&&o?n+" "+o:n||o}const i=(0,r(7549).NUMBERS)();i.numericOrdinal=function(t){const e=n.Grammar.getInstance().getParameter("gender");return t.toString()+("f"===e?"a":"o")},i.numberToWords=function(t){if(0===t)return i.zero;if(t>=Math.pow(10,36))return t.toString();let e=0,r="";for(;t>0;){const n=t%1e3;if(n){let t=i.large[e];const s=o(n);e?1===n?(t=t.match("/^mil( |$)/")?t:"un "+t,r=t+(r?" "+r:"")):(t=t.replace(/\u00f3n$/,"ones"),r=o(n)+" "+t+(r?" "+r:"")):r=s}t=Math.floor(t/1e3),e++}return r},i.numberToOrdinal=function(t,e){if(t>1999)return t.toString()+"a";if(t<=12)return i.special.onesOrdinals[t-1];const r=[];if(t>=1e3&&(t-=1e3,r.push("mil\xe9sima")),!t)return r.join(" ");let n=0;return n=Math.floor(t/100),n>0&&(r.push(i.special.hundredsOrdinals[n-1]),t%=100),t<=12?r.push(i.special.onesOrdinals[t-1]):(n=Math.floor(t/10),n>0&&(r.push(i.special.tensOrdinals[n-1]),t%=10),t>0&&r.push(i.special.onesOrdinals[t-1])),r.join(" ")},e.default=i},2350:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0});const n=r(5897),o=r(2105),i=r(7549);function s(t){let e=t%1e3,r="";if(r+=u.ones[Math.floor(e/100)]?u.ones[Math.floor(e/100)]+"-cent":"",e%=100,e){r+=r?"-":"";let t=u.ones[e];if(t)r+=t;else{const n=u.tens[Math.floor(e/10)];n.match(/-dix$/)?(t=u.ones[e%10+10],r+=n.replace(/-dix$/,"")+"-"+t):r+=n+(e%10?"-"+u.ones[e%10]:"")}}const n=r.match(/s-\w+$/);return n?r.replace(/s-\w+$/,n[0].slice(1)):r.replace(/-un$/,"-et-un")}function a(t){if(0===t)return u.zero;if(t>=Math.pow(10,36))return t.toString();u.special["tens-"+n.default.getInstance().subiso]&&(u.tens=u.special["tens-"+n.default.getInstance().subiso]);let e=0,r="";for(;t>0;){const n=t%1e3;if(n){let t=u.large[e];const o=s(n);if(t&&t.match(/^mille /)){const n=t.replace(/^mille /,"");r=r.match(RegExp(n))?o+(e?"-mille-":"")+r:r.match(RegExp(n.replace(/s$/,"")))?o+(e?"-mille-":"")+r.replace(n.replace(/s$/,""),n):o+(e?"-"+t+"-":"")+r}else t=1===n&&t?t.replace(/s$/,""):t,r=o+(e?"-"+t+"-":"")+r}t=Math.floor(t/1e3),e++}return r.replace(/-$/,"")}const l={1:"uni\xe8me",2:"demi",3:"tiers",4:"quart"};function c(t){if(1===t)return"premi\xe8re";let e=a(t);return e.match(/^neuf$/)?e=e.slice(0,-1)+"v":e.match(/cinq$/)?e+="u":e.match(/trois$/)?e+="":(e.match(/e$/)||e.match(/s$/))&&(e=e.slice(0,-1)),e+="i\xe8me",e}const u=(0,i.NUMBERS)();u.wordOrdinal=c,u.numericOrdinal=function(t){const e=o.Grammar.getInstance().getParameter("gender");return 1===t?t.toString()+("m"===e?"er":"re"):t.toString()+"e"},u.numberToWords=a,u.numberToOrdinal=function(t,e){const r=l[t]||c(t);return 3===t?r:e?r+"s":r},e.default=u},4438:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0});const n=r(2105);function o(t){if(0===t)return s.zero;if(t>=Math.pow(10,32))return t.toString();let e=0,r="";const n=function(t){let e=t%1e3,r="";return r+=s.ones[Math.floor(e/100)]?s.ones[Math.floor(e/100)]+s.numSep+s.special.hundred:"",e%=100,e&&(r+=r?s.numSep:"",r+=s.ones[e]),r}(t%1e3);if(!(t=Math.floor(t/1e3)))return n;for(;t>0;){const n=t%100;n&&(r=s.ones[n]+s.numSep+s.large[e]+(r?s.numSep+r:"")),t=Math.floor(t/100),e++}return n?r+s.numSep+n:r}function i(t){const e=n.Grammar.getInstance().getParameter("gender");if(t<=0)return t.toString();if(t<10)return"f"===e?s.special.ordinalsFeminine[t]:s.special.ordinalsMasculine[t];return o(t)+("f"===e?"\u0935\u0940\u0902":"\u0935\u093e\u0901")}const s=(0,r(7549).NUMBERS)();s.wordOrdinal=i,s.numericOrdinal=function(t){const e=n.Grammar.getInstance().getParameter("gender");return t>0&&t<10?"f"===e?s.special.simpleSmallOrdinalsFeminine[t]:s.special.simpleSmallOrdinalsMasculine[t]:t.toString().split("").map((function(t){const e=parseInt(t,10);return isNaN(e)?"":s.special.simpleNumbers[e]})).join("")+("f"===e?"\u0935\u0940\u0902":"\u0935\u093e\u0901")},s.numberToWords=o,s.numberToOrdinal=function(t,e){return t<=10?s.special.smallDenominators[t]:i(t)+" \u0905\u0902\u0936"},e.default=s},8825:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0});const n=r(2105);function o(t){let e=t%1e3,r="";if(r+=a.ones[Math.floor(e/100)]?a.ones[Math.floor(e/100)]+a.numSep+"cento":"",e%=100,e){r+=r?a.numSep:"";const t=a.ones[e];if(t)r+=t;else{let t=a.tens[Math.floor(e/10)];const n=e%10;1!==n&&8!==n||(t=t.slice(0,-1)),r+=t,r+=n?a.numSep+a.ones[e%10]:""}}return r}function i(t){if(0===t)return a.zero;if(t>=Math.pow(10,36))return t.toString();if(1===t&&n.Grammar.getInstance().getParameter("fraction"))return"un";let e=0,r="";for(;t>0;){t%1e3&&(r=o(t%1e3)+(e?"-"+a.large[e]+"-":"")+r),t=Math.floor(t/1e3),e++}return r.replace(/-$/,"")}function s(t){const e="m"===n.Grammar.getInstance().getParameter("gender")?"o":"a";let r=a.special.onesOrdinals[t];return r?r.slice(0,-1)+e:(r=i(t),r.slice(0,-1)+"esim"+e)}const a=(0,r(7549).NUMBERS)();a.wordOrdinal=s,a.numericOrdinal=function(t){const e=n.Grammar.getInstance().getParameter("gender");return t.toString()+("m"===e?"o":"a")},a.numberToWords=i,a.numberToOrdinal=function(t,e){if(2===t)return e?"mezzi":"mezzo";const r=s(t);if(!e)return r;const n=r.match(/o$/)?"i":"e";return r.slice(0,-1)+n},e.default=a},3720:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0});function n(t){return t.toString().split("").map((function(t){return o.ones[parseInt(t,10)]})).join("")}const o=(0,r(7549).NUMBERS)();o.numberToWords=n,o.numberToOrdinal=n,e.default=o},8274:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0});const n=r(5897);function o(t,e=!1){let r=t%1e3,n="";const o=Math.floor(r/100),s=a.ones[o];if(n+=s?(1===o?"":s)+"hundre":"",r%=100,r){if(n+=n?"og":"",e){const t=a.special.smallOrdinals[r];if(t)return n+t;if(r%10)return n+a.tens[Math.floor(r/10)]+a.special.smallOrdinals[r%10]}n+=a.ones[r]||a.tens[Math.floor(r/10)]+(r%10?a.ones[r%10]:"")}return e?i(n):n}function i(t){const e=a.special.endOrdinal[0];return"a"===e&&t.match(/en$/)?t.slice(0,-2)+a.special.endOrdinal:t.match(/(d|n)$/)||t.match(/hundre$/)?t+"de":t.match(/i$/)?t+a.special.endOrdinal:"a"===e&&t.match(/e$/)?t.slice(0,-1)+a.special.endOrdinal:(t.match(/e$/),t+"nde")}function s(t){return u(t,!0)}const a=(0,r(7549).NUMBERS)();function l(t,e=!1){return t===a.ones[1]?"ein"===t?"eitt ":e?"et":"ett":t}function c(t,e=!1){let r=t%1e3,n="",o=a.ones[Math.floor(r/100)];if(n+=o?l(o)+"hundre":"",r%=100,r){if(n+=n?"og":"",e){const t=a.special.smallOrdinals[r];if(t)return n+t}if(o=a.ones[r],o)n+=o;else{const t=a.tens[Math.floor(r/10)];o=a.ones[r%10],n+=o?o+"og"+t:t}}return e?i(n):n}function u(t,e=!1){const r="alt"===n.default.getInstance().subiso?function(t,e=!1){if(0===t)return e?a.special.smallOrdinals[0]:a.zero;if(t>=Math.pow(10,36))return t.toString();let r=0,n="";for(;t>0;){const o=t%1e3;if(o){const i=c(t%1e3,!r&&e);!r&&e&&(e=!e),n=(1===r?l(i,!0):i)+(r>1?a.numSep:"")+(r?a.large[r]+(r>1&&o>1?"er":""):"")+(r>1&&n?a.numSep:"")+n}t=Math.floor(t/1e3),r++}return e?n+(n.match(/tusen$/)?"de":"te"):n}(t,e):function(t,e=!1){if(0===t)return e?a.special.smallOrdinals[0]:a.zero;if(t>=Math.pow(10,36))return t.toString();let r=0,n="";for(;t>0;){const i=t%1e3;if(i){const s=o(t%1e3,!r&&e);!r&&e&&(e=!e),n=s+(r?" "+a.large[r]+(r>1&&i>1?"er":"")+(n?" ":""):"")+n}t=Math.floor(t/1e3),r++}return e?n+(n.match(/tusen$/)?"de":"te"):n}(t,e);return r}a.wordOrdinal=s,a.numericOrdinal=function(t){return t.toString()+"."},a.numberToWords=u,a.numberToOrdinal=function(t,e){return s(t)},e.default=a},3898:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0});function n(t){let e=t%1e3,r="";const n=Math.floor(e/100);return r+=s.ones[n]?(1===n?"":s.ones[n]+s.numSep)+"hundra":"",e%=100,e&&(r+=r?s.numSep:"",r+=s.ones[e]||s.tens[Math.floor(e/10)]+(e%10?s.numSep+s.ones[e%10]:"")),r}function o(t,e=!1){if(0===t)return s.zero;if(t>=Math.pow(10,36))return t.toString();let r=0,o="";for(;t>0;){const i=t%1e3;if(i){const a=s.large[r],l=i>1&&r>1&&!e?"er":"";o=(1===r&&1===i?"":(r>1&&1===i?"en":n(t%1e3))+(r>1?" ":""))+(r?a+l+(r>1?" ":""):"")+o}t=Math.floor(t/1e3),r++}return o.replace(/ $/,"")}function i(t){let e=o(t,!0);return e.match(/^noll$/)?e="nollte":e.match(/ett$/)?e=e.replace(/ett$/,"f\xf6rsta"):e.match(/tv\xe5$/)?e=e.replace(/tv\xe5$/,"andra"):e.match(/tre$/)?e=e.replace(/tre$/,"tredje"):e.match(/fyra$/)?e=e.replace(/fyra$/,"fj\xe4rde"):e.match(/fem$/)?e=e.replace(/fem$/,"femte"):e.match(/sex$/)?e=e.replace(/sex$/,"sj\xe4tte"):e.match(/sju$/)?e=e.replace(/sju$/,"sjunde"):e.match(/\xe5tta$/)?e=e.replace(/\xe5tta$/,"\xe5ttonde"):e.match(/nio$/)?e=e.replace(/nio$/,"nionde"):e.match(/tio$/)?e=e.replace(/tio$/,"tionde"):e.match(/elva$/)?e=e.replace(/elva$/,"elfte"):e.match(/tolv$/)?e=e.replace(/tolv$/,"tolfte"):e.match(/tusen$/)?e=e.replace(/tusen$/,"tusonde"):e.match(/jard$/)||e.match(/jon$/)?e+="te":e+="de",e}const s=(0,r(7549).NUMBERS)();s.wordOrdinal=i,s.numericOrdinal=function(t){const e=t.toString();return e.match(/11$|12$/)?e+":e":e+(e.match(/1$|2$/)?":a":":e")},s.numberToWords=o,s.numberToOrdinal=function(t,e){if(1===t)return"hel";if(2===t)return e?"halva":"halv";let r=i(t);return r=r.match(/de$/)?r.replace(/de$/,""):r,r+(e?"delar":"del")},e.default=s},4977:function(t,e){function r(t,e=""){if(!t.childNodes||!t.childNodes[0]||!t.childNodes[0].childNodes||t.childNodes[0].childNodes.length<2||"number"!==t.childNodes[0].childNodes[0].tagName||"integer"!==t.childNodes[0].childNodes[0].getAttribute("role")||"number"!==t.childNodes[0].childNodes[1].tagName||"integer"!==t.childNodes[0].childNodes[1].getAttribute("role"))return{convertible:!1,content:t.textContent};const r=t.childNodes[0].childNodes[1].textContent,n=t.childNodes[0].childNodes[0].textContent,o=Number(r),i=Number(n);return isNaN(o)||isNaN(i)?{convertible:!1,content:`${n} ${e} ${r}`}:{convertible:!0,enumerator:i,denominator:o}}Object.defineProperty(e,"__esModule",{value:!0}),e.vulgarFractionSmall=e.convertVulgarFraction=e.Combiners=e.siCombiner=e.identityTransformer=e.pluralCase=void 0,e.pluralCase=function(t,e){return t.toString()},e.identityTransformer=function(t){return t.toString()},e.siCombiner=function(t,e){return t+e.toLowerCase()},e.Combiners={},e.Combiners.identityCombiner=function(t,e,r){return t+e+r},e.Combiners.prefixCombiner=function(t,e,r){return t=r?r+" "+t:t,e?e+" "+t:t},e.Combiners.postfixCombiner=function(t,e,r){return t=r?r+" "+t:t,e?t+" "+e:t},e.Combiners.romanceCombiner=function(t,e,r){return t=r?t+" "+r:t,e?t+" "+e:t},e.convertVulgarFraction=r,e.vulgarFractionSmall=function(t,e,n){const o=r(t);if(o.convertible){const t=o.enumerator,r=o.denominator;return t>0&&t0&&r{const s=this.parseCstr(e.toString().replace(o,""));this.addRule(new i.SpeechRule(t,s,n,r))}))}getFullPreconditions(t){const e=this.preconditions.get(t);return e||!this.inherits?e:this.inherits.getFullPreconditions(t)}definePrecondition(t,e,r,...n){const o=this.parsePrecondition(r,n),i=this.parseCstr(e);o&&i?(o.rank=this.rank++,this.preconditions.set(t,new l(i,o))):console.error(`Precondition Error: ${r}, (${e})`)}inheritRules(){if(!this.inherits||!this.inherits.getSpeechRules().length)return;const t=new RegExp("^\\w+\\.\\w+\\."+(this.domain?"\\w+\\.":""));this.inherits.getSpeechRules().forEach((e=>{const r=this.parseCstr(e.dynamicCstr.toString().replace(t,""));this.addRule(new i.SpeechRule(e.name,r,e.precondition,e.action))}))}ignoreRules(t,...e){let r=this.findAllRules((e=>e.name===t));if(!e.length)return void r.forEach(this.deleteRule.bind(this));let n=[];for(const t of e){const e=this.parseCstr(t);for(const t of r)e.equal(t.dynamicCstr)?this.deleteRule(t):n.push(t);r=n,n=[]}}parsePrecondition_(t){const e=this.context.customGenerators.lookup(t);return e?e():[t]}}e.BaseRuleStore=a;class l{constructor(t,e){this.base=t,this._conditions=[],this.constraints=[],this.allCstr={},this.constraints.push(t),this.addCondition(t,e)}get conditions(){return this._conditions}addConstraint(t){if(this.constraints.filter((e=>e.equal(t))).length)return;this.constraints.push(t);const e=[];for(const[r,n]of this.conditions)this.base.equal(r)&&e.push([t,n]);this._conditions=this._conditions.concat(e)}addBaseCondition(t){this.addCondition(this.base,t)}addFullCondition(t){this.constraints.forEach((e=>this.addCondition(e,t)))}addCondition(t,e){const r=t.toString()+" "+e.toString();this.allCstr.condStr||(this.allCstr[r]=!0,this._conditions.push([t,e]))}}e.Condition=l},2469:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.BrailleStore=void 0;const n=r(7630),o=r(9935);class i extends o.MathStore{constructor(){super(...arguments),this.modality="braille",this.customTranscriptions={"\u22ca":"\u2808\u2821\u2833"}}evaluateString(t){const e=[],r=Array.from(t);for(let t=0;tt.push(this.getProperty(e).slice()))),t}toString(){const t=[];return this.order.forEach((e=>t.push(e+": "+this.getProperty(e).toString()))),t.join("\n")}}e.DynamicProperties=n;class o extends n{constructor(t,e){const r={};for(const[e,n]of Object.entries(t))r[e]=[n];super(r,e),this.components=t}static createCstr(...t){const e=o.DEFAULT_ORDER,r={};for(let n=0,o=t.length,i=e.length;n{const r=e.indexOf(t);return-1!==r&&e.splice(r,1)}))}getComponents(){return this.components}getValue(t){return this.components[t]}getValues(){return this.order.map((t=>this.getValue(t)))}allProperties(){const t=super.allProperties();for(let e,r,n=0;e=t[n],r=this.order[n];n++){const t=this.getValue(r);-1===e.indexOf(t)&&e.unshift(t)}return t}toString(){return this.getValues().join(".")}equal(t){const e=t.getAxes();if(this.order.length!==e.length)return!1;for(let r,n=0;r=e[n];n++){const e=this.getValue(r);if(!e||t.getValue(r)!==e)return!1}return!0}}e.DynamicCstr=o,o.DEFAULT_ORDER=[r.LOCALE,r.MODALITY,r.DOMAIN,r.STYLE,r.TOPIC],o.BASE_LOCALE="base",o.DEFAULT_VALUE="default",o.DEFAULT_VALUES={[r.LOCALE]:"en",[r.DOMAIN]:o.DEFAULT_VALUE,[r.STYLE]:o.DEFAULT_VALUE,[r.TOPIC]:o.DEFAULT_VALUE,[r.MODALITY]:"speech"};e.DynamicCstrParser=class{constructor(t){this.order=t}parse(t){const e=t.split("."),r={};if(e.length>this.order.length)throw new Error("Invalid dynamic constraint: "+r);let n=0;for(let t,o=0;t=this.order[o],e.length;o++,n++){const n=e.shift();r[t]=n}return new o(r,this.order.slice(0,n))}};e.DefaultComparator=class{constructor(t,e=new n(t.getProperties(),t.getOrder())){this.reference=t,this.fallback=e,this.order=this.reference.getOrder()}getReference(){return this.reference}setReference(t,e){this.reference=t,this.fallback=e||new n(t.getProperties(),t.getOrder()),this.order=this.reference.getOrder()}match(t){const e=t.getAxes();return e.length===this.reference.getAxes().length&&e.every((e=>{const r=t.getValue(e);return r===this.reference.getValue(e)||-1!==this.fallback.getProperty(e).indexOf(r)}))}compare(t,e){let r=!1;for(let n,o=0;n=this.order[o];o++){const o=t.getValue(n),i=e.getValue(n);if(!r){const t=this.reference.getValue(n);if(t===o&&t!==i)return-1;if(t===i&&t!==o)return 1;if(t===o&&t===i)continue;t!==o&&t!==i&&(r=!0)}const s=this.fallback.getProperty(n),a=s.indexOf(o),l=s.indexOf(i);if(a!h.equal(t.cstr))),l.push(m),this.rules.set(e,l),f.setReference(d)}lookupRule(t,e){let r=this.getRules(e.getValue(o.Axis.LOCALE));return r=r.filter((function(t){return i.testDynamicConstraints_(e,t)})),1===r.length?r[0]:r.length?r.sort(((t,e)=>n.default.getInstance().comparator.compare(t.cstr,e.cstr)))[0]:null}}e.MathSimpleStore=i},9935:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.MathStore=void 0;const n=r(707),o=r(4356),i=r(7630),s=r(4504),a=r(4650);class l extends s.BaseRuleStore{constructor(){super(),this.annotators=[],this.parseMethods.Alias=this.defineAlias,this.parseMethods.SpecializedRule=this.defineSpecializedRule,this.parseMethods.Specialized=this.defineSpecialized}initialize(){this.initialized||(this.annotations(),this.initialized=!0)}annotations(){for(let t,e=0;t=this.annotators[e];e++)(0,i.activate)(this.domain,t)}defineAlias(t,e,...r){const n=this.parsePrecondition(e,r);if(!n)return void console.error(`Precondition Error: ${e} ${r}`);const o=this.preconditions.get(t);o?o.addFullCondition(n):console.error(`Alias Error: No precondition by the name of ${t}`)}defineRulesAlias(t,e,...r){const n=this.findAllRules((function(e){return e.name===t}));if(0===n.length)throw new a.OutputError("Rule with name "+t+" does not exist.");const o=[];n.forEach((t=>{(t=>{const e=t.dynamicCstr.toString(),r=t.action.toString();for(let t,n=0;t=o[n];n++)if(t.action===r&&t.cstr===e)return!1;return o.push({cstr:e,action:r}),!0})(t)&&this.addAlias_(t,e,r)}))}defineSpecializedRule(t,e,r,n){const o=this.parseCstr(e),i=this.findRule((e=>e.name===t&&o.equal(e.dynamicCstr))),s=this.parseCstr(r);if(!i&&n)throw new a.OutputError("Rule named "+t+" with style "+e+" does not exist.");const l=n?a.Action.fromString(n):i.action,c=new a.SpeechRule(i.name,s,i.precondition,l);this.addRule(c)}defineSpecialized(t,e,r){const n=this.parseCstr(r);if(!n)return void console.error(`Dynamic Constraint Error: ${r}`);const o=this.preconditions.get(t);o?o.addConstraint(n):console.error(`Alias Error: No precondition by the name of ${t}`)}evaluateString(t){const e=[];if(t.match(/^\s+$/))return e;let r=this.matchNumber_(t);if(r&&r.length===t.length)return e.push(this.evaluateCharacter(r.number)),e;const i=n.removeEmpty(t.replace(/\s/g," ").split(" "));for(let t,n=0;t=i[n];n++)if(1===t.length)e.push(this.evaluateCharacter(t));else if(t.match(new RegExp("^["+o.LOCALE.MESSAGES.regexp.TEXT+"]+$")))e.push(this.evaluateCharacter(t));else{let n=t;for(;n;){r=this.matchNumber_(n);const t=n.match(new RegExp("^["+o.LOCALE.MESSAGES.regexp.TEXT+"]+"));if(r)e.push(this.evaluateCharacter(r.number)),n=n.substring(r.length);else if(t)e.push(this.evaluateCharacter(t[0])),n=n.substring(t[0].length);else{const t=Array.from(n),r=t[0];e.push(this.evaluateCharacter(r)),n=t.slice(1).join("")}}}return e}parse(t){super.parse(t),this.annotators=t.annotators||[]}addAlias_(t,e,r){const n=this.parsePrecondition(e,r),o=new a.SpeechRule(t.name,t.dynamicCstr,n,t.action);o.name=t.name,this.addRule(o)}matchNumber_(t){const e=t.match(new RegExp("^"+o.LOCALE.MESSAGES.regexp.NUMBER)),r=t.match(new RegExp("^"+l.regexp.NUMBER));if(!e&&!r)return null;const n=r&&r[0]===t;if(e&&e[0]===t||!n)return e?{number:e[0],length:e[0].length}:null;return{number:r[0].replace(new RegExp(l.regexp.DIGIT_GROUP,"g"),"X").replace(new RegExp(l.regexp.DECIMAL_MARK,"g"),o.LOCALE.MESSAGES.regexp.DECIMAL_MARK).replace(/X/g,o.LOCALE.MESSAGES.regexp.DIGIT_GROUP.replace(/\\/g,"")),length:r[0].length}}}e.MathStore=l,l.regexp={NUMBER:"((\\d{1,3})(?=(,| ))((,| )\\d{3})*(\\.\\d+)?)|^\\d*\\.\\d+|^\\d+",DECIMAL_MARK:"\\.",DIGIT_GROUP:","}},4650:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.OutputError=e.Precondition=e.Action=e.Component=e.ActionType=e.SpeechRule=void 0;const n=r(5897),o=r(2105);var i;function s(t){switch(t){case"[n]":return i.NODE;case"[m]":return i.MULTI;case"[t]":return i.TEXT;case"[p]":return i.PERSONALITY;default:throw"Parse error: "+t}}e.SpeechRule=class{constructor(t,e,r,n){this.name=t,this.dynamicCstr=e,this.precondition=r,this.action=n,this.context=null}toString(){return this.name+" | "+this.dynamicCstr.toString()+" | "+this.precondition.toString()+" ==> "+this.action.toString()}},function(t){t.NODE="NODE",t.MULTI="MULTI",t.TEXT="TEXT",t.PERSONALITY="PERSONALITY"}(i=e.ActionType||(e.ActionType={}));class a{constructor({type:t,content:e,attributes:r,grammar:n}){this.type=t,this.content=e,this.attributes=r,this.grammar=n}static grammarFromString(t){return o.Grammar.parseInput(t)}static fromString(t){const e={type:s(t.substring(0,3))};let r=t.slice(3).trim();if(!r)throw new u("Missing content.");switch(e.type){case i.TEXT:if('"'===r[0]){const t=p(r,"\\(")[0].trim();if('"'!==t.slice(-1))throw new u("Invalid string syntax.");e.content=t,r=r.slice(t.length).trim(),-1===r.indexOf("(")&&(r="");break}case i.NODE:case i.MULTI:{const t=r.indexOf(" (");if(-1===t){e.content=r.trim(),r="";break}e.content=r.substring(0,t).trim(),r=r.slice(t).trim()}}if(r){const t=a.attributesFromString(r);t.grammar&&(e.grammar=t.grammar,delete t.grammar),Object.keys(t).length&&(e.attributes=t)}return new a(e)}static attributesFromString(t){if("("!==t[0]||")"!==t.slice(-1))throw new u("Invalid attribute expression: "+t);const e={},r=p(t.slice(1,-1),",");for(let t=0,n=r.length;t0?"("+t.join(", ")+")":""}getAttributes(){const t=[];for(const e in this.attributes){const r=this.attributes[e];"true"===r?t.push(e):t.push(e+":"+r)}return t}}e.Component=a;class l{constructor(t){this.components=t}static fromString(t){const e=p(t,";").filter((function(t){return t.match(/\S/)})).map((function(t){return t.trim()})),r=[];for(let t=0,n=e.length;t0?r[0]:null}applyConstraint(t,e){return!!this.applyQuery(t,e)||n.evaluateBoolean(e,t)}constructString(t,e){if(!e)return"";if('"'===e.charAt(0))return e.slice(1,-1);const r=this.customStrings.lookup(e);return r?r(t):n.evaluateString(e,t)}parse(t){const e=Array.isArray(t)?t:Object.entries(t);for(let t,r=0;t=e[r];r++){switch(t[0].slice(0,3)){case"CQF":this.customQueries.add(t[0],t[1]);break;case"CSF":this.customStrings.add(t[0],t[1]);break;case"CTF":this.contextFunctions.add(t[0],t[1]);break;case"CGF":this.customGenerators.add(t[0],t[1]);break;default:console.error("FunctionError: Invalid function name "+t[0])}}}}},2362:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.storeFactory=e.SpeechRuleEngine=void 0;const n=r(7052),o=r(2057),i=r(5740),s=r(5897),a=r(4440),l=r(5274),c=r(7283),u=r(7599),p=r(2469),h=r(1676),f=r(2105),d=r(9935),m=r(4650),y=r(4508);class g{constructor(){this.trie=null,this.evaluators_={},this.trie=new y.Trie}static getInstance(){return g.instance=g.instance||new g,g.instance}static debugSpeechRule(t,e){const r=t.precondition,n=t.context.applyQuery(e,r.query);o.Debugger.getInstance().output(r.query,n?n.toString():n),r.constraints.forEach((r=>o.Debugger.getInstance().output(r,t.context.applyConstraint(e,r))))}static debugNamedSpeechRule(t,e){const r=g.getInstance().trie.collectRules().filter((e=>e.name==t));for(let n,i=0;n=r[i];i++)o.Debugger.getInstance().output("Rule",t,"DynamicCstr:",n.dynamicCstr.toString(),"number",i),g.debugSpeechRule(n,e)}evaluateNode(t){(0,l.updateEvaluator)(t);const e=(new Date).getTime();let r=[];try{r=this.evaluateNode_(t)}catch(t){console.error("Something went wrong computing speech."),o.Debugger.getInstance().output(t)}const n=(new Date).getTime();return o.Debugger.getInstance().output("Time:",n-e),r}toString(){return this.trie.collectRules().map((t=>t.toString())).join("\n")}runInSetting(t,e){const r=s.default.getInstance(),n={};for(const e in t)n[e]=r[e],r[e]=t[e];r.setDynamicCstr();const o=e();for(const t in n)r[t]=n[t];return r.setDynamicCstr(),o}addStore(t){const e=v(t);"abstract"!==e.kind&&e.getSpeechRules().forEach((t=>this.trie.addRule(t))),this.addEvaluator(e)}processGrammar(t,e,r){const n={};for(const o in r){const i=r[o];n[o]="string"==typeof i?t.constructString(e,i):i}f.Grammar.getInstance().pushState(n)}addEvaluator(t){const e=t.evaluateDefault.bind(t),r=this.evaluators_[t.locale];if(r)return void(r[t.modality]=e);const n={};n[t.modality]=e,this.evaluators_[t.locale]=n}getEvaluator(t,e){const r=this.evaluators_[t]||this.evaluators_[h.DynamicCstr.DEFAULT_VALUES[h.Axis.LOCALE]];return r[e]||r[h.DynamicCstr.DEFAULT_VALUES[h.Axis.MODALITY]]}enumerate(t){return this.trie.enumerate(t)}evaluateNode_(t){return t?(this.updateConstraint_(),this.evaluateTree_(t)):[]}evaluateTree_(t){const e=s.default.getInstance();let r;o.Debugger.getInstance().output(e.mode!==a.Mode.HTTP?t.toString():t),f.Grammar.getInstance().setAttribute(t);const i=this.lookupRule(t,e.dynamicCstr);if(!i)return e.strict?[]:(r=this.getEvaluator(e.locale,e.modality)(t),t.attributes&&this.addPersonality_(r,{},!1,t),r);o.Debugger.getInstance().generateOutput((()=>["Apply Rule:",i.name,i.dynamicCstr.toString(),(e.mode,a.Mode.HTTP,t).toString()]));const c=i.context,u=i.action.components;r=[];for(let e,o=0;e=u[o];o++){let o=[];const i=e.content||"",a=e.attributes||{};let u=!1;e.grammar&&this.processGrammar(c,t,e.grammar);let p=null;if(a.engine){p=s.default.getInstance().dynamicCstr.getComponents();const t=f.Grammar.parseInput(a.engine);s.default.getInstance().setDynamicCstr(t)}switch(e.type){case m.ActionType.NODE:{const e=c.applyQuery(t,i);e&&(o=this.evaluateTree_(e))}break;case m.ActionType.MULTI:{u=!0;const e=c.applySelector(t,i);e.length>0&&(o=this.evaluateNodeList_(c,e,a.sepFunc,c.constructString(t,a.separator),a.ctxtFunc,c.constructString(t,a.context)))}break;case m.ActionType.TEXT:{const e=a.span,r={};if(e){const n=(0,l.evalXPath)(e,t);n.length&&(r.extid=n[0].getAttribute("extid"))}const s=c.constructString(t,i);(s||""===s)&&(o=Array.isArray(s)?s.map((function(t){return n.AuditoryDescription.create({text:t.speech,attributes:t.attributes},{adjust:!0})})):[n.AuditoryDescription.create({text:s,attributes:r},{adjust:!0})])}break;case m.ActionType.PERSONALITY:default:o=[n.AuditoryDescription.create({text:i})]}o[0]&&!u&&(a.context&&(o[0].context=c.constructString(t,a.context)+(o[0].context||"")),a.annotation&&(o[0].annotation=a.annotation)),this.addLayout(o,a,u),e.grammar&&f.Grammar.getInstance().popState(),r=r.concat(this.addPersonality_(o,a,u,t)),p&&s.default.getInstance().setDynamicCstr(p)}return r}evaluateNodeList_(t,e,r,o,i,s){if(!e.length)return[];const a=o||"",l=s||"",c=t.contextFunctions.lookup(i),u=c?c(e,l):function(){return l},p=t.contextFunctions.lookup(r),h=p?p(e,a):function(){return[n.AuditoryDescription.create({text:a},{translate:!0})]};let f=[];for(let t,r=0;t=e[r];r++){const n=this.evaluateTree_(t);if(n.length>0&&(n[0].context=u()+(n[0].context||""),f=f.concat(n),r=0;e--){const n=r[e].name;!t.attributes[n]&&n.match(/^ext/)&&(t.attributes[n]=r[e].value)}}}addRelativePersonality_(t,e){if(!t.personality)return t.personality=e,t;const r=t.personality;for(const t in e)r[t]&&"number"==typeof r[t]&&"number"==typeof e[t]?r[t]=r[t]+e[t]:r[t]||(r[t]=e[t]);return t}updateConstraint_(){const t=s.default.getInstance().dynamicCstr,e=s.default.getInstance().strict,r=this.trie,n={};let o=t.getValue(h.Axis.LOCALE),i=t.getValue(h.Axis.MODALITY),a=t.getValue(h.Axis.DOMAIN);r.hasSubtrie([o,i,a])||(a=h.DynamicCstr.DEFAULT_VALUES[h.Axis.DOMAIN],r.hasSubtrie([o,i,a])||(i=h.DynamicCstr.DEFAULT_VALUES[h.Axis.MODALITY],r.hasSubtrie([o,i,a])||(o=h.DynamicCstr.DEFAULT_VALUES[h.Axis.LOCALE]))),n[h.Axis.LOCALE]=[o],n[h.Axis.MODALITY]=["summary"!==i?i:h.DynamicCstr.DEFAULT_VALUES[h.Axis.MODALITY]],n[h.Axis.DOMAIN]=["speech"!==i?h.DynamicCstr.DEFAULT_VALUES[h.Axis.DOMAIN]:a];const l=t.getOrder();for(let r,o=0;r=l[o];o++)if(!n[r]){const o=t.getValue(r),i=this.makeSet_(o,t.preference),s=h.DynamicCstr.DEFAULT_VALUES[r];e||o===s||i.push(s),n[r]=i}t.updateProperties(n)}makeSet_(t,e){return e&&Object.keys(e).length?t.split(":"):[t]}lookupRule(t,e){if(!t||t.nodeType!==i.NodeType.ELEMENT_NODE&&t.nodeType!==i.NodeType.TEXT_NODE)return null;const r=this.lookupRules(t,e);return r.length>0?this.pickMostConstraint_(e,r):null}lookupRules(t,e){return this.trie.lookupRules(t,e.allProperties())}pickMostConstraint_(t,e){const r=s.default.getInstance().comparator;return e.sort((function(t,e){return r.compare(t.dynamicCstr,e.dynamicCstr)||e.precondition.priority-t.precondition.priority||e.precondition.constraints.length-t.precondition.constraints.length||e.precondition.rank-t.precondition.rank})),o.Debugger.getInstance().generateOutput((()=>e.map((t=>t.name+"("+t.dynamicCstr.toString()+")"))).bind(this)),e[0]}}e.SpeechRuleEngine=g;const b=new Map;function v(t){const e=`${t.locale}.${t.modality}.${t.domain}`;if("actions"===t.kind){const r=b.get(e);return r.parse(t),r}u.init(),t&&!t.functions&&(t.functions=c.getStore(t.locale,t.modality,t.domain));const r="braille"===t.modality?new p.BrailleStore:new d.MathStore;return b.set(e,r),t.inherits&&(r.inherits=b.get(`${t.inherits}.${t.modality}.${t.domain}`)),r.parse(t),r.initialize(),r}e.storeFactory=v,s.default.nodeEvaluator=g.getInstance().evaluateNode.bind(g.getInstance())},5662:function(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.CustomGenerators=e.ContextFunctions=e.CustomStrings=e.CustomQueries=void 0;class r{constructor(t,e){this.prefix=t,this.store=e}add(t,e){this.checkCustomFunctionSyntax_(t)&&(this.store[t]=e)}addStore(t){const e=Object.keys(t.store);for(let r,n=0;r=e[n];n++)this.add(r,t.store[r])}lookup(t){return this.store[t]}checkCustomFunctionSyntax_(t){const e=new RegExp("^"+this.prefix);return!!t.match(e)||(console.error("FunctionError: Invalid function name. Expected prefix "+this.prefix),!1)}}e.CustomQueries=class extends r{constructor(){super("CQF",{})}};e.CustomStrings=class extends r{constructor(){super("CSF",{})}};e.ContextFunctions=class extends r{constructor(){super("CTF",{})}};e.CustomGenerators=class extends r{constructor(){super("CGF",{})}}},365:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.contentIterator=e.pauseSeparator=e.nodeCounter=void 0;const n=r(7052),o=r(5274),i=r(5897);e.nodeCounter=function(t,e){const r=t.length;let n=0,o=e;return e||(o=""),function(){return n0?o.evalXPath("../../content/*",t[0]):[],function(){const t=r.shift(),o=e?[n.AuditoryDescription.create({text:e},{translate:!0})]:[];if(!t)return o;const s=i.default.evaluateNode(t);return o.concat(s)}}},1414:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.getTreeFromString=e.getTree=e.xmlTree=void 0;const n=r(5740),o=r(7075);function i(t){return new o.SemanticTree(t)}e.xmlTree=function(t){return i(t).xml()},e.getTree=i,e.getTreeFromString=function(t){return i(n.parseInput(t))}},7630:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.annotate=e.activate=e.register=e.visitors=e.annotators=void 0;const n=r(9265);e.annotators=new Map,e.visitors=new Map,e.register=function(t){const r=t.domain+":"+t.name;t instanceof n.SemanticAnnotator?e.annotators.set(r,t):e.visitors.set(r,t)},e.activate=function(t,r){const n=t+":"+r,o=e.annotators.get(n)||e.visitors.get(n);o&&(o.active=!0)},e.annotate=function(t){for(const r of e.annotators.values())r.active&&r.annotate(t);for(const r of e.visitors.values())r.active&&r.visit(t,Object.assign({},r.def))}},9265:function(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.SemanticVisitor=e.SemanticAnnotator=void 0;e.SemanticAnnotator=class{constructor(t,e,r){this.domain=t,this.name=e,this.func=r,this.active=!1}annotate(t){t.childNodes.forEach(this.annotate.bind(this)),t.addAnnotation(this.domain,this.func(t))}};e.SemanticVisitor=class{constructor(t,e,r,n={}){this.domain=t,this.name=e,this.func=r,this.def=n,this.active=!1}visit(t,e){let r=this.func(t,e);t.addAnnotation(this.domain,r[0]);for(let e,n=0;e=t.childNodes[n];n++)r=this.visit(e,r[1]);return r}}},3588:function(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.lookupSecondary=e.isEmbellishedType=e.isMatchingFence=e.functionApplication=e.invisibleComma=e.invisiblePlus=e.invisibleTimes=e.lookupMeaning=e.lookupRole=e.lookupType=e.equal=e.allLettersRegExp=void 0;const r=String.fromCodePoint(8291),n=["\uff0c","\ufe50",",",r],o=["\xaf","\u2012","\u2013","\u2014","\u2015","\ufe58","-","\u207b","\u208b","\u2212","\u2796","\ufe63","\uff0d","\u2010","\u2011","\u203e","_"],i=["~","\u0303","\u223c","\u02dc","\u223d","\u02f7","\u0334","\u0330"],s={"(":")","[":"]","{":"}","\u2045":"\u2046","\u2329":"\u232a","\u2768":"\u2769","\u276a":"\u276b","\u276c":"\u276d","\u276e":"\u276f","\u2770":"\u2771","\u2772":"\u2773","\u2774":"\u2775","\u27c5":"\u27c6","\u27e6":"\u27e7","\u27e8":"\u27e9","\u27ea":"\u27eb","\u27ec":"\u27ed","\u27ee":"\u27ef","\u2983":"\u2984","\u2985":"\u2986","\u2987":"\u2988","\u2989":"\u298a","\u298b":"\u298c","\u298d":"\u298e","\u298f":"\u2990","\u2991":"\u2992","\u2993":"\u2994","\u2995":"\u2996","\u2997":"\u2998","\u29d8":"\u29d9","\u29da":"\u29db","\u29fc":"\u29fd","\u2e22":"\u2e23","\u2e24":"\u2e25","\u2e26":"\u2e27","\u2e28":"\u2e29","\u3008":"\u3009","\u300a":"\u300b","\u300c":"\u300d","\u300e":"\u300f","\u3010":"\u3011","\u3014":"\u3015","\u3016":"\u3017","\u3018":"\u3019","\u301a":"\u301b","\u301d":"\u301e","\ufd3e":"\ufd3f","\ufe17":"\ufe18","\ufe59":"\ufe5a","\ufe5b":"\ufe5c","\ufe5d":"\ufe5e","\uff08":"\uff09","\uff3b":"\uff3d","\uff5b":"\uff5d","\uff5f":"\uff60","\uff62":"\uff63","\u2308":"\u2309","\u230a":"\u230b","\u230c":"\u230d","\u230e":"\u230f","\u231c":"\u231d","\u231e":"\u231f","\u239b":"\u239e","\u239c":"\u239f","\u239d":"\u23a0","\u23a1":"\u23a4","\u23a2":"\u23a5","\u23a3":"\u23a6","\u23a7":"\u23ab","\u23a8":"\u23ac","\u23a9":"\u23ad","\u23b0":"\u23b1","\u23b8":"\u23b9"},a={"\u23b4":"\u23b5","\u23dc":"\u23dd","\u23de":"\u23df","\u23e0":"\u23e1","\ufe35":"\ufe36","\ufe37":"\ufe38","\ufe39":"\ufe3a","\ufe3b":"\ufe3c","\ufe3d":"\ufe3e","\ufe3f":"\ufe40","\ufe41":"\ufe42","\ufe43":"\ufe44","\ufe47":"\ufe48"},l=Object.keys(s),c=Object.values(s);c.push("\u301f");const u=Object.keys(a),p=Object.values(a),h=["|","\xa6","\u2223","\u23d0","\u23b8","\u23b9","\u2758","\uff5c","\uffe4","\ufe31","\ufe32"],f=["\u2016","\u2225","\u2980","\u2af4"],d=["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"],m=["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","\u0131","\u0237"],y=["\uff21","\uff22","\uff23","\uff24","\uff25","\uff26","\uff27","\uff28","\uff29","\uff2a","\uff2b","\uff2c","\uff2d","\uff2e","\uff2f","\uff30","\uff31","\uff32","\uff33","\uff34","\uff35","\uff36","\uff37","\uff38","\uff39","\uff3a"],g=["\uff41","\uff42","\uff43","\uff44","\uff45","\uff46","\uff47","\uff48","\uff49","\uff4a","\uff4b","\uff4c","\uff4d","\uff4e","\uff4f","\uff50","\uff51","\uff52","\uff53","\uff54","\uff55","\uff56","\uff57","\uff58","\uff59","\uff5a"],b=["\ud835\udc00","\ud835\udc01","\ud835\udc02","\ud835\udc03","\ud835\udc04","\ud835\udc05","\ud835\udc06","\ud835\udc07","\ud835\udc08","\ud835\udc09","\ud835\udc0a","\ud835\udc0b","\ud835\udc0c","\ud835\udc0d","\ud835\udc0e","\ud835\udc0f","\ud835\udc10","\ud835\udc11","\ud835\udc12","\ud835\udc13","\ud835\udc14","\ud835\udc15","\ud835\udc16","\ud835\udc17","\ud835\udc18","\ud835\udc19"],v=["\ud835\udc1a","\ud835\udc1b","\ud835\udc1c","\ud835\udc1d","\ud835\udc1e","\ud835\udc1f","\ud835\udc20","\ud835\udc21","\ud835\udc22","\ud835\udc23","\ud835\udc24","\ud835\udc25","\ud835\udc26","\ud835\udc27","\ud835\udc28","\ud835\udc29","\ud835\udc2a","\ud835\udc2b","\ud835\udc2c","\ud835\udc2d","\ud835\udc2e","\ud835\udc2f","\ud835\udc30","\ud835\udc31","\ud835\udc32","\ud835\udc33"],_=["\ud835\udc34","\ud835\udc35","\ud835\udc36","\ud835\udc37","\ud835\udc38","\ud835\udc39","\ud835\udc3a","\ud835\udc3b","\ud835\udc3c","\ud835\udc3d","\ud835\udc3e","\ud835\udc3f","\ud835\udc40","\ud835\udc41","\ud835\udc42","\ud835\udc43","\ud835\udc44","\ud835\udc45","\ud835\udc46","\ud835\udc47","\ud835\udc48","\ud835\udc49","\ud835\udc4a","\ud835\udc4b","\ud835\udc4c","\ud835\udc4d"],S=["\ud835\udc4e","\ud835\udc4f","\ud835\udc50","\ud835\udc51","\ud835\udc52","\ud835\udc53","\ud835\udc54","\u210e","\ud835\udc56","\ud835\udc57","\ud835\udc58","\ud835\udc59","\ud835\udc5a","\ud835\udc5b","\ud835\udc5c","\ud835\udc5d","\ud835\udc5e","\ud835\udc5f","\ud835\udc60","\ud835\udc61","\ud835\udc62","\ud835\udc63","\ud835\udc64","\ud835\udc65","\ud835\udc66","\ud835\udc67","\ud835\udea4","\ud835\udea5"],M=["\ud835\udc68","\ud835\udc69","\ud835\udc6a","\ud835\udc6b","\ud835\udc6c","\ud835\udc6d","\ud835\udc6e","\ud835\udc6f","\ud835\udc70","\ud835\udc71","\ud835\udc72","\ud835\udc73","\ud835\udc74","\ud835\udc75","\ud835\udc76","\ud835\udc77","\ud835\udc78","\ud835\udc79","\ud835\udc7a","\ud835\udc7b","\ud835\udc7c","\ud835\udc7d","\ud835\udc7e","\ud835\udc7f","\ud835\udc80","\ud835\udc81"],O=["\ud835\udc82","\ud835\udc83","\ud835\udc84","\ud835\udc85","\ud835\udc86","\ud835\udc87","\ud835\udc88","\ud835\udc89","\ud835\udc8a","\ud835\udc8b","\ud835\udc8c","\ud835\udc8d","\ud835\udc8e","\ud835\udc8f","\ud835\udc90","\ud835\udc91","\ud835\udc92","\ud835\udc93","\ud835\udc94","\ud835\udc95","\ud835\udc96","\ud835\udc97","\ud835\udc98","\ud835\udc99","\ud835\udc9a","\ud835\udc9b"],x=["\ud835\udc9c","\u212c","\ud835\udc9e","\ud835\udc9f","\u2130","\u2131","\ud835\udca2","\u210b","\u2110","\ud835\udca5","\ud835\udca6","\u2112","\u2133","\ud835\udca9","\ud835\udcaa","\ud835\udcab","\ud835\udcac","\u211b","\ud835\udcae","\ud835\udcaf","\ud835\udcb0","\ud835\udcb1","\ud835\udcb2","\ud835\udcb3","\ud835\udcb4","\ud835\udcb5","\u2118"],E=["\ud835\udcb6","\ud835\udcb7","\ud835\udcb8","\ud835\udcb9","\u212f","\ud835\udcbb","\u210a","\ud835\udcbd","\ud835\udcbe","\ud835\udcbf","\ud835\udcc0","\ud835\udcc1","\ud835\udcc2","\ud835\udcc3","\u2134","\ud835\udcc5","\ud835\udcc6","\ud835\udcc7","\ud835\udcc8","\ud835\udcc9","\ud835\udcca","\ud835\udccb","\ud835\udccc","\ud835\udccd","\ud835\udcce","\ud835\udccf","\u2113"],A=["\ud835\udcd0","\ud835\udcd1","\ud835\udcd2","\ud835\udcd3","\ud835\udcd4","\ud835\udcd5","\ud835\udcd6","\ud835\udcd7","\ud835\udcd8","\ud835\udcd9","\ud835\udcda","\ud835\udcdb","\ud835\udcdc","\ud835\udcdd","\ud835\udcde","\ud835\udcdf","\ud835\udce0","\ud835\udce1","\ud835\udce2","\ud835\udce3","\ud835\udce4","\ud835\udce5","\ud835\udce6","\ud835\udce7","\ud835\udce8","\ud835\udce9"],C=["\ud835\udcea","\ud835\udceb","\ud835\udcec","\ud835\udced","\ud835\udcee","\ud835\udcef","\ud835\udcf0","\ud835\udcf1","\ud835\udcf2","\ud835\udcf3","\ud835\udcf4","\ud835\udcf5","\ud835\udcf6","\ud835\udcf7","\ud835\udcf8","\ud835\udcf9","\ud835\udcfa","\ud835\udcfb","\ud835\udcfc","\ud835\udcfd","\ud835\udcfe","\ud835\udcff","\ud835\udd00","\ud835\udd01","\ud835\udd02","\ud835\udd03"],T=["\ud835\udd04","\ud835\udd05","\u212d","\ud835\udd07","\ud835\udd08","\ud835\udd09","\ud835\udd0a","\u210c","\u2111","\ud835\udd0d","\ud835\udd0e","\ud835\udd0f","\ud835\udd10","\ud835\udd11","\ud835\udd12","\ud835\udd13","\ud835\udd14","\u211c","\ud835\udd16","\ud835\udd17","\ud835\udd18","\ud835\udd19","\ud835\udd1a","\ud835\udd1b","\ud835\udd1c","\u2128"],N=["\ud835\udd1e","\ud835\udd1f","\ud835\udd20","\ud835\udd21","\ud835\udd22","\ud835\udd23","\ud835\udd24","\ud835\udd25","\ud835\udd26","\ud835\udd27","\ud835\udd28","\ud835\udd29","\ud835\udd2a","\ud835\udd2b","\ud835\udd2c","\ud835\udd2d","\ud835\udd2e","\ud835\udd2f","\ud835\udd30","\ud835\udd31","\ud835\udd32","\ud835\udd33","\ud835\udd34","\ud835\udd35","\ud835\udd36","\ud835\udd37"],w=["\ud835\udd38","\ud835\udd39","\u2102","\ud835\udd3b","\ud835\udd3c","\ud835\udd3d","\ud835\udd3e","\u210d","\ud835\udd40","\ud835\udd41","\ud835\udd42","\ud835\udd43","\ud835\udd44","\u2115","\ud835\udd46","\u2119","\u211a","\u211d","\ud835\udd4a","\ud835\udd4b","\ud835\udd4c","\ud835\udd4d","\ud835\udd4e","\ud835\udd4f","\ud835\udd50","\u2124"],L=["\ud835\udd52","\ud835\udd53","\ud835\udd54","\ud835\udd55","\ud835\udd56","\ud835\udd57","\ud835\udd58","\ud835\udd59","\ud835\udd5a","\ud835\udd5b","\ud835\udd5c","\ud835\udd5d","\ud835\udd5e","\ud835\udd5f","\ud835\udd60","\ud835\udd61","\ud835\udd62","\ud835\udd63","\ud835\udd64","\ud835\udd65","\ud835\udd66","\ud835\udd67","\ud835\udd68","\ud835\udd69","\ud835\udd6a","\ud835\udd6b"],I=["\ud835\udd6c","\ud835\udd6d","\ud835\udd6e","\ud835\udd6f","\ud835\udd70","\ud835\udd71","\ud835\udd72","\ud835\udd73","\ud835\udd74","\ud835\udd75","\ud835\udd76","\ud835\udd77","\ud835\udd78","\ud835\udd79","\ud835\udd7a","\ud835\udd7b","\ud835\udd7c","\ud835\udd7d","\ud835\udd7e","\ud835\udd7f","\ud835\udd80","\ud835\udd81","\ud835\udd82","\ud835\udd83","\ud835\udd84","\ud835\udd85"],P=["\ud835\udd86","\ud835\udd87","\ud835\udd88","\ud835\udd89","\ud835\udd8a","\ud835\udd8b","\ud835\udd8c","\ud835\udd8d","\ud835\udd8e","\ud835\udd8f","\ud835\udd90","\ud835\udd91","\ud835\udd92","\ud835\udd93","\ud835\udd94","\ud835\udd95","\ud835\udd96","\ud835\udd97","\ud835\udd98","\ud835\udd99","\ud835\udd9a","\ud835\udd9b","\ud835\udd9c","\ud835\udd9d","\ud835\udd9e","\ud835\udd9f"],R=["\ud835\udda0","\ud835\udda1","\ud835\udda2","\ud835\udda3","\ud835\udda4","\ud835\udda5","\ud835\udda6","\ud835\udda7","\ud835\udda8","\ud835\udda9","\ud835\uddaa","\ud835\uddab","\ud835\uddac","\ud835\uddad","\ud835\uddae","\ud835\uddaf","\ud835\uddb0","\ud835\uddb1","\ud835\uddb2","\ud835\uddb3","\ud835\uddb4","\ud835\uddb5","\ud835\uddb6","\ud835\uddb7","\ud835\uddb8","\ud835\uddb9"],k=["\ud835\uddba","\ud835\uddbb","\ud835\uddbc","\ud835\uddbd","\ud835\uddbe","\ud835\uddbf","\ud835\uddc0","\ud835\uddc1","\ud835\uddc2","\ud835\uddc3","\ud835\uddc4","\ud835\uddc5","\ud835\uddc6","\ud835\uddc7","\ud835\uddc8","\ud835\uddc9","\ud835\uddca","\ud835\uddcb","\ud835\uddcc","\ud835\uddcd","\ud835\uddce","\ud835\uddcf","\ud835\uddd0","\ud835\uddd1","\ud835\uddd2","\ud835\uddd3"],j=["\ud835\uddd4","\ud835\uddd5","\ud835\uddd6","\ud835\uddd7","\ud835\uddd8","\ud835\uddd9","\ud835\uddda","\ud835\udddb","\ud835\udddc","\ud835\udddd","\ud835\uddde","\ud835\udddf","\ud835\udde0","\ud835\udde1","\ud835\udde2","\ud835\udde3","\ud835\udde4","\ud835\udde5","\ud835\udde6","\ud835\udde7","\ud835\udde8","\ud835\udde9","\ud835\uddea","\ud835\uddeb","\ud835\uddec","\ud835\udded"],B=["\ud835\uddee","\ud835\uddef","\ud835\uddf0","\ud835\uddf1","\ud835\uddf2","\ud835\uddf3","\ud835\uddf4","\ud835\uddf5","\ud835\uddf6","\ud835\uddf7","\ud835\uddf8","\ud835\uddf9","\ud835\uddfa","\ud835\uddfb","\ud835\uddfc","\ud835\uddfd","\ud835\uddfe","\ud835\uddff","\ud835\ude00","\ud835\ude01","\ud835\ude02","\ud835\ude03","\ud835\ude04","\ud835\ude05","\ud835\ude06","\ud835\ude07"],D=["\ud835\ude08","\ud835\ude09","\ud835\ude0a","\ud835\ude0b","\ud835\ude0c","\ud835\ude0d","\ud835\ude0e","\ud835\ude0f","\ud835\ude10","\ud835\ude11","\ud835\ude12","\ud835\ude13","\ud835\ude14","\ud835\ude15","\ud835\ude16","\ud835\ude17","\ud835\ude18","\ud835\ude19","\ud835\ude1a","\ud835\ude1b","\ud835\ude1c","\ud835\ude1d","\ud835\ude1e","\ud835\ude1f","\ud835\ude20","\ud835\ude21"],F=["\ud835\ude22","\ud835\ude23","\ud835\ude24","\ud835\ude25","\ud835\ude26","\ud835\ude27","\ud835\ude28","\ud835\ude29","\ud835\ude2a","\ud835\ude2b","\ud835\ude2c","\ud835\ude2d","\ud835\ude2e","\ud835\ude2f","\ud835\ude30","\ud835\ude31","\ud835\ude32","\ud835\ude33","\ud835\ude34","\ud835\ude35","\ud835\ude36","\ud835\ude37","\ud835\ude38","\ud835\ude39","\ud835\ude3a","\ud835\ude3b"],H=["\ud835\ude3c","\ud835\ude3d","\ud835\ude3e","\ud835\ude3f","\ud835\ude40","\ud835\ude41","\ud835\ude42","\ud835\ude43","\ud835\ude44","\ud835\ude45","\ud835\ude46","\ud835\ude47","\ud835\ude48","\ud835\ude49","\ud835\ude4a","\ud835\ude4b","\ud835\ude4c","\ud835\ude4d","\ud835\ude4e","\ud835\ude4f","\ud835\ude50","\ud835\ude51","\ud835\ude52","\ud835\ude53","\ud835\ude54","\ud835\ude55"],U=["\ud835\ude56","\ud835\ude57","\ud835\ude58","\ud835\ude59","\ud835\ude5a","\ud835\ude5b","\ud835\ude5c","\ud835\ude5d","\ud835\ude5e","\ud835\ude5f","\ud835\ude60","\ud835\ude61","\ud835\ude62","\ud835\ude63","\ud835\ude64","\ud835\ude65","\ud835\ude66","\ud835\ude67","\ud835\ude68","\ud835\ude69","\ud835\ude6a","\ud835\ude6b","\ud835\ude6c","\ud835\ude6d","\ud835\ude6e","\ud835\ude6f"],X=["\ud835\ude70","\ud835\ude71","\ud835\ude72","\ud835\ude73","\ud835\ude74","\ud835\ude75","\ud835\ude76","\ud835\ude77","\ud835\ude78","\ud835\ude79","\ud835\ude7a","\ud835\ude7b","\ud835\ude7c","\ud835\ude7d","\ud835\ude7e","\ud835\ude7f","\ud835\ude80","\ud835\ude81","\ud835\ude82","\ud835\ude83","\ud835\ude84","\ud835\ude85","\ud835\ude86","\ud835\ude87","\ud835\ude88","\ud835\ude89"],V=["\ud835\ude8a","\ud835\ude8b","\ud835\ude8c","\ud835\ude8d","\ud835\ude8e","\ud835\ude8f","\ud835\ude90","\ud835\ude91","\ud835\ude92","\ud835\ude93","\ud835\ude94","\ud835\ude95","\ud835\ude96","\ud835\ude97","\ud835\ude98","\ud835\ude99","\ud835\ude9a","\ud835\ude9b","\ud835\ude9c","\ud835\ude9d","\ud835\ude9e","\ud835\ude9f","\ud835\udea0","\ud835\udea1","\ud835\udea2","\ud835\udea3"],q=["\u2145","\u2146","\u2147","\u2148","\u2149"],W=["\u0391","\u0392","\u0393","\u0394","\u0395","\u0396","\u0397","\u0398","\u0399","\u039a","\u039b","\u039c","\u039d","\u039e","\u039f","\u03a0","\u03a1","\u03a3","\u03a4","\u03a5","\u03a6","\u03a7","\u03a8","\u03a9"],G=["\u03b1","\u03b2","\u03b3","\u03b4","\u03b5","\u03b6","\u03b7","\u03b8","\u03b9","\u03ba","\u03bb","\u03bc","\u03bd","\u03be","\u03bf","\u03c0","\u03c1","\u03c2","\u03c3","\u03c4","\u03c5","\u03c6","\u03c7","\u03c8","\u03c9"],z=["\ud835\udea8","\ud835\udea9","\ud835\udeaa","\ud835\udeab","\ud835\udeac","\ud835\udead","\ud835\udeae","\ud835\udeaf","\ud835\udeb0","\ud835\udeb1","\ud835\udeb2","\ud835\udeb3","\ud835\udeb4","\ud835\udeb5","\ud835\udeb6","\ud835\udeb7","\ud835\udeb8","\ud835\udeba","\ud835\udebb","\ud835\udebc","\ud835\udebd","\ud835\udebe","\ud835\udebf","\ud835\udec0"],J=["\ud835\udec2","\ud835\udec3","\ud835\udec4","\ud835\udec5","\ud835\udec6","\ud835\udec7","\ud835\udec8","\ud835\udec9","\ud835\udeca","\ud835\udecb","\ud835\udecc","\ud835\udecd","\ud835\udece","\ud835\udecf","\ud835\uded0","\ud835\uded1","\ud835\uded2","\ud835\uded3","\ud835\uded4","\ud835\uded5","\ud835\uded6","\ud835\uded7","\ud835\uded8","\ud835\uded9","\ud835\udeda"],K=["\ud835\udee2","\ud835\udee3","\ud835\udee4","\ud835\udee5","\ud835\udee6","\ud835\udee7","\ud835\udee8","\ud835\udee9","\ud835\udeea","\ud835\udeeb","\ud835\udeec","\ud835\udeed","\ud835\udeee","\ud835\udeef","\ud835\udef0","\ud835\udef1","\ud835\udef2","\ud835\udef4","\ud835\udef5","\ud835\udef6","\ud835\udef7","\ud835\udef8","\ud835\udef9","\ud835\udefa"],$=["\ud835\udefc","\ud835\udefd","\ud835\udefe","\ud835\udeff","\ud835\udf00","\ud835\udf01","\ud835\udf02","\ud835\udf03","\ud835\udf04","\ud835\udf05","\ud835\udf06","\ud835\udf07","\ud835\udf08","\ud835\udf09","\ud835\udf0a","\ud835\udf0b","\ud835\udf0c","\ud835\udf0d","\ud835\udf0e","\ud835\udf0f","\ud835\udf10","\ud835\udf11","\ud835\udf12","\ud835\udf13","\ud835\udf14"],Y=["\ud835\udf1c","\ud835\udf1d","\ud835\udf1e","\ud835\udf1f","\ud835\udf20","\ud835\udf21","\ud835\udf22","\ud835\udf23","\ud835\udf24","\ud835\udf25","\ud835\udf26","\ud835\udf27","\ud835\udf28","\ud835\udf29","\ud835\udf2a","\ud835\udf2b","\ud835\udf2c","\ud835\udf2e","\ud835\udf2f","\ud835\udf30","\ud835\udf31","\ud835\udf32","\ud835\udf33","\ud835\udf34"],Z=["\ud835\udf36","\ud835\udf37","\ud835\udf38","\ud835\udf39","\ud835\udf3a","\ud835\udf3b","\ud835\udf3c","\ud835\udf3d","\ud835\udf3e","\ud835\udf3f","\ud835\udf40","\ud835\udf41","\ud835\udf42","\ud835\udf43","\ud835\udf44","\ud835\udf45","\ud835\udf46","\ud835\udf47","\ud835\udf48","\ud835\udf49","\ud835\udf4a","\ud835\udf4b","\ud835\udf4c","\ud835\udf4d","\ud835\udf4e"],Q=["\ud835\udf56","\ud835\udf57","\ud835\udf58","\ud835\udf59","\ud835\udf5a","\ud835\udf5b","\ud835\udf5c","\ud835\udf5d","\ud835\udf5e","\ud835\udf5f","\ud835\udf60","\ud835\udf61","\ud835\udf62","\ud835\udf63","\ud835\udf64","\ud835\udf65","\ud835\udf66","\ud835\udf68","\ud835\udf69","\ud835\udf6a","\ud835\udf6b","\ud835\udf6c","\ud835\udf6d","\ud835\udf6e"],tt=["\ud835\udf70","\ud835\udf71","\ud835\udf72","\ud835\udf73","\ud835\udf74","\ud835\udf75","\ud835\udf76","\ud835\udf77","\ud835\udf78","\ud835\udf79","\ud835\udf7a","\ud835\udf7b","\ud835\udf7c","\ud835\udf7d","\ud835\udf7e","\ud835\udf7f","\ud835\udf80","\ud835\udf81","\ud835\udf82","\ud835\udf83","\ud835\udf84","\ud835\udf85","\ud835\udf86","\ud835\udf87","\ud835\udf88"],et=["\ud835\udf90","\ud835\udf91","\ud835\udf92","\ud835\udf93","\ud835\udf94","\ud835\udf95","\ud835\udf96","\ud835\udf97","\ud835\udf98","\ud835\udf99","\ud835\udf9a","\ud835\udf9b","\ud835\udf9c","\ud835\udf9d","\ud835\udf9e","\ud835\udf9f","\ud835\udfa0","\ud835\udfa2","\ud835\udfa3","\ud835\udfa4","\ud835\udfa5","\ud835\udfa6","\ud835\udfa7","\ud835\udfa8"],rt=["\ud835\udfaa","\ud835\udfab","\ud835\udfac","\ud835\udfad","\ud835\udfae","\ud835\udfaf","\ud835\udfb0","\ud835\udfb1","\ud835\udfb2","\ud835\udfb3","\ud835\udfb4","\ud835\udfb5","\ud835\udfb6","\ud835\udfb7","\ud835\udfb8","\ud835\udfb9","\ud835\udfba","\ud835\udfbb","\ud835\udfbc","\ud835\udfbd","\ud835\udfbe","\ud835\udfbf","\ud835\udfc0","\ud835\udfc1","\ud835\udfc2"],nt=["\u213c","\u213d","\u213e","\u213f"],ot=["\u03d0","\u03d1","\u03d5","\u03d6","\u03d7","\u03f0","\u03f1","\u03f5","\u03f6","\u03f4"],it=["\ud835\udedc","\ud835\udedd","\ud835\udede","\ud835\udedf","\ud835\udee0","\ud835\udee1"],st=["\ud835\udf16","\ud835\udf17","\ud835\udf18","\ud835\udf19","\ud835\udf1a","\ud835\udf1b"],at=["\ud835\udf8a","\ud835\udf8b","\ud835\udf8c","\ud835\udf8d","\ud835\udf8e","\ud835\udf8f"],lt=["\u2135","\u2136","\u2137","\u2138"],ct=d.concat(m,y,g,b,v,_,M,O,S,x,E,A,C,T,N,w,L,I,P,R,k,j,B,D,F,H,U,X,V,q,W,G,z,J,K,$,Y,Z,Q,tt,nt,ot,et,rt,it,st,at,lt);e.allLettersRegExp=new RegExp(ct.join("|"));const ut=["+","\xb1","\u2213","\u2214","\u2227","\u2228","\u2229","\u222a","\u228c","\u228d","\u228e","\u2293","\u2294","\u229d","\u229e","\u22a4","\u22a5","\u22ba","\u22bb","\u22bc","\u22c4","\u22ce","\u22cf","\u22d2","\u22d3","\u2a5e","\u2295","\u22d4"],pt=String.fromCodePoint(8292);ut.push(pt);const ht=["\u2020","\u2021","\u2210","\u2217","\u2218","\u2219","\u2240","\u229a","\u229b","\u22a0","\u22a1","\u22c5","\u22c6","\u22c7","\u22c8","\u22c9","\u22ca","\u22cb","\u22cc","\u25cb","\xb7","*","\u2297","\u2299"],ft=String.fromCodePoint(8290);ht.push(ft);const dt=String.fromCodePoint(8289),mt=["\xbc","\xbd","\xbe","\u2150","\u2151","\u2152","\u2153","\u2154","\u2155","\u2156","\u2157","\u2158","\u2159","\u215a","\u215b","\u215c","\u215d","\u215e","\u215f","\u2189"],yt=["\xb2","\xb3","\xb9","\u2070","\u2074","\u2075","\u2076","\u2077","\u2078","\u2079"].concat(["\u2080","\u2081","\u2082","\u2083","\u2084","\u2085","\u2086","\u2087","\u2088","\u2089"],["\u2460","\u2461","\u2462","\u2463","\u2464","\u2465","\u2466","\u2467","\u2468","\u2469","\u246a","\u246b","\u246c","\u246d","\u246e","\u246f","\u2470","\u2471","\u2472","\u2473","\u24ea","\u24eb","\u24ec","\u24ed","\u24ee","\u24ef","\u24f0","\u24f1","\u24f2","\u24f3","\u24f4","\u24f5","\u24f6","\u24f7","\u24f8","\u24f9","\u24fa","\u24fb","\u24fc","\u24fd","\u24fe","\u24ff","\u2776","\u2777","\u2778","\u2779","\u277a","\u277b","\u277c","\u277d","\u277e","\u277f","\u2780","\u2781","\u2782","\u2783","\u2784","\u2785","\u2786","\u2787","\u2788","\u2789","\u278a","\u278b","\u278c","\u278d","\u278e","\u278f","\u2790","\u2791","\u2792","\u2793","\u3248","\u3249","\u324a","\u324b","\u324c","\u324d","\u324e","\u324f","\u3251","\u3252","\u3253","\u3254","\u3255","\u3256","\u3257","\u3258","\u3259","\u325a","\u325b","\u325c","\u325d","\u325e","\u325f","\u32b1","\u32b2","\u32b3","\u32b4","\u32b5","\u32b6","\u32b7","\u32b8","\u32b9","\u32ba","\u32bb","\u32bc","\u32bd","\u32be","\u32bf"],["\u2474","\u2475","\u2476","\u2477","\u2478","\u2479","\u247a","\u247b","\u247c","\u247d","\u247e","\u247f","\u2480","\u2481","\u2482","\u2483","\u2484","\u2485","\u2486","\u2487"],["\u2488","\u2489","\u248a","\u248b","\u248c","\u248d","\u248e","\u248f","\u2490","\u2491","\u2492","\u2493","\u2494","\u2495","\u2496","\u2497","\u2498","\u2499","\u249a","\u249b","\ud83c\udd00","\ud83c\udd01","\ud83c\udd02","\ud83c\udd03","\ud83c\udd04","\ud83c\udd05","\ud83c\udd06","\ud83c\udd07","\ud83c\udd08","\ud83c\udd09","\ud83c\udd0a"]),gt=["cos","cot","csc","sec","sin","tan","arccos","arccot","arccsc","arcsec","arcsin","arctan","arc cos","arc cot","arc csc","arc sec","arc sin","arc tan"].concat(["cosh","coth","csch","sech","sinh","tanh","arcosh","arcoth","arcsch","arsech","arsinh","artanh","arccosh","arccoth","arccsch","arcsech","arcsinh","arctanh"],["deg","det","dim","hom","ker","Tr","tr"],["log","ln","lg","exp","expt","gcd","gcd","arg","im","re","Pr"]),bt=[{set:["!",'"',"#","%","&",";","?","@","\\","\xa1","\xa7","\xb6","\xbf","\u2017","\u2020","\u2021","\u2022","\u2023","\u2024","\u2025","\u2027","\u2030","\u2031","\u2038","\u203b","\u203c","\u203d","\u203e","\u2041","\u2042","\u2043","\u2047","\u2048","\u2049","\u204b","\u204c","\u204d","\u204e","\u204f","\u2050","\u2051","\u2053","\u2055","\u2056","\u2058","\u2059","\u205a","\u205b","\u205c","\u205d","\u205e","\ufe10","\ufe14","\ufe15","\ufe16","\ufe30","\ufe45","\ufe46","\ufe49","\ufe4a","\ufe4b","\ufe4c","\ufe54","\ufe56","\ufe57","\ufe5f","\ufe60","\ufe61","\ufe68","\ufe6a","\ufe6b","\uff01","\uff02","\uff03","\uff05","\uff06","\uff07","\uff0a","\uff0f","\uff1b","\uff1f","\uff20","\uff3c"],type:"punctuation",role:"unknown"},{set:["\ufe13",":","\uff1a","\ufe55"],type:"punctuation",role:"colon"},{set:n,type:"punctuation",role:"comma"},{set:["\u2026","\u22ee","\u22ef","\u22f0","\u22f1","\ufe19"],type:"punctuation",role:"ellipsis"},{set:[".","\ufe52","\uff0e"],type:"punctuation",role:"fullstop"},{set:o,type:"operator",role:"dash"},{set:i,type:"operator",role:"tilde"},{set:["'","\u2032","\u2033","\u2034","\u2035","\u2036","\u2037","\u2057","\u02b9","\u02ba"],type:"punctuation",role:"prime"},{set:["\xb0"],type:"punctuation",role:"degree"},{set:l,type:"fence",role:"open"},{set:c,type:"fence",role:"close"},{set:u,type:"fence",role:"top"},{set:p,type:"fence",role:"bottom"},{set:h,type:"fence",role:"neutral"},{set:f,type:"fence",role:"metric"},{set:m,type:"identifier",role:"latinletter",font:"normal"},{set:d,type:"identifier",role:"latinletter",font:"normal"},{set:g,type:"identifier",role:"latinletter",font:"normal"},{set:y,type:"identifier",role:"latinletter",font:"normal"},{set:v,type:"identifier",role:"latinletter",font:"bold"},{set:b,type:"identifier",role:"latinletter",font:"bold"},{set:S,type:"identifier",role:"latinletter",font:"italic"},{set:_,type:"identifier",role:"latinletter",font:"italic"},{set:O,type:"identifier",role:"latinletter",font:"bold-italic"},{set:M,type:"identifier",role:"latinletter",font:"bold-italic"},{set:E,type:"identifier",role:"latinletter",font:"script"},{set:x,type:"identifier",role:"latinletter",font:"script"},{set:C,type:"identifier",role:"latinletter",font:"bold-script"},{set:A,type:"identifier",role:"latinletter",font:"bold-script"},{set:N,type:"identifier",role:"latinletter",font:"fraktur"},{set:T,type:"identifier",role:"latinletter",font:"fraktur"},{set:L,type:"identifier",role:"latinletter",font:"double-struck"},{set:w,type:"identifier",role:"latinletter",font:"double-struck"},{set:P,type:"identifier",role:"latinletter",font:"bold-fraktur"},{set:I,type:"identifier",role:"latinletter",font:"bold-fraktur"},{set:k,type:"identifier",role:"latinletter",font:"sans-serif"},{set:R,type:"identifier",role:"latinletter",font:"sans-serif"},{set:B,type:"identifier",role:"latinletter",font:"sans-serif-bold"},{set:j,type:"identifier",role:"latinletter",font:"sans-serif-bold"},{set:F,type:"identifier",role:"latinletter",font:"sans-serif-italic"},{set:D,type:"identifier",role:"latinletter",font:"sans-serif-italic"},{set:U,type:"identifier",role:"latinletter",font:"sans-serif-bold-italic"},{set:H,type:"identifier",role:"latinletter",font:"sans-serif-bold-italic"},{set:V,type:"identifier",role:"latinletter",font:"monospace"},{set:X,type:"identifier",role:"latinletter",font:"monospace"},{set:q,type:"identifier",role:"latinletter",font:"double-struck-italic"},{set:G,type:"identifier",role:"greekletter",font:"normal"},{set:W,type:"identifier",role:"greekletter",font:"normal"},{set:J,type:"identifier",role:"greekletter",font:"bold"},{set:z,type:"identifier",role:"greekletter",font:"bold"},{set:$,type:"identifier",role:"greekletter",font:"italic"},{set:K,type:"identifier",role:"greekletter",font:"italic"},{set:Z,type:"identifier",role:"greekletter",font:"bold-italic"},{set:Y,type:"identifier",role:"greekletter",font:"bold-italic"},{set:tt,type:"identifier",role:"greekletter",font:"sans-serif-bold"},{set:Q,type:"identifier",role:"greekletter",font:"sans-serif-bold"},{set:et,type:"identifier",role:"greekletter",font:"sans-serif-bold-italic"},{set:rt,type:"identifier",role:"greekletter",font:"sans-serif-bold-italic"},{set:nt,type:"identifier",role:"greekletter",font:"double-struck"},{set:ot,type:"identifier",role:"greekletter",font:"normal"},{set:it,type:"identifier",role:"greekletter",font:"bold"},{set:st,type:"identifier",role:"greekletter",font:"italic"},{set:at,type:"identifier",role:"greekletter",font:"sans-serif-bold"},{set:lt,type:"identifier",role:"otherletter",font:"normal"},{set:["0","1","2","3","4","5","6","7","8","9"],type:"number",role:"integer",font:"normal"},{set:["\uff10","\uff11","\uff12","\uff13","\uff14","\uff15","\uff16","\uff17","\uff18","\uff19"],type:"number",role:"integer",font:"normal"},{set:["\ud835\udfce","\ud835\udfcf","\ud835\udfd0","\ud835\udfd1","\ud835\udfd2","\ud835\udfd3","\ud835\udfd4","\ud835\udfd5","\ud835\udfd6","\ud835\udfd7"],type:"number",role:"integer",font:"bold"},{set:["\ud835\udfd8","\ud835\udfd9","\ud835\udfda","\ud835\udfdb","\ud835\udfdc","\ud835\udfdd","\ud835\udfde","\ud835\udfdf","\ud835\udfe0","\ud835\udfe1"],type:"number",role:"integer",font:"double-struck"},{set:["\ud835\udfe2","\ud835\udfe3","\ud835\udfe4","\ud835\udfe5","\ud835\udfe6","\ud835\udfe7","\ud835\udfe8","\ud835\udfe9","\ud835\udfea","\ud835\udfeb"],type:"number",role:"integer",font:"sans-serif"},{set:["\ud835\udfec","\ud835\udfed","\ud835\udfee","\ud835\udfef","\ud835\udff0","\ud835\udff1","\ud835\udff2","\ud835\udff3","\ud835\udff4","\ud835\udff5"],type:"number",role:"integer",font:"sans-serif-bold"},{set:["\ud835\udff6","\ud835\udff7","\ud835\udff8","\ud835\udff9","\ud835\udffa","\ud835\udffb","\ud835\udffc","\ud835\udffd","\ud835\udffe","\ud835\udfff"],type:"number",role:"integer",font:"monospace"},{set:mt,type:"number",role:"float"},{set:yt,type:"number",role:"othernumber"},{set:ut,type:"operator",role:"addition"},{set:ht,type:"operator",role:"multiplication"},{set:["\xaf","-","\u2052","\u207b","\u208b","\u2212","\u2216","\u2238","\u2242","\u2296","\u229f","\u2796","\u2a29","\u2a2a","\u2a2b","\u2a2c","\u2a3a","\u2a41","\ufe63","\uff0d","\u2010","\u2011"],type:"operator",role:"subtraction"},{set:["/","\xf7","\u2044","\u2215","\u2298","\u27cc","\u29bc","\u2a38"],type:"operator",role:"division"},{set:["\u2200","\u2203","\u2206","\u2207","\u2202","\u2201","\u2204"],type:"operator",role:"prefix operator"},{set:["\ud835\udec1","\ud835\udedb","\ud835\udfca","\ud835\udfcb"],type:"operator",role:"prefix operator",font:"bold"},{set:["\ud835\udefb","\ud835\udf15"],type:"operator",role:"prefix operator",font:"italic"},{set:["\ud835\udf6f","\ud835\udf89"],type:"operator",role:"prefix operator",font:"sans-serif-bold"},{set:["=","~","\u207c","\u208c","\u223c","\u223d","\u2243","\u2245","\u2248","\u224a","\u224b","\u224c","\u224d","\u224e","\u2251","\u2252","\u2253","\u2254","\u2255","\u2256","\u2257","\u2258","\u2259","\u225a","\u225b","\u225c","\u225d","\u225e","\u225f","\u2261","\u2263","\u29e4","\u2a66","\u2a6e","\u2a6f","\u2a70","\u2a71","\u2a72","\u2a73","\u2a74","\u2a75","\u2a76","\u2a77","\u2a78","\u22d5","\u2a6d","\u2a6a","\u2a6b","\u2a6c","\ufe66","\uff1d","\u2a6c","\u229c","\u2237"],type:"relation",role:"equality"},{set:["<",">","\u2241","\u2242","\u2244","\u2246","\u2247","\u2249","\u224f","\u2250","\u2260","\u2262","\u2264","\u2265","\u2266","\u2267","\u2268","\u2269","\u226a","\u226b","\u226c","\u226d","\u226e","\u226f","\u2270","\u2271","\u2272","\u2273","\u2274","\u2275","\u2276","\u2277","\u2278","\u2279","\u227a","\u227b","\u227c","\u227d","\u227e","\u227f","\u2280","\u2281","\u22d6","\u22d7","\u22d8","\u22d9","\u22da","\u22db","\u22dc","\u22dd","\u22de","\u22df","\u22e0","\u22e1","\u22e6","\u22e7","\u22e8","\u22e9","\u2a79","\u2a7a","\u2a7b","\u2a7c","\u2a7d","\u2a7e","\u2a7f","\u2a80","\u2a81","\u2a82","\u2a83","\u2a84","\u2a85","\u2a86","\u2a87","\u2a88","\u2a89","\u2a8a","\u2a8b","\u2a8c","\u2a8d","\u2a8e","\u2a8f","\u2a90","\u2a91","\u2a92","\u2a93","\u2a94","\u2a95","\u2a96","\u2a97","\u2a98","\u2a99","\u2a9a","\u2a9b","\u2a9c","\u2a9d","\u2a9e","\u2a9f","\u2aa0","\u2aa1","\u2aa2","\u2aa3","\u2aa4","\u2aa5","\u2aa6","\u2aa7","\u2aa8","\u2aa9","\u2aaa","\u2aab","\u2aac","\u2aad","\u2aae","\u2aaf","\u2ab0","\u2ab1","\u2ab2","\u2ab3","\u2ab4","\u2ab5","\u2ab6","\u2ab7","\u2ab8","\u2ab9","\u2aba","\u2abb","\u2abc","\u2af7","\u2af8","\u2af9","\u2afa","\u29c0","\u29c1","\ufe64","\ufe65","\uff1c","\uff1e"],type:"relation",role:"inequality"},{set:["\u22e2","\u22e3","\u22e4","\u22e5","\u2282","\u2283","\u2284","\u2285","\u2286","\u2287","\u2288","\u2289","\u228a","\u228b","\u228f","\u2290","\u2291","\u2292","\u2abd","\u2abe","\u2abf","\u2ac0","\u2ac1","\u2ac2","\u2ac3","\u2ac4","\u2ac5","\u2ac6","\u2ac7","\u2ac8","\u2ac9","\u2aca","\u2acb","\u2acc","\u2acd","\u2ace","\u2acf","\u2ad0","\u2ad1","\u2ad2","\u2ad3","\u2ad4","\u2ad5","\u2ad6","\u2ad7","\u2ad8","\u22d0","\u22d1","\u22ea","\u22eb","\u22ec","\u22ed","\u22b2","\u22b3","\u22b4","\u22b5"],type:"relation",role:"set"},{set:["\u22a2","\u22a3","\u22a6","\u22a7","\u22a8","\u22a9","\u22aa","\u22ab","\u22ac","\u22ad","\u22ae","\u22af","\u2ade","\u2adf","\u2ae0","\u2ae1","\u2ae2","\u2ae3","\u2ae4","\u2ae5","\u2ae6","\u2ae7","\u2ae8","\u2ae9","\u2aea","\u2aeb","\u2aec","\u2aed"],type:"relation",role:"unknown"},{set:["\u2190","\u2191","\u2192","\u2193","\u2194","\u2195","\u2196","\u2197","\u2198","\u2199","\u219a","\u219b","\u219c","\u219d","\u219e","\u219f","\u21a0","\u21a1","\u21a2","\u21a3","\u21a4","\u21a5","\u21a6","\u21a7","\u21a8","\u21a9","\u21aa","\u21ab","\u21ac","\u21ad","\u21ae","\u21af","\u21b0","\u21b1","\u21b2","\u21b3","\u21b4","\u21b5","\u21b6","\u21b7","\u21b8","\u21b9","\u21ba","\u21bb","\u21c4","\u21c5","\u21c6","\u21c7","\u21c8","\u21c9","\u21ca","\u21cd","\u21ce","\u21cf","\u21d0","\u21d1","\u21d2","\u21d3","\u21d4","\u21d5","\u21d6","\u21d7","\u21d8","\u21d9","\u21da","\u21db","\u21dc","\u21dd","\u21de","\u21df","\u21e0","\u21e1","\u21e2","\u21e3","\u21e4","\u21e5","\u21e6","\u21e7","\u21e8","\u21e9","\u21ea","\u21eb","\u21ec","\u21ed","\u21ee","\u21ef","\u21f0","\u21f1","\u21f2","\u21f3","\u21f4","\u21f5","\u21f6","\u21f7","\u21f8","\u21f9","\u21fa","\u21fb","\u21fc","\u21fd","\u21fe","\u21ff","\u2301","\u2303","\u2304","\u2324","\u238b","\u2794","\u2798","\u2799","\u279a","\u279b","\u279c","\u279d","\u279e","\u279f","\u27a0","\u27a1","\u27a2","\u27a3","\u27a4","\u27a5","\u27a6","\u27a7","\u27a8","\u27a9","\u27aa","\u27ab","\u27ac","\u27ad","\u27ae","\u27af","\u27b1","\u27b2","\u27b3","\u27b4","\u27b5","\u27b6","\u27b7","\u27b8","\u27b9","\u27ba","\u27bb","\u27bc","\u27bd","\u27be","\u27f0","\u27f1","\u27f2","\u27f3","\u27f4","\u27f5","\u27f6","\u27f7","\u27f8","\u27f9","\u27fa","\u27fb","\u27fc","\u27fd","\u27fe","\u27ff","\u2900","\u2901","\u2902","\u2903","\u2904","\u2905","\u2906","\u2907","\u2908","\u2909","\u290a","\u290b","\u290c","\u290d","\u290e","\u290f","\u2910","\u2911","\u2912","\u2913","\u2914","\u2915","\u2916","\u2917","\u2918","\u2919","\u291a","\u291b","\u291c","\u291d","\u291e","\u291f","\u2920","\u2921","\u2922","\u2923","\u2924","\u2925","\u2926","\u2927","\u2928","\u2929","\u292a","\u292d","\u292e","\u292f","\u2930","\u2931","\u2932","\u2933","\u2934","\u2935","\u2936","\u2937","\u2938","\u2939","\u293a","\u293b","\u293c","\u293d","\u293e","\u293f","\u2940","\u2941","\u2942","\u2943","\u2944","\u2945","\u2946","\u2947","\u2948","\u2949","\u2970","\u2971","\u2972","\u2973","\u2974","\u2975","\u2976","\u2977","\u2978","\u2979","\u297a","\u297b","\u29b3","\u29b4","\u29bd","\u29ea","\u29ec","\u29ed","\u2a17","\u2b00","\u2b01","\u2b02","\u2b03","\u2b04","\u2b05","\u2b06","\u2b07","\u2b08","\u2b09","\u2b0a","\u2b0b","\u2b0c","\u2b0d","\u2b0e","\u2b0f","\u2b10","\u2b11","\u2b30","\u2b31","\u2b32","\u2b33","\u2b34","\u2b35","\u2b36","\u2b37","\u2b38","\u2b39","\u2b3a","\u2b3b","\u2b3c","\u2b3d","\u2b3e","\u2b3f","\u2b40","\u2b41","\u2b42","\u2b43","\u2b44","\u2b45","\u2b46","\u2b47","\u2b48","\u2b49","\u2b4a","\u2b4b","\u2b4c","\uffe9","\uffea","\uffeb","\uffec","\u21bc","\u21bd","\u21be","\u21bf","\u21c0","\u21c1","\u21c2","\u21c3","\u21cb","\u21cc","\u294a","\u294b","\u294c","\u294d","\u294e","\u294f","\u2950","\u2951","\u2952","\u2953","\u2954","\u2955","\u2956","\u2957","\u2958","\u2959","\u295a","\u295b","\u295c","\u295d","\u295e","\u295f","\u2960","\u2961","\u2962","\u2963","\u2964","\u2965","\u2966","\u2967","\u2968","\u2969","\u296a","\u296b","\u296c","\u296d","\u296e","\u296f","\u297c","\u297d","\u297e","\u297f"],type:"relation",role:"arrow"},{set:["\u2208","\u220a","\u22f2","\u22f3","\u22f4","\u22f5","\u22f6","\u22f7","\u22f8","\u22f9","\u22ff"],type:"operator",role:"element"},{set:["\u2209"],type:"operator",role:"nonelement"},{set:["\u220b","\u220d","\u22fa","\u22fb","\u22fc","\u22fd","\u22fe"],type:"operator",role:"reelement"},{set:["\u220c"],type:"operator",role:"renonelement"},{set:["\u2140","\u220f","\u2210","\u2211","\u22c0","\u22c1","\u22c2","\u22c3","\u2a00","\u2a01","\u2a02","\u2a03","\u2a04","\u2a05","\u2a06","\u2a07","\u2a08","\u2a09","\u2a0a","\u2a0b","\u2afc","\u2aff"],type:"largeop",role:"sum"},{set:["\u222b","\u222c","\u222d","\u222e","\u222f","\u2230","\u2231","\u2232","\u2233","\u2a0c","\u2a0d","\u2a0e","\u2a0f","\u2a10","\u2a11","\u2a12","\u2a13","\u2a14","\u2a15","\u2a16","\u2a17","\u2a18","\u2a19","\u2a1a","\u2a1b","\u2a1c"],type:"largeop",role:"integral"},{set:["\u221f","\u2220","\u2221","\u2222","\u22be","\u22bf","\u25b3","\u25b7","\u25bd","\u25c1"],type:"operator",role:"geometry"},{set:["inf","lim","liminf","limsup","max","min","sup","injlim","projlim","inj lim","proj lim"],type:"function",role:"limit function"},{set:gt,type:"function",role:"prefix function"},{set:["mod","rem"],type:"operator",role:"prefix function"}],vt=function(){const t={};for(let e,r=0;e=bt[r];r++)e.set.forEach((function(r){t[r]={role:e.role||"unknown",type:e.type||"unknown",font:e.font||"unknown"}}));return t}();e.equal=function(t,e){return t.type===e.type&&t.role===e.role&&t.font===e.font},e.lookupType=function(t){var e;return(null===(e=vt[t])||void 0===e?void 0:e.type)||"unknown"},e.lookupRole=function(t){var e;return(null===(e=vt[t])||void 0===e?void 0:e.role)||"unknown"},e.lookupMeaning=function(t){return vt[t]||{role:"unknown",type:"unknown",font:"unknown"}},e.invisibleTimes=function(){return ft},e.invisiblePlus=function(){return pt},e.invisibleComma=function(){return r},e.functionApplication=function(){return dt},e.isMatchingFence=function(t,e){return-1!==h.indexOf(t)||-1!==f.indexOf(t)?t===e:s[t]===e||a[t]===e},e.isEmbellishedType=function(t){return"operator"===t||"relation"===t||"fence"===t||"punctuation"===t};const _t=new Map;function St(t,e){return`${t} ${e}`}function Mt(t,e,r=""){for(const n of e)_t.set(St(t,n),r||t)}Mt("d",["d","\u2146","\uff44","\ud835\udc1d","\ud835\udc51","\ud835\udcb9","\ud835\udced","\ud835\udd21","\ud835\udd55","\ud835\udd89","\ud835\uddbd","\ud835\uddf1","\ud835\ude25","\ud835\ude8d"]),Mt("bar",o),Mt("tilde",i),e.lookupSecondary=function(t,e){return _t.get(St(t,e))}},8158:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.SemanticMeaningCollator=e.SemanticNodeCollator=e.SemanticDefault=void 0;const n=r(3588),o=r(3882);class i{constructor(){this.map={}}static key(t,e){return e?t+":"+e:t}add(t,e){this.map[i.key(t,e.font)]=e}addNode(t){this.add(t.textContent,t.meaning())}retrieve(t,e){return this.map[i.key(t,e)]}retrieveNode(t){return this.retrieve(t.textContent,t.font)}size(){return Object.keys(this.map).length}}e.SemanticDefault=i;class s{constructor(){this.map={}}add(t,e){const r=this.map[t];r?r.push(e):this.map[t]=[e]}retrieve(t,e){return this.map[i.key(t,e)]}retrieveNode(t){return this.retrieve(t.textContent,t.font)}copy(){const t=this.copyCollator();for(const e in this.map)t.map[e]=this.map[e];return t}minimize(){for(const t in this.map)1===this.map[t].length&&delete this.map[t]}minimalCollator(){const t=this.copy();for(const e in t.map)1===t.map[e].length&&delete t.map[e];return t}isMultiValued(){for(const t in this.map)if(this.map[t].length>1)return!0;return!1}isEmpty(){return!Object.keys(this.map).length}}class a extends s{copyCollator(){return new a}add(t,e){const r=i.key(t,e.font);super.add(r,e)}addNode(t){this.add(t.textContent,t)}toString(){const t=[];for(const e in this.map){const r=Array(e.length+3).join(" "),n=this.map[e],o=[];for(let t,e=0;t=n[e];e++)o.push(t.toString());t.push(e+": "+o.join("\n"+r))}return t.join("\n")}collateMeaning(){const t=new l;for(const e in this.map)t.map[e]=this.map[e].map((function(t){return t.meaning()}));return t}}e.SemanticNodeCollator=a;class l extends s{copyCollator(){return new l}add(t,e){const r=this.retrieve(t,e.font);if(!r||!r.find((function(t){return n.equal(t,e)}))){const r=i.key(t,e.font);super.add(r,e)}}addNode(t){this.add(t.textContent,t.meaning())}toString(){const t=[];for(const e in this.map){const r=Array(e.length+3).join(" "),n=this.map[e],o=[];for(let t,e=0;t=n[e];e++)o.push("{type: "+t.type+", role: "+t.role+", font: "+t.font+"}");t.push(e+": "+o.join("\n"+r))}return t.join("\n")}reduce(){for(const t in this.map)1!==this.map[t].length&&(this.map[t]=(0,o.reduce)(this.map[t]))}default(){const t=new i;for(const e in this.map)1===this.map[e].length&&(t.map[e]=this.map[e][0]);return t}newDefault(){const t=this.default();this.reduce();const e=this.default();return t.size()!==e.size()?e:null}}e.SemanticMeaningCollator=l},9911:function(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.SemanticMultiHeuristic=e.SemanticTreeHeuristic=e.SemanticAbstractHeuristic=void 0;class r{constructor(t,e,r=(t=>!1)){this.name=t,this.apply=e,this.applicable=r}}e.SemanticAbstractHeuristic=r;e.SemanticTreeHeuristic=class extends r{};e.SemanticMultiHeuristic=class extends r{}},7516:function(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.lookup=e.run=e.add=e.blacklist=e.flags=e.updateFactory=e.factory=void 0,e.factory=null,e.updateFactory=function(t){e.factory=t};const r=new Map;function n(t){return r.get(t)}e.flags={combine_juxtaposition:!0,convert_juxtaposition:!0,multioperator:!0},e.blacklist={},e.add=function(t){const n=t.name;r.set(n,t),e.flags[n]||(e.flags[n]=!1)},e.run=function(t,r,o){const i=n(t);return i&&!e.blacklist[t]&&(e.flags[t]||i.applicable(r))?i.apply(r):o?o(r):r},e.lookup=n},94:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0});const n=r(2057),o=r(5897),i=r(3588),s=r(7516),a=r(9911),l=r(5609),c=r(3308),u=r(4795);function p(t,e,r){let n=null;if(!t.length)return n;const o=r[r.length-1],i=o&&o.length,s=e&&e.length,a=c.default.getInstance();if(i&&s){if("infixop"===e[0].type&&"implicit"===e[0].role)return n=t.pop(),o.push(a.postfixNode_(o.pop(),t)),n;n=t.shift();const r=a.prefixNode_(e.shift(),t);return e.unshift(r),n}return i?(o.push(a.postfixNode_(o.pop(),t)),n):(s&&e.unshift(a.prefixNode_(e.shift(),t)),n)}function h(t,e,r){if(!e.length)return t;const o=t.pop(),i=e.shift(),a=r.shift();if(l.isImplicitOp(i)){n.Debugger.getInstance().output("Juxta Heuristic Case 2");const s=(o?[o,i]:[i]).concat(a);return h(t.concat(s),e,r)}if(!o)return n.Debugger.getInstance().output("Juxta Heuristic Case 3"),h([i].concat(a),e,r);const c=a.shift();if(!c){n.Debugger.getInstance().output("Juxta Heuristic Case 9");const a=s.factory.makeBranchNode("infixop",[o,e.shift()],[i],i.textContent);return a.role="implicit",s.run("combine_juxtaposition",a),e.unshift(a),h(t,e,r)}if(l.isOperator(o)||l.isOperator(c))return n.Debugger.getInstance().output("Juxta Heuristic Case 4"),h(t.concat([o,i,c]).concat(a),e,r);let u=null;return l.isImplicitOp(o)&&l.isImplicitOp(c)?(n.Debugger.getInstance().output("Juxta Heuristic Case 5"),o.contentNodes.push(i),o.contentNodes=o.contentNodes.concat(c.contentNodes),o.childNodes.push(c),o.childNodes=o.childNodes.concat(c.childNodes),c.childNodes.forEach((t=>t.parent=o)),i.parent=o,o.addMathmlNodes(i.mathml),o.addMathmlNodes(c.mathml),u=o):l.isImplicitOp(o)?(n.Debugger.getInstance().output("Juxta Heuristic Case 6"),o.contentNodes.push(i),o.childNodes.push(c),c.parent=o,i.parent=o,o.addMathmlNodes(i.mathml),o.addMathmlNodes(c.mathml),u=o):l.isImplicitOp(c)?(n.Debugger.getInstance().output("Juxta Heuristic Case 7"),c.contentNodes.unshift(i),c.childNodes.unshift(o),o.parent=c,i.parent=c,c.addMathmlNodes(i.mathml),c.addMathmlNodes(o.mathml),u=c):(n.Debugger.getInstance().output("Juxta Heuristic Case 8"),u=s.factory.makeBranchNode("infixop",[o,c],[i],i.textContent),u.role="implicit"),t.push(u),h(t.concat(a),e,r)}s.add(new a.SemanticTreeHeuristic("combine_juxtaposition",(function(t){for(let e,r=t.childNodes.length-1;e=t.childNodes[r];r--)l.isImplicitOp(e)&&!e.nobreaking&&(t.childNodes.splice(r,1,...e.childNodes),t.contentNodes.splice(r,0,...e.contentNodes),e.childNodes.concat(e.contentNodes).forEach((function(e){e.parent=t})),t.addMathmlNodes(e.mathml));return t}))),s.add(new a.SemanticTreeHeuristic("propagateSimpleFunction",(t=>("infixop"!==t.type&&"fraction"!==t.type||!t.childNodes.every(l.isSimpleFunction)||(t.role="composed function"),t)),(t=>"clearspeak"===o.default.getInstance().domain))),s.add(new a.SemanticTreeHeuristic("simpleNamedFunction",(t=>("unit"!==t.role&&-1!==["f","g","h","F","G","H"].indexOf(t.textContent)&&(t.role="simple function"),t)),(t=>"clearspeak"===o.default.getInstance().domain))),s.add(new a.SemanticTreeHeuristic("propagateComposedFunction",(t=>("fenced"===t.type&&"composed function"===t.childNodes[0].role&&(t.role="composed function"),t)),(t=>"clearspeak"===o.default.getInstance().domain))),s.add(new a.SemanticTreeHeuristic("multioperator",(t=>{if("unknown"!==t.role||t.textContent.length<=1)return;const e=[...t.textContent].map(i.lookupMeaning).reduce((function(t,e){return t&&e.role&&"unknown"!==e.role&&e.role!==t?"unknown"===t?e.role:null:t}),"unknown");e&&(t.role=e)}))),s.add(new a.SemanticMultiHeuristic("convert_juxtaposition",(t=>{let e=u.partitionNodes(t,(function(t){return t.textContent===i.invisibleTimes()&&"operator"===t.type}));e=e.rel.length?function(t){const e=[],r=[];let n=t.comp.shift(),o=null,i=[];for(;t.comp.length;)if(i=[],n.length)o&&e.push(o),r.push(n),o=t.rel.shift(),n=t.comp.shift();else{for(o&&i.push(o);!n.length&&t.comp.length;)n=t.comp.shift(),i.push(t.rel.shift());o=p(i,n,r)}i.length||n.length?(e.push(o),r.push(n)):(i.push(o),p(i,n,r));return{rel:e,comp:r}}(e):e,t=e.comp[0];for(let r,n,o=1;r=e.comp[o],n=e.rel[o-1];o++)t.push(n),t=t.concat(r);return e=u.partitionNodes(t,(function(t){return t.textContent===i.invisibleTimes()&&("operator"===t.type||"infixop"===t.type)})),e.rel.length?h(e.comp.shift(),e.rel,e.comp):t}))),s.add(new a.SemanticTreeHeuristic("simple2prefix",(t=>(t.textContent.length>1&&!t.textContent[0].match(/[A-Z]/)&&(t.role="prefix function"),t)),(t=>"braille"===o.default.getInstance().modality&&"identifier"===t.type))),s.add(new a.SemanticTreeHeuristic("detect_cycle",(t=>{t.type="matrix",t.role="cycle";const e=t.childNodes[0];return e.type="row",e.role="cycle",e.textContent="",e.contentNodes=[],t}),(t=>"fenced"===t.type&&"infixop"===t.childNodes[0].type&&"implicit"===t.childNodes[0].role&&t.childNodes[0].childNodes.every((function(t){return"number"===t.type}))&&t.childNodes[0].contentNodes.every((function(t){return"space"===t.role})))))},7228:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.SemanticMathml=void 0;const n=r(5740),o=r(5250),i=r(5609),s=r(3308),a=r(4795);class l extends o.SemanticAbstractParser{constructor(){super("MathML"),this.parseMap_={SEMANTICS:this.semantics_.bind(this),MATH:this.rows_.bind(this),MROW:this.rows_.bind(this),MPADDED:this.rows_.bind(this),MSTYLE:this.rows_.bind(this),MFRAC:this.fraction_.bind(this),MSUB:this.limits_.bind(this),MSUP:this.limits_.bind(this),MSUBSUP:this.limits_.bind(this),MOVER:this.limits_.bind(this),MUNDER:this.limits_.bind(this),MUNDEROVER:this.limits_.bind(this),MROOT:this.root_.bind(this),MSQRT:this.sqrt_.bind(this),MTABLE:this.table_.bind(this),MLABELEDTR:this.tableLabeledRow_.bind(this),MTR:this.tableRow_.bind(this),MTD:this.tableCell_.bind(this),MS:this.text_.bind(this),MTEXT:this.text_.bind(this),MSPACE:this.space_.bind(this),"ANNOTATION-XML":this.text_.bind(this),MI:this.identifier_.bind(this),MN:this.number_.bind(this),MO:this.operator_.bind(this),MFENCED:this.fenced_.bind(this),MENCLOSE:this.enclosed_.bind(this),MMULTISCRIPTS:this.multiscripts_.bind(this),ANNOTATION:this.empty_.bind(this),NONE:this.empty_.bind(this),MACTION:this.action_.bind(this)};const t={type:"identifier",role:"numbersetletter",font:"double-struck"};["C","H","N","P","Q","R","Z","\u2102","\u210d","\u2115","\u2119","\u211a","\u211d","\u2124"].forEach((e=>this.getFactory().defaultMap.add(e,t)).bind(this))}static getAttribute_(t,e,r){if(!t.hasAttribute(e))return r;const n=t.getAttribute(e);return n.match(/^\s*$/)?null:n}parse(t){s.default.getInstance().setNodeFactory(this.getFactory());const e=n.toArray(t.childNodes),r=n.tagName(t),o=this.parseMap_[r],i=(o||this.dummy_.bind(this))(t,e);return a.addAttributes(i,t),-1!==["MATH","MROW","MPADDED","MSTYLE","SEMANTICS"].indexOf(r)||(i.mathml.unshift(t),i.mathmlTree=t),i}semantics_(t,e){return e.length?this.parse(e[0]):this.getFactory().makeEmptyNode()}rows_(t,e){const r=t.getAttribute("semantics");if(r&&r.match("bspr_"))return s.default.proof(t,r,this.parseList.bind(this));let n;return 1===(e=a.purgeNodes(e)).length?(n=this.parse(e[0]),"empty"!==n.type||n.mathmlTree||(n.mathmlTree=t)):n=s.default.getInstance().row(this.parseList(e)),n.mathml.unshift(t),n}fraction_(t,e){if(!e.length)return this.getFactory().makeEmptyNode();const r=this.parse(e[0]),n=e[1]?this.parse(e[1]):this.getFactory().makeEmptyNode();return s.default.getInstance().fractionLikeNode(r,n,t.getAttribute("linethickness"),"true"===t.getAttribute("bevelled"))}limits_(t,e){return s.default.getInstance().limitNode(n.tagName(t),this.parseList(e))}root_(t,e){return e[1]?this.getFactory().makeBranchNode("root",[this.parse(e[1]),this.parse(e[0])],[]):this.sqrt_(t,e)}sqrt_(t,e){const r=this.parseList(a.purgeNodes(e));return this.getFactory().makeBranchNode("sqrt",[s.default.getInstance().row(r)],[])}table_(t,e){const r=t.getAttribute("semantics");if(r&&r.match("bspr_"))return s.default.proof(t,r,this.parseList.bind(this));const n=this.getFactory().makeBranchNode("table",this.parseList(e),[]);return n.mathmlTree=t,s.default.tableToMultiline(n),n}tableRow_(t,e){const r=this.getFactory().makeBranchNode("row",this.parseList(e),[]);return r.role="table",r}tableLabeledRow_(t,e){if(!e.length)return this.tableRow_(t,e);const r=this.parse(e[0]);r.role="label";const n=this.getFactory().makeBranchNode("row",this.parseList(e.slice(1)),[r]);return n.role="table",n}tableCell_(t,e){const r=this.parseList(a.purgeNodes(e));let n;n=r.length?1===r.length&&i.isType(r[0],"empty")?r:[s.default.getInstance().row(r)]:[];const o=this.getFactory().makeBranchNode("cell",n,[]);return o.role="table",o}space_(t,e){const r=t.getAttribute("width"),o=r&&r.match(/[a-z]*$/);if(!o)return this.empty_(t,e);const i=o[0],a=parseFloat(r.slice(0,o.index)),l={cm:.4,pc:.5,em:.5,ex:1,in:.15,pt:5,mm:5}[i];if(!l||isNaN(a)||a1?this.parse(e[1]):this.getFactory().makeUnprocessed(t)}dummy_(t,e){const r=this.getFactory().makeUnprocessed(t);return r.role=t.tagName,r.textContent=t.textContent,r}leaf_(t,e){if(1===e.length&&e[0].nodeType!==n.NodeType.TEXT_NODE){const r=this.getFactory().makeUnprocessed(t);return r.role=e[0].tagName,a.addAttributes(r,e[0]),r}return this.getFactory().makeLeafNode(t.textContent,s.default.getInstance().font(t.getAttribute("mathvariant")))}}e.SemanticMathml=l},5952:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.SemanticNode=void 0;const n=r(5740),o=r(3588),i=r(4795);class s{constructor(t){this.id=t,this.mathml=[],this.parent=null,this.type="unknown",this.role="unknown",this.font="unknown",this.embellished=null,this.fencePointer="",this.childNodes=[],this.textContent="",this.mathmlTree=null,this.contentNodes=[],this.annotation={},this.attributes={},this.nobreaking=!1}static fromXml(t){const e=parseInt(t.getAttribute("id"),10),r=new s(e);return r.type=t.tagName,s.setAttribute(r,t,"role"),s.setAttribute(r,t,"font"),s.setAttribute(r,t,"embellished"),s.setAttribute(r,t,"fencepointer","fencePointer"),t.getAttribute("annotation")&&r.parseAnnotation(t.getAttribute("annotation")),i.addAttributes(r,t),s.processChildren(r,t),r}static setAttribute(t,e,r,n){n=n||r;const o=e.getAttribute(r);o&&(t[n]=o)}static processChildren(t,e){for(const r of n.toArray(e.childNodes)){if(r.nodeType===n.NodeType.TEXT_NODE){t.textContent=r.textContent;continue}const e=n.toArray(r.childNodes).map(s.fromXml);e.forEach((e=>e.parent=t)),"CONTENT"===n.tagName(r)?t.contentNodes=e:t.childNodes=e}}querySelectorAll(t){let e=[];for(let r,n=0;r=this.childNodes[n];n++)e=e.concat(r.querySelectorAll(t));for(let r,n=0;r=this.contentNodes[n];n++)e=e.concat(r.querySelectorAll(t));return t(this)&&e.unshift(this),e}xml(t,e){const r=function(r,n){const o=n.map((function(r){return r.xml(t,e)})),i=t.createElementNS("",r);for(let t,e=0;t=o[e];e++)i.appendChild(t);return i},n=t.createElementNS("",this.type);return e||this.xmlAttributes(n),n.textContent=this.textContent,this.contentNodes.length>0&&n.appendChild(r("content",this.contentNodes)),this.childNodes.length>0&&n.appendChild(r("children",this.childNodes)),n}toString(t=!1){const e=n.parseInput("");return n.serializeXml(this.xml(e,t))}allAttributes(){const t=[];return t.push(["role",this.role]),"unknown"!==this.font&&t.push(["font",this.font]),Object.keys(this.annotation).length&&t.push(["annotation",this.xmlAnnotation()]),this.embellished&&t.push(["embellished",this.embellished]),this.fencePointer&&t.push(["fencepointer",this.fencePointer]),t.push(["id",this.id.toString()]),t}xmlAnnotation(){const t=[];for(const e in this.annotation)this.annotation[e].forEach((function(r){t.push(e+":"+r)}));return t.join(";")}toJson(){const t={};t.type=this.type;const e=this.allAttributes();for(let r,n=0;r=e[n];n++)t[r[0]]=r[1].toString();return this.textContent&&(t.$t=this.textContent),this.childNodes.length&&(t.children=this.childNodes.map((function(t){return t.toJson()}))),this.contentNodes.length&&(t.content=this.contentNodes.map((function(t){return t.toJson()}))),t}updateContent(t,e){const r=e?t.replace(/^[ \f\n\r\t\v\u200b]*/,"").replace(/[ \f\n\r\t\v\u200b]*$/,""):t.trim();if(t=t&&!r?t:r,this.textContent===t)return;const n=(0,o.lookupMeaning)(t);this.textContent=t,this.role=n.role,this.type=n.type,this.font=n.font}addMathmlNodes(t){for(let e,r=0;e=t[r];r++)-1===this.mathml.indexOf(e)&&this.mathml.push(e)}appendChild(t){this.childNodes.push(t),this.addMathmlNodes(t.mathml),t.parent=this}replaceChild(t,e){const r=this.childNodes.indexOf(t);if(-1===r)return;t.parent=null,e.parent=this,this.childNodes[r]=e;const n=t.mathml.filter((function(t){return-1===e.mathml.indexOf(t)})),o=e.mathml.filter((function(e){return-1===t.mathml.indexOf(e)}));this.removeMathmlNodes(n),this.addMathmlNodes(o)}appendContentNode(t){t&&(this.contentNodes.push(t),this.addMathmlNodes(t.mathml),t.parent=this)}removeContentNode(t){if(t){const e=this.contentNodes.indexOf(t);-1!==e&&this.contentNodes.slice(e,1)}}equals(t){if(!t)return!1;if(this.type!==t.type||this.role!==t.role||this.textContent!==t.textContent||this.childNodes.length!==t.childNodes.length||this.contentNodes.length!==t.contentNodes.length)return!1;for(let e,r,n=0;e=this.childNodes[n],r=t.childNodes[n];n++)if(!e.equals(r))return!1;for(let e,r,n=0;e=this.contentNodes[n],r=t.contentNodes[n];n++)if(!e.equals(r))return!1;return!0}displayTree(){console.info(this.displayTree_(0))}addAnnotation(t,e){e&&this.addAnnotation_(t,e)}getAnnotation(t){const e=this.annotation[t];return e||[]}hasAnnotation(t,e){const r=this.annotation[t];return!!r&&-1!==r.indexOf(e)}parseAnnotation(t){const e=t.split(";");for(let t=0,r=e.length;t1)return!1;const r=e[0];if("infixop"===r.type){if("implicit"!==r.role)return!1;if(r.childNodes.some((t=>i(t,"infixop"))))return!1}return!0},e.isPrefixFunctionBoundary=function(t){return c(t)&&!a(t,"division")||i(t,"appl")||l(t)},e.isBigOpBoundary=function(t){return c(t)||l(t)},e.isIntegralDxBoundary=function(t,e){return!!e&&i(e,"identifier")&&n.lookupSecondary("d",t.textContent)},e.isIntegralDxBoundarySingle=function(t){if(i(t,"identifier")){const e=t.textContent[0];return e&&t.textContent[1]&&n.lookupSecondary("d",e)}return!1},e.isGeneralFunctionBoundary=l,e.isEmbellished=function(t){return t.embellished?t.embellished:n.isEmbellishedType(t.type)?t.type:null},e.isOperator=c,e.isRelation=u,e.isPunctuation=p,e.isFence=h,e.isElligibleEmbellishedFence=function(t){return!(!t||!h(t))&&(!t.embellished||f(t))},e.isTableOrMultiline=d,e.tableIsMatrixOrVector=function(t){return!!t&&m(t)&&d(t.childNodes[0])},e.isFencedElement=m,e.tableIsCases=function(t,e){return e.length>0&&a(e[e.length-1],"openfence")},e.tableIsMultiline=function(t){return t.childNodes.every((function(t){return t.childNodes.length<=1}))},e.lineIsLabelled=function(t){return i(t,"line")&&t.contentNodes.length&&a(t.contentNodes[0],"label")},e.isBinomial=function(t){return 2===t.childNodes.length},e.isLimitBase=function t(e){return i(e,"largeop")||i(e,"limboth")||i(e,"limlower")||i(e,"limupper")||i(e,"function")&&a(e,"limit function")||(i(e,"overscore")||i(e,"underscore"))&&t(e.childNodes[0])},e.isSimpleFunctionHead=function(t){return"identifier"===t.type||"latinletter"===t.role||"greekletter"===t.role||"otherletter"===t.role},e.singlePunctAtPosition=function(t,e,r){return 1===e.length&&("punctuation"===t[r].type||"punctuation"===t[r].embellished)&&t[r]===e[0]},e.isSimpleFunction=function(t){return i(t,"identifier")&&a(t,"simple function")},e.isLeftBrace=y,e.isRightBrace=g,e.isSetNode=function(t){return y(t.contentNodes[0])&&g(t.contentNodes[1])},e.illegalSingleton_=["punctuation","punctuated","relseq","multirel","table","multiline","cases","inference"],e.scriptedElement_=["limupper","limlower","limboth","subscript","superscript","underscore","overscore","tensor"],e.isSingletonSetContent=function t(r){const n=r.type;return-1===e.illegalSingleton_.indexOf(n)&&("infixop"!==n||"implicit"===r.role)&&("fenced"===n?"leftright"!==r.role||t(r.childNodes[0]):-1===e.scriptedElement_.indexOf(n)||t(r.childNodes[0]))},e.isNumber=b,e.isUnitCounter=function(t){return b(t)||"vulgar"===t.role||"mixed"===t.role},e.isPureUnit=function(t){const e=t.childNodes;return"unit"===t.role&&(!e.length||"unit"===e[0].role)},e.isImplicit=function(t){return"implicit"===t.role||"unit"===t.role&&!!t.contentNodes.length&&t.contentNodes[0].textContent===n.invisibleTimes()},e.isImplicitOp=function(t){return"infixop"===t.type&&"implicit"===t.role},e.isNeutralFence=v,e.compareNeutralFences=function(t,e){return v(t)&&v(e)&&(0,o.getEmbellishedInner)(t).textContent===(0,o.getEmbellishedInner)(e).textContent},e.elligibleLeftNeutral=function(t){return!!v(t)&&(!t.embellished||"superscript"!==t.type&&"subscript"!==t.type&&("tensor"!==t.type||"empty"===t.childNodes[3].type&&"empty"===t.childNodes[4].type))},e.elligibleRightNeutral=function(t){return!!v(t)&&(!t.embellished||("tensor"!==t.type||"empty"===t.childNodes[1].type&&"empty"===t.childNodes[2].type))},e.isMembership=function(t){return["element","nonelement","reelement","renonelement"].includes(t.role)}},3308:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0});const n=r(5740),o=r(3588),i=r(7516),s=r(6537),a=r(5609),l=r(4795);class c{constructor(){this.funcAppls={},this.factory_=new s.SemanticNodeFactory,i.updateFactory(this.factory_)}static getInstance(){return c.instance=c.instance||new c,c.instance}static tableToMultiline(t){if(a.tableIsMultiline(t)){t.type="multiline";for(let e,r=0;e=t.childNodes[r];r++)c.rowToLine_(e,"multiline");1===t.childNodes.length&&!a.lineIsLabelled(t.childNodes[0])&&a.isFencedElement(t.childNodes[0].childNodes[0])&&c.tableToMatrixOrVector_(c.rewriteFencedLine_(t)),c.binomialForm_(t),c.classifyMultiline(t)}else c.classifyTable(t)}static number(t){"unknown"!==t.type&&"identifier"!==t.type||(t.type="number"),c.numberRole_(t),c.exprFont_(t)}static classifyMultiline(t){let e=0;const r=t.childNodes.length;let n;for(;e=r)return;const o=n.childNodes[0].role;"unknown"!==o&&t.childNodes.every((function(t){const e=t.childNodes[0];return!e||e.role===o&&(a.isType(e,"relation")||a.isType(e,"relseq"))}))&&(t.role=o)}static classifyTable(t){const e=c.computeColumns_(t);c.classifyByColumns_(t,e,"equality")||c.classifyByColumns_(t,e,"inequality",["equality"])||c.classifyByColumns_(t,e,"arrow")||c.detectCaleyTable(t)}static detectCaleyTable(t){if(!t.mathmlTree)return!1;const e=t.mathmlTree,r=e.getAttribute("columnlines"),n=e.getAttribute("rowlines");return!(!r||!n)&&(!(!c.cayleySpacing(r)||!c.cayleySpacing(n))&&(t.role="cayley",!0))}static cayleySpacing(t){const e=t.split(" ");return("solid"===e[0]||"dashed"===e[0])&&e.slice(1).every((t=>"none"===t))}static proof(t,e,r){const n=c.separateSemantics(e);return c.getInstance().proof(t,n,r)}static findSemantics(t,e,r){const n=null==r?null:r,o=c.getSemantics(t);return!!o&&(!!o[e]&&(null==n||o[e]===n))}static getSemantics(t){const e=t.getAttribute("semantics");return e?c.separateSemantics(e):null}static removePrefix(t){const[,...e]=t.split("_");return e.join("_")}static separateSemantics(t){const e={};return t.split(";").forEach((function(t){const[r,n]=t.split(":");e[c.removePrefix(r)]=n})),e}static matchSpaces_(t,e){for(let r,n=0;r=e[n];n++){const e=t[n].mathmlTree,o=t[n+1].mathmlTree;if(!e||!o)continue;const i=e.nextSibling;if(!i||i===o)continue;const s=c.getSpacer_(i);s&&(r.mathml.push(s),r.mathmlTree=s,r.role="space")}}static getSpacer_(t){if("MSPACE"===n.tagName(t))return t;for(;l.hasEmptyTag(t)&&1===t.childNodes.length;)if(t=t.childNodes[0],"MSPACE"===n.tagName(t))return t;return null}static fenceToPunct_(t){const e=c.FENCE_TO_PUNCT_[t.role];if(e){for(;t.embellished;)t.embellished="punctuation",a.isRole(t,"subsup")||a.isRole(t,"underover")||(t.role=e),t=t.childNodes[0];t.type="punctuation",t.role=e}}static classifyFunction_(t,e){if("appl"===t.type||"bigop"===t.type||"integral"===t.type)return"";if(e[0]&&e[0].textContent===o.functionApplication()){c.getInstance().funcAppls[t.id]=e.shift();let r="simple function";return i.run("simple2prefix",t),"prefix function"!==t.role&&"limit function"!==t.role||(r=t.role),c.propagateFunctionRole_(t,r),"prefix"}const r=c.CLASSIFY_FUNCTION_[t.role];return r||(a.isSimpleFunctionHead(t)?"simple":"")}static propagateFunctionRole_(t,e){if(t){if("infixop"===t.type)return;a.isRole(t,"subsup")||a.isRole(t,"underover")||(t.role=e),c.propagateFunctionRole_(t.childNodes[0],e)}}static getFunctionOp_(t,e){if(e(t))return t;for(let r,n=0;r=t.childNodes[n];n++){const t=c.getFunctionOp_(r,e);if(t)return t}return null}static tableToMatrixOrVector_(t){const e=t.childNodes[0];a.isType(e,"multiline")?c.tableToVector_(t):c.tableToMatrix_(t),t.contentNodes.forEach(e.appendContentNode.bind(e));for(let t,r=0;t=e.childNodes[r];r++)c.assignRoleToRow_(t,c.getComponentRoles_(e));return e.parent=null,e}static tableToVector_(t){const e=t.childNodes[0];e.type="vector",1!==e.childNodes.length?c.binomialForm_(e):c.tableToSquare_(t)}static binomialForm_(t){a.isBinomial(t)&&(t.role="binomial",t.childNodes[0].role="binomial",t.childNodes[1].role="binomial")}static tableToMatrix_(t){const e=t.childNodes[0];e.type="matrix",e.childNodes&&e.childNodes.length>0&&e.childNodes[0].childNodes&&e.childNodes.length===e.childNodes[0].childNodes.length?c.tableToSquare_(t):e.childNodes&&1===e.childNodes.length&&(e.role="rowvector")}static tableToSquare_(t){const e=t.childNodes[0];a.isNeutralFence(t)?e.role="determinant":e.role="squarematrix"}static getComponentRoles_(t){const e=t.role;return e&&"unknown"!==e?e:t.type.toLowerCase()||"unknown"}static tableToCases_(t,e){for(let e,r=0;e=t.childNodes[r];r++)c.assignRoleToRow_(e,"cases");return t.type="cases",t.appendContentNode(e),a.tableIsMultiline(t)&&c.binomialForm_(t),t}static rewriteFencedLine_(t){const e=t.childNodes[0],r=t.childNodes[0].childNodes[0],n=t.childNodes[0].childNodes[0].childNodes[0];return r.parent=t.parent,t.parent=r,n.parent=e,r.childNodes=[t],e.childNodes=[n],r}static rowToLine_(t,e){const r=e||"unknown";a.isType(t,"row")&&(t.type="line",t.role=r,1===t.childNodes.length&&a.isType(t.childNodes[0],"cell")&&(t.childNodes=t.childNodes[0].childNodes,t.childNodes.forEach((function(e){e.parent=t}))))}static assignRoleToRow_(t,e){a.isType(t,"line")?t.role=e:a.isType(t,"row")&&(t.role=e,t.childNodes.forEach((function(t){a.isType(t,"cell")&&(t.role=e)})))}static nextSeparatorFunction_(t){let e;if(t){if(t.match(/^\s+$/))return null;e=t.replace(/\s/g,"").split("").filter((function(t){return t}))}else e=[","];return function(){return e.length>1?e.shift():e[0]}}static numberRole_(t){if("unknown"!==t.role)return;const e=[...t.textContent].filter((t=>t.match(/[^\s]/))),r=e.map(o.lookupMeaning);if(r.every((function(t){return"number"===t.type&&"integer"===t.role||"punctuation"===t.type&&"comma"===t.role})))return t.role="integer",void("0"===e[0]&&t.addAnnotation("general","basenumber"));r.every((function(t){return"number"===t.type&&"integer"===t.role||"punctuation"===t.type}))?t.role="float":t.role="othernumber"}static exprFont_(t){if("unknown"!==t.font)return;const e=[...t.textContent].map(o.lookupMeaning).reduce((function(t,e){return t&&e.font&&"unknown"!==e.font&&e.font!==t?"unknown"===t?e.font:null:t}),"unknown");e&&(t.font=e)}static purgeFences_(t){const e=t.rel,r=t.comp,n=[],o=[];for(;e.length>0;){const t=e.shift();let i=r.shift();a.isElligibleEmbellishedFence(t)?(n.push(t),o.push(i)):(c.fenceToPunct_(t),i.push(t),i=i.concat(r.shift()),r.unshift(i))}return o.push(r.shift()),{rel:n,comp:o}}static rewriteFencedNode_(t){const e=t.contentNodes[0],r=t.contentNodes[1];let n=c.rewriteFence_(t,e);return t.contentNodes[0]=n.fence,n=c.rewriteFence_(n.node,r),t.contentNodes[1]=n.fence,t.contentNodes[0].parent=t,t.contentNodes[1].parent=t,n.node.parent=null,n.node}static rewriteFence_(t,e){if(!e.embellished)return{node:t,fence:e};const r=e.childNodes[0],n=c.rewriteFence_(t,r);return a.isType(e,"superscript")||a.isType(e,"subscript")||a.isType(e,"tensor")?(a.isRole(e,"subsup")||(e.role=t.role),r!==n.node&&(e.replaceChild(r,n.node),r.parent=t),c.propagateFencePointer_(e,r),{node:e,fence:n.fence}):(e.replaceChild(r,n.fence),e.mathmlTree&&-1===e.mathml.indexOf(e.mathmlTree)&&e.mathml.push(e.mathmlTree),{node:n.node,fence:e})}static propagateFencePointer_(t,e){t.fencePointer=e.fencePointer||e.id.toString(),t.embellished=null}static classifyByColumns_(t,e,r,n){return!!(3===e.length&&c.testColumns_(e,1,(t=>c.isPureRelation_(t,r)))||2===e.length&&(c.testColumns_(e,1,(t=>c.isEndRelation_(t,r)||c.isPureRelation_(t,r)))||c.testColumns_(e,0,(t=>c.isEndRelation_(t,r,!0)||c.isPureRelation_(t,r)))))&&(t.role=r,!0)}static isEndRelation_(t,e,r){const n=r?t.childNodes.length-1:0;return a.isType(t,"relseq")&&a.isRole(t,e)&&a.isType(t.childNodes[n],"empty")}static isPureRelation_(t,e){return a.isType(t,"relation")&&a.isRole(t,e)}static computeColumns_(t){const e=[];for(let r,n=0;r=t.childNodes[n];n++)for(let t,n=0;t=r.childNodes[n];n++){e[n]?e[n].push(t):e[n]=[t]}return e}static testColumns_(t,e,r){const n=t[e];return!!n&&(n.some((function(t){return t.childNodes.length&&r(t.childNodes[0])}))&&n.every((function(t){return!t.childNodes.length||r(t.childNodes[0])})))}setNodeFactory(t){c.getInstance().factory_=t,i.updateFactory(c.getInstance().factory_)}getNodeFactory(){return c.getInstance().factory_}identifierNode(t,e,r){if("MathML-Unit"===r)t.type="identifier",t.role="unit";else if(!e&&1===t.textContent.length&&("integer"===t.role||"latinletter"===t.role||"greekletter"===t.role)&&"normal"===t.font)return t.font="italic",i.run("simpleNamedFunction",t);return"unknown"===t.type&&(t.type="identifier"),c.exprFont_(t),i.run("simpleNamedFunction",t)}implicitNode(t){if(t=c.getInstance().getMixedNumbers_(t),1===(t=c.getInstance().combineUnits_(t)).length)return t[0];const e=c.getInstance().implicitNode_(t);return i.run("combine_juxtaposition",e)}text(t,e){return c.exprFont_(t),t.type="text","MS"===e?(t.role="string",t):"MSPACE"===e||t.textContent.match(/^\s*$/)?(t.role="space",t):t}row(t){return 0===(t=t.filter((function(t){return!a.isType(t,"empty")}))).length?c.getInstance().factory_.makeEmptyNode():(t=c.getInstance().getFencesInRow_(t),t=c.getInstance().tablesInRow(t),t=c.getInstance().getPunctuationInRow_(t),t=c.getInstance().getTextInRow_(t),t=c.getInstance().getFunctionsInRow_(t),c.getInstance().relationsInRow_(t))}limitNode(t,e){if(!e.length)return c.getInstance().factory_.makeEmptyNode();let r,n=e[0],o="unknown";if(!e[1])return n;if(a.isLimitBase(n)){r=c.MML_TO_LIMIT_[t];const i=r.length;if(o=r.type,e=e.slice(0,r.length+1),1===i&&a.isAccent(e[1])||2===i&&a.isAccent(e[1])&&a.isAccent(e[2]))return r=c.MML_TO_BOUNDS_[t],c.getInstance().accentNode_(n,e,r.type,r.length,r.accent);if(2===i){if(a.isAccent(e[1]))return n=c.getInstance().accentNode_(n,[n,e[1]],{MSUBSUP:"subscript",MUNDEROVER:"underscore"}[t],1,!0),e[2]?c.getInstance().makeLimitNode_(n,[n,e[2]],null,"limupper"):n;if(e[2]&&a.isAccent(e[2]))return n=c.getInstance().accentNode_(n,[n,e[2]],{MSUBSUP:"superscript",MUNDEROVER:"overscore"}[t],1,!0),c.getInstance().makeLimitNode_(n,[n,e[1]],null,"limlower");e[i]||(o="limlower")}return c.getInstance().makeLimitNode_(n,e,null,o)}return r=c.MML_TO_BOUNDS_[t],c.getInstance().accentNode_(n,e,r.type,r.length,r.accent)}tablesInRow(t){let e=l.partitionNodes(t,a.tableIsMatrixOrVector),r=[];for(let t,n=0;t=e.rel[n];n++)r=r.concat(e.comp.shift()),r.push(c.tableToMatrixOrVector_(t));r=r.concat(e.comp.shift()),e=l.partitionNodes(r,a.isTableOrMultiline),r=[];for(let t,n=0;t=e.rel[n];n++){const n=e.comp.shift();a.tableIsCases(t,n)&&c.tableToCases_(t,n.pop()),r=r.concat(n),r.push(t)}return r.concat(e.comp.shift())}mfenced(t,e,r,n){if(r&&n.length>0){const t=c.nextSeparatorFunction_(r),e=[n.shift()];n.forEach((r=>{e.push(c.getInstance().factory_.makeContentNode(t())),e.push(r)})),n=e}return t&&e?c.getInstance().horizontalFencedNode_(c.getInstance().factory_.makeContentNode(t),c.getInstance().factory_.makeContentNode(e),n):(t&&n.unshift(c.getInstance().factory_.makeContentNode(t)),e&&n.push(c.getInstance().factory_.makeContentNode(e)),c.getInstance().row(n))}fractionLikeNode(t,e,r,n){let o;if(!n&&l.isZeroLength(r)){const r=c.getInstance().factory_.makeBranchNode("line",[t],[]),n=c.getInstance().factory_.makeBranchNode("line",[e],[]);return o=c.getInstance().factory_.makeBranchNode("multiline",[r,n],[]),c.binomialForm_(o),c.classifyMultiline(o),o}return o=c.getInstance().fractionNode_(t,e),n&&o.addAnnotation("general","bevelled"),o}tensor(t,e,r,n,o){const i=c.getInstance().factory_.makeBranchNode("tensor",[t,c.getInstance().scriptNode_(e,"leftsub"),c.getInstance().scriptNode_(r,"leftsuper"),c.getInstance().scriptNode_(n,"rightsub"),c.getInstance().scriptNode_(o,"rightsuper")],[]);return i.role=t.role,i.embellished=a.isEmbellished(t),i}pseudoTensor(t,e,r){const n=t=>!a.isType(t,"empty"),o=e.filter(n).length,i=r.filter(n).length;if(!o&&!i)return t;const s=o?i?"MSUBSUP":"MSUB":"MSUP",l=[t];return o&&l.push(c.getInstance().scriptNode_(e,"rightsub",!0)),i&&l.push(c.getInstance().scriptNode_(r,"rightsuper",!0)),c.getInstance().limitNode(s,l)}font(t){const e=c.MATHJAX_FONTS[t];return e||t}proof(t,e,r){if(e.inference||e.axiom||console.log("Noise"),e.axiom){const e=c.getInstance().cleanInference(t.childNodes),n=e.length?c.getInstance().factory_.makeBranchNode("inference",r(e),[]):c.getInstance().factory_.makeEmptyNode();return n.role="axiom",n.mathmlTree=t,n}const n=c.getInstance().inference(t,e,r);return e.proof&&(n.role="proof",n.childNodes[0].role="final"),n}inference(t,e,r){if(e.inferenceRule){const e=c.getInstance().getFormulas(t,[],r);return c.getInstance().factory_.makeBranchNode("inference",[e.conclusion,e.premises],[])}const o=e.labelledRule,i=n.toArray(t.childNodes),s=[];"left"!==o&&"both"!==o||s.push(c.getInstance().getLabel(t,i,r,"left")),"right"!==o&&"both"!==o||s.push(c.getInstance().getLabel(t,i,r,"right"));const a=c.getInstance().getFormulas(t,i,r),l=c.getInstance().factory_.makeBranchNode("inference",[a.conclusion,a.premises],s);return l.mathmlTree=t,l}getLabel(t,e,r,o){const i=c.getInstance().findNestedRow(e,"prooflabel",o),s=c.getInstance().factory_.makeBranchNode("rulelabel",r(n.toArray(i.childNodes)),[]);return s.role=o,s.mathmlTree=i,s}getFormulas(t,e,r){const o=e.length?c.getInstance().findNestedRow(e,"inferenceRule"):t,i="up"===c.getSemantics(o).inferenceRule,s=i?o.childNodes[1]:o.childNodes[0],a=i?o.childNodes[0]:o.childNodes[1],l=s.childNodes[0].childNodes[0],u=n.toArray(l.childNodes[0].childNodes),p=[];let h=1;for(const t of u)h%2&&p.push(t.childNodes[0]),h++;const f=r(p),d=r(n.toArray(a.childNodes[0].childNodes))[0],m=c.getInstance().factory_.makeBranchNode("premises",f,[]);m.mathmlTree=l;const y=c.getInstance().factory_.makeBranchNode("conclusion",[d],[]);return y.mathmlTree=a.childNodes[0].childNodes[0],{conclusion:y,premises:m}}findNestedRow(t,e,r){return c.getInstance().findNestedRow_(t,e,0,r)}cleanInference(t){return n.toArray(t).filter((function(t){return"MSPACE"!==n.tagName(t)}))}operatorNode(t){return"unknown"===t.type&&(t.type="operator"),i.run("multioperator",t)}implicitNode_(t){const e=c.getInstance().factory_.makeMultipleContentNodes(t.length-1,o.invisibleTimes());c.matchSpaces_(t,e);const r=c.getInstance().infixNode_(t,e[0]);return r.role="implicit",e.forEach((function(t){t.parent=r})),r.contentNodes=e,r}infixNode_(t,e){const r=c.getInstance().factory_.makeBranchNode("infixop",t,[e],l.getEmbellishedInner(e).textContent);return r.role=e.role,i.run("propagateSimpleFunction",r)}explicitMixed_(t){const e=l.partitionNodes(t,(function(t){return t.textContent===o.invisiblePlus()}));if(!e.rel.length)return t;let r=[];for(let t,n=0;t=e.rel[n];n++){const o=e.comp[n],i=e.comp[n+1],s=o.length-1;if(o[s]&&i[0]&&a.isType(o[s],"number")&&!a.isRole(o[s],"mixed")&&a.isType(i[0],"fraction")){const t=c.getInstance().factory_.makeBranchNode("number",[o[s],i[0]],[]);t.role="mixed",r=r.concat(o.slice(0,s)),r.push(t),i.shift()}else r=r.concat(o),r.push(t)}return r.concat(e.comp[e.comp.length-1])}concatNode_(t,e,r){if(0===e.length)return t;const n=e.map((function(t){return l.getEmbellishedInner(t).textContent})).join(" "),o=c.getInstance().factory_.makeBranchNode(r,[t],e,n);return e.length>1&&(o.role="multiop"),o}prefixNode_(t,e){const r=l.partitionNodes(e,(t=>a.isRole(t,"subtraction")));let n=c.getInstance().concatNode_(t,r.comp.pop(),"prefixop");for(1===n.contentNodes.length&&"addition"===n.contentNodes[0].role&&"+"===n.contentNodes[0].textContent&&(n.role="positive");r.rel.length>0;)n=c.getInstance().concatNode_(n,[r.rel.pop()],"prefixop"),n.role="negative",n=c.getInstance().concatNode_(n,r.comp.pop(),"prefixop");return n}postfixNode_(t,e){return e.length?c.getInstance().concatNode_(t,e,"postfixop"):t}combineUnits_(t){const e=l.partitionNodes(t,(function(t){return!a.isRole(t,"unit")}));if(t.length===e.rel.length)return e.rel;const r=[];let n,o;do{const t=e.comp.shift();n=e.rel.shift();let i=null;o=r.pop(),o&&(t.length&&a.isUnitCounter(o)?t.unshift(o):r.push(o)),1===t.length&&(i=t.pop()),t.length>1&&(i=c.getInstance().implicitNode_(t),i.role="unit"),i&&r.push(i),n&&r.push(n)}while(n);return r}getMixedNumbers_(t){const e=l.partitionNodes(t,(function(t){return a.isType(t,"fraction")&&a.isRole(t,"vulgar")}));if(!e.rel.length)return t;let r=[];for(let t,n=0;t=e.rel[n];n++){const o=e.comp[n],i=o.length-1;if(o[i]&&a.isType(o[i],"number")&&(a.isRole(o[i],"integer")||a.isRole(o[i],"float"))){const e=c.getInstance().factory_.makeBranchNode("number",[o[i],t],[]);e.role="mixed",r=r.concat(o.slice(0,i)),r.push(e)}else r=r.concat(o),r.push(t)}return r.concat(e.comp[e.comp.length-1])}getTextInRow_(t){if(t.length<=1)return t;const e=l.partitionNodes(t,(t=>a.isType(t,"text")));if(0===e.rel.length)return t;const r=[];let n=e.comp[0];n.length>0&&r.push(c.getInstance().row(n));for(let t,o=0;t=e.rel[o];o++)r.push(t),n=e.comp[o+1],n.length>0&&r.push(c.getInstance().row(n));return[c.getInstance().dummyNode_(r)]}relationsInRow_(t){const e=l.partitionNodes(t,a.isRelation),r=e.rel[0];if(!r)return c.getInstance().operationsInRow_(t);if(1===t.length)return t[0];const n=e.comp.map(c.getInstance().operationsInRow_);let o;return e.rel.some((function(t){return!t.equals(r)}))?(o=c.getInstance().factory_.makeBranchNode("multirel",n,e.rel),e.rel.every((function(t){return t.role===r.role}))&&(o.role=r.role),o):(o=c.getInstance().factory_.makeBranchNode("relseq",n,e.rel,l.getEmbellishedInner(r).textContent),o.role=r.role,o)}operationsInRow_(t){if(0===t.length)return c.getInstance().factory_.makeEmptyNode();if(1===(t=c.getInstance().explicitMixed_(t)).length)return t[0];const e=[];for(;t.length>0&&a.isOperator(t[0]);)e.push(t.shift());if(0===t.length)return c.getInstance().prefixNode_(e.pop(),e);if(1===t.length)return c.getInstance().prefixNode_(t[0],e);t=i.run("convert_juxtaposition",t);const r=l.sliceNodes(t,a.isOperator),n=c.getInstance().prefixNode_(c.getInstance().implicitNode(r.head),e);return r.div?c.getInstance().operationsTree_(r.tail,n,r.div):n}operationsTree_(t,e,r,n){const o=n||[];if(0===t.length){if(o.unshift(r),"infixop"===e.type){const t=c.getInstance().postfixNode_(e.childNodes.pop(),o);return e.appendChild(t),e}return c.getInstance().postfixNode_(e,o)}const i=l.sliceNodes(t,a.isOperator);if(0===i.head.length)return o.push(i.div),c.getInstance().operationsTree_(i.tail,e,r,o);const s=c.getInstance().prefixNode_(c.getInstance().implicitNode(i.head),o),u=c.getInstance().appendOperand_(e,r,s);return i.div?c.getInstance().operationsTree_(i.tail,u,i.div,[]):u}appendOperand_(t,e,r){if("infixop"!==t.type)return c.getInstance().infixNode_([t,r],e);const n=c.getInstance().appendDivisionOp_(t,e,r);return n||(c.getInstance().appendExistingOperator_(t,e,r)?t:"multiplication"===e.role?c.getInstance().appendMultiplicativeOp_(t,e,r):c.getInstance().appendAdditiveOp_(t,e,r))}appendDivisionOp_(t,e,r){return"division"===e.role?a.isImplicit(t)?c.getInstance().infixNode_([t,r],e):c.getInstance().appendLastOperand_(t,e,r):"division"===t.role?c.getInstance().infixNode_([t,r],e):null}appendLastOperand_(t,e,r){let n=t,o=t.childNodes[t.childNodes.length-1];for(;o&&"infixop"===o.type&&!a.isImplicit(o);)n=o,o=n.childNodes[t.childNodes.length-1];const i=c.getInstance().infixNode_([n.childNodes.pop(),r],e);return n.appendChild(i),t}appendMultiplicativeOp_(t,e,r){if(a.isImplicit(t))return c.getInstance().infixNode_([t,r],e);let n=t,o=t.childNodes[t.childNodes.length-1];for(;o&&"infixop"===o.type&&!a.isImplicit(o);)n=o,o=n.childNodes[t.childNodes.length-1];const i=c.getInstance().infixNode_([n.childNodes.pop(),r],e);return n.appendChild(i),t}appendAdditiveOp_(t,e,r){return c.getInstance().infixNode_([t,r],e)}appendExistingOperator_(t,e,r){return!(!t||"infixop"!==t.type||a.isImplicit(t))&&(t.contentNodes[0].equals(e)?(t.appendContentNode(e),t.appendChild(r),!0):c.getInstance().appendExistingOperator_(t.childNodes[t.childNodes.length-1],e,r))}getFencesInRow_(t){let e=l.partitionNodes(t,a.isFence);e=c.purgeFences_(e);const r=e.comp.shift();return c.getInstance().fences_(e.rel,e.comp,[],[r])}fences_(t,e,r,n){if(0===t.length&&0===r.length)return n[0];const o=t=>a.isRole(t,"open");if(0===t.length){const t=n.shift();for(;r.length>0;){if(o(r[0])){const e=r.shift();c.fenceToPunct_(e),t.push(e)}else{const e=l.sliceNodes(r,o),i=e.head.length-1,s=c.getInstance().neutralFences_(e.head,n.slice(0,i));n=n.slice(i),t.push(...s),e.div&&e.tail.unshift(e.div),r=e.tail}t.push(...n.shift())}return t}const i=r[r.length-1],s=t[0].role;if("open"===s||a.isNeutralFence(t[0])&&(!i||!a.compareNeutralFences(t[0],i))){r.push(t.shift());const o=e.shift();return o&&n.push(o),c.getInstance().fences_(t,e,r,n)}if(i&&"close"===s&&"open"===i.role){const o=c.getInstance().horizontalFencedNode_(r.pop(),t.shift(),n.pop());return n.push(n.pop().concat([o],e.shift())),c.getInstance().fences_(t,e,r,n)}if(i&&a.compareNeutralFences(t[0],i)){if(!a.elligibleLeftNeutral(i)||!a.elligibleRightNeutral(t[0])){r.push(t.shift());const o=e.shift();return o&&n.push(o),c.getInstance().fences_(t,e,r,n)}const o=c.getInstance().horizontalFencedNode_(r.pop(),t.shift(),n.pop());return n.push(n.pop().concat([o],e.shift())),c.getInstance().fences_(t,e,r,n)}if(i&&"close"===s&&a.isNeutralFence(i)&&r.some(o)){const i=l.sliceNodes(r,o,!0),s=n.pop(),a=n.length-i.tail.length+1,u=c.getInstance().neutralFences_(i.tail,n.slice(a));n=n.slice(0,a);const p=c.getInstance().horizontalFencedNode_(i.div,t.shift(),n.pop().concat(u,s));return n.push(n.pop().concat([p],e.shift())),c.getInstance().fences_(t,e,i.head,n)}const u=t.shift();return c.fenceToPunct_(u),n.push(n.pop().concat([u],e.shift())),c.getInstance().fences_(t,e,r,n)}neutralFences_(t,e){if(0===t.length)return t;if(1===t.length)return c.fenceToPunct_(t[0]),t;const r=t.shift();if(!a.elligibleLeftNeutral(r)){c.fenceToPunct_(r);const n=e.shift();return n.unshift(r),n.concat(c.getInstance().neutralFences_(t,e))}const n=l.sliceNodes(t,(function(t){return a.compareNeutralFences(t,r)}));if(!n.div){c.fenceToPunct_(r);const n=e.shift();return n.unshift(r),n.concat(c.getInstance().neutralFences_(t,e))}if(!a.elligibleRightNeutral(n.div))return c.fenceToPunct_(n.div),t.unshift(r),c.getInstance().neutralFences_(t,e);const o=c.getInstance().combineFencedContent_(r,n.div,n.head,e);if(n.tail.length>0){const t=o.shift(),e=c.getInstance().neutralFences_(n.tail,o);return t.concat(e)}return o[0]}combineFencedContent_(t,e,r,n){if(0===r.length){const r=c.getInstance().horizontalFencedNode_(t,e,n.shift());return n.length>0?n[0].unshift(r):n=[[r]],n}const o=n.shift(),i=r.length-1,s=n.slice(0,i),a=(n=n.slice(i)).shift(),l=c.getInstance().neutralFences_(r,s);o.push(...l),o.push(...a);const u=c.getInstance().horizontalFencedNode_(t,e,o);return n.length>0?n[0].unshift(u):n=[[u]],n}horizontalFencedNode_(t,e,r){const n=c.getInstance().row(r);let o=c.getInstance().factory_.makeBranchNode("fenced",[n],[t,e]);return"open"===t.role?(c.getInstance().classifyHorizontalFence_(o),o=i.run("propagateComposedFunction",o)):o.role=t.role,o=i.run("detect_cycle",o),c.rewriteFencedNode_(o)}classifyHorizontalFence_(t){t.role="leftright";const e=t.childNodes;if(!a.isSetNode(t)||e.length>1)return;if(0===e.length||"empty"===e[0].type)return void(t.role="set empty");const r=e[0].type;if(1===e.length&&a.isSingletonSetContent(e[0]))return void(t.role="set singleton");const n=e[0].role;if("punctuated"===r&&"sequence"===n){if("comma"!==e[0].contentNodes[0].role)return 1!==e[0].contentNodes.length||"vbar"!==e[0].contentNodes[0].role&&"colon"!==e[0].contentNodes[0].role?void 0:(t.role="set extended",void c.getInstance().setExtension_(t));t.role="set collection"}}setExtension_(t){const e=t.childNodes[0].childNodes[0];e&&"infixop"===e.type&&1===e.contentNodes.length&&a.isMembership(e.contentNodes[0])&&(e.addAnnotation("set","intensional"),e.contentNodes[0].addAnnotation("set","intensional"))}getPunctuationInRow_(t){if(t.length<=1)return t;const e=t=>{const e=t.type;return"punctuation"===e||"text"===e||"operator"===e||"relation"===e},r=l.partitionNodes(t,(function(r){if(!a.isPunctuation(r))return!1;if(a.isPunctuation(r)&&!a.isRole(r,"ellipsis"))return!0;const n=t.indexOf(r);if(0===n)return!t[1]||!e(t[1]);const o=t[n-1];if(n===t.length-1)return!e(o);const i=t[n+1];return!e(o)||!e(i)}));if(0===r.rel.length)return t;const n=[];let o=r.comp.shift();o.length>0&&n.push(c.getInstance().row(o));let i=0;for(;r.comp.length>0;)n.push(r.rel[i++]),o=r.comp.shift(),o.length>0&&n.push(c.getInstance().row(o));return[c.getInstance().punctuatedNode_(n,r.rel)]}punctuatedNode_(t,e){const r=c.getInstance().factory_.makeBranchNode("punctuated",t,e);if(e.length===t.length){const t=e[0].role;if("unknown"!==t&&e.every((function(e){return e.role===t})))return r.role=t,r}return a.singlePunctAtPosition(t,e,0)?r.role="startpunct":a.singlePunctAtPosition(t,e,t.length-1)?r.role="endpunct":e.every((t=>a.isRole(t,"dummy")))?r.role="text":e.every((t=>a.isRole(t,"space")))?r.role="space":r.role="sequence",r}dummyNode_(t){const e=c.getInstance().factory_.makeMultipleContentNodes(t.length-1,o.invisibleComma());return e.forEach((function(t){t.role="dummy"})),c.getInstance().punctuatedNode_(t,e)}accentRole_(t,e){if(!a.isAccent(t))return!1;const r=t.textContent,n=o.lookupSecondary("bar",r)||o.lookupSecondary("tilde",r)||t.role;return t.role="underscore"===e?"underaccent":"overaccent",t.addAnnotation("accent",n),!0}accentNode_(t,e,r,n,o){const i=(e=e.slice(0,n+1))[1],s=e[2];let a;if(!o&&s&&(a=c.getInstance().factory_.makeBranchNode("subscript",[t,i],[]),a.role="subsup",e=[a,s],r="superscript"),o){const n=c.getInstance().accentRole_(i,r);if(s){c.getInstance().accentRole_(s,"overscore")&&!n?(a=c.getInstance().factory_.makeBranchNode("overscore",[t,s],[]),e=[a,i],r="underscore"):(a=c.getInstance().factory_.makeBranchNode("underscore",[t,i],[]),e=[a,s],r="overscore"),a.role="underover"}}return c.getInstance().makeLimitNode_(t,e,a,r)}makeLimitNode_(t,e,r,n){if("limupper"===n&&"limlower"===t.type)return t.childNodes.push(e[1]),e[1].parent=t,t.type="limboth",t;if("limlower"===n&&"limupper"===t.type)return t.childNodes.splice(1,-1,e[1]),e[1].parent=t,t.type="limboth",t;const o=c.getInstance().factory_.makeBranchNode(n,e,[]),i=a.isEmbellished(t);return r&&(r.embellished=i),o.embellished=i,o.role=t.role,o}getFunctionsInRow_(t,e){const r=e||[];if(0===t.length)return r;const n=t.shift(),o=c.classifyFunction_(n,t);if(!o)return r.push(n),c.getInstance().getFunctionsInRow_(t,r);const i=c.getInstance().getFunctionsInRow_(t,[]),s=c.getInstance().getFunctionArgs_(n,i,o);return r.concat(s)}getFunctionArgs_(t,e,r){let n,o,i;switch(r){case"integral":{const r=c.getInstance().getIntegralArgs_(e);if(!r.intvar&&!r.integrand.length)return r.rest.unshift(t),r.rest;const n=c.getInstance().row(r.integrand);return i=c.getInstance().integralNode_(t,n,r.intvar),r.rest.unshift(i),r.rest}case"prefix":if(e[0]&&"fenced"===e[0].type){const r=e.shift();return a.isNeutralFence(r)||(r.role="leftright"),i=c.getInstance().functionNode_(t,r),e.unshift(i),e}if(n=l.sliceNodes(e,a.isPrefixFunctionBoundary),n.head.length)o=c.getInstance().row(n.head),n.div&&n.tail.unshift(n.div);else{if(!n.div||!a.isType(n.div,"appl"))return e.unshift(t),e;o=n.div}return i=c.getInstance().functionNode_(t,o),n.tail.unshift(i),n.tail;case"bigop":return n=l.sliceNodes(e,a.isBigOpBoundary),n.head.length?(o=c.getInstance().row(n.head),i=c.getInstance().bigOpNode_(t,o),n.div&&n.tail.unshift(n.div),n.tail.unshift(i),n.tail):(e.unshift(t),e);default:{if(0===e.length)return[t];const r=e[0];return"fenced"===r.type&&!a.isNeutralFence(r)&&a.isSimpleFunctionScope(r)?(r.role="leftright",c.propagateFunctionRole_(t,"simple function"),i=c.getInstance().functionNode_(t,e.shift()),e.unshift(i),e):(e.unshift(t),e)}}}getIntegralArgs_(t,e=[]){if(0===t.length)return{integrand:e,intvar:null,rest:t};const r=t[0];if(a.isGeneralFunctionBoundary(r))return{integrand:e,intvar:null,rest:t};if(a.isIntegralDxBoundarySingle(r))return r.role="integral",{integrand:e,intvar:r,rest:t.slice(1)};if(t[1]&&a.isIntegralDxBoundary(r,t[1])){const n=c.getInstance().prefixNode_(t[1],[r]);return n.role="integral",{integrand:e,intvar:n,rest:t.slice(2)}}return e.push(t.shift()),c.getInstance().getIntegralArgs_(t,e)}functionNode_(t,e){const r=c.getInstance().factory_.makeContentNode(o.functionApplication()),n=c.getInstance().funcAppls[t.id];n&&(r.mathmlTree=n.mathmlTree,r.mathml=n.mathml,r.annotation=n.annotation,r.attributes=n.attributes,delete c.getInstance().funcAppls[t.id]),r.type="punctuation",r.role="application";const i=c.getFunctionOp_(t,(function(t){return a.isType(t,"function")||a.isType(t,"identifier")&&a.isRole(t,"simple function")}));return c.getInstance().functionalNode_("appl",[t,e],i,[r])}bigOpNode_(t,e){const r=c.getFunctionOp_(t,(t=>a.isType(t,"largeop")));return c.getInstance().functionalNode_("bigop",[t,e],r,[])}integralNode_(t,e,r){e=e||c.getInstance().factory_.makeEmptyNode(),r=r||c.getInstance().factory_.makeEmptyNode();const n=c.getFunctionOp_(t,(t=>a.isType(t,"largeop")));return c.getInstance().functionalNode_("integral",[t,e,r],n,[])}functionalNode_(t,e,r,n){const o=e[0];let i;r&&(i=r.parent,n.push(r));const s=c.getInstance().factory_.makeBranchNode(t,e,n);return s.role=o.role,i&&(r.parent=i),s}fractionNode_(t,e){const r=c.getInstance().factory_.makeBranchNode("fraction",[t,e],[]);return r.role=r.childNodes.every((function(t){return a.isType(t,"number")&&a.isRole(t,"integer")}))?"vulgar":r.childNodes.every(a.isPureUnit)?"unit":"division",i.run("propagateSimpleFunction",r)}scriptNode_(t,e,r){let n;switch(t.length){case 0:n=c.getInstance().factory_.makeEmptyNode();break;case 1:if(n=t[0],r)return n;break;default:n=c.getInstance().dummyNode_(t)}return n.role=e,n}findNestedRow_(t,e,r,o){if(r>3)return null;for(let i,s=0;i=t[s];s++){const t=n.tagName(i);if("MSPACE"!==t){if("MROW"===t)return c.getInstance().findNestedRow_(n.toArray(i.childNodes),e,r+1,o);if(c.findSemantics(i,e,o))return i}}return null}}e.default=c,c.FENCE_TO_PUNCT_={metric:"metric",neutral:"vbar",open:"openfence",close:"closefence"},c.MML_TO_LIMIT_={MSUB:{type:"limlower",length:1},MUNDER:{type:"limlower",length:1},MSUP:{type:"limupper",length:1},MOVER:{type:"limupper",length:1},MSUBSUP:{type:"limboth",length:2},MUNDEROVER:{type:"limboth",length:2}},c.MML_TO_BOUNDS_={MSUB:{type:"subscript",length:1,accent:!1},MSUP:{type:"superscript",length:1,accent:!1},MSUBSUP:{type:"subscript",length:2,accent:!1},MUNDER:{type:"underscore",length:1,accent:!0},MOVER:{type:"overscore",length:1,accent:!0},MUNDEROVER:{type:"underscore",length:2,accent:!0}},c.CLASSIFY_FUNCTION_={integral:"integral",sum:"bigop","prefix function":"prefix","limit function":"prefix","simple function":"prefix","composed function":"prefix"},c.MATHJAX_FONTS={"-tex-caligraphic":"caligraphic","-tex-caligraphic-bold":"caligraphic-bold","-tex-calligraphic":"caligraphic","-tex-calligraphic-bold":"caligraphic-bold","-tex-oldstyle":"oldstyle","-tex-oldstyle-bold":"oldstyle-bold","-tex-mathit":"italic"}},5656:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.SemanticSkeleton=void 0;const n=r(707),o=r(5274),i=r(2298);class s{constructor(t){this.parents=null,this.levelsMap=null,t=0===t?t:t||[],this.array=t}static fromTree(t){return s.fromNode(t.root)}static fromNode(t){return new s(s.fromNode_(t))}static fromString(t){return new s(s.fromString_(t))}static simpleCollapseStructure(t){return"number"==typeof t}static contentCollapseStructure(t){return!!t&&!s.simpleCollapseStructure(t)&&"c"===t[0]}static interleaveIds(t,e){return n.interleaveLists(s.collapsedLeafs(t),s.collapsedLeafs(e))}static collapsedLeafs(...t){return t.reduce(((t,e)=>{return t.concat((r=e,s.simpleCollapseStructure(r)?[r]:(r=r,s.contentCollapseStructure(r[1])?r.slice(2):r.slice(1))));var r}),[])}static fromStructure(t,e){return new s(s.tree_(t,e.root))}static combineContentChildren(t,e,r){switch(t.type){case"relseq":case"infixop":case"multirel":return n.interleaveLists(r,e);case"prefixop":return e.concat(r);case"postfixop":return r.concat(e);case"fenced":return r.unshift(e[0]),r.push(e[1]),r;case"appl":return[r[0],e[0],r[1]];case"root":return[r[1],r[0]];case"row":case"line":return e.length&&r.unshift(e[0]),r;default:return r}}static makeSexp_(t){return s.simpleCollapseStructure(t)?t.toString():s.contentCollapseStructure(t)?"(c "+t.slice(1).map(s.makeSexp_).join(" ")+")":"("+t.map(s.makeSexp_).join(" ")+")"}static fromString_(t){let e=t.replace(/\(/g,"[");return e=e.replace(/\)/g,"]"),e=e.replace(/ /g,","),e=e.replace(/c/g,'"c"'),JSON.parse(e)}static fromNode_(t){if(!t)return[];const e=t.contentNodes;let r;e.length&&(r=e.map(s.fromNode_),r.unshift("c"));const n=t.childNodes;if(!n.length)return e.length?[t.id,r]:t.id;const o=n.map(s.fromNode_);return e.length&&o.unshift(r),o.unshift(t.id),o}static tree_(t,e){if(!e)return[];if(!e.childNodes.length)return e.id;const r=e.id,n=[r],a=o.evalXPath(`.//self::*[@${i.Attribute.ID}=${r}]`,t)[0],l=s.combineContentChildren(e,e.contentNodes.map((function(t){return t})),e.childNodes.map((function(t){return t})));a&&s.addOwns_(a,l);for(let e,r=0;e=l[r];r++)n.push(s.tree_(t,e));return n}static addOwns_(t,e){const r=t.getAttribute(i.Attribute.COLLAPSED),n=r?s.realLeafs_(s.fromString(r).array):e.map((t=>t.id));t.setAttribute(i.Attribute.OWNS,n.join(" "))}static realLeafs_(t){if(s.simpleCollapseStructure(t))return[t];if(s.contentCollapseStructure(t))return[];t=t;let e=[];for(let r=1;rs.simpleCollapseStructure(t)?t:s.contentCollapseStructure(t)?t[1]:t[0]))}subtreeNodes(t){if(!this.isRoot(t))return[];const e=(t,r)=>{s.simpleCollapseStructure(t)?r.push(t):(t=t,s.contentCollapseStructure(t)&&(t=t.slice(1)),t.forEach((t=>e(t,r))))},r=this.levelsMap[t],n=[];return e(r.slice(1),n),n}}e.SemanticSkeleton=s},7075:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.SemanticTree=void 0;const n=r(5740),o=r(7630),i=r(9265),s=r(7228),a=r(5952),l=r(5609);r(94);class c{constructor(t){this.mathml=t,this.parser=new s.SemanticMathml,this.root=this.parser.parse(t),this.collator=this.parser.getFactory().leafMap.collateMeaning();const e=this.collator.newDefault();e&&(this.parser=new s.SemanticMathml,this.parser.getFactory().defaultMap=e,this.root=this.parser.parse(t)),u.visit(this.root,{}),(0,o.annotate)(this.root)}static empty(){const t=n.parseInput(""),e=new c(t);return e.mathml=t,e}static fromNode(t,e){const r=c.empty();return r.root=t,e&&(r.mathml=e),r}static fromRoot(t,e){let r=t;for(;r.parent;)r=r.parent;const n=c.fromNode(r);return e&&(n.mathml=e),n}static fromXml(t){const e=c.empty();return t.childNodes[0]&&(e.root=a.SemanticNode.fromXml(t.childNodes[0])),e}xml(t){const e=n.parseInput(""),r=this.root.xml(e.ownerDocument,t);return e.appendChild(r),e}toString(t){return n.serializeXml(this.xml(t))}formatXml(t){const e=this.toString(t);return n.formatXml(e)}displayTree(){this.root.displayTree()}replaceNode(t,e){const r=t.parent;r?r.replaceChild(t,e):this.root=e}toJson(){const t={};return t.stree=this.root.toJson(),t}}e.SemanticTree=c;const u=new i.SemanticVisitor("general","unit",((t,e)=>{if("infixop"===t.type&&("multiplication"===t.role||"implicit"===t.role)){const e=t.childNodes;e.length&&(l.isPureUnit(e[0])||l.isUnitCounter(e[0]))&&t.childNodes.slice(1).every(l.isPureUnit)&&(t.role="unit")}return!1}))},4795:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.partitionNodes=e.sliceNodes=e.getEmbellishedInner=e.addAttributes=e.isZeroLength=e.purgeNodes=e.isOrphanedGlyph=e.hasDisplayTag=e.hasEmptyTag=e.hasIgnoreTag=e.hasLeafTag=e.hasMathTag=e.directSpeechKeys=e.DISPLAYTAGS=e.EMPTYTAGS=e.IGNORETAGS=e.LEAFTAGS=void 0;const n=r(5740);function o(t){return!!t&&-1!==e.LEAFTAGS.indexOf(n.tagName(t))}function i(t,e,r){r&&t.reverse();const n=[];for(let o,i=0;o=t[i];i++){if(e(o))return r?{head:t.slice(i+1).reverse(),div:o,tail:n.reverse()}:{head:n,div:o,tail:t.slice(i+1)};n.push(o)}return r?{head:[],div:null,tail:n.reverse()}:{head:n,div:null,tail:[]}}e.LEAFTAGS=["MO","MI","MN","MTEXT","MS","MSPACE"],e.IGNORETAGS=["MERROR","MPHANTOM","MALIGNGROUP","MALIGNMARK","MPRESCRIPTS","ANNOTATION","ANNOTATION-XML"],e.EMPTYTAGS=["MATH","MROW","MPADDED","MACTION","NONE","MSTYLE","SEMANTICS"],e.DISPLAYTAGS=["MROOT","MSQRT"],e.directSpeechKeys=["aria-label","exact-speech","alt"],e.hasMathTag=function(t){return!!t&&"MATH"===n.tagName(t)},e.hasLeafTag=o,e.hasIgnoreTag=function(t){return!!t&&-1!==e.IGNORETAGS.indexOf(n.tagName(t))},e.hasEmptyTag=function(t){return!!t&&-1!==e.EMPTYTAGS.indexOf(n.tagName(t))},e.hasDisplayTag=function(t){return!!t&&-1!==e.DISPLAYTAGS.indexOf(n.tagName(t))},e.isOrphanedGlyph=function(t){return!!t&&"MGLYPH"===n.tagName(t)&&!o(t.parentNode)},e.purgeNodes=function(t){const r=[];for(let o,i=0;o=t[i];i++){if(o.nodeType!==n.NodeType.ELEMENT_NODE)continue;const t=n.tagName(o);-1===e.IGNORETAGS.indexOf(t)&&(-1!==e.EMPTYTAGS.indexOf(t)&&0===o.childNodes.length||r.push(o))}return r},e.isZeroLength=function(t){if(!t)return!1;if(-1!==["negativeveryverythinmathspace","negativeverythinmathspace","negativethinmathspace","negativemediummathspace","negativethickmathspace","negativeverythickmathspace","negativeveryverythickmathspace"].indexOf(t))return!0;const e=t.match(/[0-9.]+/);return!!e&&0===parseFloat(e[0])},e.addAttributes=function(t,r){if(r.hasAttributes()){const n=r.attributes;for(let r=n.length-1;r>=0;r--){const o=n[r].name;o.match(/^ext/)&&(t.attributes[o]=n[r].value,t.nobreaking=!0),-1!==e.directSpeechKeys.indexOf(o)&&(t.attributes["ext-speech"]=n[r].value,t.nobreaking=!0),o.match(/texclass$/)&&(t.attributes.texclass=n[r].value),"href"===o&&(t.attributes.href=n[r].value,t.nobreaking=!0)}}},e.getEmbellishedInner=function t(e){return e&&e.embellished&&e.childNodes.length>0?t(e.childNodes[0]):e},e.sliceNodes=i,e.partitionNodes=function(t,e){let r=t;const n=[],o=[];let s=null;do{s=i(r,e),o.push(s.head),n.push(s.div),r=s.tail}while(s.div);return n.pop(),{rel:n,comp:o}}},6278:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.AbstractSpeechGenerator=void 0;const n=r(6828),o=r(2298),i=r(1214),s=r(9543);e.AbstractSpeechGenerator=class{constructor(){this.modality=o.addPrefix("speech"),this.rebuilt_=null,this.options_={}}getRebuilt(){return this.rebuilt_}setRebuilt(t){this.rebuilt_=t}setOptions(t){this.options_=t||{},this.modality=o.addPrefix(this.options_.modality||"speech")}getOptions(){return this.options_}start(){}end(){}generateSpeech(t,e){return this.rebuilt_||(this.rebuilt_=new i.RebuildStree(e)),(0,n.setup)(this.options_),s.computeMarkup(this.getRebuilt().xml)}}},1452:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.AdhocSpeechGenerator=void 0;const n=r(6278);class o extends n.AbstractSpeechGenerator{getSpeech(t,e){const r=this.generateSpeech(t,e);return t.setAttribute(this.modality,r),r}}e.AdhocSpeechGenerator=o},5152:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.ColorGenerator=void 0;const n=r(2298),o=r(8396),i=r(1214),s=r(1204),a=r(6278);class l extends a.AbstractSpeechGenerator{constructor(){super(...arguments),this.modality=(0,n.addPrefix)("foreground"),this.contrast=new o.ContrastPicker}static visitStree_(t,e,r){if(t.childNodes.length){if(t.contentNodes.length&&("punctuated"===t.type&&t.contentNodes.forEach((t=>r[t.id]=!0)),"implicit"!==t.role&&e.push(t.contentNodes.map((t=>t.id)))),t.childNodes.length){if("implicit"===t.role){const n=[];let o=[];for(const e of t.childNodes){const t=[];l.visitStree_(e,t,r),t.length<=2&&n.push(t.shift()),o=o.concat(t)}return e.push(n),void o.forEach((t=>e.push(t)))}t.childNodes.forEach((t=>l.visitStree_(t,e,r)))}}else r[t.id]||e.push(t.id)}getSpeech(t,e){return s.getAttribute(t,this.modality)}generateSpeech(t,e){return this.getRebuilt()||this.setRebuilt(new i.RebuildStree(t)),this.colorLeaves_(t),s.getAttribute(t,this.modality)}colorLeaves_(t){const e=[];l.visitStree_(this.getRebuilt().streeRoot,e,{});for(const r of e){const e=this.contrast.generate();let n=!1;n=Array.isArray(r)?r.map((r=>this.colorLeave_(t,r,e))).reduce(((t,e)=>t||e),!1):this.colorLeave_(t,r.toString(),e),n&&this.contrast.increment()}}colorLeave_(t,e,r){const n=s.getBySemanticId(t,e);return!!n&&(n.setAttribute(this.modality,r),!0)}}e.ColorGenerator=l},6604:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.DirectSpeechGenerator=void 0;const n=r(1204),o=r(6278);class i extends o.AbstractSpeechGenerator{getSpeech(t,e){return n.getAttribute(t,this.modality)}}e.DirectSpeechGenerator=i},3123:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.DummySpeechGenerator=void 0;const n=r(6278);class o extends n.AbstractSpeechGenerator{getSpeech(t,e){return""}}e.DummySpeechGenerator=o},5858:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.NodeSpeechGenerator=void 0;const n=r(1204),o=r(4598);class i extends o.TreeSpeechGenerator{getSpeech(t,e){return super.getSpeech(t,e),n.getAttribute(t,this.modality)}}e.NodeSpeechGenerator=i},9552:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.generatorMapping_=e.generator=void 0;const n=r(1452),o=r(5152),i=r(6604),s=r(3123),a=r(5858),l=r(597),c=r(4598);e.generator=function(t){return(e.generatorMapping_[t]||e.generatorMapping_.Direct)()},e.generatorMapping_={Adhoc:()=>new n.AdhocSpeechGenerator,Color:()=>new o.ColorGenerator,Direct:()=>new i.DirectSpeechGenerator,Dummy:()=>new s.DummySpeechGenerator,Node:()=>new a.NodeSpeechGenerator,Summary:()=>new l.SummarySpeechGenerator,Tree:()=>new c.TreeSpeechGenerator}},9543:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.computeSummary_=e.retrieveSummary=e.connectAllMactions=e.connectMactions=e.nodeAtPosition_=e.computePrefix_=e.retrievePrefix=e.addPrefix=e.addModality=e.addSpeech=e.recomputeMarkup=e.computeMarkup=e.recomputeSpeech=e.computeSpeech=void 0;const n=r(8290),o=r(5740),i=r(5274),s=r(2298),a=r(2362),l=r(7075),c=r(1204);function u(t){return a.SpeechRuleEngine.getInstance().evaluateNode(t)}function p(t){return u(l.SemanticTree.fromNode(t).xml())}function h(t){const e=p(t);return n.markup(e)}function f(t){const e=d(t);return n.markup(e)}function d(t){const e=l.SemanticTree.fromRoot(t),r=i.evalXPath('.//*[@id="'+t.id+'"]',e.xml());let n=r[0];return r.length>1&&(n=m(t,r)||n),n?a.SpeechRuleEngine.getInstance().runInSetting({modality:"prefix",domain:"default",style:"default",strict:!0,speech:!0},(function(){return a.SpeechRuleEngine.getInstance().evaluateNode(n)})):[]}function m(t,e){const r=e[0];if(!t.parent)return r;const n=[];for(;t;)n.push(t.id),t=t.parent;const o=function(t,e){for(;e.length&&e.shift().toString()===t.getAttribute("id")&&t.parentNode&&t.parentNode.parentNode;)t=t.parentNode.parentNode;return!e.length};for(let t,r=0;t=e[r];r++)if(o(t,n.slice()))return t;return r}function y(t){return t?a.SpeechRuleEngine.getInstance().runInSetting({modality:"summary",strict:!1,speech:!0},(function(){return a.SpeechRuleEngine.getInstance().evaluateNode(t)})):[]}e.computeSpeech=u,e.recomputeSpeech=p,e.computeMarkup=function(t){const e=u(t);return n.markup(e)},e.recomputeMarkup=h,e.addSpeech=function(t,e,r){const i=o.querySelectorAllByAttrValue(r,"id",e.id.toString())[0],a=i?n.markup(u(i)):h(e);t.setAttribute(s.Attribute.SPEECH,a)},e.addModality=function(t,e,r){const n=h(e);t.setAttribute(r,n)},e.addPrefix=function(t,e){const r=f(e);r&&t.setAttribute(s.Attribute.PREFIX,r)},e.retrievePrefix=f,e.computePrefix_=d,e.nodeAtPosition_=m,e.connectMactions=function(t,e,r){const n=o.querySelectorAll(e,"maction");for(let e,i=0;e=n[i];i++){const n=e.getAttribute("id"),i=o.querySelectorAllByAttrValue(t,"id",n)[0];if(!i)continue;const a=e.childNodes[1],l=a.getAttribute(s.Attribute.ID);let u=c.getBySemanticId(t,l);if(u&&"dummy"!==u.getAttribute(s.Attribute.TYPE))continue;if(u=i.childNodes[0],u.getAttribute("sre-highlighter-added"))continue;const p=a.getAttribute(s.Attribute.PARENT);p&&u.setAttribute(s.Attribute.PARENT,p),u.setAttribute(s.Attribute.TYPE,"dummy"),u.setAttribute(s.Attribute.ID,l);o.querySelectorAllByAttrValue(r,"id",l)[0].setAttribute("alternative",l)}},e.connectAllMactions=function(t,e){const r=o.querySelectorAll(t,"maction");for(let t,n=0;t=r[n];n++){const r=t.childNodes[1].getAttribute(s.Attribute.ID);o.querySelectorAllByAttrValue(e,"id",r)[0].setAttribute("alternative",r)}},e.retrieveSummary=function(t){const e=y(t);return n.markup(e)},e.computeSummary_=y},597:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.SummarySpeechGenerator=void 0;const n=r(6278),o=r(9543);class i extends n.AbstractSpeechGenerator{getSpeech(t,e){return o.connectAllMactions(e,this.getRebuilt().xml),this.generateSpeech(t,e)}}e.SummarySpeechGenerator=i},4598:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.TreeSpeechGenerator=void 0;const n=r(2298),o=r(1204),i=r(6278),s=r(9543);class a extends i.AbstractSpeechGenerator{getSpeech(t,e){const r=this.generateSpeech(t,e),i=this.getRebuilt().nodeDict;for(const r in i){const a=i[r],l=o.getBySemanticId(e,r),c=o.getBySemanticId(t,r);l&&c&&(this.modality&&this.modality!==n.Attribute.SPEECH?s.addModality(c,a,this.modality):s.addSpeech(c,a,this.getRebuilt().xml),this.modality===n.Attribute.SPEECH&&s.addPrefix(c,a))}return r}}e.TreeSpeechGenerator=a},313:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.INTERVALS=e.makeLetter=e.numberRules=e.alphabetRules=e.getFont=e.makeInterval=e.generate=e.makeDomains_=e.Domains_=e.Base=e.Embellish=e.Font=void 0;const n=r(5897),o=r(7491),i=r(4356),s=r(2536),a=r(2780);var l,c,u;function p(){const t=i.LOCALE.ALPHABETS,r=(t,e)=>{const r={};return Object.keys(t).forEach((t=>r[t]=!0)),Object.keys(e).forEach((t=>r[t]=!0)),Object.keys(r)};e.Domains_.small=r(t.smallPrefix,t.letterTrans),e.Domains_.capital=r(t.capPrefix,t.letterTrans),e.Domains_.digit=r(t.digitPrefix,t.digitTrans)}function h(t){const e=t.toString(16).toUpperCase();return e.length>3?e:("000"+e).slice(-4)}function f([t,e],r){const n=parseInt(t,16),o=parseInt(e,16),i=[];for(let t=n;t<=o;t++){let e=h(t);!1!==r[e]&&(e=r[e]||e,i.push(e))}return i}function d(t){const e="normal"===t||"fullwidth"===t?"":i.LOCALE.MESSAGES.font[t]||i.LOCALE.MESSAGES.embellish[t]||"";return(0,s.localeFontCombiner)(e)}function m(t,r,n,o,s,a){const l=d(o);for(let o,c,u,p=0;o=t[p],c=r[p],u=n[p];p++){const t=a?i.LOCALE.ALPHABETS.capPrefix:i.LOCALE.ALPHABETS.smallPrefix,r=a?e.Domains_.capital:e.Domains_.small;g(l.combiner,o,c,u,l.font,t,s,i.LOCALE.ALPHABETS.letterTrans,r)}}function y(t,r,n,o,s){const a=d(n);for(let n,l,c=0;n=t[c],l=r[c];c++){const t=i.LOCALE.ALPHABETS.digitPrefix,r=c+s;g(a.combiner,n,l,r,a.font,t,o,i.LOCALE.ALPHABETS.digitTrans,e.Domains_.digit)}}function g(t,e,r,n,o,i,s,l,c){for(let u,p=0;u=c[p];p++){const c=u in l?l[u]:l.default,p=u in i?i[u]:i.default;a.defineRule(e.toString(),u,"default",s,r,t(c(n),o,p))}}!function(t){t.BOLD="bold",t.BOLDFRAKTUR="bold-fraktur",t.BOLDITALIC="bold-italic",t.BOLDSCRIPT="bold-script",t.DOUBLESTRUCK="double-struck",t.FULLWIDTH="fullwidth",t.FRAKTUR="fraktur",t.ITALIC="italic",t.MONOSPACE="monospace",t.NORMAL="normal",t.SCRIPT="script",t.SANSSERIF="sans-serif",t.SANSSERIFITALIC="sans-serif-italic",t.SANSSERIFBOLD="sans-serif-bold",t.SANSSERIFBOLDITALIC="sans-serif-bold-italic"}(l=e.Font||(e.Font={})),function(t){t.SUPER="super",t.SUB="sub",t.CIRCLED="circled",t.PARENTHESIZED="parenthesized",t.PERIOD="period",t.NEGATIVECIRCLED="negative-circled",t.DOUBLECIRCLED="double-circled",t.CIRCLEDSANSSERIF="circled-sans-serif",t.NEGATIVECIRCLEDSANSSERIF="negative-circled-sans-serif",t.COMMA="comma",t.SQUARED="squared",t.NEGATIVESQUARED="negative-squared"}(c=e.Embellish||(e.Embellish={})),function(t){t.LATINCAP="latinCap",t.LATINSMALL="latinSmall",t.GREEKCAP="greekCap",t.GREEKSMALL="greekSmall",t.DIGIT="digit"}(u=e.Base||(e.Base={})),e.Domains_={small:["default"],capital:["default"],digit:["default"]},e.makeDomains_=p,e.generate=function(t){const r=n.default.getInstance().locale;n.default.getInstance().locale=t,o.setLocale(),a.addSymbolRules({locale:t}),p();const s=e.INTERVALS;for(let t,e=0;t=s[e];e++){const e=f(t.interval,t.subst),r=e.map((function(t){return String.fromCodePoint(parseInt(t,16))}));if("offset"in t)y(e,r,t.font,t.category,t.offset||0);else{m(e,r,i.LOCALE.ALPHABETS[t.base],t.font,t.category,!!t.capital)}}n.default.getInstance().locale=r,o.setLocale()},e.makeInterval=f,e.getFont=d,e.alphabetRules=m,e.numberRules=y,e.makeLetter=g,e.INTERVALS=[{interval:["1D400","1D419"],base:u.LATINCAP,subst:{},capital:!0,category:"Lu",font:l.BOLD},{interval:["1D41A","1D433"],base:u.LATINSMALL,subst:{},capital:!1,category:"Ll",font:l.BOLD},{interval:["1D56C","1D585"],base:u.LATINCAP,subst:{},capital:!0,category:"Lu",font:l.BOLDFRAKTUR},{interval:["1D586","1D59F"],base:u.LATINSMALL,subst:{},capital:!1,category:"Ll",font:l.BOLDFRAKTUR},{interval:["1D468","1D481"],base:u.LATINCAP,subst:{},capital:!0,category:"Lu",font:l.BOLDITALIC},{interval:["1D482","1D49B"],base:u.LATINSMALL,subst:{},capital:!1,category:"Ll",font:l.BOLDITALIC},{interval:["1D4D0","1D4E9"],base:u.LATINCAP,subst:{},capital:!0,category:"Lu",font:l.BOLDSCRIPT},{interval:["1D4EA","1D503"],base:u.LATINSMALL,subst:{},capital:!1,category:"Ll",font:l.BOLDSCRIPT},{interval:["1D538","1D551"],base:u.LATINCAP,subst:{"1D53A":"2102","1D53F":"210D","1D545":"2115","1D547":"2119","1D548":"211A","1D549":"211D","1D551":"2124"},capital:!0,category:"Lu",font:l.DOUBLESTRUCK},{interval:["1D552","1D56B"],base:u.LATINSMALL,subst:{},capital:!1,category:"Ll",font:l.DOUBLESTRUCK},{interval:["1D504","1D51D"],base:u.LATINCAP,subst:{"1D506":"212D","1D50B":"210C","1D50C":"2111","1D515":"211C","1D51D":"2128"},capital:!0,category:"Lu",font:l.FRAKTUR},{interval:["1D51E","1D537"],base:u.LATINSMALL,subst:{},capital:!1,category:"Ll",font:l.FRAKTUR},{interval:["FF21","FF3A"],base:u.LATINCAP,subst:{},capital:!0,category:"Lu",font:l.FULLWIDTH},{interval:["FF41","FF5A"],base:u.LATINSMALL,subst:{},capital:!1,category:"Ll",font:l.FULLWIDTH},{interval:["1D434","1D44D"],base:u.LATINCAP,subst:{},capital:!0,category:"Lu",font:l.ITALIC},{interval:["1D44E","1D467"],base:u.LATINSMALL,subst:{"1D455":"210E"},capital:!1,category:"Ll",font:l.ITALIC},{interval:["1D670","1D689"],base:u.LATINCAP,subst:{},capital:!0,category:"Lu",font:l.MONOSPACE},{interval:["1D68A","1D6A3"],base:u.LATINSMALL,subst:{},capital:!1,category:"Ll",font:l.MONOSPACE},{interval:["0041","005A"],base:u.LATINCAP,subst:{},capital:!0,category:"Lu",font:l.NORMAL},{interval:["0061","007A"],base:u.LATINSMALL,subst:{},capital:!1,category:"Ll",font:l.NORMAL},{interval:["1D49C","1D4B5"],base:u.LATINCAP,subst:{"1D49D":"212C","1D4A0":"2130","1D4A1":"2131","1D4A3":"210B","1D4A4":"2110","1D4A7":"2112","1D4A8":"2133","1D4AD":"211B"},capital:!0,category:"Lu",font:l.SCRIPT},{interval:["1D4B6","1D4CF"],base:u.LATINSMALL,subst:{"1D4BA":"212F","1D4BC":"210A","1D4C4":"2134"},capital:!1,category:"Ll",font:l.SCRIPT},{interval:["1D5A0","1D5B9"],base:u.LATINCAP,subst:{},capital:!0,category:"Lu",font:l.SANSSERIF},{interval:["1D5BA","1D5D3"],base:u.LATINSMALL,subst:{},capital:!1,category:"Ll",font:l.SANSSERIF},{interval:["1D608","1D621"],base:u.LATINCAP,subst:{},capital:!0,category:"Lu",font:l.SANSSERIFITALIC},{interval:["1D622","1D63B"],base:u.LATINSMALL,subst:{},capital:!1,category:"Ll",font:l.SANSSERIFITALIC},{interval:["1D5D4","1D5ED"],base:u.LATINCAP,subst:{},capital:!0,category:"Lu",font:l.SANSSERIFBOLD},{interval:["1D5EE","1D607"],base:u.LATINSMALL,subst:{},capital:!1,category:"Ll",font:l.SANSSERIFBOLD},{interval:["1D63C","1D655"],base:u.LATINCAP,subst:{},capital:!0,category:"Lu",font:l.SANSSERIFBOLDITALIC},{interval:["1D656","1D66F"],base:u.LATINSMALL,subst:{},capital:!1,category:"Ll",font:l.SANSSERIFBOLDITALIC},{interval:["0391","03A9"],base:u.GREEKCAP,subst:{"03A2":"03F4"},capital:!0,category:"Lu",font:l.NORMAL},{interval:["03B0","03D0"],base:u.GREEKSMALL,subst:{"03B0":"2207","03CA":"2202","03CB":"03F5","03CC":"03D1","03CD":"03F0","03CE":"03D5","03CF":"03F1","03D0":"03D6"},capital:!1,category:"Ll",font:l.NORMAL},{interval:["1D6A8","1D6C0"],base:u.GREEKCAP,subst:{},capital:!0,category:"Lu",font:l.BOLD},{interval:["1D6C1","1D6E1"],base:u.GREEKSMALL,subst:{},capital:!1,category:"Ll",font:l.BOLD},{interval:["1D6E2","1D6FA"],base:u.GREEKCAP,subst:{},capital:!0,category:"Lu",font:l.ITALIC},{interval:["1D6FB","1D71B"],base:u.GREEKSMALL,subst:{},capital:!1,category:"Ll",font:l.ITALIC},{interval:["1D71C","1D734"],base:u.GREEKCAP,subst:{},capital:!0,category:"Lu",font:l.BOLDITALIC},{interval:["1D735","1D755"],base:u.GREEKSMALL,subst:{},capital:!1,category:"Ll",font:l.BOLDITALIC},{interval:["1D756","1D76E"],base:u.GREEKCAP,subst:{},capital:!0,category:"Lu",font:l.SANSSERIFBOLD},{interval:["1D76F","1D78F"],base:u.GREEKSMALL,subst:{},capital:!1,category:"Ll",font:l.SANSSERIFBOLD},{interval:["1D790","1D7A8"],base:u.GREEKCAP,subst:{},capital:!0,category:"Lu",font:l.SANSSERIFBOLDITALIC},{interval:["1D7A9","1D7C9"],base:u.GREEKSMALL,subst:{},capital:!1,category:"Ll",font:l.SANSSERIFBOLDITALIC},{interval:["0030","0039"],base:u.DIGIT,subst:{},offset:0,category:"Nd",font:l.NORMAL},{interval:["2070","2079"],base:u.DIGIT,subst:{2071:"00B9",2072:"00B2",2073:"00B3"},offset:0,category:"No",font:c.SUPER},{interval:["2080","2089"],base:u.DIGIT,subst:{},offset:0,category:"No",font:c.SUB},{interval:["245F","2473"],base:u.DIGIT,subst:{"245F":"24EA"},offset:0,category:"No",font:c.CIRCLED},{interval:["3251","325F"],base:u.DIGIT,subst:{},offset:21,category:"No",font:c.CIRCLED},{interval:["32B1","32BF"],base:u.DIGIT,subst:{},offset:36,category:"No",font:c.CIRCLED},{interval:["2474","2487"],base:u.DIGIT,subst:{},offset:1,category:"No",font:c.PARENTHESIZED},{interval:["2487","249B"],base:u.DIGIT,subst:{2487:"1F100"},offset:0,category:"No",font:c.PERIOD},{interval:["2775","277F"],base:u.DIGIT,subst:{2775:"24FF"},offset:0,category:"No",font:c.NEGATIVECIRCLED},{interval:["24EB","24F4"],base:u.DIGIT,subst:{},offset:11,category:"No",font:c.NEGATIVECIRCLED},{interval:["24F5","24FE"],base:u.DIGIT,subst:{},offset:1,category:"No",font:c.DOUBLECIRCLED},{interval:["277F","2789"],base:u.DIGIT,subst:{"277F":"1F10B"},offset:0,category:"No",font:c.CIRCLEDSANSSERIF},{interval:["2789","2793"],base:u.DIGIT,subst:{2789:"1F10C"},offset:0,category:"No",font:c.NEGATIVECIRCLEDSANSSERIF},{interval:["FF10","FF19"],base:u.DIGIT,subst:{},offset:0,category:"Nd",font:l.FULLWIDTH},{interval:["1D7CE","1D7D7"],base:u.DIGIT,subst:{},offset:0,category:"Nd",font:l.BOLD},{interval:["1D7D8","1D7E1"],base:u.DIGIT,subst:{},offset:0,category:"Nd",font:l.DOUBLESTRUCK},{interval:["1D7E2","1D7EB"],base:u.DIGIT,subst:{},offset:0,category:"Nd",font:l.SANSSERIF},{interval:["1D7EC","1D7F5"],base:u.DIGIT,subst:{},offset:0,category:"Nd",font:l.SANSSERIFBOLD},{interval:["1D7F6","1D7FF"],base:u.DIGIT,subst:{},offset:0,category:"Nd",font:l.MONOSPACE},{interval:["1F101","1F10A"],base:u.DIGIT,subst:{},offset:0,category:"No",font:c.COMMA},{interval:["24B6","24CF"],base:u.LATINCAP,subst:{},capital:!0,category:"So",font:c.CIRCLED},{interval:["24D0","24E9"],base:u.LATINSMALL,subst:{},capital:!1,category:"So",font:c.CIRCLED},{interval:["1F110","1F129"],base:u.LATINCAP,subst:{},capital:!0,category:"So",font:c.PARENTHESIZED},{interval:["249C","24B5"],base:u.LATINSMALL,subst:{},capital:!1,category:"So",font:c.PARENTHESIZED},{interval:["1F130","1F149"],base:u.LATINCAP,subst:{},capital:!0,category:"So",font:c.SQUARED},{interval:["1F170","1F189"],base:u.LATINCAP,subst:{},capital:!0,category:"So",font:c.NEGATIVESQUARED},{interval:["1F150","1F169"],base:u.LATINCAP,subst:{},capital:!0,category:"So",font:c.NEGATIVECIRCLED}]},8504:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.Parser=e.Comparator=e.ClearspeakPreferences=void 0;const n=r(5897),o=r(4440),i=r(1676),s=r(1676),a=r(2780),l=r(2362);class c extends i.DynamicCstr{constructor(t,e){super(t),this.preference=e}static comparator(){return new p(n.default.getInstance().dynamicCstr,s.DynamicProperties.createProp([i.DynamicCstr.DEFAULT_VALUES[s.Axis.LOCALE]],[i.DynamicCstr.DEFAULT_VALUES[s.Axis.MODALITY]],[i.DynamicCstr.DEFAULT_VALUES[s.Axis.DOMAIN]],[i.DynamicCstr.DEFAULT_VALUES[s.Axis.STYLE]]))}static fromPreference(t){const e=t.split(":"),r={},n=u.getProperties(),o=Object.keys(n);for(let t,i=0;t=e[i];i++){const e=t.split("_");if(-1===o.indexOf(e[0]))continue;const i=e[1];i&&i!==c.AUTO&&-1!==n[e[0]].indexOf(i)&&(r[e[0]]=e[1])}return r}static toPreference(t){const e=Object.keys(t),r=[];for(let n=0;ns?-1:i0&&e<20&&r>0&&r<11}function O(t){return o.default.getInstance().style===t}function x(t){if(!t.hasAttribute("annotation"))return!1;const e=t.getAttribute("annotation");return!!/clearspeak:simple$|clearspeak:simple;/.exec(e)}function E(t){if(x(t))return!0;if("subscript"!==t.tagName)return!1;const e=t.childNodes[0].childNodes,r=e[1];return"identifier"===e[0].tagName&&(A(r)||"infixop"===r.tagName&&r.hasAttribute("role")&&"implicit"===r.getAttribute("role")&&C(r))}function A(t){return"number"===t.tagName&&t.hasAttribute("role")&&"integer"===t.getAttribute("role")}function C(t){return i.evalXPath("children/*",t).every((t=>A(t)||"identifier"===t.tagName))}function T(t){return"text"===t.type||"punctuated"===t.type&&"text"===t.role&&_(t.childNodes[0])&&N(t.childNodes.slice(1))||"identifier"===t.type&&"unit"===t.role||"infixop"===t.type&&("implicit"===t.role||"unit"===t.role)}function N(t){for(let e=0;e10?s.LOCALE.NUMBERS.numericOrdinal(e):s.LOCALE.NUMBERS.wordOrdinal(e)},e.NESTING_DEPTH=null,e.nestingDepth=function(t){let r=0;const n=t.textContent,o="open"===t.getAttribute("role")?0:1;let i=t.parentNode;for(;i;)"fenced"===i.tagName&&i.childNodes[0].childNodes[o].textContent===n&&r++,i=i.parentNode;return e.NESTING_DEPTH=r>1?s.LOCALE.NUMBERS.wordOrdinal(r):"",e.NESTING_DEPTH},e.matchingFences=function(t){const e=t.previousSibling;let r,n;return e?(r=e,n=t):(r=t,n=t.nextSibling),n&&(0,h.isMatchingFence)(r.textContent,n.textContent)?[t]:[]},e.insertNesting=w,l.Grammar.getInstance().setCorrection("insertNesting",w),e.fencedArguments=function(t){const e=n.toArray(t.parentNode.childNodes),r=i.evalXPath("../../children/*",t),o=e.indexOf(t);return I(r[o])||I(r[o+1])?[t]:[]},e.simpleArguments=function(t){const e=n.toArray(t.parentNode.childNodes),r=i.evalXPath("../../children/*",t),o=e.indexOf(t);return L(r[o])&&r[o+1]&&(L(r[o+1])||"root"===r[o+1].tagName||"sqrt"===r[o+1].tagName||"superscript"===r[o+1].tagName&&r[o+1].childNodes[0].childNodes[0]&&("number"===r[o+1].childNodes[0].childNodes[0].tagName||"identifier"===r[o+1].childNodes[0].childNodes[0].tagName)&&("2"===r[o+1].childNodes[0].childNodes[1].textContent||"3"===r[o+1].childNodes[0].childNodes[1].textContent))?[t]:[]},e.simpleFactor_=L,e.fencedFactor_=I,e.layoutFactor_=P,e.wordOrdinal=function(t){return s.LOCALE.NUMBERS.wordOrdinal(parseInt(t.textContent,10))}},6141:function(t,e,r){var n=this&&this.__awaiter||function(t,e,r,n){return new(r||(r=Promise))((function(o,i){function s(t){try{l(n.next(t))}catch(t){i(t)}}function a(t){try{l(n.throw(t))}catch(t){i(t)}}function l(t){var e;t.done?o(t.value):(e=t.value,e instanceof r?e:new r((function(t){t(e)}))).then(s,a)}l((n=n.apply(t,e||[])).next())}))};Object.defineProperty(e,"__esModule",{value:!0}),e.loadAjax=e.loadFileSync=e.loadFile=e.parseMaps=e.retrieveFiles=e.standardLoader=e.loadLocale=e.store=void 0;const o=r(2139),i=r(5897),s=r(4440),a=r(7248),l=r(2315),c=r(1676),u=r(2780),p=r(2362),h=r(7491),f=r(313);e.store=u;const d={functions:u.addFunctionRules,symbols:u.addSymbolRules,units:u.addUnitRules,si:u.setSiPrefixes};let m=!1;function y(t=i.default.getInstance().locale){i.EnginePromise.loaded[t]||(i.EnginePromise.loaded[t]=[!1,!1],function(t){if(i.default.getInstance().isIE&&i.default.getInstance().mode===s.Mode.HTTP)return void S(t);b(t)}(t))}function g(){switch(i.default.getInstance().mode){case s.Mode.ASYNC:return M;case s.Mode.HTTP:return x;case s.Mode.SYNC:default:return O}}function b(t){const e=i.default.getInstance().customLoader?i.default.getInstance().customLoader:g(),r=new Promise((r=>{e(t).then((e=>{v(e),i.EnginePromise.loaded[t]=[!0,!0],r(t)}),(e=>{i.EnginePromise.loaded[t]=[!0,!1],console.error(`Unable to load locale: ${t}`),i.default.getInstance().locale=i.default.getInstance().defaultLocale,r(t)}))}));i.EnginePromise.promises[t]=r}function v(t){_(JSON.parse(t))}function _(t,e){let r=!0;for(let n,o=0;n=Object.keys(t)[o];o++){const o=n.split("/");e&&e!==o[0]||("rules"===o[1]?p.SpeechRuleEngine.getInstance().addStore(t[n]):"messages"===o[1]?(0,h.completeLocale)(t[n]):(r&&(f.generate(o[0]),r=!1),t[n].forEach(d[o[1]])))}}function S(t,e){let r=e||1;o.mapsForIE?_(o.mapsForIE,t):r<=5&&setTimeout((()=>S(t,r++)).bind(this),300)}function M(t){const e=a.localePath(t);return new Promise(((t,r)=>{l.default.fs.readFile(e,"utf8",((e,n)=>{if(e)return r(e);t(n)}))}))}function O(t){const e=a.localePath(t);return new Promise(((t,r)=>{let n="{}";try{n=l.default.fs.readFileSync(e,"utf8")}catch(t){return r(t)}t(n)}))}function x(t){const e=a.localePath(t),r=new XMLHttpRequest;return new Promise(((t,n)=>{r.onreadystatechange=function(){if(4===r.readyState){const e=r.status;0===e||e>=200&&e<400?t(r.responseText):n(e)}},r.open("GET",e,!0),r.send()}))}e.loadLocale=function(t=i.default.getInstance().locale){return n(this,void 0,void 0,(function*(){return m||(y(c.DynamicCstr.BASE_LOCALE),m=!0),i.EnginePromise.promises[c.DynamicCstr.BASE_LOCALE].then((()=>n(this,void 0,void 0,(function*(){const e=i.default.getInstance().defaultLocale;return e?(y(e),i.EnginePromise.promises[e].then((()=>n(this,void 0,void 0,(function*(){return y(t),i.EnginePromise.promises[t]}))))):(y(t),i.EnginePromise.promises[t])}))))}))},e.standardLoader=g,e.retrieveFiles=b,e.parseMaps=v,e.loadFile=M,e.loadFileSync=O,e.loadAjax=x},7088:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.leftSubscriptBrief=e.leftSuperscriptBrief=e.leftSubscriptVerbose=e.leftSuperscriptVerbose=e.baselineBrief=e.baselineVerbose=void 0;const n=r(1378);e.baselineVerbose=function(t){return n.baselineVerbose(t).replace(/-$/,"")},e.baselineBrief=function(t){return n.baselineBrief(t).replace(/-$/,"")},e.leftSuperscriptVerbose=function(t){return n.superscriptVerbose(t).replace(/^exposant/,"exposant gauche")},e.leftSubscriptVerbose=function(t){return n.subscriptVerbose(t).replace(/^indice/,"indice gauche")},e.leftSuperscriptBrief=function(t){return n.superscriptBrief(t).replace(/^sup/,"sup gauche")},e.leftSubscriptBrief=function(t){return n.subscriptBrief(t).replace(/^sub/,"sub gauche")}},9577:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.MathspeakRules=void 0;const n=r(1676),o=r(365),i=r(7088),s=r(1378),a=r(8437),l=r(7283),c=r(7598);e.MathspeakRules=function(){l.addStore(n.DynamicCstr.BASE_LOCALE+".speech.mathspeak","",{CQFspaceoutNumber:s.spaceoutNumber,CQFspaceoutIdentifier:s.spaceoutIdentifier,CSFspaceoutText:s.spaceoutText,CSFopenFracVerbose:s.openingFractionVerbose,CSFcloseFracVerbose:s.closingFractionVerbose,CSFoverFracVerbose:s.overFractionVerbose,CSFopenFracBrief:s.openingFractionBrief,CSFcloseFracBrief:s.closingFractionBrief,CSFopenFracSbrief:s.openingFractionSbrief,CSFcloseFracSbrief:s.closingFractionSbrief,CSFoverFracSbrief:s.overFractionSbrief,CSFvulgarFraction:a.vulgarFraction,CQFvulgarFractionSmall:s.isSmallVulgarFraction,CSFopenRadicalVerbose:s.openingRadicalVerbose,CSFcloseRadicalVerbose:s.closingRadicalVerbose,CSFindexRadicalVerbose:s.indexRadicalVerbose,CSFopenRadicalBrief:s.openingRadicalBrief,CSFcloseRadicalBrief:s.closingRadicalBrief,CSFindexRadicalBrief:s.indexRadicalBrief,CSFopenRadicalSbrief:s.openingRadicalSbrief,CSFindexRadicalSbrief:s.indexRadicalSbrief,CQFisSmallRoot:s.smallRoot,CSFsuperscriptVerbose:s.superscriptVerbose,CSFsuperscriptBrief:s.superscriptBrief,CSFsubscriptVerbose:s.subscriptVerbose,CSFsubscriptBrief:s.subscriptBrief,CSFbaselineVerbose:s.baselineVerbose,CSFbaselineBrief:s.baselineBrief,CSFleftsuperscriptVerbose:s.superscriptVerbose,CSFleftsubscriptVerbose:s.subscriptVerbose,CSFrightsuperscriptVerbose:s.superscriptVerbose,CSFrightsubscriptVerbose:s.subscriptVerbose,CSFleftsuperscriptBrief:s.superscriptBrief,CSFleftsubscriptBrief:s.subscriptBrief,CSFrightsuperscriptBrief:s.superscriptBrief,CSFrightsubscriptBrief:s.subscriptBrief,CSFunderscript:s.nestedUnderscript,CSFoverscript:s.nestedOverscript,CSFendscripts:s.endscripts,CTFordinalCounter:a.ordinalCounter,CTFwordCounter:a.wordCounter,CTFcontentIterator:o.contentIterator,CQFdetIsSimple:s.determinantIsSimple,CSFRemoveParens:s.removeParens,CQFresetNesting:s.resetNestingDepth,CGFbaselineConstraint:s.generateBaselineConstraint,CGFtensorRules:s.generateTensorRules}),l.addStore("es.speech.mathspeak",n.DynamicCstr.BASE_LOCALE+".speech.mathspeak",{CTFunitMultipliers:c.unitMultipliers,CQFoneLeft:c.oneLeft}),l.addStore("fr.speech.mathspeak",n.DynamicCstr.BASE_LOCALE+".speech.mathspeak",{CSFbaselineVerbose:i.baselineVerbose,CSFbaselineBrief:i.baselineBrief,CSFleftsuperscriptVerbose:i.leftSuperscriptVerbose,CSFleftsubscriptVerbose:i.leftSubscriptVerbose,CSFleftsuperscriptBrief:i.leftSuperscriptBrief,CSFleftsubscriptBrief:i.leftSubscriptBrief})}},1378:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.smallRoot=e.generateTensorRules=e.removeParens=e.generateBaselineConstraint=e.determinantIsSimple=e.nestedOverscript=e.endscripts=e.overscoreNestingDepth=e.nestedUnderscript=e.underscoreNestingDepth=e.indexRadicalSbrief=e.openingRadicalSbrief=e.indexRadicalBrief=e.closingRadicalBrief=e.openingRadicalBrief=e.indexRadicalVerbose=e.closingRadicalVerbose=e.openingRadicalVerbose=e.getRootIndex=e.nestedRadical=e.radicalNestingDepth=e.baselineBrief=e.baselineVerbose=e.superscriptBrief=e.superscriptVerbose=e.subscriptBrief=e.subscriptVerbose=e.nestedSubSuper=e.isSmallVulgarFraction=e.overFractionSbrief=e.closingFractionSbrief=e.openingFractionSbrief=e.closingFractionBrief=e.openingFractionBrief=e.overFractionVerbose=e.closingFractionVerbose=e.openingFractionVerbose=e.nestedFraction=e.fractionNestingDepth=e.computeNestingDepth_=e.containsAttr=e.getNestingDepth=e.resetNestingDepth=e.nestingBarriers=e.spaceoutIdentifier=e.spaceoutNumber=e.spaceoutNodes=e.spaceoutText=void 0;const n=r(707),o=r(5740),i=r(5274),s=r(4356),a=r(3308);let l={};function c(t,e){const r=Array.from(t.textContent),n=[],o=a.default.getInstance(),i=t.ownerDocument;for(let t,s=0;t=r[s];s++){const r=o.getNodeFactory().makeLeafNode(t,"unknown"),s=o.identifierNode(r,"unknown","");e(s),n.push(s.xml(i))}return n}function u(t,r,i,s,a,c){s=s||e.nestingBarriers,a=a||{},c=c||function(t){return!1};const u=o.serializeXml(r);if(l[t]||(l[t]={}),l[t][u])return l[t][u];if(c(r)||i.indexOf(r.tagName)<0)return 0;const p=h(r,i,n.setdifference(s,i),a,c,0);return l[t][u]=p,p}function p(t,e){if(!t.attributes)return!1;const r=o.toArray(t.attributes);for(let t,n=0;t=r[n];n++)if(e[t.nodeName]===t.nodeValue)return!0;return!1}function h(t,e,r,n,i,s){if(i(t)||r.indexOf(t.tagName)>-1||p(t,n))return s;if(e.indexOf(t.tagName)>-1&&s++,!t.childNodes||0===t.childNodes.length)return s;const a=o.toArray(t.childNodes);return Math.max.apply(null,a.map((function(t){return h(t,e,r,n,i,s)})))}function f(t){return u("fraction",t,["fraction"],e.nestingBarriers,{},s.LOCALE.FUNCTIONS.fracNestDepth)}function d(t,e,r){const n=f(t),o=Array(n).fill(e);return r&&o.push(r),o.join(s.LOCALE.MESSAGES.regexp.JOINER_FRAC)}function m(t,e,r){for(;t.parentNode;){const n=t.parentNode,o=n.parentNode;if(!o)break;const i=t.getAttribute&&t.getAttribute("role");("subscript"===o.tagName&&t===n.childNodes[1]||"tensor"===o.tagName&&i&&("leftsub"===i||"rightsub"===i))&&(e=r.sub+s.LOCALE.MESSAGES.regexp.JOINER_SUBSUPER+e),("superscript"===o.tagName&&t===n.childNodes[1]||"tensor"===o.tagName&&i&&("leftsuper"===i||"rightsuper"===i))&&(e=r.sup+s.LOCALE.MESSAGES.regexp.JOINER_SUBSUPER+e),t=o}return e.trim()}function y(t){return u("radical",t,["sqrt","root"],e.nestingBarriers,{})}function g(t,e,r){const n=y(t),o=b(t);return r=o?s.LOCALE.FUNCTIONS.combineRootIndex(r,o):r,1===n?r:s.LOCALE.FUNCTIONS.combineNestedRadical(e,s.LOCALE.FUNCTIONS.radicalNestDepth(n-1),r)}function b(t){const e="sqrt"===t.tagName?"2":i.evalXPath("children/*[1]",t)[0].textContent.trim();return s.LOCALE.MESSAGES.MSroots[e]||""}function v(t){return u("underscore",t,["underscore"],e.nestingBarriers,{},(function(t){return t.tagName&&"underscore"===t.tagName&&"underaccent"===t.childNodes[0].childNodes[1].getAttribute("role")}))}function _(t){return u("overscore",t,["overscore"],e.nestingBarriers,{},(function(t){return t.tagName&&"overscore"===t.tagName&&"overaccent"===t.childNodes[0].childNodes[1].getAttribute("role")}))}e.spaceoutText=function(t){return Array.from(t.textContent).join(" ")},e.spaceoutNodes=c,e.spaceoutNumber=function(t){return c(t,(function(t){t.textContent.match(/\W/)||(t.type="number")}))},e.spaceoutIdentifier=function(t){return c(t,(function(t){t.font="unknown",t.type="identifier"}))},e.nestingBarriers=["cases","cell","integral","line","matrix","multiline","overscore","root","row","sqrt","subscript","superscript","table","underscore","vector"],e.resetNestingDepth=function(t){return l={},[t]},e.getNestingDepth=u,e.containsAttr=p,e.computeNestingDepth_=h,e.fractionNestingDepth=f,e.nestedFraction=d,e.openingFractionVerbose=function(t){return d(t,s.LOCALE.MESSAGES.MS.START,s.LOCALE.MESSAGES.MS.FRAC_V)},e.closingFractionVerbose=function(t){return d(t,s.LOCALE.MESSAGES.MS.END,s.LOCALE.MESSAGES.MS.FRAC_V)},e.overFractionVerbose=function(t){return d(t,s.LOCALE.MESSAGES.MS.FRAC_OVER)},e.openingFractionBrief=function(t){return d(t,s.LOCALE.MESSAGES.MS.START,s.LOCALE.MESSAGES.MS.FRAC_B)},e.closingFractionBrief=function(t){return d(t,s.LOCALE.MESSAGES.MS.END,s.LOCALE.MESSAGES.MS.FRAC_B)},e.openingFractionSbrief=function(t){const e=f(t);return 1===e?s.LOCALE.MESSAGES.MS.FRAC_S:s.LOCALE.FUNCTIONS.combineNestedFraction(s.LOCALE.MESSAGES.MS.NEST_FRAC,s.LOCALE.FUNCTIONS.radicalNestDepth(e-1),s.LOCALE.MESSAGES.MS.FRAC_S)},e.closingFractionSbrief=function(t){const e=f(t);return 1===e?s.LOCALE.MESSAGES.MS.ENDFRAC:s.LOCALE.FUNCTIONS.combineNestedFraction(s.LOCALE.MESSAGES.MS.NEST_FRAC,s.LOCALE.FUNCTIONS.radicalNestDepth(e-1),s.LOCALE.MESSAGES.MS.ENDFRAC)},e.overFractionSbrief=function(t){const e=f(t);return 1===e?s.LOCALE.MESSAGES.MS.FRAC_OVER:s.LOCALE.FUNCTIONS.combineNestedFraction(s.LOCALE.MESSAGES.MS.NEST_FRAC,s.LOCALE.FUNCTIONS.radicalNestDepth(e-1),s.LOCALE.MESSAGES.MS.FRAC_OVER)},e.isSmallVulgarFraction=function(t){return s.LOCALE.FUNCTIONS.fracNestDepth(t)?[t]:[]},e.nestedSubSuper=m,e.subscriptVerbose=function(t){return m(t,s.LOCALE.MESSAGES.MS.SUBSCRIPT,{sup:s.LOCALE.MESSAGES.MS.SUPER,sub:s.LOCALE.MESSAGES.MS.SUB})},e.subscriptBrief=function(t){return m(t,s.LOCALE.MESSAGES.MS.SUB,{sup:s.LOCALE.MESSAGES.MS.SUP,sub:s.LOCALE.MESSAGES.MS.SUB})},e.superscriptVerbose=function(t){return m(t,s.LOCALE.MESSAGES.MS.SUPERSCRIPT,{sup:s.LOCALE.MESSAGES.MS.SUPER,sub:s.LOCALE.MESSAGES.MS.SUB})},e.superscriptBrief=function(t){return m(t,s.LOCALE.MESSAGES.MS.SUP,{sup:s.LOCALE.MESSAGES.MS.SUP,sub:s.LOCALE.MESSAGES.MS.SUB})},e.baselineVerbose=function(t){const e=m(t,"",{sup:s.LOCALE.MESSAGES.MS.SUPER,sub:s.LOCALE.MESSAGES.MS.SUB});return e?e.replace(new RegExp(s.LOCALE.MESSAGES.MS.SUB+"$"),s.LOCALE.MESSAGES.MS.SUBSCRIPT).replace(new RegExp(s.LOCALE.MESSAGES.MS.SUPER+"$"),s.LOCALE.MESSAGES.MS.SUPERSCRIPT):s.LOCALE.MESSAGES.MS.BASELINE},e.baselineBrief=function(t){return m(t,"",{sup:s.LOCALE.MESSAGES.MS.SUP,sub:s.LOCALE.MESSAGES.MS.SUB})||s.LOCALE.MESSAGES.MS.BASE},e.radicalNestingDepth=y,e.nestedRadical=g,e.getRootIndex=b,e.openingRadicalVerbose=function(t){return g(t,s.LOCALE.MESSAGES.MS.NESTED,s.LOCALE.MESSAGES.MS.STARTROOT)},e.closingRadicalVerbose=function(t){return g(t,s.LOCALE.MESSAGES.MS.NESTED,s.LOCALE.MESSAGES.MS.ENDROOT)},e.indexRadicalVerbose=function(t){return g(t,s.LOCALE.MESSAGES.MS.NESTED,s.LOCALE.MESSAGES.MS.ROOTINDEX)},e.openingRadicalBrief=function(t){return g(t,s.LOCALE.MESSAGES.MS.NEST_ROOT,s.LOCALE.MESSAGES.MS.STARTROOT)},e.closingRadicalBrief=function(t){return g(t,s.LOCALE.MESSAGES.MS.NEST_ROOT,s.LOCALE.MESSAGES.MS.ENDROOT)},e.indexRadicalBrief=function(t){return g(t,s.LOCALE.MESSAGES.MS.NEST_ROOT,s.LOCALE.MESSAGES.MS.ROOTINDEX)},e.openingRadicalSbrief=function(t){return g(t,s.LOCALE.MESSAGES.MS.NEST_ROOT,s.LOCALE.MESSAGES.MS.ROOT)},e.indexRadicalSbrief=function(t){return g(t,s.LOCALE.MESSAGES.MS.NEST_ROOT,s.LOCALE.MESSAGES.MS.INDEX)},e.underscoreNestingDepth=v,e.nestedUnderscript=function(t){const e=v(t);return Array(e).join(s.LOCALE.MESSAGES.MS.UNDER)+s.LOCALE.MESSAGES.MS.UNDERSCRIPT},e.overscoreNestingDepth=_,e.endscripts=function(t){return s.LOCALE.MESSAGES.MS.ENDSCRIPTS},e.nestedOverscript=function(t){const e=_(t);return Array(e).join(s.LOCALE.MESSAGES.MS.OVER)+s.LOCALE.MESSAGES.MS.OVERSCRIPT},e.determinantIsSimple=function(t){if("matrix"!==t.tagName||"determinant"!==t.getAttribute("role"))return[];const e=i.evalXPath("children/row/children/cell/children/*",t);for(let t,r=0;t=e[r];r++)if("number"!==t.tagName){if("identifier"===t.tagName){const e=t.getAttribute("role");if("latinletter"===e||"greekletter"===e||"otherletter"===e)continue}return[]}return[t]},e.generateBaselineConstraint=function(){const t=t=>t.map((t=>"ancestor::"+t)),e=t=>"not("+t+")",r=e(t(["subscript","superscript","tensor"]).join(" or ")),n=t(["relseq","multrel"]),o=t(["fraction","punctuation","fenced","sqrt","root"]);let i=[];for(let t,e=0;t=o[e];e++)i=i.concat(n.map((function(e){return t+"/"+e})));return[["ancestor::*/following-sibling::*",r,e(i.join(" | "))].join(" and ")]},e.removeParens=function(t){if(!t.childNodes.length||!t.childNodes[0].childNodes.length||!t.childNodes[0].childNodes[0].childNodes.length)return"";const e=t.childNodes[0].childNodes[0].childNodes[0].textContent;return e.match(/^\(.+\)$/)?e.slice(1,-1):e};const S=new Map([[3,"CSFleftsuperscript"],[4,"CSFleftsubscript"],[2,"CSFbaseline"],[1,"CSFrightsubscript"],[0,"CSFrightsuperscript"]]),M=new Map([[4,2],[3,3],[2,1],[1,4],[0,5]]);function O(t){const e=[];let r="",n="",o=parseInt(t,2);for(let t=0;t<5;t++){const i="children/*["+M.get(t)+"]";if(1&o){const e=S.get(t%5);r="[t] "+e+"Verbose; [n] "+i+";"+r,n="[t] "+e+"Brief; [n] "+i+";"+n}else e.unshift("name("+i+')="empty"');o>>=1}return[e,r,n]}e.generateTensorRules=function(t,e=!0){const r=["11111","11110","11101","11100","10111","10110","10101","10100","01111","01110","01101","01100"];for(let n,o=0;n=r[o];o++){let r="tensor"+n,[o,i,s]=O(n);t.defineRule(r,"default",i,"self::tensor",...o),e&&(t.defineRule(r,"brief",s,"self::tensor",...o),t.defineRule(r,"sbrief",s,"self::tensor",...o));const a=S.get(2);i+="; [t]"+a+"Verbose",s+="; [t]"+a+"Brief",r+="-baseline";const l="((.//*[not(*)])[last()]/@id)!=(((.//ancestor::fraction|ancestor::root|ancestor::sqrt|ancestor::cell|ancestor::line|ancestor::stree)[1]//*[not(*)])[last()]/@id)";t.defineRule(r,"default",i,"self::tensor",l,...o),e&&(t.defineRule(r,"brief",s,"self::tensor",l,...o),t.defineRule(r,"sbrief",s,"self::tensor",l,...o))}},e.smallRoot=function(t){let e=Object.keys(s.LOCALE.MESSAGES.MSroots).length;if(!e)return[];if(e++,!t.childNodes||0===t.childNodes.length||!t.childNodes[0].childNodes)return[];const r=t.childNodes[0].childNodes[0].textContent;if(!/^\d+$/.test(r))return[];const n=parseInt(r,10);return n>1&&n<=e?[t]:[]}},6922:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.implicitIterator=e.relationIterator=e.propagateNumber=e.checkParent_=e.NUMBER_INHIBITORS_=e.NUMBER_PROPAGATORS_=e.enlargeFence=e.indexRadical=e.closingRadical=e.openingRadical=e.radicalNestingDepth=e.nestedRadical=e.overBevelledFraction=e.overFraction=e.closingFraction=e.openingFraction=void 0;const n=r(7052),o=r(5740),i=r(5274),s=r(2105),a=r(5897),l=r(7630),c=r(9265),u=r(4356),p=r(1378);function h(t,e){const r=f(t);return 1===r?e:new Array(r).join(u.LOCALE.MESSAGES.MS.NESTED)+e}function f(t,e){const r=e||0;return t.parentNode?f(t.parentNode,"root"===t.tagName||"sqrt"===t.tagName?r+1:r):r}function d(t){const e="\u2820";if(1===t.length)return e+t;const r=t.split("");return r.every((function(t){return"\u2833"===t}))?e+r.join(e):t.slice(0,-1)+e+t.slice(-1)}function m(t,r){const n=t.parent;if(!n)return!1;const o=n.type;return-1!==e.NUMBER_PROPAGATORS_.indexOf(o)||"prefixop"===o&&"negative"===n.role&&!r.script||"prefixop"===o&&"geometry"===n.role||!("punctuated"!==o||r.enclosed&&"text"!==n.role)}function y(t,r){return t.childNodes.length?(-1!==e.NUMBER_INHIBITORS_.indexOf(t.type)&&(r.script=!0),"fenced"===t.type?(r.number=!1,r.enclosed=!0,["",r]):(m(t,r)&&(r.number=!0,r.enclosed=!1),["",r])):(m(t,r)&&(r.number=!0,r.script=!1,r.enclosed=!1),[r.number?"number":"",{number:!1,enclosed:r.enclosed,script:r.script}])}e.openingFraction=function(t){const e=p.fractionNestingDepth(t);return new Array(e).join(u.LOCALE.MESSAGES.MS.FRACTION_REPEAT)+u.LOCALE.MESSAGES.MS.FRACTION_START},e.closingFraction=function(t){const e=p.fractionNestingDepth(t);return new Array(e).join(u.LOCALE.MESSAGES.MS.FRACTION_REPEAT)+u.LOCALE.MESSAGES.MS.FRACTION_END},e.overFraction=function(t){const e=p.fractionNestingDepth(t);return new Array(e).join(u.LOCALE.MESSAGES.MS.FRACTION_REPEAT)+u.LOCALE.MESSAGES.MS.FRACTION_OVER},e.overBevelledFraction=function(t){const e=p.fractionNestingDepth(t);return new Array(e).join(u.LOCALE.MESSAGES.MS.FRACTION_REPEAT)+"\u2838"+u.LOCALE.MESSAGES.MS.FRACTION_OVER},e.nestedRadical=h,e.radicalNestingDepth=f,e.openingRadical=function(t){return h(t,u.LOCALE.MESSAGES.MS.STARTROOT)},e.closingRadical=function(t){return h(t,u.LOCALE.MESSAGES.MS.ENDROOT)},e.indexRadical=function(t){return h(t,u.LOCALE.MESSAGES.MS.ROOTINDEX)},e.enlargeFence=d,s.Grammar.getInstance().setCorrection("enlargeFence",d),e.NUMBER_PROPAGATORS_=["multirel","relseq","appl","row","line"],e.NUMBER_INHIBITORS_=["subscript","superscript","overscore","underscore"],e.checkParent_=m,e.propagateNumber=y,(0,l.register)(new c.SemanticVisitor("nemeth","number",y,{number:!0})),e.relationIterator=function(t,e){const r=t.slice(0);let s,l=!0;return s=t.length>0?i.evalXPath("../../content/*",t[0]):[],function(){const t=s.shift(),i=r.shift(),c=r[0],h=e?[n.AuditoryDescription.create({text:e},{translate:!0})]:[];if(!t)return h;const f=i?p.nestedSubSuper(i,"",{sup:u.LOCALE.MESSAGES.MS.SUPER,sub:u.LOCALE.MESSAGES.MS.SUB}):"",d=i&&"EMPTY"!==o.tagName(i)||l&&t.parentNode.parentNode&&t.parentNode.parentNode.previousSibling?[n.AuditoryDescription.create({text:"\u2800"+f},{})]:[],m=c&&"EMPTY"!==o.tagName(c)||!s.length&&t.parentNode.parentNode&&t.parentNode.parentNode.nextSibling?[n.AuditoryDescription.create({text:"\u2800"},{})]:[],y=a.default.evaluateNode(t);return l=!1,h.concat(d,y,m)}},e.implicitIterator=function(t,e){const r=t.slice(0);let s;return s=t.length>0?i.evalXPath("../../content/*",t[0]):[],function(){const t=r.shift(),i=r[0],a=s.shift(),l=e?[n.AuditoryDescription.create({text:e},{translate:!0})]:[];if(!a)return l;const c=t&&"NUMBER"===o.tagName(t),u=i&&"NUMBER"===o.tagName(i);return l.concat(c&&u&&"space"===a.getAttribute("role")?[n.AuditoryDescription.create({text:"\u2800"},{})]:[])}}},8437:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.ordinalPosition=e.vulgarFraction=e.wordCounter=e.ordinalCounter=void 0;const n=r(9536),o=r(5740),i=r(4356),s=r(4977);e.ordinalCounter=function(t,e){let r=0;return function(){return i.LOCALE.NUMBERS.numericOrdinal(++r)+" "+e}},e.wordCounter=function(t,e){let r=0;return function(){return i.LOCALE.NUMBERS.numberToOrdinal(++r,!1)+" "+e}},e.vulgarFraction=function(t){const e=(0,s.convertVulgarFraction)(t,i.LOCALE.MESSAGES.MS.FRAC_OVER);return e.convertible&&e.enumerator&&e.denominator?[new n.Span(i.LOCALE.NUMBERS.numberToWords(e.enumerator),{extid:t.childNodes[0].childNodes[0].getAttribute("extid"),separator:""}),new n.Span(i.LOCALE.NUMBERS.vulgarSep,{separator:""}),new n.Span(i.LOCALE.NUMBERS.numberToOrdinal(e.denominator,1!==e.enumerator),{extid:t.childNodes[0].childNodes[1].getAttribute("extid")})]:[new n.Span(e.content||"",{extid:t.getAttribute("extid")})]},e.ordinalPosition=function(t){const e=o.toArray(t.parentNode.childNodes);return i.LOCALE.NUMBERS.numericOrdinal(e.indexOf(t)+1).toString()}},9284:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.BrailleRules=e.OtherRules=e.PrefixRules=void 0;const n=r(1676),o=r(365),i=r(1378),s=r(6922),a=r(8437),l=r(7283);e.PrefixRules=function(){l.addStore("en.prefix.default","",{CSFordinalPosition:a.ordinalPosition})},e.OtherRules=function(){l.addStore("en.speech.chromevox","",{CTFnodeCounter:o.nodeCounter,CTFcontentIterator:o.contentIterator}),l.addStore("en.speech.emacspeak","en.speech.chromevox",{CQFvulgarFractionSmall:i.isSmallVulgarFraction,CSFvulgarFraction:a.vulgarFraction})},e.BrailleRules=function(){l.addStore("nemeth.braille.default",n.DynamicCstr.BASE_LOCALE+".speech.mathspeak",{CSFopenFraction:s.openingFraction,CSFcloseFraction:s.closingFraction,CSFoverFraction:s.overFraction,CSFoverBevFraction:s.overBevelledFraction,CSFopenRadical:s.openingRadical,CSFcloseRadical:s.closingRadical,CSFindexRadical:s.indexRadical,CSFsubscript:i.subscriptVerbose,CSFsuperscript:i.superscriptVerbose,CSFbaseline:i.baselineVerbose,CGFtensorRules:t=>i.generateTensorRules(t,!1),CTFrelationIterator:s.relationIterator,CTFimplicitIterator:s.implicitIterator})}},7599:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.init=e.INIT_=void 0;const n=r(5425),o=r(9577),i=r(9284);e.INIT_=!1,e.init=function(){e.INIT_||((0,o.MathspeakRules)(),(0,n.ClearspeakRules)(),(0,i.PrefixRules)(),(0,i.OtherRules)(),(0,i.BrailleRules)(),e.INIT_=!0)}},7283:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.getStore=e.addStore=e.funcStore=void 0;const n=r(1676);e.funcStore=new Map,e.addStore=function(t,r,n){const o={};if(r){const t=e.funcStore.get(r)||{};Object.assign(o,t)}e.funcStore.set(t,Object.assign(o,n))},e.getStore=function(t,r,o){return e.funcStore.get([t,r,o].join("."))||e.funcStore.get([n.DynamicCstr.DEFAULT_VALUES[n.Axis.LOCALE],r,o].join("."))||e.funcStore.get([n.DynamicCstr.BASE_LOCALE,r,o].join("."))||{}}},7598:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.oneLeft=e.leftMostUnit=e.rightMostUnit=e.unitMultipliers=void 0;const n=r(7052),o=r(5274),i=r(4356);e.unitMultipliers=function(t,e){const r=t;let o=0;return function(){const t=n.AuditoryDescription.create({text:a(r[o])&&l(r[o+1])?i.LOCALE.MESSAGES.unitTimes:""},{});return o++,[t]}};const s=["superscript","subscript","overscore","underscore"];function a(t){for(;t;){if("unit"===t.getAttribute("role"))return!0;const e=t.tagName,r=o.evalXPath("children/*",t);t=-1!==s.indexOf(e)?r[0]:r[r.length-1]}return!1}function l(t){for(;t;){if("unit"===t.getAttribute("role"))return!0;t=o.evalXPath("children/*",t)[0]}return!1}e.rightMostUnit=a,e.leftMostUnit=l,e.oneLeft=function(t){for(;t;){if("number"===t.tagName&&"1"===t.textContent)return[t];if("infixop"!==t.tagName||"multiplication"!==t.getAttribute("role")&&"implicit"!==t.getAttribute("role"))return[];t=o.evalXPath("children/*",t)[0]}return[]}},3284:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.AbstractWalker=void 0;const n=r(7052),o=r(8290),i=r(5740),s=r(4440),a=r(6828),l=r(8496),c=r(2298),u=r(4356),p=r(2105),h=r(5656),f=r(9552),d=r(9543),m=r(8504),y=r(7730),g=r(1214),b=r(179),v=r(1204),_=r(5274);class S{constructor(t,e,r,n){this.node=t,this.generator=e,this.highlighter=r,this.modifier=!1,this.keyMapping=new Map([[l.KeyCode.UP,this.up.bind(this)],[l.KeyCode.DOWN,this.down.bind(this)],[l.KeyCode.RIGHT,this.right.bind(this)],[l.KeyCode.LEFT,this.left.bind(this)],[l.KeyCode.TAB,this.repeat.bind(this)],[l.KeyCode.DASH,this.expand.bind(this)],[l.KeyCode.SPACE,this.depth.bind(this)],[l.KeyCode.HOME,this.home.bind(this)],[l.KeyCode.X,this.summary.bind(this)],[l.KeyCode.Z,this.detail.bind(this)],[l.KeyCode.V,this.virtualize.bind(this)],[l.KeyCode.P,this.previous.bind(this)],[l.KeyCode.U,this.undo.bind(this)],[l.KeyCode.LESS,this.previousRules.bind(this)],[l.KeyCode.GREATER,this.nextRules.bind(this)]]),this.cursors=[],this.xml_=null,this.rebuilt_=null,this.focus_=null,this.active_=!1,this.node.id?this.id=this.node.id:this.node.hasAttribute(S.SRE_ID_ATTR)?this.id=this.node.getAttribute(S.SRE_ID_ATTR):(this.node.setAttribute(S.SRE_ID_ATTR,S.ID_COUNTER.toString()),this.id=S.ID_COUNTER++),this.rootNode=v.getSemanticRoot(t),this.rootId=this.rootNode.getAttribute(c.Attribute.ID),this.xmlString_=n,this.moved=b.WalkerMoves.ENTER}getXml(){return this.xml_||(this.xml_=i.parseInput(this.xmlString_)),this.xml_}getRebuilt(){return this.rebuilt_||this.rebuildStree(),this.rebuilt_}isActive(){return this.active_}activate(){this.isActive()||(this.generator.start(),this.toggleActive_())}deactivate(){this.isActive()&&(b.WalkerState.setState(this.id,this.primaryId()),this.generator.end(),this.toggleActive_())}getFocus(t=!1){return this.focus_||(this.focus_=this.singletonFocus(this.rootId)),t&&this.updateFocus(),this.focus_}setFocus(t){this.focus_=t}getDepth(){return this.levels.depth()-1}isSpeech(){return this.generator.modality===c.Attribute.SPEECH}focusDomNodes(){return this.getFocus().getDomNodes()}focusSemanticNodes(){return this.getFocus().getSemanticNodes()}speech(){const t=this.focusDomNodes();if(!t.length)return"";const e=this.specialMove();if(null!==e)return e;switch(this.moved){case b.WalkerMoves.DEPTH:return this.depth_();case b.WalkerMoves.SUMMARY:return this.summary_();case b.WalkerMoves.DETAIL:return this.detail_();default:{const e=[],r=this.focusSemanticNodes();for(let n=0,o=t.length;n0}restoreState(){if(!this.highlighter)return;const t=b.WalkerState.getState(this.id);if(!t)return;let e=this.getRebuilt().nodeDict[t];const r=[];for(;e;)r.push(e.id),e=e.parent;for(r.pop();r.length>0;){this.down();const t=r.pop(),e=this.findFocusOnLevel(t);if(!e)break;this.setFocus(e)}this.moved=b.WalkerMoves.ENTER}updateFocus(){this.setFocus(y.Focus.factory(this.getFocus().getSemanticPrimary().id.toString(),this.getFocus().getSemanticNodes().map((t=>t.id.toString())),this.getRebuilt(),this.node))}rebuildStree(){this.rebuilt_=new g.RebuildStree(this.getXml()),this.rootId=this.rebuilt_.stree.root.id.toString(),this.generator.setRebuilt(this.rebuilt_),this.skeleton=h.SemanticSkeleton.fromTree(this.rebuilt_.stree),this.skeleton.populate(),this.focus_=this.singletonFocus(this.rootId),this.levels=this.initLevels(),d.connectMactions(this.node,this.getXml(),this.rebuilt_.xml)}previousLevel(){const t=this.getFocus().getDomPrimary();return t?v.getAttribute(t,c.Attribute.PARENT):this.getFocus().getSemanticPrimary().parent.id.toString()}nextLevel(){const t=this.getFocus().getDomPrimary();let e,r;if(t){e=v.splitAttribute(v.getAttribute(t,c.Attribute.CHILDREN)),r=v.splitAttribute(v.getAttribute(t,c.Attribute.CONTENT));const n=v.getAttribute(t,c.Attribute.TYPE),o=v.getAttribute(t,c.Attribute.ROLE);return this.combineContentChildren(n,o,r,e)}const n=t=>t.id.toString(),o=this.getRebuilt().nodeDict[this.primaryId()];return e=o.childNodes.map(n),r=o.contentNodes.map(n),0===e.length?[]:this.combineContentChildren(o.type,o.role,r,e)}singletonFocus(t){this.getRebuilt();const e=this.retrieveVisuals(t);return this.focusFromId(t,e)}retrieveVisuals(t){if(!this.skeleton)return[t];const e=parseInt(t,10),r=this.skeleton.subtreeNodes(e);if(!r.length)return[t];r.unshift(e);const n={},o=[];_.updateEvaluator(this.getXml());for(const t of r)n[t]||(o.push(t.toString()),n[t]=!0,this.subtreeIds(t,n));return o}subtreeIds(t,e){const r=_.evalXPath(`//*[@data-semantic-id="${t}"]`,this.getXml());_.evalXPath("*//@data-semantic-id",r[0]).forEach((t=>e[parseInt(t.textContent,10)]=!0))}focusFromId(t,e){return y.Focus.factory(t,e,this.getRebuilt(),this.node)}summary(){return this.moved=this.isSpeech()?b.WalkerMoves.SUMMARY:b.WalkerMoves.REPEAT,this.getFocus().clone()}detail(){return this.moved=this.isSpeech()?b.WalkerMoves.DETAIL:b.WalkerMoves.REPEAT,this.getFocus().clone()}specialMove(){return null}virtualize(t){return this.cursors.push({focus:this.getFocus(),levels:this.levels,undo:t||!this.cursors.length}),this.levels=this.levels.clone(),this.getFocus().clone()}previous(){const t=this.cursors.pop();return t?(this.levels=t.levels,t.focus):this.getFocus()}undo(){let t;do{t=this.cursors.pop()}while(t&&!t.undo);return t?(this.levels=t.levels,t.focus):this.getFocus()}update(t){this.generator.setOptions(t),(0,a.setup)(t).then((()=>f.generator("Tree").getSpeech(this.node,this.getXml())))}nextRules(){const t=this.generator.getOptions();return"speech"!==t.modality?this.getFocus():(s.DOMAIN_TO_STYLES[t.domain]=t.style,t.domain="mathspeak"===t.domain?"clearspeak":"mathspeak",t.style=s.DOMAIN_TO_STYLES[t.domain],this.update(t),this.moved=b.WalkerMoves.REPEAT,this.getFocus().clone())}nextStyle(t,e){if("mathspeak"===t){const t=["default","brief","sbrief"],r=t.indexOf(e);return-1===r?e:r>=t.length-1?t[0]:t[r+1]}if("clearspeak"===t){const t=m.ClearspeakPreferences.getLocalePreferences().en;if(!t)return"default";const r=m.ClearspeakPreferences.relevantPreferences(this.getFocus().getSemanticPrimary()),n=m.ClearspeakPreferences.findPreference(e,r),o=t[r].map((function(t){return t.split("_")[1]})),i=o.indexOf(n);if(-1===i)return e;const s=i>=o.length-1?o[0]:o[i+1];return m.ClearspeakPreferences.addPreference(e,r,s)}return e}previousRules(){const t=this.generator.getOptions();return"speech"!==t.modality?this.getFocus():(t.style=this.nextStyle(t.domain,t.style),this.update(t),this.moved=b.WalkerMoves.REPEAT,this.getFocus().clone())}refocus(){let t,e=this.getFocus();for(;!e.getNodes().length;){t=this.levels.peek();const r=this.up();if(!r)break;this.setFocus(r),e=this.getFocus(!0)}this.levels.push(t),this.setFocus(e)}toggleActive_(){this.active_=!this.active_}mergePrefix_(t,e=[]){const r=this.isSpeech()?this.prefix_():"";r&&t.unshift(r);const n=this.isSpeech()?this.postfix_():"";return n&&t.push(n),o.finalize(o.merge(e.concat(t)))}prefix_(){const t=this.getFocus().getDomNodes(),e=this.getFocus().getSemanticNodes();return t[0]?v.getAttribute(t[0],c.Attribute.PREFIX):d.retrievePrefix(e[0])}postfix_(){const t=this.getFocus().getDomNodes();return t[0]?v.getAttribute(t[0],c.Attribute.POSTFIX):""}depth_(){const t=p.Grammar.getInstance().getParameter("depth");p.Grammar.getInstance().setParameter("depth",!0);const e=this.getFocus().getDomPrimary(),r=this.expandable(e)?u.LOCALE.MESSAGES.navigate.EXPANDABLE:this.collapsible(e)?u.LOCALE.MESSAGES.navigate.COLLAPSIBLE:"",i=u.LOCALE.MESSAGES.navigate.LEVEL+" "+this.getDepth(),s=this.getFocus().getSemanticNodes(),a=d.retrievePrefix(s[0]),l=[new n.AuditoryDescription({text:i,personality:{}}),new n.AuditoryDescription({text:a,personality:{}}),new n.AuditoryDescription({text:r,personality:{}})];return p.Grammar.getInstance().setParameter("depth",t),o.finalize(o.markup(l))}actionable_(t){const e=null==t?void 0:t.parentNode;return e&&this.highlighter.isMactionNode(e)?e:null}summary_(){const t=this.getFocus().getSemanticPrimary().id.toString(),e=this.getRebuilt().xml.getAttribute("id")===t?this.getRebuilt().xml:i.querySelectorAllByAttrValue(this.getRebuilt().xml,"id",t)[0],r=d.retrieveSummary(e);return this.mergePrefix_([r])}detail_(){const t=this.getFocus().getSemanticPrimary().id.toString(),e=this.getRebuilt().xml.getAttribute("id")===t?this.getRebuilt().xml:i.querySelectorAllByAttrValue(this.getRebuilt().xml,"id",t)[0],r=e.getAttribute("alternative");e.removeAttribute("alternative");const n=d.computeMarkup(e),o=this.mergePrefix_([n]);return e.setAttribute("alternative",r),o}}e.AbstractWalker=S,S.ID_COUNTER=0,S.SRE_ID_ATTR="sre-explorer-id"},162:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.DummyWalker=void 0;const n=r(3284);class o extends n.AbstractWalker{up(){return null}down(){return null}left(){return null}right(){return null}repeat(){return null}depth(){return null}home(){return null}getDepth(){return 0}initLevels(){return null}combineContentChildren(t,e,r,n){return[]}findFocusOnLevel(t){return null}}e.DummyWalker=o},7730:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.Focus=void 0;const n=r(1204);class o{constructor(t,e){this.nodes=t,this.primary=e,this.domNodes=[],this.domPrimary_=null,this.allNodes=[]}static factory(t,e,r,i){const s=t=>n.getBySemanticId(i,t),a=r.nodeDict,l=s(t),c=e.map(s),u=e.map((function(t){return a[t]})),p=new o(u,a[t]);return p.domNodes=c,p.domPrimary_=l,p.allNodes=o.generateAllVisibleNodes_(e,c,a,i),p}static generateAllVisibleNodes_(t,e,r,i){const s=t=>n.getBySemanticId(i,t);let a=[];for(let n=0,l=t.length;n=e.length?null:e[t]}depth(){return this.level_.length}clone(){const t=new r;return t.level_=this.level_.slice(0),t}toString(){let t="";for(let e,r=0;e=this.level_[r];r++)t+="\n"+e.map((function(t){return t.toString()}));return t}}e.Levels=r},1214:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.RebuildStree=void 0;const n=r(5740),o=r(2298),i=r(3588),s=r(6537),a=r(3308),l=r(5656),c=r(7075),u=r(4795),p=r(1204);class h{constructor(t){this.mathml=t,this.factory=new s.SemanticNodeFactory,this.nodeDict={},this.mmlRoot=p.getSemanticRoot(t),this.streeRoot=this.assembleTree(this.mmlRoot),this.stree=c.SemanticTree.fromNode(this.streeRoot,this.mathml),this.xml=this.stree.xml(),a.default.getInstance().setNodeFactory(this.factory)}static addAttributes(t,e,r){r&&1===e.childNodes.length&&e.childNodes[0].nodeType!==n.NodeType.TEXT_NODE&&u.addAttributes(t,e.childNodes[0]),u.addAttributes(t,e)}static textContent(t,e,r){if(!r&&e.textContent)return void(t.textContent=e.textContent);const n=p.splitAttribute(p.getAttribute(e,o.Attribute.OPERATOR));n.length>1&&(t.textContent=n[1])}static isPunctuated(t){return!l.SemanticSkeleton.simpleCollapseStructure(t)&&t[1]&&l.SemanticSkeleton.contentCollapseStructure(t[1])}getTree(){return this.stree}assembleTree(t){const e=this.makeNode(t),r=p.splitAttribute(p.getAttribute(t,o.Attribute.CHILDREN)),n=p.splitAttribute(p.getAttribute(t,o.Attribute.CONTENT));if(h.addAttributes(e,t,!(r.length||n.length)),0===n.length&&0===r.length)return h.textContent(e,t),e;if(n.length>0){const t=p.getBySemanticId(this.mathml,n[0]);t&&h.textContent(e,t,!0)}e.contentNodes=n.map((t=>this.setParent(t,e))),e.childNodes=r.map((t=>this.setParent(t,e)));const i=p.getAttribute(t,o.Attribute.COLLAPSED);return i?this.postProcess(e,i):e}makeNode(t){const e=p.getAttribute(t,o.Attribute.TYPE),r=p.getAttribute(t,o.Attribute.ROLE),n=p.getAttribute(t,o.Attribute.FONT),i=p.getAttribute(t,o.Attribute.ANNOTATION)||"",s=p.getAttribute(t,o.Attribute.ID),a=p.getAttribute(t,o.Attribute.EMBELLISHED),l=p.getAttribute(t,o.Attribute.FENCEPOINTER),c=this.createNode(parseInt(s,10));return c.type=e,c.role=r,c.font=n||"unknown",c.parseAnnotation(i),l&&(c.fencePointer=l),a&&(c.embellished=a),c}makePunctuation(t){const e=this.createNode(t);return e.updateContent((0,i.invisibleComma)()),e.role="dummy",e}makePunctuated(t,e,r){const n=this.createNode(e[0]);n.type="punctuated",n.embellished=t.embellished,n.fencePointer=t.fencePointer,n.role=r;const o=e.splice(1,1)[0].slice(1);n.contentNodes=o.map(this.makePunctuation.bind(this)),this.collapsedChildren_(e)}makeEmpty(t,e,r){const n=this.createNode(e);n.type="empty",n.embellished=t.embellished,n.fencePointer=t.fencePointer,n.role=r}makeIndex(t,e,r){if(h.isPunctuated(e))return this.makePunctuated(t,e,r),void(e=e[0]);l.SemanticSkeleton.simpleCollapseStructure(e)&&!this.nodeDict[e.toString()]&&this.makeEmpty(t,e,r)}postProcess(t,e){const r=l.SemanticSkeleton.fromString(e).array;if("subsup"===t.type){const e=this.createNode(r[1][0]);return e.type="subscript",e.role="subsup",t.type="superscript",e.embellished=t.embellished,e.fencePointer=t.fencePointer,this.makeIndex(t,r[1][2],"rightsub"),this.makeIndex(t,r[2],"rightsuper"),this.collapsedChildren_(r),t}if("subscript"===t.type)return this.makeIndex(t,r[2],"rightsub"),this.collapsedChildren_(r),t;if("superscript"===t.type)return this.makeIndex(t,r[2],"rightsuper"),this.collapsedChildren_(r),t;if("tensor"===t.type)return this.makeIndex(t,r[2],"leftsub"),this.makeIndex(t,r[3],"leftsuper"),this.makeIndex(t,r[4],"rightsub"),this.makeIndex(t,r[5],"rightsuper"),this.collapsedChildren_(r),t;if("punctuated"===t.type){if(h.isPunctuated(r)){const e=r.splice(1,1)[0].slice(1);t.contentNodes=e.map(this.makePunctuation.bind(this))}return t}if("underover"===t.type){const e=this.createNode(r[1][0]);return"overaccent"===t.childNodes[1].role?(e.type="overscore",t.type="underscore"):(e.type="underscore",t.type="overscore"),e.role="underover",e.embellished=t.embellished,e.fencePointer=t.fencePointer,this.collapsedChildren_(r),t}return t}createNode(t){const e=this.factory.makeNode(t);return this.nodeDict[t.toString()]=e,e}collapsedChildren_(t){const e=t=>{const r=this.nodeDict[t[0]];r.childNodes=[];for(let n=1,o=t.length;ne.getSemanticPrimary().id===t))}}e.SemanticWalker=i},9806:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.SyntaxWalker=void 0;const n=r(707),o=r(3284),i=r(9797);class s extends o.AbstractWalker{constructor(t,e,r,n){super(t,e,r,n),this.node=t,this.generator=e,this.highlighter=r,this.levels=null,this.restoreState()}initLevels(){const t=new i.Levels;return t.push([this.primaryId()]),t}up(){super.up();const t=this.previousLevel();return t?(this.levels.pop(),this.singletonFocus(t)):null}down(){super.down();const t=this.nextLevel();if(0===t.length)return null;const e=this.singletonFocus(t[0]);return e&&this.levels.push(t),e}combineContentChildren(t,e,r,o){switch(t){case"relseq":case"infixop":case"multirel":return(0,n.interleaveLists)(o,r);case"prefixop":return r.concat(o);case"postfixop":return o.concat(r);case"matrix":case"vector":case"fenced":return o.unshift(r[0]),o.push(r[1]),o;case"cases":return o.unshift(r[0]),o;case"punctuated":return"text"===e?(0,n.interleaveLists)(o,r):o;case"appl":return[o[0],r[0],o[1]];case"root":return[o[1],o[0]];default:return o}}left(){super.left();const t=this.levels.indexOf(this.primaryId());if(null===t)return null;const e=this.levels.get(t-1);return e?this.singletonFocus(e):null}right(){super.right();const t=this.levels.indexOf(this.primaryId());if(null===t)return null;const e=this.levels.get(t+1);return e?this.singletonFocus(e):null}findFocusOnLevel(t){return this.singletonFocus(t.toString())}focusDomNodes(){return[this.getFocus().getDomPrimary()]}focusSemanticNodes(){return[this.getFocus().getSemanticPrimary()]}}e.SyntaxWalker=s},1799:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.TableWalker=void 0;const n=r(5740),o=r(8496),i=r(9806),s=r(179);class a extends i.SyntaxWalker{constructor(t,e,r,n){super(t,e,r,n),this.node=t,this.generator=e,this.highlighter=r,this.firstJump=null,this.key_=null,this.row_=0,this.currentTable_=null,this.keyMapping.set(o.KeyCode.ZERO,this.jumpCell.bind(this)),this.keyMapping.set(o.KeyCode.ONE,this.jumpCell.bind(this)),this.keyMapping.set(o.KeyCode.TWO,this.jumpCell.bind(this)),this.keyMapping.set(o.KeyCode.THREE,this.jumpCell.bind(this)),this.keyMapping.set(o.KeyCode.FOUR,this.jumpCell.bind(this)),this.keyMapping.set(o.KeyCode.FIVE,this.jumpCell.bind(this)),this.keyMapping.set(o.KeyCode.SIX,this.jumpCell.bind(this)),this.keyMapping.set(o.KeyCode.SEVEN,this.jumpCell.bind(this)),this.keyMapping.set(o.KeyCode.EIGHT,this.jumpCell.bind(this)),this.keyMapping.set(o.KeyCode.NINE,this.jumpCell.bind(this))}move(t){this.key_=t;const e=super.move(t);return this.modifier=!1,e}up(){return this.moved=s.WalkerMoves.UP,this.eligibleCell_()?this.verticalMove_(!1):super.up()}down(){return this.moved=s.WalkerMoves.DOWN,this.eligibleCell_()?this.verticalMove_(!0):super.down()}jumpCell(){if(!this.isInTable_()||null===this.key_)return this.getFocus();if(this.moved===s.WalkerMoves.ROW){this.moved=s.WalkerMoves.CELL;const t=this.key_-o.KeyCode.ZERO;return this.isLegalJump_(this.row_,t)?this.jumpCell_(this.row_,t):this.getFocus()}const t=this.key_-o.KeyCode.ZERO;return t>this.currentTable_.childNodes.length?this.getFocus():(this.row_=t,this.moved=s.WalkerMoves.ROW,this.getFocus().clone())}undo(){const t=super.undo();return t===this.firstJump&&(this.firstJump=null),t}eligibleCell_(){const t=this.getFocus().getSemanticPrimary();return this.modifier&&"cell"===t.type&&-1!==a.ELIGIBLE_CELL_ROLES.indexOf(t.role)}verticalMove_(t){const e=this.previousLevel();if(!e)return null;const r=this.getFocus(),n=this.levels.indexOf(this.primaryId()),o=this.levels.pop(),i=this.levels.indexOf(e),s=this.levels.get(t?i+1:i-1);if(!s)return this.levels.push(o),null;this.setFocus(this.singletonFocus(s));const a=this.nextLevel();return a[n]?(this.levels.push(a),this.singletonFocus(a[n])):(this.setFocus(r),this.levels.push(o),null)}jumpCell_(t,e){this.firstJump?this.virtualize(!1):(this.firstJump=this.getFocus(),this.virtualize(!0));const r=this.currentTable_.id.toString();let n;do{n=this.levels.pop()}while(-1===n.indexOf(r));this.levels.push(n),this.setFocus(this.singletonFocus(r)),this.levels.push(this.nextLevel());const o=this.currentTable_.childNodes[t-1];return this.setFocus(this.singletonFocus(o.id.toString())),this.levels.push(this.nextLevel()),this.singletonFocus(o.childNodes[e-1].id.toString())}isLegalJump_(t,e){const r=n.querySelectorAllByAttrValue(this.getRebuilt().xml,"id",this.currentTable_.id.toString())[0];if(!r||r.hasAttribute("alternative"))return!1;const o=this.currentTable_.childNodes[t-1];if(!o)return!1;const i=n.querySelectorAllByAttrValue(r,"id",o.id.toString())[0];return!(!i||i.hasAttribute("alternative"))&&!(!o||!o.childNodes[e-1])}isInTable_(){let t=this.getFocus().getSemanticPrimary();for(;t;){if(-1!==a.ELIGIBLE_TABLE_TYPES.indexOf(t.type))return this.currentTable_=t,!0;t=t.parent}return!1}}e.TableWalker=a,a.ELIGIBLE_CELL_ROLES=["determinant","rowvector","binomial","squarematrix","multiline","matrix","vector","cases","table"],a.ELIGIBLE_TABLE_TYPES=["multiline","matrix","vector","cases","table"]},179:function(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.WalkerState=e.WalkerMoves=void 0,function(t){t.UP="up",t.DOWN="down",t.LEFT="left",t.RIGHT="right",t.REPEAT="repeat",t.DEPTH="depth",t.ENTER="enter",t.EXPAND="expand",t.HOME="home",t.SUMMARY="summary",t.DETAIL="detail",t.ROW="row",t.CELL="cell"}(e.WalkerMoves||(e.WalkerMoves={}));class r{static resetState(t){delete r.STATE[t]}static setState(t,e){r.STATE[t]=e}static getState(t){return r.STATE[t]}}e.WalkerState=r,r.STATE={}},3362:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.walkerMapping_=e.walker=void 0;const n=r(162),o=r(6295),i=r(9806),s=r(1799);e.walker=function(t,r,n,o,i){return(e.walkerMapping_[t.toLowerCase()]||e.walkerMapping_.dummy)(r,n,o,i)},e.walkerMapping_={dummy:(t,e,r,o)=>new n.DummyWalker(t,e,r,o),semantic:(t,e,r,n)=>new o.SemanticWalker(t,e,r,n),syntax:(t,e,r,n)=>new i.SyntaxWalker(t,e,r,n),table:(t,e,r,n)=>new s.TableWalker(t,e,r,n)}},1204:function(t,e,r){Object.defineProperty(e,"__esModule",{value:!0}),e.getBySemanticId=e.getSemanticRoot=e.getAttribute=e.splitAttribute=void 0;const n=r(5740),o=r(2298);e.splitAttribute=function(t){return t?t.split(/,/):[]},e.getAttribute=function(t,e){return t.getAttribute(e)},e.getSemanticRoot=function(t){if(t.hasAttribute(o.Attribute.TYPE)&&!t.hasAttribute(o.Attribute.PARENT))return t;const e=n.querySelectorAllByAttr(t,o.Attribute.TYPE);for(let t,r=0;t=e[r];r++)if(!t.hasAttribute(o.Attribute.PARENT))return t;return t},e.getBySemanticId=function(t,e){return t.getAttribute(o.Attribute.ID)===e?t:n.querySelectorAllByAttrValue(t,o.Attribute.ID,e)[0]}}},__webpack_module_cache__={};function __webpack_require__(t){var e=__webpack_module_cache__[t];if(void 0!==e)return e.exports;var r=__webpack_module_cache__[t]={exports:{}};return __webpack_modules__[t].call(r.exports,r,r.exports,__webpack_require__),r.exports}__webpack_require__.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}();var __webpack_exports__={};!function(){var t=__webpack_require__(9515),e=__webpack_require__(3282),r=__webpack_require__(235),n=__webpack_require__(265),o=__webpack_require__(2388);function i(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=new Array(e);r0&&void 0!==arguments[0]?arguments[0]:[],e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];if(MathJax.startup){e&&(MathJax.startup.registerConstructor("tex",MathJax._.input.tex_ts.TeX),MathJax.startup.useInput("tex")),MathJax.config.tex||(MathJax.config.tex={});var r=MathJax.config.tex.packages;MathJax.config.tex.packages=t,r&&(0,xt.insert)(MathJax.config.tex,{packages:r})}}(["base","ams","newcommand","noundefined","require","autoload","configmacros"]);var pe=__webpack_require__(2892),he=__webpack_require__(625),fe=__webpack_require__(2769);MathJax.loader&&MathJax.loader.checkVersion("input/mml",e.VERSION,"input"),(0,t.combineWithMathJax)({_:{input:{mathml_ts:pe,mathml:{FindMathML:he,MathMLCompile:fe}}}}),MathJax.startup&&(MathJax.startup.registerConstructor("mml",pe.MathML),MathJax.startup.useInput("mml")),MathJax.loader&&MathJax.loader.pathFilters.add((function(t){return t.name=t.name.replace(/\/util\/entities\/.*?\.js/,"/input/mml/entities.js"),!0}));var de=__webpack_require__(50),me=__webpack_require__(8042),ye=__webpack_require__(8270),ge=__webpack_require__(6797),be=__webpack_require__(5355),ve=__webpack_require__(9261),_e=__webpack_require__(9086),Se=__webpack_require__(95),Me=__webpack_require__(1148),Oe=__webpack_require__(8102),xe=__webpack_require__(804),Ee=__webpack_require__(8147),Ae=__webpack_require__(2275),Ce=__webpack_require__(9063),Te=__webpack_require__(6911),Ne=__webpack_require__(1653),we=__webpack_require__(6781),Le=__webpack_require__(6460),Ie=__webpack_require__(6287),Pe=__webpack_require__(5964),Re=__webpack_require__(8776),ke=__webpack_require__(4798),je=__webpack_require__(4597),Be=__webpack_require__(2970),De=__webpack_require__(5610),Fe=__webpack_require__(4300),He=__webpack_require__(8002),Ue=__webpack_require__(7056),Xe=__webpack_require__(1259),Ve=__webpack_require__(3571),qe=__webpack_require__(6590),We=__webpack_require__(8650),Ge=__webpack_require__(421),ze=__webpack_require__(5884),Je=__webpack_require__(5552),Ke=__webpack_require__(3055),$e=__webpack_require__(7519),Ye=__webpack_require__(4420),Ze=__webpack_require__(9800),Qe=__webpack_require__(1160),tr=__webpack_require__(1956),er=__webpack_require__(7490),rr=__webpack_require__(7313),nr=__webpack_require__(7555),or=__webpack_require__(2688),ir=__webpack_require__(5636),sr=__webpack_require__(5723),ar=__webpack_require__(8009),lr=__webpack_require__(5023),cr=__webpack_require__(7096),ur=__webpack_require__(6898),pr=__webpack_require__(6991),hr=__webpack_require__(8411),fr=__webpack_require__(4126),dr=__webpack_require__(258),mr=__webpack_require__(4093),yr=__webpack_require__(905),gr=__webpack_require__(6237),br=__webpack_require__(5164),vr=__webpack_require__(6319),_r=__webpack_require__(5766),Sr=__webpack_require__(1971),Mr=__webpack_require__(167),Or=__webpack_require__(5806);MathJax.loader&&MathJax.loader.checkVersion("output/chtml",e.VERSION,"output"),(0,t.combineWithMathJax)({_:{output:{chtml_ts:de,chtml:{FontData:me,Notation:ye,Usage:ge,Wrapper:be,WrapperFactory:ve,Wrappers_ts:_e,Wrappers:{TeXAtom:Se,TextNode:Me,maction:Oe,math:xe,menclose:Ee,mfenced:Ae,mfrac:Ce,mglyph:Te,mi:Ne,mmultiscripts:we,mn:Le,mo:Ie,mpadded:Pe,mroot:Re,mrow:ke,ms:je,mspace:Be,msqrt:De,msubsup:Fe,mtable:He,mtd:Ue,mtext:Xe,mtr:Ve,munderover:qe,scriptbase:We,semantics:Ge}},common:{FontData:ze,Notation:Je,OutputJax:Ke,Wrapper:$e,WrapperFactory:Ye,Wrappers:{TeXAtom:Ze,TextNode:Qe,maction:tr,math:er,menclose:rr,mfenced:nr,mfrac:or,mglyph:ir,mi:sr,mmultiscripts:ar,mn:lr,mo:cr,mpadded:ur,mroot:pr,mrow:hr,ms:fr,mspace:dr,msqrt:mr,msubsup:yr,mtable:gr,mtd:br,mtext:vr,mtr:_r,munderover:Sr,scriptbase:Mr,semantics:Or}}}}}),MathJax.loader&&(0,t.combineDefaults)(MathJax.config.loader,"output/chtml",{checkReady:function(){return MathJax.loader.load("output/chtml/fonts/tex")}}),MathJax.startup&&(MathJax.startup.registerConstructor("chtml",de.CHTML),MathJax.startup.useOutput("chtml"));var xr=__webpack_require__(2760),Er=__webpack_require__(4005),Ar=__webpack_require__(1015),Cr=__webpack_require__(6555),Tr=__webpack_require__(2183),Nr=__webpack_require__(3490),wr=__webpack_require__(9056),Lr=__webpack_require__(3019),Ir=__webpack_require__(2713),Pr=__webpack_require__(7517),Rr=__webpack_require__(4182),kr=__webpack_require__(2679),jr=__webpack_require__(5469),Br=__webpack_require__(775),Dr=__webpack_require__(9551),Fr=__webpack_require__(6530),Hr=__webpack_require__(4409),Ur=__webpack_require__(5292),Xr=__webpack_require__(3980),Vr=__webpack_require__(1103),qr=__webpack_require__(9124),Wr=__webpack_require__(6001),Gr=__webpack_require__(3696),zr=__webpack_require__(9587),Jr=__webpack_require__(8348),Kr=__webpack_require__(1376),$r=__webpack_require__(1439),Yr=__webpack_require__(331),Zr=__webpack_require__(4886),Qr=__webpack_require__(4471),tn=__webpack_require__(5181),en=__webpack_require__(3526),rn=__webpack_require__(5649),nn=__webpack_require__(7153),on=__webpack_require__(5745),sn=__webpack_require__(1411),an=__webpack_require__(6384),ln=__webpack_require__(6041),cn=__webpack_require__(8199),un=__webpack_require__(9848),pn=__webpack_require__(7906),hn=__webpack_require__(2644),fn=__webpack_require__(4926);if(MathJax.loader&&MathJax.loader.checkVersion("output/chtml/fonts/tex",e.VERSION,"chtml-font"),(0,t.combineWithMathJax)({_:{output:{chtml:{fonts:{tex_ts:xr,tex:{"bold-italic":Er,bold:Ar,"fraktur-bold":Cr,fraktur:Tr,italic:Nr,largeop:wr,monospace:Lr,normal:Ir,"sans-serif-bold-italic":Pr,"sans-serif-bold":Rr,"sans-serif-italic":kr,"sans-serif":jr,smallop:Br,"tex-calligraphic-bold":Dr,"tex-size3":Fr,"tex-size4":Hr,"tex-variant":Ur}}},common:{fonts:{tex:{"bold-italic":Xr,bold:Vr,delimiters:qr,"double-struck":Wr,"fraktur-bold":Gr,fraktur:zr,italic:Jr,largeop:Kr,monospace:$r,normal:Yr,"sans-serif-bold-italic":Zr,"sans-serif-bold":Qr,"sans-serif-italic":tn,"sans-serif":en,"script-bold":rn,script:nn,smallop:on,"tex-calligraphic-bold":sn,"tex-calligraphic":an,"tex-mathit":ln,"tex-oldstyle-bold":cn,"tex-oldstyle":un,"tex-size3":pn,"tex-size4":hn,"tex-variant":fn}}}}}}),MathJax.startup){(0,t.combineDefaults)(MathJax.config,"chtml",{fontURL:n.Package.resolvePath("output/chtml/fonts/woff-v2",!1)});var dn=(0,xt.selectOptionsFromKeys)(MathJax.config.chtml||{},xr.TeXFont.OPTIONS);(0,t.combineDefaults)(MathJax.config,"chtml",{font:new xr.TeXFont(dn)})}var mn=__webpack_require__(5865),yn=__webpack_require__(8310),gn=__webpack_require__(4001),bn=__webpack_require__(473),vn=__webpack_require__(4414);MathJax.loader&&MathJax.loader.checkVersion("ui/menu",e.VERSION,"ui"),(0,t.combineWithMathJax)({_:{ui:{menu:{MJContextMenu:mn,Menu:yn,MenuHandler:gn,MmlVisitor:bn,SelectableInfo:vn}}}}),MathJax.startup&&"undefined"!=typeof window&&MathJax.startup.extendHandler((function(t){return(0,gn.MenuHandler)(t)}),20);var _n=__webpack_require__(351);function Sn(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=new Array(e);r Date: Thu, 28 May 2026 06:26:58 +0200 Subject: [PATCH 045/129] Inline MathJax for offline HTML reports --- docs/dev/plans/project-summary-rendering.md | 2 +- .../project/categories/report/default.py | 9 +-- src/easydiffraction/report/html_renderer.py | 55 +++++++++++++++++++ .../report/templates/html/report.html.j2 | 5 ++ 4 files changed, 64 insertions(+), 7 deletions(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index b4fe606fb..bea70bc43 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -586,7 +586,7 @@ exceptions. below) per AGENTS.md §Workflow's two-phase split. - Commit: `Vendor MathJax tex-mml-chtml bundle`. -- [ ] **P1.13 — Wire MathJax loader through `html_offline`** +- [x] **P1.13 — Wire MathJax loader through `html_offline`** - Files: existing `src/easydiffraction/report/templates/html/report.html.j2`; existing diff --git a/src/easydiffraction/project/categories/report/default.py b/src/easydiffraction/project/categories/report/default.py index 235f93d19..4eaaab592 100644 --- a/src/easydiffraction/project/categories/report/default.py +++ b/src/easydiffraction/project/categories/report/default.py @@ -408,12 +408,9 @@ def save_html(self, offline: bool = False) -> pathlib.Path: Path of the written HTML report. """ - from easydiffraction.report.html_renderer import html_report_path # noqa: PLC0415 + from easydiffraction.report.html_renderer import save_html_report # noqa: PLC0415 - output_path = html_report_path(self.project) - output_path.parent.mkdir(parents=True, exist_ok=True) - output_path.write_text(self.as_html(offline=offline), encoding='utf-8') - return output_path + return save_html_report(self.project, self.data_context(), offline=offline) def as_html(self, offline: bool = False) -> str: """ @@ -422,7 +419,7 @@ def as_html(self, offline: bool = False) -> str: Parameters ---------- offline : bool, default=False - Whether to embed Plotly JavaScript assets. + Whether to embed HTML JavaScript assets. Returns ------- diff --git a/src/easydiffraction/report/html_renderer.py b/src/easydiffraction/report/html_renderer.py index 7e40fd81f..9fe5c8bfd 100644 --- a/src/easydiffraction/report/html_renderer.py +++ b/src/easydiffraction/report/html_renderer.py @@ -5,12 +5,15 @@ from __future__ import annotations import pathlib +import shutil +from importlib.resources import as_file from importlib.resources import files from jinja2 import Environment from jinja2 import PackageLoader _TEMPLATE_NAME = 'html/report.html.j2' +_MATHJAX_FILENAME = 'mathjax-tex-mml-chtml.js' def html_report_path( @@ -70,6 +73,7 @@ def render_html_report( Complete HTML document. """ template_context = dict(context) + template_context['html_offline'] = offline template_context['stylesheet'] = _stylesheet_text() template_context['figures'] = _figure_html_context( context.get('figures'), @@ -78,6 +82,43 @@ def render_html_report( return _environment().get_template(_TEMPLATE_NAME).render(**template_context) +def save_html_report( + project: object, + context: dict[str, object], + *, + offline: bool = False, + path: str | pathlib.Path | None = None, +) -> pathlib.Path: + """ + Write an HTML report and any required local assets. + + Parameters + ---------- + project : object + Project instance. + context : dict[str, object] + Data returned by ``Report.data_context()``. + offline : bool, default=False + Whether local HTML assets should be copied next to the report. + path : str | pathlib.Path | None, default=None + Explicit report path. + + Returns + ------- + pathlib.Path + Path of the written HTML report. + """ + output_path = html_report_path(project, path) + output_path.parent.mkdir(parents=True, exist_ok=True) + output_path.write_text( + render_html_report(context, offline=offline), + encoding='utf-8', + ) + if offline: + _copy_mathjax(output_path.parent) + return output_path + + def _environment() -> Environment: """Return the Jinja environment for report templates.""" return Environment( @@ -98,6 +139,20 @@ def _stylesheet_text() -> str: return stylesheet.read_text(encoding='utf-8') +def _copy_mathjax(report_dir: pathlib.Path) -> None: + """Copy the vendored MathJax bundle next to an HTML report.""" + vendor_dir = report_dir / 'vendor' + vendor_dir.mkdir(parents=True, exist_ok=True) + resource = files('easydiffraction.report').joinpath( + 'templates', + 'html', + 'vendor', + _MATHJAX_FILENAME, + ) + with as_file(resource) as source_path: + shutil.copy2(source_path, vendor_dir / _MATHJAX_FILENAME) + + def _figure_html_context( figures: object, *, diff --git a/src/easydiffraction/report/templates/html/report.html.j2 b/src/easydiffraction/report/templates/html/report.html.j2 index 6dbb44396..59853b39d 100644 --- a/src/easydiffraction/report/templates/html/report.html.j2 +++ b/src/easydiffraction/report/templates/html/report.html.j2 @@ -30,6 +30,11 @@ {{ project.title or project.name or "EasyDiffraction report" }} + {% if html_offline %} + + {% else %} + + {% endif %}
From 4c70fff7facdafc24eaae11e1edad5781ea3c013 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 28 May 2026 06:30:11 +0200 Subject: [PATCH 046/129] Add x_descriptor and fit_data_arrays on ExperimentBase --- docs/dev/plans/project-summary-rendering.md | 2 +- .../experiment/categories/data/bragg_pd.py | 27 +++++++++++++++++++ .../experiment/categories/data/total_pd.py | 20 ++++++++++++++ .../datablocks/experiment/item/base.py | 19 +++++++++++++ 4 files changed, 67 insertions(+), 1 deletion(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index bea70bc43..5db3c5196 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -608,7 +608,7 @@ exceptions. - Commit: `Inline MathJax when html_offline=True`. -- [ ] **P1.14 — Add `x_descriptor` + `fit_data_arrays()` on `ExperimentBase` and data categories** +- [x] **P1.14 — Add `x_descriptor` + `fit_data_arrays()` on `ExperimentBase` and data categories** - Files: existing `src/easydiffraction/datablocks/experiment/item/base.py` (the experiment base — see diff --git a/src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py b/src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py index 9721c38bd..6fa8e1c70 100644 --- a/src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py +++ b/src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py @@ -553,6 +553,19 @@ def intensity_bkg(self) -> np.ndarray: dtype=float, # TODO: needed? DataTypes.NUMERIC? ) + def fit_data_arrays(self) -> dict[str, np.ndarray | None]: + """Return arrays needed to draw the fit-data chart.""" + meas = self.intensity_meas + calc = self.intensity_calc + return { + 'x': self.x, + 'meas': meas, + 'meas_su': self.intensity_meas_su, + 'calc': calc, + 'diff': meas - calc, + 'bkg': self.intensity_bkg, + } + @DataFactory.register class PdCwlData(PdDataBase): @@ -622,6 +635,13 @@ def two_theta(self) -> np.ndarray: dtype=float, # TODO: needed? DataTypes.NUMERIC? ) + @property + def x_descriptor(self) -> NumericDescriptor: + """Descriptor that owns the 2θ x-axis metadata.""" + if self._items: + return self._items[0].two_theta + return self._item_type().two_theta + @property def x(self) -> np.ndarray: """Alias for two_theta.""" @@ -703,6 +723,13 @@ def time_of_flight(self) -> np.ndarray: dtype=float, # TODO: needed? DataTypes.NUMERIC? ) + @property + def x_descriptor(self) -> NumericDescriptor: + """Descriptor that owns the TOF x-axis metadata.""" + if self._items: + return self._items[0].time_of_flight + return self._item_type().time_of_flight + @property def x(self) -> np.ndarray: """Alias for time_of_flight.""" diff --git a/src/easydiffraction/datablocks/experiment/categories/data/total_pd.py b/src/easydiffraction/datablocks/experiment/categories/data/total_pd.py index 66f526cb3..b729009a4 100644 --- a/src/easydiffraction/datablocks/experiment/categories/data/total_pd.py +++ b/src/easydiffraction/datablocks/experiment/categories/data/total_pd.py @@ -317,6 +317,19 @@ def intensity_bkg(self) -> np.ndarray: """Background is always zero for PDF data.""" return np.zeros_like(self.intensity_calc) + def fit_data_arrays(self) -> dict[str, np.ndarray | None]: + """Return arrays needed to draw the fit-data chart.""" + meas = self.intensity_meas + calc = self.intensity_calc + return { + 'x': self.x, + 'meas': meas, + 'meas_su': self.intensity_meas_su, + 'calc': calc, + 'diff': meas - calc, + 'bkg': self.intensity_bkg, + } + @DataFactory.register class TotalData(TotalDataBase): @@ -367,6 +380,13 @@ def _create_items_set_xcoord_and_id(self, values: object) -> None: # Public properties # ------------------------------------------------------------------ + @property + def x_descriptor(self) -> NumericDescriptor: + """Descriptor that owns the r-space x-axis metadata.""" + if self._items: + return self._items[0].r + return self._item_type().r + @property def x(self) -> np.ndarray: """Get the r values for data points included in calculations.""" diff --git a/src/easydiffraction/datablocks/experiment/item/base.py b/src/easydiffraction/datablocks/experiment/item/base.py index d1f4b4184..1e8b49ac2 100644 --- a/src/easydiffraction/datablocks/experiment/item/base.py +++ b/src/easydiffraction/datablocks/experiment/item/base.py @@ -36,6 +36,7 @@ from easydiffraction.utils.utils import render_cif if TYPE_CHECKING: + from easydiffraction.core.variable import NumericDescriptor from easydiffraction.datablocks.experiment.categories.experiment_type import ExperimentType from easydiffraction.datablocks.structure.collection import Structures @@ -505,6 +506,15 @@ def refln(self) -> object: """Reflection collection for this experiment.""" return self._refln + @property + def x_descriptor(self) -> NumericDescriptor | None: + """Return None because single-crystal data has no 1-D x axis.""" + return None + + def fit_data_arrays(self) -> dict[str, np.ndarray | None]: + """Return no 1-D fit arrays for single-crystal experiments.""" + return {} + def _calculator_support_category(self) -> object | None: """ Return the reflection collection that constrains calculators. @@ -620,6 +630,15 @@ def data(self) -> object: """Data collection for this experiment.""" return self._data + @property + def x_descriptor(self) -> NumericDescriptor: + """Descriptor that owns the powder experiment's x-axis metadata.""" + return self.data.x_descriptor + + def fit_data_arrays(self) -> dict[str, np.ndarray | None]: + """Return arrays needed to draw the powder fit-data chart.""" + return self.data.fit_data_arrays() + def _calculator_support_category(self) -> object | None: """ Return the powder data collection that constrains calculators. From f1f59ebd0d64d69679939a827ade5ec8cf490a51 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 28 May 2026 06:32:54 +0200 Subject: [PATCH 047/129] Replace fit_data payload with descriptor-driven shape --- docs/dev/plans/project-summary-rendering.md | 2 +- src/easydiffraction/report/data_context.py | 231 ++++---------------- 2 files changed, 38 insertions(+), 195 deletions(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 5db3c5196..25144765d 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -702,7 +702,7 @@ exceptions. - Commit: `Add x_descriptor and fit_data_arrays on ExperimentBase`. -- [ ] **P1.15 — Replace `fit_data` shape in `data_context()`** +- [x] **P1.15 — Replace `fit_data` shape in `data_context()`** - Files: existing `src/easydiffraction/report/data_context.py`. - Replace the previous `figures.fit_per_experiment` diff --git a/src/easydiffraction/report/data_context.py b/src/easydiffraction/report/data_context.py index 356227bdf..8364a6d29 100644 --- a/src/easydiffraction/report/data_context.py +++ b/src/easydiffraction/report/data_context.py @@ -8,10 +8,7 @@ from datetime import UTC from datetime import datetime -import numpy as np - from easydiffraction.core.variable import Parameter -from easydiffraction.datablocks.experiment.item.base import intensity_category_for from easydiffraction.io.cif.serialize import format_param_value from easydiffraction.utils.utils import package_version @@ -152,9 +149,6 @@ def build(self) -> dict[str, object]: 'refinement': self._refinement_context(), 'software': self._software_context(), 'publication': self._publication_context(), - 'figures': { - 'fit_per_experiment': self._fit_figure_context(experiments), - }, 'metadata': { 'easydiffraction_version': package_version('easydiffraction'), 'generated_at': datetime.now(tz=UTC).isoformat(timespec='seconds'), @@ -289,6 +283,7 @@ def _experiment_context(self, experiment: object) -> dict[str, object]: context='latex', ), 'measured_range': _value(_safe_attr(experiment, 'measured_range')), + 'fit_data': _fit_data_context(experiment), } def _refinement_context(self) -> dict[str, object]: @@ -351,18 +346,6 @@ def _publication_context(self) -> dict[str, object]: ], } - def _fit_figure_context( - self, - experiments: list[object], - ) -> dict[str, object]: - """Return measured-vs-calculated figures by experiment id.""" - figures = {} - for experiment in experiments: - figure = _fit_figure(experiment) - if figure is not None: - figures[str(_safe_attr(experiment, 'name'))] = figure - return figures - def build_report_data_context(project: object) -> dict[str, object]: """ @@ -457,189 +440,49 @@ def _software_role_context(role: object) -> dict[str, object]: } -def _fit_figure(experiment: object) -> object | None: - """Return a measured-vs-calculated Plotly figure if data exist.""" - try: - pattern = intensity_category_for(experiment) - except AttributeError: +def _fit_data_context(experiment: object) -> dict[str, object] | None: + """Return descriptor-driven fit data for one experiment.""" + x_descriptor = _safe_attr(experiment, 'x_descriptor') + if x_descriptor is None: return None - y_meas = _numeric_array(_safe_attr(pattern, 'intensity_meas')) - y_calc = _numeric_array(_safe_attr(pattern, 'intensity_calc')) - if y_meas is None or y_calc is None: - return None - _validate_same_length(experiment, y_meas, y_calc) - - if _is_single_crystal(experiment): - return _single_crystal_fit_figure(experiment, pattern, y_meas, y_calc) - return _line_fit_figure(experiment, pattern, y_meas, y_calc) - - -def _single_crystal_fit_figure( - experiment: object, - pattern: object, - y_meas: np.ndarray, - y_calc: np.ndarray, -) -> object: - """Return a single-crystal measured-vs-calculated figure.""" - go = _plotly_go() - trace_kwargs = { - 'x': y_calc, - 'y': y_meas, - 'mode': 'markers', - 'name': 'Measured', + arrays = experiment.fit_data_arrays() + return { + 'x': { + 'values': arrays['x'], + 'name': x_descriptor.name, + 'units': x_descriptor.units, + 'display_name': x_descriptor.resolve_display_name('html'), + 'latex_name': x_descriptor.resolve_display_name('latex'), + 'display_units': x_descriptor.resolve_display_units('html'), + 'latex_units': x_descriptor.resolve_display_units('latex'), + }, + 'series': { + 'meas': { + 'values': arrays['meas'], + 'su': arrays['meas_su'], + 'label': 'Measured', + }, + 'calc': _series_context(arrays['calc'], 'Calculated'), + 'diff': _series_context(arrays['diff'], 'Difference'), + 'bkg': _optional_series_context(arrays['bkg'], 'Background'), + }, } - y_meas_su = _numeric_array(_safe_attr(pattern, 'intensity_meas_su')) - if y_meas_su is not None and y_meas_su.size == y_meas.size: - trace_kwargs['error_y'] = { - 'type': 'data', - 'array': y_meas_su, - 'visible': True, - } - fig = go.Figure() - fig.add_trace(go.Scatter(**trace_kwargs)) - _add_diagonal_trace(fig, y_meas, y_calc) - _configure_fit_figure( - fig, - experiment, - x_title='I²calc', - y_title='I²meas', - ) - return fig - - -def _line_fit_figure( - experiment: object, - pattern: object, - y_meas: np.ndarray, - y_calc: np.ndarray, -) -> object | None: - """Return a line measured-vs-calculated figure.""" - x_values, x_title = _line_fit_x_values(experiment, pattern) - if x_values is None: - return None - _validate_same_length(experiment, x_values, y_meas) - - go = _plotly_go() - fig = go.Figure() - fig.add_trace( - go.Scatter( - x=x_values, - y=y_meas, - mode='markers', - name='Measured', - ) - ) - fig.add_trace( - go.Scatter( - x=x_values, - y=y_calc, - mode='lines', - name='Calculated', - ) - ) - y_bkg = _numeric_array(_safe_attr(pattern, 'intensity_bkg')) - if y_bkg is not None and y_bkg.size == x_values.size: - fig.add_trace( - go.Scatter( - x=x_values, - y=y_bkg, - mode='lines', - name='Background', - ) - ) - _configure_fit_figure(fig, experiment, x_title=x_title, y_title='Intensity') - return fig - - -def _line_fit_x_values( - experiment: object, - pattern: object, -) -> tuple[np.ndarray | None, str]: - """Return line-plot x values and axis title.""" - beam_mode = str(_attr_value(_safe_attr(experiment, 'type'), 'beam_mode')).lower() - if beam_mode == 'time-of-flight': - return _numeric_array(_safe_attr(pattern, 'time_of_flight')), 'Time of flight' - return _numeric_array(_safe_attr(pattern, 'two_theta')), '2θ' - - -def _add_diagonal_trace( - fig: object, - y_meas: np.ndarray, - y_calc: np.ndarray, -) -> None: - """Add the y=x reference line to a scatter figure.""" - go = _plotly_go() - lower = float(min(np.min(y_meas), np.min(y_calc))) - upper = float(max(np.max(y_meas), np.max(y_calc))) - fig.add_trace( - go.Scatter( - x=[lower, upper], - y=[lower, upper], - mode='lines', - name='I²meas = I²calc', - ) - ) - - -def _configure_fit_figure( - fig: object, - experiment: object, - *, - x_title: str, - y_title: str, -) -> None: - """Apply shared report-figure layout.""" - experiment_id = _safe_attr(experiment, 'name') or 'experiment' - fig.update_layout( - template='plotly_white', - title=f"Measured vs calculated: {experiment_id}", - xaxis_title=x_title, - yaxis_title=y_title, - height=440, - margin={'l': 64, 'r': 24, 't': 64, 'b': 56}, - legend={'orientation': 'h', 'yanchor': 'bottom', 'y': 1.02}, - ) - - -def _validate_same_length( - experiment: object, - left: np.ndarray, - right: np.ndarray, -) -> None: - """Raise if report figure arrays have inconsistent lengths.""" - if left.size == right.size: - return - experiment_id = _safe_attr(experiment, 'name') or type(experiment).__name__ - msg = ( - f"Cannot build report figure for experiment '{experiment_id}': " - f'intensity arrays have lengths {left.size} and {right.size}.' - ) - raise ValueError(msg) - - -def _is_single_crystal(experiment: object) -> bool: - """Return whether the experiment is single-crystal data.""" - expt_type = _safe_attr(experiment, 'type') - return str(_attr_value(expt_type, 'sample_form')).lower() == 'single crystal' - - -def _numeric_array(value: object) -> np.ndarray | None: - """Return a numeric one-dimensional array, if present.""" - if value is None: - return None - array = np.asarray(_value(value), dtype=float) - if array.ndim != 1 or array.size == 0: - return None - return array +def _series_context(values: object, label: str) -> dict[str, object]: + """Return one required fit-data series.""" + return {'values': values, 'label': label} -def _plotly_go() -> object: - """Return Plotly graph objects for report figures.""" - import plotly.graph_objects as go # noqa: PLC0415 - return go +def _optional_series_context( + values: object, + label: str, +) -> dict[str, object] | None: + """Return one optional fit-data series.""" + if values is None: + return None + return _series_context(values, label) def _collection_values(collection: object) -> Iterable[object]: From b6e1bd582e8272a0f5d1f60b7c404416e328e361 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 28 May 2026 06:37:51 +0200 Subject: [PATCH 048/129] Emit pgfplots CSV and replace includegraphics with pgfplots blocks --- docs/dev/plans/project-summary-rendering.md | 2 +- .../report/templates/tex/report.tex.j2 | 26 +++- src/easydiffraction/report/tex_renderer.py | 116 +++++++++++++++++- 3 files changed, 138 insertions(+), 6 deletions(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 25144765d..b3289b581 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -755,7 +755,7 @@ exceptions. - Commit: `Replace fit_data payload with descriptor-driven shape`. -- [ ] **P1.16 — pgfplots CSV emitter + TeX template** +- [x] **P1.16 — pgfplots CSV emitter + TeX template** - Files: existing `src/easydiffraction/report/tex_renderer.py`; renamed diff --git a/src/easydiffraction/report/templates/tex/report.tex.j2 b/src/easydiffraction/report/templates/tex/report.tex.j2 index 223569531..f24c83c47 100644 --- a/src/easydiffraction/report/templates/tex/report.tex.j2 +++ b/src/easydiffraction/report/templates/tex/report.tex.j2 @@ -1,5 +1,6 @@ \documentclass{styles/iucrjournals} -{% raw %}\graphicspath{{figures/}}{% endraw %} +\usepackage{pgfplots} +\pgfplotsset{compat=1.18} {% macro tex_field_header(field, fallback) -%} {{ field.label or fallback }}{% if field.units %} ({{ field.units }}){% endif %} {%- endmacro %} @@ -101,10 +102,29 @@ Calculator & {{ experiment.calculator.type | tex }} \\ {{ tex_field_header(experiment.diffrn_latex.ambient_pressure, "Pressure") }} & {{ experiment.diffrn.ambient_pressure | tex_number }} \\ \end{tabular} -{% if tex.fit_figure_paths[experiment.id] %} +{% if experiment.fit_data and tex.fit_csv_paths[experiment.id] %} \begin{figure}[H] \centering -\includegraphics[width=\linewidth]{ {{- tex.fit_figure_paths[experiment.id] -}} } +\begin{tikzpicture} +\begin{axis}[ +width=\linewidth, +height=0.48\linewidth, +xlabel={{{ experiment.fit_data.x.latex_name }}{% if experiment.fit_data.x.latex_units %} ({{ experiment.fit_data.x.latex_units }}){% endif %}}, +ylabel={Intensity}, +legend pos=north east, +] +\addplot+[only marks, mark size=0.5pt] table[x=x, y=meas, col sep=comma] { {{- tex.fit_csv_paths[experiment.id] -}} }; +\addlegendentry{ {{- experiment.fit_data.series.meas.label | tex -}} } +\addplot+[no markers] table[x=x, y=calc, col sep=comma] { {{- tex.fit_csv_paths[experiment.id] -}} }; +\addlegendentry{ {{- experiment.fit_data.series.calc.label | tex -}} } +\addplot+[no markers, dashed] table[x=x, y=diff, col sep=comma] { {{- tex.fit_csv_paths[experiment.id] -}} }; +\addlegendentry{ {{- experiment.fit_data.series.diff.label | tex -}} } +{% if experiment.fit_data.series.bkg %} +\addplot+[no markers, dotted] table[x=x, y=bkg, col sep=comma] { {{- tex.fit_csv_paths[experiment.id] -}} }; +\addlegendentry{ {{- experiment.fit_data.series.bkg.label | tex -}} } +{% endif %} +\end{axis} +\end{tikzpicture} \caption{Observed and calculated fit for {{ experiment.id | tex }}.} \end{figure} {% endif %} diff --git a/src/easydiffraction/report/tex_renderer.py b/src/easydiffraction/report/tex_renderer.py index ace883f73..b734393fb 100644 --- a/src/easydiffraction/report/tex_renderer.py +++ b/src/easydiffraction/report/tex_renderer.py @@ -4,6 +4,7 @@ from __future__ import annotations +import csv import pathlib from importlib.resources import files @@ -74,7 +75,7 @@ def render_tex_report(context: dict[str, object]) -> str: Complete LaTeX document. """ template_context = dict(context) - template_context['tex'] = {'fit_figure_paths': {}} + template_context['tex'] = {'fit_csv_paths': _fit_csv_paths(context)} return _environment().get_template(_TEMPLATE_NAME).render(**template_context) @@ -109,7 +110,7 @@ def save_tex_report( styles_dir.mkdir(parents=True, exist_ok=True) template_context = dict(context) - template_context['tex'] = {'fit_figure_paths': {}} + template_context['tex'] = {'fit_csv_paths': _write_fit_csvs(context, tex_dir)} output_path.write_text( _render_prepared_context(template_context), encoding='utf-8', @@ -136,6 +137,117 @@ def _environment() -> Environment: return environment +def _write_fit_csvs( + context: dict[str, object], + out_dir: pathlib.Path, +) -> dict[str, str]: + """Write fit-data CSV files and return paths for TeX.""" + paths = {} + for experiment in _experiment_contexts(context): + fit_data = experiment.get('fit_data') + if fit_data is None: + continue + experiment_id = str(experiment.get('id') or 'experiment') + csv_path = _write_fit_csv(experiment_id, fit_data, out_dir) + paths[experiment_id] = f'data/{csv_path.name}' + return paths + + +def _write_fit_csv( + expt_id: str, + fit_data: dict[str, object], + out_dir: pathlib.Path, +) -> pathlib.Path: + """Write one fit-data CSV file under ``out_dir / 'data'``.""" + data_dir = out_dir / 'data' + data_dir.mkdir(parents=True, exist_ok=True) + csv_path = data_dir / _fit_csv_filename(expt_id) + columns = _fit_csv_columns(fit_data) + _validate_fit_csv_columns(expt_id, columns) + with csv_path.open('w', newline='', encoding='utf-8') as handle: + writer = csv.writer(handle) + writer.writerow([name for name, _values in columns]) + writer.writerows(zip(*(values for _name, values in columns), strict=True)) + return csv_path + + +def _fit_csv_paths(context: dict[str, object]) -> dict[str, str]: + """Return expected fit-data CSV paths for TeX rendering.""" + paths = {} + for experiment in _experiment_contexts(context): + if experiment.get('fit_data') is None: + continue + experiment_id = str(experiment.get('id') or 'experiment') + paths[experiment_id] = f'data/{_fit_csv_filename(experiment_id)}' + return paths + + +def _experiment_contexts(context: dict[str, object]) -> list[dict[str, object]]: + """Return experiment contexts from a report context.""" + experiments = context.get('experiments') + if not isinstance(experiments, list): + return [] + return [ + experiment + for experiment in experiments + if isinstance(experiment, dict) + ] + + +def _fit_csv_filename(expt_id: str) -> str: + """Return a filesystem-safe fit-data CSV filename.""" + safe_id = ''.join( + char if char.isascii() and (char.isalnum() or char in {'-', '_'}) else '_' + for char in expt_id + ).strip('_') + if not safe_id: + safe_id = 'experiment' + return f'{safe_id}.csv' + + +def _fit_csv_columns(fit_data: dict[str, object]) -> list[tuple[str, list[object]]]: + """Return ordered CSV columns for one fit-data payload.""" + x_data = fit_data['x'] + series = fit_data['series'] + meas = series['meas'] + calc = series['calc'] + diff = series['diff'] + bkg = series['bkg'] + + columns = [ + ('x', list(x_data['values'])), + ('meas', list(meas['values'])), + ] + if meas['su'] is not None: + columns.append(('meas_su', list(meas['su']))) + columns.extend( + [ + ('calc', list(calc['values'])), + ('diff', list(diff['values'])), + ] + ) + if bkg is not None: + columns.append(('bkg', list(bkg['values']))) + return columns + + +def _validate_fit_csv_columns( + expt_id: str, + columns: list[tuple[str, list[object]]], +) -> None: + """Raise if fit-data CSV columns have inconsistent lengths.""" + expected = len(columns[0][1]) + for name, values in columns[1:]: + if len(values) == expected: + continue + msg = ( + f"Cannot write report CSV for experiment '{expt_id}': " + f"column 'x' has length {expected}, but column " + f"'{name}' has length {len(values)}." + ) + raise ValueError(msg) + + def _copy_style_files(styles_dir: pathlib.Path) -> None: """Copy vendored LaTeX style files into a report bundle.""" source = files('easydiffraction.report').joinpath( From a55815707d97f8c2af52b02724d33e509b040fe8 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 28 May 2026 06:39:46 +0200 Subject: [PATCH 049/129] Build Plotly fit figures from fit_data context --- docs/dev/plans/project-summary-rendering.md | 2 +- src/easydiffraction/report/html_renderer.py | 175 +++++++++++++++--- .../report/templates/html/report.html.j2 | 4 +- 3 files changed, 154 insertions(+), 27 deletions(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index b3289b581..6c933a135 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -780,7 +780,7 @@ exceptions. every kaleido call site are gone. - Commit: `Emit pgfplots CSV and replace includegraphics with pgfplots blocks`. -- [ ] **P1.17 — HTML Plotly builder consumes `fit_data` directly** +- [x] **P1.17 — HTML Plotly builder consumes `fit_data` directly** - Files: existing `src/easydiffraction/report/html_renderer.py`; existing diff --git a/src/easydiffraction/report/html_renderer.py b/src/easydiffraction/report/html_renderer.py index 9fe5c8bfd..1aeef577f 100644 --- a/src/easydiffraction/report/html_renderer.py +++ b/src/easydiffraction/report/html_renderer.py @@ -75,8 +75,8 @@ def render_html_report( template_context = dict(context) template_context['html_offline'] = offline template_context['stylesheet'] = _stylesheet_text() - template_context['figures'] = _figure_html_context( - context.get('figures'), + template_context['fit_figures'] = _fit_figure_html_context( + context, offline=offline, ) return _environment().get_template(_TEMPLATE_NAME).render(**template_context) @@ -153,39 +153,166 @@ def _copy_mathjax(report_dir: pathlib.Path) -> None: shutil.copy2(source_path, vendor_dir / _MATHJAX_FILENAME) -def _figure_html_context( - figures: object, +def _fit_figure_html_context( + context: dict[str, object], *, offline: bool, -) -> dict[str, object]: - """Return HTML snippets for figure slots.""" - if not isinstance(figures, dict): - return {'fit_per_experiment': {}} - - return { - 'fit_per_experiment': _figure_map_html( - figures.get('fit_per_experiment'), - offline=offline, - ), - } - - -def _figure_map_html(figures: object, *, offline: bool) -> dict[str, str]: - """Convert one figure mapping into HTML snippets.""" - if not isinstance(figures, dict): - return {} - +) -> dict[str, str]: + """Return fit figure HTML snippets by experiment id.""" include_plotlyjs: bool | str = True if offline else 'cdn' rendered: dict[str, str] = {} - for key, figure in figures.items(): - rendered[str(key)] = _figure_html(figure, include_plotlyjs=include_plotlyjs) + for experiment in _experiment_contexts(context): + fit_data = experiment.get('fit_data') + if fit_data is None: + continue + experiment_id = str(experiment.get('id') or 'experiment') + figure = _fit_data_figure(experiment_id, fit_data) + rendered[experiment_id] = _figure_html( + figure, + include_plotlyjs=include_plotlyjs, + ) include_plotlyjs = False return rendered +def _fit_data_figure(experiment_id: str, fit_data: dict[str, object]) -> object: + """Build a Plotly fit figure from one fit-data payload.""" + go = _plotly_go() + x_data = fit_data['x'] + series = fit_data['series'] + x_values = _value_list(x_data['values']) + y_meas = _value_list(series['meas']['values']) + y_calc = _value_list(series['calc']['values']) + y_diff = _value_list(series['diff']['values']) + _validate_same_length(experiment_id, 'meas', x_values, y_meas) + _validate_same_length(experiment_id, 'calc', x_values, y_calc) + _validate_same_length(experiment_id, 'diff', x_values, y_diff) + + measured_trace = { + 'x': x_values, + 'y': y_meas, + 'mode': 'markers', + 'name': series['meas']['label'], + } + y_meas_su = series['meas']['su'] + if y_meas_su is not None: + su_values = _value_list(y_meas_su) + _validate_same_length(experiment_id, 'meas_su', x_values, su_values) + measured_trace['error_y'] = { + 'type': 'data', + 'array': su_values, + 'visible': True, + } + + fig = go.Figure() + fig.add_trace(go.Scatter(**measured_trace)) + fig.add_trace( + go.Scatter( + x=x_values, + y=y_calc, + mode='lines', + name=series['calc']['label'], + ) + ) + bkg = series['bkg'] + if bkg is not None: + y_bkg = _value_list(bkg['values']) + _validate_same_length(experiment_id, 'bkg', x_values, y_bkg) + fig.add_trace( + go.Scatter( + x=x_values, + y=y_bkg, + mode='lines', + name=bkg['label'], + ) + ) + fig.add_trace( + go.Scatter( + x=x_values, + y=y_diff, + mode='lines', + line={'dash': 'dash'}, + name=series['diff']['label'], + ) + ) + _configure_fit_figure( + fig, + experiment_id=experiment_id, + x_title=_axis_title(x_data), + ) + return fig + + def _figure_html(figure: object, *, include_plotlyjs: bool | str) -> str: """Return an HTML snippet for one figure-like object.""" to_html = getattr(figure, 'to_html', None) if callable(to_html): return to_html(full_html=False, include_plotlyjs=include_plotlyjs) return str(figure) + + +def _configure_fit_figure( + fig: object, + *, + experiment_id: str, + x_title: str, +) -> None: + """Apply shared report-figure layout.""" + fig.update_layout( + template='plotly_white', + title=f'Measured vs calculated: {experiment_id}', + xaxis_title=x_title, + yaxis_title='Intensity', + height=440, + margin={'l': 64, 'r': 24, 't': 64, 'b': 56}, + legend={'orientation': 'h', 'yanchor': 'bottom', 'y': 1.02}, + ) + + +def _axis_title(x_data: dict[str, object]) -> str: + """Return a display axis title for fit figures.""" + units = x_data['display_units'] + if units: + return f"{x_data['display_name']} ({units})" + return str(x_data['display_name']) + + +def _validate_same_length( + experiment_id: str, + column_name: str, + x_values: list[object], + y_values: list[object], +) -> None: + """Raise if fit-data arrays have inconsistent lengths.""" + if len(x_values) == len(y_values): + return + msg = ( + f"Cannot build report figure for experiment '{experiment_id}': " + f"column 'x' has length {len(x_values)}, but column " + f"'{column_name}' has length {len(y_values)}." + ) + raise ValueError(msg) + + +def _value_list(values: object) -> list[object]: + """Return a list suitable for Plotly and length validation.""" + return list(values) + + +def _experiment_contexts(context: dict[str, object]) -> list[dict[str, object]]: + """Return experiment contexts from a report context.""" + experiments = context.get('experiments') + if not isinstance(experiments, list): + return [] + return [ + experiment + for experiment in experiments + if isinstance(experiment, dict) + ] + + +def _plotly_go() -> object: + """Return Plotly graph objects for report figures.""" + import plotly.graph_objects as go # noqa: PLC0415 + + return go diff --git a/src/easydiffraction/report/templates/html/report.html.j2 b/src/easydiffraction/report/templates/html/report.html.j2 index 59853b39d..d6e92bd3b 100644 --- a/src/easydiffraction/report/templates/html/report.html.j2 +++ b/src/easydiffraction/report/templates/html/report.html.j2 @@ -252,8 +252,8 @@ - {% if figures.fit_per_experiment[experiment.id] %} -
{{ figures.fit_per_experiment[experiment.id] | safe }}
+ {% if fit_figures[experiment.id] %} +
{{ fit_figures[experiment.id] | safe }}
{% endif %} {% endfor %} From 5aad96f06916b40fed5c17e46eaa287b5e2b1c5b Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 28 May 2026 06:40:55 +0200 Subject: [PATCH 050/129] Replace stale figures references with data --- docs/dev/plans/project-summary-rendering.md | 2 +- docs/docs/user-guide/analysis-workflow/report.md | 4 ++-- src/easydiffraction/report/pdf_compiler.py | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 6c933a135..fa3a9c7d5 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -801,7 +801,7 @@ exceptions. - Commit: `Build Plotly fit figures from fit_data context`. -- [ ] **P1.18 — Clean up stale `figures/` references in code and docs** +- [x] **P1.18 — Clean up stale `figures/` references in code and docs** - Files: `src/easydiffraction/project/categories/report/default.py`, `src/easydiffraction/report/tex_renderer.py`, diff --git a/docs/docs/user-guide/analysis-workflow/report.md b/docs/docs/user-guide/analysis-workflow/report.md index d4810f3af..abc705e73 100644 --- a/docs/docs/user-guide/analysis-workflow/report.md +++ b/docs/docs/user-guide/analysis-workflow/report.md @@ -73,8 +73,8 @@ project.report.save_pdf(style='iucr') ``` `save_pdf()` always writes the TeX bundle first. If no TeX engine is on -`PATH`, EasyDiffraction leaves the TeX files in `reports/tex/`, prints -a short install hint, and does not raise. +`PATH`, EasyDiffraction leaves the `.tex`, `data/`, and `styles/` files +under `reports/tex/`, prints a short install hint, and does not raise. The command line mirrors the same split: diff --git a/src/easydiffraction/report/pdf_compiler.py b/src/easydiffraction/report/pdf_compiler.py index c5a916aca..bf06819e6 100644 --- a/src/easydiffraction/report/pdf_compiler.py +++ b/src/easydiffraction/report/pdf_compiler.py @@ -19,7 +19,8 @@ conda install -c conda-forge tectonic # or any TeX Live distribution (latexmk / pdflatex) Then re-run project.save() with 'pdf' in project.report.formats or -project.report.save_pdf().""" +project.report.save_pdf(). +The .tex, data/, and styles/ bundle remains under reports/tex/.""" def save_pdf_report( From 8558eef081e9bb8f58bf87ab6380679e7be00012 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 28 May 2026 06:43:27 +0200 Subject: [PATCH 051/129] Update report tutorials for single-style output --- docs/dev/plans/project-summary-rendering.md | 2 +- docs/docs/api-reference/report.md | 3 -- docs/docs/tutorials/ed-14.ipynb | 25 ++++++++++++ docs/docs/tutorials/ed-14.py | 16 ++++---- docs/docs/tutorials/ed-3.ipynb | 39 +++++++------------ docs/docs/tutorials/ed-3.py | 15 +++---- .../user-guide/analysis-workflow/report.md | 12 ++++-- 7 files changed, 60 insertions(+), 52 deletions(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index fa3a9c7d5..b1d059b93 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -821,7 +821,7 @@ exceptions. - Commit: `Replace stale figures/ references with data/`. -- [ ] **P1.19 — Update tutorials and user-guide for the new surface** +- [x] **P1.19 — Update tutorials and user-guide for the new surface** - Files: `docs/docs/tutorials/ed-3.py`, `docs/docs/tutorials/ed-14.py`, any other tutorial `.py` referencing `style=` or `--style` (grep at step diff --git a/docs/docs/api-reference/report.md b/docs/docs/api-reference/report.md index 4e6ac9c43..a86db090b 100644 --- a/docs/docs/api-reference/report.md +++ b/docs/docs/api-reference/report.md @@ -7,7 +7,6 @@ - html - tex - pdf - - style - html_offline - formats - data_context @@ -23,5 +22,3 @@ ## Enums ::: easydiffraction.report.enums.ReportFormatEnum - -::: easydiffraction.report.enums.ReportStyleEnum diff --git a/docs/docs/tutorials/ed-14.ipynb b/docs/docs/tutorials/ed-14.ipynb index f5243fb0f..43b600762 100644 --- a/docs/docs/tutorials/ed-14.ipynb +++ b/docs/docs/tutorials/ed-14.ipynb @@ -438,6 +438,31 @@ "source": [ "structure.show_as_cif()" ] + }, + { + "cell_type": "markdown", + "id": "40", + "metadata": {}, + "source": [ + "## Step 6: Generate Report\n", + "\n", + "By default, no report files are generated. Here we enable HTML and\n", + "TeX reports for regular project saves, then request a one-off PDF.\n", + "The generated report files will be saved in the `reports` folder of\n", + "the project directory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41", + "metadata": {}, + "outputs": [], + "source": [ + "project.report.formats = ['html', 'tex']\n", + "project.save()\n", + "project.report.save_pdf()" + ] } ], "metadata": { diff --git a/docs/docs/tutorials/ed-14.py b/docs/docs/tutorials/ed-14.py index 9dc00b7e5..e1991d02e 100644 --- a/docs/docs/tutorials/ed-14.py +++ b/docs/docs/tutorials/ed-14.py @@ -162,14 +162,12 @@ # %% [markdown] # ## Step 6: Generate Report # -# By default, no report files are generated. Here we enable all -# supported report formats. The generated report files will be saved in -# the `reports` folder of the project directory. +# By default, no report files are generated. Here we enable HTML and +# TeX reports for regular project saves, then request a one-off PDF. +# The generated report files will be saved in the `reports` folder of +# the project directory. # %% -project.report.cif = True -project.report.html = True -project.report.tex = True -project.report.pdf = True - -project.save() \ No newline at end of file +project.report.formats = ['html', 'tex'] +project.save() +project.report.save_pdf() diff --git a/docs/docs/tutorials/ed-3.ipynb b/docs/docs/tutorials/ed-3.ipynb index 664d4cd23..985f02bc3 100644 --- a/docs/docs/tutorials/ed-3.ipynb +++ b/docs/docs/tutorials/ed-3.ipynb @@ -1546,7 +1546,14 @@ "id": "151", "metadata": {}, "source": [ - "#### Save Project State and HTML Report" + "## Step 5: Generate Report\n", + "\n", + "This final section shows how to review the results of the analysis.\n", + "\n", + "By default, no report files are generated. Here we enable HTML and\n", + "TeX reports for regular project saves, then request a one-off PDF.\n", + "The generated report files will be saved in the `reports` folder of\n", + "the project directory." ] }, { @@ -1556,39 +1563,19 @@ "metadata": {}, "outputs": [], "source": [ - "project.report.formats = ['html']\n", - "project.save()" - ] - }, - { - "cell_type": "markdown", - "id": "153", - "metadata": {}, - "source": [ - "## Step 5: Report\n", - "\n", - "This final section shows how to review the results of the analysis.\n", - "\n", - "The saved HTML report is available under `reports/.html`\n", - "inside the project directory." - ] - }, - { - "cell_type": "markdown", - "id": "154", - "metadata": {}, - "source": [ - "#### Show Project Report" + "project.report.formats = ['html', 'tex']\n", + "project.save()\n", + "project.report.save_pdf()" ] }, { "cell_type": "code", "execution_count": null, - "id": "155", + "id": "153", "metadata": {}, "outputs": [], "source": [ - "project.report.show_report()" + "# project.report.show_report() # Need this?" ] } ], diff --git a/docs/docs/tutorials/ed-3.py b/docs/docs/tutorials/ed-3.py index ee3f8a788..bfd02e483 100644 --- a/docs/docs/tutorials/ed-3.py +++ b/docs/docs/tutorials/ed-3.py @@ -624,18 +624,15 @@ # # This final section shows how to review the results of the analysis. # -# By default, no report files are generated. Here we enable all -# supported report formats. The generated report files will be saved in -# the `reports` folder of the project directory. +# By default, no report files are generated. Here we enable HTML and +# TeX reports for regular project saves, then request a one-off PDF. +# The generated report files will be saved in the `reports` folder of +# the project directory. # %% -# project.report.formats = ['cif', 'html', 'tex', 'pdf'] # Need this? -project.report.cif = True -project.report.html = True -project.report.tex = True -project.report.pdf = True - +project.report.formats = ['html', 'tex'] project.save() +project.report.save_pdf() # %% # project.report.show_report() # Need this? diff --git a/docs/docs/user-guide/analysis-workflow/report.md b/docs/docs/user-guide/analysis-workflow/report.md index abc705e73..12cc58a71 100644 --- a/docs/docs/user-guide/analysis-workflow/report.md +++ b/docs/docs/user-guide/analysis-workflow/report.md @@ -47,7 +47,6 @@ report formats. | `project.report.html` | `bool` | Write an HTML report. | | `project.report.tex` | `bool` | Write a TeX report bundle. | | `project.report.pdf` | `bool` | Write a PDF report when a TeX engine is available. | -| `project.report.style` | `str` | TeX/PDF template style: `'iucr'` or `'revtex'`. | | `project.report.html_offline` | `bool` | Embed HTML assets instead of using CDN links. | The `formats` property is a compact way to set the four format flags: @@ -68,19 +67,24 @@ configuration: ```python project.report.save_html() project.report.save_cif() -project.report.save_tex(style='iucr') -project.report.save_pdf(style='iucr') +project.report.save_tex() +project.report.save_pdf() ``` `save_pdf()` always writes the TeX bundle first. If no TeX engine is on `PATH`, EasyDiffraction leaves the `.tex`, `data/`, and `styles/` files under `reports/tex/`, prints a short install hint, and does not raise. +HTML reports load Plotly and MathJax from CDNs by default. Set +`project.report.html_offline = True` to make the HTML report usable +without network access; this embeds Plotly in the HTML and copies the +vendored MathJax bundle next to it, adding about 4.5 MB total. + The command line mirrors the same split: ```bash ed save path/to/project -ed save-report path/to/project --html --tex --pdf --style iucr +ed save-report path/to/project --html --tex --pdf ``` `ed save` uses the persisted `project.report` configuration. From d528158fb9d38bf437758587aad8145ec3fda60c Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 28 May 2026 06:43:54 +0200 Subject: [PATCH 052/129] Reach Phase 1 review gate --- docs/dev/plans/project-summary-rendering.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index b1d059b93..1e42726c7 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -843,7 +843,7 @@ exceptions. - Commit: `Update tutorials and docs for single-style report surface`. -- [ ] **P1.20 — Reach Phase 1 review gate** +- [x] **P1.20 — Reach Phase 1 review gate** - No-code step. Mark every `[ ]` above as `[x]`; commit the plan-file update alone. - Commit: `Reach Phase 1 review gate`. From cddff54aa778f9b393de0d716e2d8aaac0b7520d Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 28 May 2026 06:54:50 +0200 Subject: [PATCH 053/129] Fix TeX report axis label template syntax --- src/easydiffraction/report/templates/tex/report.tex.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/easydiffraction/report/templates/tex/report.tex.j2 b/src/easydiffraction/report/templates/tex/report.tex.j2 index f24c83c47..feebedd27 100644 --- a/src/easydiffraction/report/templates/tex/report.tex.j2 +++ b/src/easydiffraction/report/templates/tex/report.tex.j2 @@ -109,7 +109,7 @@ Calculator & {{ experiment.calculator.type | tex }} \\ \begin{axis}[ width=\linewidth, height=0.48\linewidth, -xlabel={{{ experiment.fit_data.x.latex_name }}{% if experiment.fit_data.x.latex_units %} ({{ experiment.fit_data.x.latex_units }}){% endif %}}, +xlabel={ {{ experiment.fit_data.x.latex_name }}{% if experiment.fit_data.x.latex_units %} ({{ experiment.fit_data.x.latex_units }}){% endif %} }, ylabel={Intensity}, legend pos=north east, ] From 7a56c3f390a5ff60794a920fb6b395be879de360 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 28 May 2026 07:28:15 +0200 Subject: [PATCH 054/129] Keep fit running when PDF reporting fails --- src/easydiffraction/__init__.py | 33 +++++++ src/easydiffraction/__main__.py | 6 +- .../project/categories/report/default.py | 6 +- src/easydiffraction/report/pdf_compiler.py | 65 ++++++++++++-- src/easydiffraction/report/tex_renderer.py | 12 ++- .../project/categories/report/test_default.py | 36 ++++++-- .../report/test_pdf_compiler.py | 86 ++++++++++++++++++- .../report/test_tex_renderer.py | 67 ++++++++++++--- tests/unit/easydiffraction/test___main__.py | 48 +++++++++-- 9 files changed, 320 insertions(+), 39 deletions(-) diff --git a/src/easydiffraction/__init__.py b/src/easydiffraction/__init__.py index 0e69a9c7e..d647a12a8 100644 --- a/src/easydiffraction/__init__.py +++ b/src/easydiffraction/__init__.py @@ -1,6 +1,39 @@ # SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause +from __future__ import annotations + +import os +import pathlib +import tempfile + + +def _path_is_writable(path: pathlib.Path) -> bool: + """Return whether a directory can be used for runtime cache files.""" + try: + path.mkdir(parents=True, exist_ok=True) + probe = path / '.easydiffraction-write-test' + probe.write_text('', encoding='utf-8') + probe.unlink() + except OSError: + return False + return True + + +def _ensure_matplotlib_config_dir() -> None: + """Set a stable Matplotlib cache dir when the default is unusable.""" + if os.environ.get('MPLCONFIGDIR'): + return + default_dir = pathlib.Path.home() / '.matplotlib' + if _path_is_writable(default_dir): + return + fallback_dir = pathlib.Path(tempfile.gettempdir()) / 'easydiffraction-matplotlib' + if _path_is_writable(fallback_dir): + os.environ['MPLCONFIGDIR'] = str(fallback_dir) + + +_ensure_matplotlib_config_dir() + from easydiffraction.datablocks.experiment.item.factory import ExperimentFactory from easydiffraction.datablocks.structure.item.factory import StructureFactory from easydiffraction.io.ascii import extract_data_paths_from_dir diff --git a/src/easydiffraction/__main__.py b/src/easydiffraction/__main__.py index d57c947f2..115ea4fc5 100644 --- a/src/easydiffraction/__main__.py +++ b/src/easydiffraction/__main__.py @@ -168,11 +168,13 @@ def _save_report_outputs( report_paths.append(tex_path) if pdf: if tex_path is None: - report_paths.append(report.save_pdf()) + pdf_path = report.save_pdf() else: from easydiffraction.report.pdf_compiler import compile_pdf_report # noqa: PLC0415 - report_paths.append(compile_pdf_report(tex_path)) + pdf_path = compile_pdf_report(tex_path) + if pdf_path.is_file(): + report_paths.append(pdf_path) return report_paths diff --git a/src/easydiffraction/project/categories/report/default.py b/src/easydiffraction/project/categories/report/default.py index 4eaaab592..a90f47c4f 100644 --- a/src/easydiffraction/project/categories/report/default.py +++ b/src/easydiffraction/project/categories/report/default.py @@ -506,11 +506,13 @@ def _save_configured(self) -> list[pathlib.Path]: report_paths.append(tex_path) elif report_format is ReportFormatEnum.PDF: if tex_path is None: - report_paths.append(self.save_pdf()) + pdf_path = self.save_pdf() else: from easydiffraction.report.pdf_compiler import ( # noqa: PLC0415 compile_pdf_report, ) - report_paths.append(compile_pdf_report(tex_path)) + pdf_path = compile_pdf_report(tex_path) + if pdf_path.is_file(): + report_paths.append(pdf_path) return report_paths diff --git a/src/easydiffraction/report/pdf_compiler.py b/src/easydiffraction/report/pdf_compiler.py index bf06819e6..083377319 100644 --- a/src/easydiffraction/report/pdf_compiler.py +++ b/src/easydiffraction/report/pdf_compiler.py @@ -13,6 +13,11 @@ from easydiffraction.utils.logging import log _ENGINE_ORDER = ('tectonic', 'latexmk', 'pdflatex') +_ENGINE_RUNTIME_FAILURE_MARKERS = ( + 'panicked at', + 'event loop thread panicked', + 'Attempted to create a NULL object', +) _INSTALL_HINT = """PDF skipped: no TeX engine on PATH. Install one with: pixi add tectonic @@ -73,29 +78,57 @@ def compile_pdf_report(tex_path: pathlib.Path) -> pathlib.Path: If a discovered TeX engine fails to compile the report. """ pdf_path = tex_path.parent.parent / f'{tex_path.stem}.pdf' - engine = _find_engine() - if engine is None: + pdf_path.unlink(missing_ok=True) + engines = _find_engines() + if not engines: log.warning(_INSTALL_HINT) return pdf_path - _compile_pdf(engine, tex_path, pdf_path) + runtime_failures = [] + for engine in engines: + runtime_failure = _compile_pdf(engine, tex_path, pdf_path) + if runtime_failure is None: + return pdf_path + runtime_failures.append(runtime_failure) + if runtime_failures: + _warn_engine_runtime_failure(runtime_failures) return pdf_path -def _find_engine() -> tuple[str, str] | None: - """Return the first available TeX engine.""" +def _find_engines() -> list[tuple[str, str]]: + """Return available TeX engines in preferred order.""" + engines = [] for engine_name in _ENGINE_ORDER: executable = shutil.which(engine_name) if executable is not None: - return engine_name, executable - return None + engines.append((engine_name, executable)) + return engines + + +def _find_engine() -> tuple[str, str] | None: + """Return the first available TeX engine.""" + engines = _find_engines() + if not engines: + return None + return engines[0] + + +def _is_engine_runtime_failure( + engine_name: str, + result: subprocess.CompletedProcess[str], +) -> bool: + """Return whether the TeX engine failed before compilation.""" + if engine_name != 'tectonic': + return False + output = f'{result.stderr}\n{result.stdout}' + return any(marker in output for marker in _ENGINE_RUNTIME_FAILURE_MARKERS) def _compile_pdf( engine: tuple[str, str], tex_path: pathlib.Path, pdf_path: pathlib.Path, -) -> None: +) -> str | None: """Compile one TeX document with a discovered engine.""" engine_name, executable = engine compile_tex_path = tex_path.resolve() @@ -115,6 +148,8 @@ def _compile_pdf( check=False, ) if result.returncode != 0: + if _is_engine_runtime_failure(engine_name, result): + return _compiler_error_message(engine_name, tex_path, result) msg = _compiler_error_message(engine_name, tex_path, result) raise RuntimeError(msg) if not compile_pdf_path.is_file(): @@ -123,6 +158,20 @@ def _compile_pdf( f"'{pdf_path}'." ) raise RuntimeError(msg) + return None + + +def _warn_engine_runtime_failure( + failures: list[str], +) -> None: + """Warn when a TeX engine crashes before compiling LaTeX.""" + details = '\n\n'.join(failures) + msg = ( + 'PDF skipped: the TeX engine failed before LaTeX compilation. ' + 'The .tex, data/, and styles/ bundle remains under reports/tex/.\n' + f'{details}' + ) + log.warning(msg) def _compile_command( diff --git a/src/easydiffraction/report/tex_renderer.py b/src/easydiffraction/report/tex_renderer.py index b734393fb..476fa566c 100644 --- a/src/easydiffraction/report/tex_renderer.py +++ b/src/easydiffraction/report/tex_renderer.py @@ -6,6 +6,7 @@ import csv import pathlib +import shutil from importlib.resources import files from jinja2 import Environment @@ -106,7 +107,7 @@ def save_tex_report( tex_dir = output_path.parent styles_dir = tex_dir / 'styles' - tex_dir.mkdir(parents=True, exist_ok=True) + _prepare_tex_bundle(tex_dir) styles_dir.mkdir(parents=True, exist_ok=True) template_context = dict(context) @@ -137,6 +138,15 @@ def _environment() -> Environment: return environment +def _prepare_tex_bundle(tex_dir: pathlib.Path) -> None: + """Remove managed bundle subdirectories before writing TeX assets.""" + tex_dir.mkdir(parents=True, exist_ok=True) + for dirname in ('data', 'styles', 'figures'): + path = tex_dir / dirname + if path.exists(): + shutil.rmtree(path) + + def _write_fit_csvs( context: dict[str, object], out_dir: pathlib.Path, diff --git a/tests/unit/easydiffraction/project/categories/report/test_default.py b/tests/unit/easydiffraction/project/categories/report/test_default.py index c8bdcb8ca..1b6cf231d 100644 --- a/tests/unit/easydiffraction/project/categories/report/test_default.py +++ b/tests/unit/easydiffraction/project/categories/report/test_default.py @@ -14,20 +14,21 @@ def test_save_configured_reuses_tex_bundle_for_pdf(tmp_path, monkeypatch): calls = [] report = Report() report.formats = ['tex', 'pdf'] - report.style = 'revtex' - def fake_save_tex(self, *, style): + def fake_save_tex(self): del self - calls.append(('tex', style)) + calls.append('tex') return tex_path - def fake_save_pdf(self, *, style): - del self, style + def fake_save_pdf(self): + del self msg = 'save_pdf should not regenerate TeX when tex was already saved.' raise AssertionError(msg) def fake_compile_pdf_report(path): calls.append(('pdf', path)) + pdf_path.parent.mkdir(parents=True, exist_ok=True) + pdf_path.write_text('%PDF', encoding='utf-8') return pdf_path monkeypatch.setattr(Report, 'save_tex', fake_save_tex) @@ -35,4 +36,27 @@ def fake_compile_pdf_report(path): monkeypatch.setattr(pdf_compiler, 'compile_pdf_report', fake_compile_pdf_report) assert report._save_configured() == [tex_path, pdf_path] - assert calls == [('tex', 'revtex'), ('pdf', tex_path)] + assert calls == ['tex', ('pdf', tex_path)] + + +def test_save_configured_omits_missing_compiled_pdf(tmp_path, monkeypatch): + from easydiffraction.project.categories.report.default import Report + from easydiffraction.report import pdf_compiler + + tex_path = tmp_path / 'reports' / 'tex' / 'demo.tex' + pdf_path = tmp_path / 'reports' / 'demo.pdf' + report = Report() + report.formats = ['tex', 'pdf'] + + def fake_save_tex(self): + del self + return tex_path + + def fake_compile_pdf_report(path): + assert path == tex_path + return pdf_path + + monkeypatch.setattr(Report, 'save_tex', fake_save_tex) + monkeypatch.setattr(pdf_compiler, 'compile_pdf_report', fake_compile_pdf_report) + + assert report._save_configured() == [tex_path] diff --git a/tests/unit/easydiffraction/report/test_pdf_compiler.py b/tests/unit/easydiffraction/report/test_pdf_compiler.py index f09909c80..59f1bbfcd 100644 --- a/tests/unit/easydiffraction/report/test_pdf_compiler.py +++ b/tests/unit/easydiffraction/report/test_pdf_compiler.py @@ -41,7 +41,7 @@ def fake_run( monkeypatch.setattr(pdf_compiler.subprocess, 'run', fake_run) - pdf_compiler._compile_pdf(('tectonic', 'tectonic'), tex_path, pdf_path) + assert pdf_compiler._compile_pdf(('tectonic', 'tectonic'), tex_path, pdf_path) is None assert calls[0]['command'] == [ 'tectonic', @@ -50,3 +50,87 @@ def fake_run( 'report.tex', ] assert calls[0]['cwd'] == tex_dir.resolve() + + +def test_compile_pdf_report_skips_tectonic_runtime_failure(tmp_path, monkeypatch): + from easydiffraction.report import pdf_compiler + + reports_dir = tmp_path / 'reports' + tex_dir = reports_dir / 'tex' + tex_dir.mkdir(parents=True) + tex_path = tex_dir / 'report.tex' + pdf_path = reports_dir / 'report.pdf' + tex_path.write_text(r'\documentclass{article}', encoding='utf-8') + pdf_path.write_text('%PDF stale', encoding='utf-8') + warnings = [] + + def fake_run( + command, + *, + cwd, + env, + text, + capture_output, + check, + ): + del cwd, env, text, capture_output, check + return subprocess.CompletedProcess( + command, + 101, + '', + 'panicked at Attempted to create a NULL object', + ) + + monkeypatch.setattr(pdf_compiler, '_find_engines', lambda: [('tectonic', 'tectonic')]) + monkeypatch.setattr(pdf_compiler.subprocess, 'run', fake_run) + monkeypatch.setattr(pdf_compiler.log, 'warning', warnings.append) + + assert pdf_compiler.compile_pdf_report(tex_path) == pdf_path + assert not pdf_path.exists() + assert warnings + assert warnings[0].startswith('PDF skipped:') + + +def test_compile_pdf_report_uses_fallback_after_runtime_failure(tmp_path, monkeypatch): + from easydiffraction.report import pdf_compiler + + reports_dir = tmp_path / 'reports' + tex_dir = reports_dir / 'tex' + tex_dir.mkdir(parents=True) + tex_path = tex_dir / 'report.tex' + pdf_path = reports_dir / 'report.pdf' + tex_path.write_text(r'\documentclass{article}', encoding='utf-8') + + def fake_run( + command, + *, + cwd, + env, + text, + capture_output, + check, + ): + del cwd, env, text, capture_output, check + if command[0] == 'tectonic': + return subprocess.CompletedProcess( + command, + 101, + '', + 'event loop thread panicked', + ) + pdf_path.write_text('%PDF', encoding='utf-8') + return subprocess.CompletedProcess(command, 0, '', '') + + def fail_warning(message): + raise AssertionError(message) + + monkeypatch.setattr( + pdf_compiler, + '_find_engines', + lambda: [('tectonic', 'tectonic'), ('pdflatex', 'pdflatex')], + ) + monkeypatch.setattr(pdf_compiler.subprocess, 'run', fake_run) + monkeypatch.setattr(pdf_compiler.log, 'warning', fail_warning) + + assert pdf_compiler.compile_pdf_report(tex_path) == pdf_path + assert pdf_path.is_file() diff --git a/tests/unit/easydiffraction/report/test_tex_renderer.py b/tests/unit/easydiffraction/report/test_tex_renderer.py index 8313024f5..f1bde4bbb 100644 --- a/tests/unit/easydiffraction/report/test_tex_renderer.py +++ b/tests/unit/easydiffraction/report/test_tex_renderer.py @@ -58,13 +58,18 @@ def _minimal_context() -> dict[str, object]: } -def test_render_tex_report_keeps_latex_graphicspath_braces(): +def _latex_field(label: str, units: str = '') -> dict[str, str]: + """Return one rendered-field metadata mapping.""" + return {'label': label, 'units': units} + + +def test_render_tex_report_renders_default_document(): from easydiffraction.report.tex_renderer import render_tex_report - for style in ('iucr', 'revtex'): - tex = render_tex_report(_minimal_context(), style=style) + tex = render_tex_report(_minimal_context()) - assert r'\graphicspath{{figures/}}' in tex + assert r'\documentclass{styles/iucrjournals}' in tex + assert r'\section{Project summary}' in tex def test_render_tex_report_preserves_structure_uncertainty_text(): @@ -84,6 +89,14 @@ def test_render_tex_report_preserves_structure_uncertainty_text(): 'angle_beta': '90.()', 'angle_gamma': '90.()', }, + 'cell_latex': { + 'length_a': _latex_field('$a$', r'\AA'), + 'length_b': _latex_field('$b$', r'\AA'), + 'length_c': _latex_field('$c$', r'\AA'), + 'angle_alpha': _latex_field(r'$\alpha$', 'deg'), + 'angle_beta': _latex_field(r'$\beta$', 'deg'), + 'angle_gamma': _latex_field(r'$\gamma$', 'deg'), + }, 'atom_sites': [ { 'label': 'Si1', @@ -95,6 +108,9 @@ def test_render_tex_report_preserves_structure_uncertainty_text(): 'adp_iso': '0.00658(14)', } ], + 'atom_site_latex': { + 'adp_iso': _latex_field('$U_{iso}$', r'\AA^2'), + }, 'atom_site_aniso': [ { 'label': 'Si1', @@ -106,16 +122,41 @@ def test_render_tex_report_preserves_structure_uncertainty_text(): 'adp_23': '0.00189(13)', } ], + 'atom_site_aniso_latex': { + 'adp_11': _latex_field('$U_{11}$', r'\AA^2'), + 'adp_22': _latex_field('$U_{22}$', r'\AA^2'), + 'adp_33': _latex_field('$U_{33}$', r'\AA^2'), + 'adp_12': _latex_field('$U_{12}$', r'\AA^2'), + 'adp_13': _latex_field('$U_{13}$', r'\AA^2'), + 'adp_23': _latex_field('$U_{23}$', r'\AA^2'), + }, } ] - for style in ('iucr', 'revtex'): - tex = render_tex_report(context, style=style) + tex = render_tex_report(context) + + assert '11.985(31)' in tex + assert '0.00658(14)' in tex + assert '-0.00048(25)' in tex + assert ( + r'\end{tabular}' '\n\n' r'\begin{tabular}{lllllll}' + '\nLabel &' + ) in tex + + +def test_save_tex_report_removes_stale_managed_bundle_dirs(tmp_path): + from easydiffraction.report.tex_renderer import save_tex_report + + tex_dir = tmp_path / 'reports' / 'tex' + tex_path = tex_dir / 'report.tex' + for dirname in ('data', 'styles', 'figures'): + stale_path = tex_dir / dirname / 'stale.txt' + stale_path.parent.mkdir(parents=True, exist_ok=True) + stale_path.write_text('stale', encoding='utf-8') + + assert save_tex_report(object(), _minimal_context(), path=tex_path) == tex_path - assert '11.985(31)' in tex - assert '0.00658(14)' in tex - assert '-0.00048(25)' in tex - assert ( - r'\end{tabular}' '\n\n' r'\begin{tabular}{lllllll}' - '\nLabel & ADP 11' - ) in tex + assert not (tex_dir / 'data').exists() + assert not (tex_dir / 'figures').exists() + assert not (tex_dir / 'styles' / 'stale.txt').exists() + assert (tex_dir / 'styles' / 'iucrjournals.cls').is_file() diff --git a/tests/unit/easydiffraction/test___main__.py b/tests/unit/easydiffraction/test___main__.py index 740476684..0cdc7f4d7 100644 --- a/tests/unit/easydiffraction/test___main__.py +++ b/tests/unit/easydiffraction/test___main__.py @@ -88,17 +88,18 @@ def test_save_report_outputs_reuses_tex_bundle_for_pdf(tmp_path, monkeypatch): pdf_path = tmp_path / 'reports' / 'demo.pdf' calls = [] - def fake_save_tex(*, style): - calls.append(('tex', style)) + def fake_save_tex(): + calls.append('tex') return tex_path - def fake_save_pdf(*, style): - del style + def fake_save_pdf(): msg = 'save_pdf should not regenerate TeX when tex was already saved.' raise AssertionError(msg) def fake_compile_pdf_report(path): calls.append(('pdf', path)) + pdf_path.parent.mkdir(parents=True, exist_ok=True) + pdf_path.write_text('%PDF', encoding='utf-8') return pdf_path project = SimpleNamespace( @@ -117,12 +118,47 @@ def fake_compile_pdf_report(path): html=False, tex=True, pdf=True, - style='revtex', offline=False, ) assert report_paths == [tex_path, pdf_path] - assert calls == [('tex', 'revtex'), ('pdf', tex_path)] + assert calls == ['tex', ('pdf', tex_path)] + + +def test_save_report_outputs_omits_missing_compiled_pdf(tmp_path, monkeypatch): + import easydiffraction.__main__ as main_mod + from easydiffraction.report import pdf_compiler + + tex_path = tmp_path / 'reports' / 'tex' / 'demo.tex' + pdf_path = tmp_path / 'reports' / 'demo.pdf' + + def fake_save_tex(): + return tex_path + + def fake_compile_pdf_report(path): + assert path == tex_path + return pdf_path + + project = SimpleNamespace( + report=SimpleNamespace( + save_cif=None, + save_html=None, + save_tex=fake_save_tex, + save_pdf=None, + ) + ) + monkeypatch.setattr(pdf_compiler, 'compile_pdf_report', fake_compile_pdf_report) + + report_paths = main_mod._save_report_outputs( + project, + cif=False, + html=False, + tex=True, + pdf=True, + offline=False, + ) + + assert report_paths == [tex_path] def test_cli_project_first_argument_normalization_supports_global_data_commands(): From 86c4b193377930a1d7e9f1adbe34ae731ab21f3d Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 28 May 2026 07:41:33 +0200 Subject: [PATCH 055/129] Escape plain labels in TeX reports --- .../report/templates/tex/report.tex.j2 | 8 +++- src/easydiffraction/report/tex_renderer.py | 11 +++++ .../report/test_tex_renderer.py | 45 +++++++++++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/easydiffraction/report/templates/tex/report.tex.j2 b/src/easydiffraction/report/templates/tex/report.tex.j2 index feebedd27..0a61ccfcb 100644 --- a/src/easydiffraction/report/templates/tex/report.tex.j2 +++ b/src/easydiffraction/report/templates/tex/report.tex.j2 @@ -2,7 +2,9 @@ \usepackage{pgfplots} \pgfplotsset{compat=1.18} {% macro tex_field_header(field, fallback) -%} -{{ field.label or fallback }}{% if field.units %} ({{ field.units }}){% endif %} +{% set label = (field.label or fallback) | tex_markup -%} +{% set units = field.units | tex_markup -%} +{{ label }}{% if field.units %} ({{ units }}){% endif %} {%- endmacro %} \title{ {{- (publication.body.title or project.title or project.name or "EasyDiffraction report") | tex -}} } @@ -106,10 +108,12 @@ Calculator & {{ experiment.calculator.type | tex }} \\ \begin{figure}[H] \centering \begin{tikzpicture} +{% set x_label = experiment.fit_data.x.latex_name | tex_markup -%} +{% set x_units = experiment.fit_data.x.latex_units | tex_markup -%} \begin{axis}[ width=\linewidth, height=0.48\linewidth, -xlabel={ {{ experiment.fit_data.x.latex_name }}{% if experiment.fit_data.x.latex_units %} ({{ experiment.fit_data.x.latex_units }}){% endif %} }, +xlabel={ {{ x_label }}{% if experiment.fit_data.x.latex_units %} ({{ x_units }}){% endif %} }, ylabel={Intensity}, legend pos=north east, ] diff --git a/src/easydiffraction/report/tex_renderer.py b/src/easydiffraction/report/tex_renderer.py index 476fa566c..4084e606b 100644 --- a/src/easydiffraction/report/tex_renderer.py +++ b/src/easydiffraction/report/tex_renderer.py @@ -134,6 +134,7 @@ def _environment() -> Environment: lstrip_blocks=True, ) environment.filters['tex'] = _tex_escape + environment.filters['tex_markup'] = _tex_markup environment.filters['tex_number'] = _tex_number return environment @@ -279,6 +280,16 @@ def _tex_number(value: object, digits: int = 6) -> str: return _tex_escape(value) +def _tex_markup(value: object) -> str: + """Escape plain text while preserving explicit TeX snippets.""" + if value is None: + return '' + text = str(value) + if '\\' in text or '$' in text: + return text + return _tex_escape(text) + + def _tex_escape(value: object) -> str: """Escape user-provided text for TeX output.""" if value is None: diff --git a/tests/unit/easydiffraction/report/test_tex_renderer.py b/tests/unit/easydiffraction/report/test_tex_renderer.py index f1bde4bbb..5a12b4ac3 100644 --- a/tests/unit/easydiffraction/report/test_tex_renderer.py +++ b/tests/unit/easydiffraction/report/test_tex_renderer.py @@ -144,6 +144,51 @@ def test_render_tex_report_preserves_structure_uncertainty_text(): ) in tex +def test_render_tex_report_escapes_plain_latex_field_labels(): + from easydiffraction.report.tex_renderer import render_tex_report + + context = _minimal_context() + context['experiments'] = [ + { + 'id': 'hrpt', + 'type': { + 'sample_form': 'powder', + 'radiation_probe': 'neutron', + 'beam_mode': 'constant wavelength', + 'scattering_type': 'bragg', + }, + 'calculator': {'type': 'cryspy'}, + 'diffrn': { + 'ambient_temperature': '', + 'ambient_pressure': '', + }, + 'diffrn_latex': { + 'ambient_temperature': _latex_field('ambient_temperature', 'K'), + 'ambient_pressure': _latex_field('ambient_pressure', 'kPa'), + }, + 'fit_data': { + 'x': { + 'values': [1.0], + 'latex_name': 'time_of_flight', + 'latex_units': 'micro_seconds', + }, + 'series': { + 'meas': {'values': [1.0], 'su': None, 'label': 'Measured'}, + 'calc': {'values': [1.0], 'label': 'Calculated'}, + 'diff': {'values': [0.0], 'label': 'Difference'}, + 'bkg': None, + }, + }, + } + ] + + tex = render_tex_report(context) + + assert r'ambient\_temperature (K)' in tex + assert r'ambient\_pressure (kPa)' in tex + assert r'xlabel={ time\_of\_flight (micro\_seconds) }' in tex + + def test_save_tex_report_removes_stale_managed_bundle_dirs(tmp_path): from easydiffraction.report.tex_renderer import save_tex_report From 5a770f06cccbd82d74da1a50a41916e19bc21a6e Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 28 May 2026 09:16:36 +0200 Subject: [PATCH 056/129] Update report templates with composite fit plots --- .../experiment/categories/diffrn/default.py | 4 + src/easydiffraction/display/plotters/base.py | 1 + .../display/plotters/plotly.py | 71 +++++- src/easydiffraction/display/plotting.py | 14 ++ src/easydiffraction/report/data_context.py | 58 +++++ src/easydiffraction/report/fit_plot.py | 133 ++++++++++ src/easydiffraction/report/html_renderer.py | 164 +++++++------ .../report/templates/html/report.html.j2 | 230 +++++++++--------- .../report/templates/html/style.css | 190 +++++---------- .../report/templates/tex/report.tex.j2 | 225 ++++++++++++----- src/easydiffraction/report/tex_renderer.py | 55 ++++- .../display/plotters/test_plotly.py | 2 + .../easydiffraction/display/test_plotting.py | 2 + .../report/test_data_context.py | 85 ++++++- .../easydiffraction/report/test_fit_plot.py | 47 ++++ .../report/test_html_renderer.py | 93 +++++++ .../report/test_tex_renderer.py | 90 ++++++- 17 files changed, 1073 insertions(+), 391 deletions(-) create mode 100644 src/easydiffraction/report/fit_plot.py create mode 100644 tests/unit/easydiffraction/report/test_fit_plot.py diff --git a/src/easydiffraction/datablocks/experiment/categories/diffrn/default.py b/src/easydiffraction/datablocks/experiment/categories/diffrn/default.py index 36206a94a..c5319c02d 100644 --- a/src/easydiffraction/datablocks/experiment/categories/diffrn/default.py +++ b/src/easydiffraction/datablocks/experiment/categories/diffrn/default.py @@ -33,7 +33,9 @@ def __init__(self) -> None: description='Mean temperature during measurement', units='kelvins', display_handler=DisplayHandler( + display_name='Temperature', display_units='K', + latex_name='Temperature', latex_units='K', ), value_spec=AttributeSpec( @@ -49,7 +51,9 @@ def __init__(self) -> None: description='Mean hydrostatic pressure during measurement', units='kilopascals', display_handler=DisplayHandler( + display_name='Pressure', display_units='kPa', + latex_name='Pressure', latex_units='kPa', ), value_spec=AttributeSpec( diff --git a/src/easydiffraction/display/plotters/base.py b/src/easydiffraction/display/plotters/base.py index a080ba8e7..e013bee1b 100644 --- a/src/easydiffraction/display/plotters/base.py +++ b/src/easydiffraction/display/plotters/base.py @@ -66,6 +66,7 @@ class PowderMeasVsCalcSpec: y_calc_name: str | None = None y_calc_line_dash: str | None = None excluded_ranges: tuple[tuple[float, float], ...] = () + y_meas_su: np.ndarray | None = None class XAxisType(StrEnum): diff --git a/src/easydiffraction/display/plotters/plotly.py b/src/easydiffraction/display/plotters/plotly.py index afa99da90..5430dc05d 100644 --- a/src/easydiffraction/display/plotters/plotly.py +++ b/src/easydiffraction/display/plotters/plotly.py @@ -1394,6 +1394,26 @@ def plot_powder_meas_vs_calc( Bragg row is added only when tick data is available. The residual row is added only when residual data is requested. """ + fig = self.build_powder_meas_vs_calc_figure(plot_spec=plot_spec) + self._show_figure(fig) + + def build_powder_meas_vs_calc_figure( + self, + plot_spec: PowderMeasVsCalcSpec, + ) -> object: + """ + Build a composite powder Plotly figure without displaying it. + + Parameters + ---------- + plot_spec : PowderMeasVsCalcSpec + Composite powder-plot inputs and layout settings. + + Returns + ------- + object + Configured :class:`plotly.graph_objects.Figure`. + """ layout = self._get_powder_composite_rows(plot_spec) x_min, x_max = self._composite_x_range(np.asarray(plot_spec.x)) main_y_min, main_y_max = self._get_main_intensity_range(plot_spec) @@ -1432,7 +1452,7 @@ def plot_powder_meas_vs_calc( residual_limit=residual_limit, ) - self._show_figure(fig) + return fig @staticmethod def _create_powder_composite_figure(layout: PowderCompositeRows) -> object: @@ -1476,6 +1496,13 @@ def _add_main_intensity_traces( customdata=hover_data, hovertemplate=hover_template, ) + if plot_spec.y_meas_su is not None: + meas_trace.error_y = { + 'type': 'data', + 'array': plot_spec.y_meas_su, + 'visible': True, + 'color': DEFAULT_COLORS['meas'], + } fig.add_trace(meas_trace, row=1, col=1) if plot_spec.y_bkg is not None: @@ -1797,6 +1824,45 @@ def plot_single_crystal( # Intentionally unused; accepted for API compatibility del height + fig = self.build_single_crystal_figure( + x_calc=x_calc, + y_meas=y_meas, + y_meas_su=y_meas_su, + axes_labels=axes_labels, + title=title, + ) + self._show_figure(fig) + + def build_single_crystal_figure( + self, + *, + x_calc: object, + y_meas: object, + y_meas_su: object, + axes_labels: object, + title: str, + ) -> object: + """ + Build a single-crystal Plotly figure without displaying it. + + Parameters + ---------- + x_calc : object + 1D array-like of calculated values (x-axis). + y_meas : object + 1D array-like of measured values (y-axis). + y_meas_su : object + 1D array-like of measurement uncertainties. + axes_labels : object + Pair of strings for the x and y titles. + title : str + Figure title. + + Returns + ------- + object + Configured :class:`plotly.graph_objects.Figure`. + """ data = [ self._get_single_crystal_trace( x_calc, @@ -1811,8 +1877,7 @@ def plot_single_crystal( shapes=[self._get_diagonal_shape()], ) - fig = self._get_figure(data, layout) - self._show_figure(fig) + return self._get_figure(data, layout) def plot_scatter( self, diff --git a/src/easydiffraction/display/plotting.py b/src/easydiffraction/display/plotting.py index ce838dbdd..448f6cb73 100644 --- a/src/easydiffraction/display/plotting.py +++ b/src/easydiffraction/display/plotting.py @@ -194,6 +194,7 @@ class _PowderMeasVsCalcSeries: y_meas: np.ndarray y_calc: np.ndarray + y_meas_su: np.ndarray | None = None y_bkg: np.ndarray | None = None @@ -5401,6 +5402,17 @@ def _plot_meas_vs_calc_data( y_calc = self._filtered_y_array( pattern.intensity_calc, ctx['x_array'], ctx['x_min'], ctx['x_max'] ) + y_meas_su_raw = getattr(pattern, 'intensity_meas_su', None) + y_meas_su = ( + self._filtered_y_array( + y_meas_su_raw, + ctx['x_array'], + ctx['x_min'], + ctx['x_max'], + ) + if y_meas_su_raw is not None + else None + ) y_bkg_raw = getattr(pattern, 'intensity_bkg', None) y_bkg = ( self._filtered_y_array(y_bkg_raw, ctx['x_array'], ctx['x_min'], ctx['x_max']) @@ -5413,6 +5425,7 @@ def _plot_meas_vs_calc_data( powder_series = _PowderMeasVsCalcSeries( y_meas=y_meas, y_calc=y_calc, + y_meas_su=y_meas_su, y_bkg=y_bkg, ) excluded_ranges = ( @@ -5515,6 +5528,7 @@ def _plot_powder_bragg_meas_vs_calc( height=self._composite_plot_height(), y_bkg=series.y_bkg, excluded_ranges=excluded_ranges, + y_meas_su=series.y_meas_su, ) self._backend.plot_powder_meas_vs_calc(plot_spec=plot_spec) diff --git a/src/easydiffraction/report/data_context.py b/src/easydiffraction/report/data_context.py index 8364a6d29..96c562fb2 100644 --- a/src/easydiffraction/report/data_context.py +++ b/src/easydiffraction/report/data_context.py @@ -9,6 +9,9 @@ from datetime import datetime from easydiffraction.core.variable import Parameter +from easydiffraction.display.plotters.base import DEFAULT_AXES_LABELS +from easydiffraction.display.plotters.base import DEFAULT_X_AXIS +from easydiffraction.display.plotting import Plotter from easydiffraction.io.cif.serialize import format_param_value from easydiffraction.utils.utils import package_version @@ -447,6 +450,12 @@ def _fit_data_context(experiment: object) -> dict[str, object] | None: return None arrays = experiment.fit_data_arrays() + axes_labels = _fit_data_axes_labels(experiment, x_descriptor) + bragg_tick_sets = _fit_data_bragg_tick_sets( + experiment, + x_axis=x_descriptor.name, + x_values=arrays['x'], + ) return { 'x': { 'values': arrays['x'], @@ -457,6 +466,7 @@ def _fit_data_context(experiment: object) -> dict[str, object] | None: 'display_units': x_descriptor.resolve_display_units('html'), 'latex_units': x_descriptor.resolve_display_units('latex'), }, + 'axes_labels': axes_labels, 'series': { 'meas': { 'values': arrays['meas'], @@ -467,9 +477,57 @@ def _fit_data_context(experiment: object) -> dict[str, object] | None: 'diff': _series_context(arrays['diff'], 'Difference'), 'bkg': _optional_series_context(arrays['bkg'], 'Background'), }, + 'bragg_tick_sets': bragg_tick_sets, } +def _fit_data_axes_labels(experiment: object, x_descriptor: object) -> list[str]: + """Return Plotly display-axis labels for a report fit figure.""" + experiment_type = _safe_attr(experiment, 'type') + try: + sample_form = experiment_type.sample_form.value + scattering_type = experiment_type.scattering_type.value + beam_mode = experiment_type.beam_mode.value + x_axis = DEFAULT_X_AXIS[sample_form, scattering_type, beam_mode] + return list(DEFAULT_AXES_LABELS[sample_form, scattering_type, x_axis]) + except (AttributeError, KeyError): + units = x_descriptor.resolve_display_units('html') + display_name = x_descriptor.resolve_display_name('html') + x_label = f'{display_name} ({units})' if units else display_name + return [x_label, 'Intensity (arb. units)'] + + +def _fit_data_bragg_tick_sets( + experiment: object, + *, + x_axis: object, + x_values: object, +) -> object: + """Return Bragg tick sets for powder Bragg report figures.""" + if not _is_powder_bragg_experiment(experiment): + return () + + values = list(x_values) + if not values: + return () + + return Plotter._extract_bragg_tick_sets( + experiment=experiment, + expt_name=str(_safe_attr(experiment, 'name') or 'experiment'), + x_axis=x_axis, + x_min=float(min(values)), + x_max=float(max(values)), + ) + + +def _is_powder_bragg_experiment(experiment: object) -> bool: + """Return whether an experiment can use powder Bragg plot panels.""" + experiment_type = _safe_attr(experiment, 'type') + sample_form = _value(_safe_attr(experiment_type, 'sample_form')) + scattering_type = _value(_safe_attr(experiment_type, 'scattering_type')) + return sample_form == 'powder' and scattering_type == 'bragg' + + def _series_context(values: object, label: str) -> dict[str, object]: """Return one required fit-data series.""" return {'values': values, 'label': label} diff --git a/src/easydiffraction/report/fit_plot.py b/src/easydiffraction/report/fit_plot.py new file mode 100644 index 000000000..53b9bb6b0 --- /dev/null +++ b/src/easydiffraction/report/fit_plot.py @@ -0,0 +1,133 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Shared report styling for fit-quality figures.""" + +from __future__ import annotations + +import re +from typing import Any + +import numpy as np + +from easydiffraction.display.plotters.base import SERIES_CONFIG +from easydiffraction.display.plotters.plotly import BACKGROUND_LINE_WIDTH +from easydiffraction.display.plotters.plotly import BRAGG_TICK_COLORS +from easydiffraction.display.plotters.plotly import CALCULATED_LINE_WIDTH +from easydiffraction.display.plotters.plotly import DEFAULT_COLORS +from easydiffraction.display.plotters.plotly import MAIN_INTENSITY_RANGE_MARGIN_FRACTION +from easydiffraction.display.plotters.plotly import MEASURED_LINE_WIDTH +from easydiffraction.display.plotters.plotly import RESIDUAL_LINE_WIDTH +from easydiffraction.display.plotting import DEFAULT_RESIDUAL_HEIGHT_FRACTION + +_COLOR_PATTERN = re.compile(r'rgb\((\d+),\s*(\d+),\s*(\d+)\)') +_STYLE_SOURCE_KEYS = { + 'meas': 'meas', + 'bkg': 'bkg', + 'calc': 'calc', + 'diff': 'resid', +} +_LINE_WIDTHS = { + 'meas': MEASURED_LINE_WIDTH, + 'bkg': BACKGROUND_LINE_WIDTH, + 'calc': CALCULATED_LINE_WIDTH, + 'diff': RESIDUAL_LINE_WIDTH, +} +_LEGEND_RANKS = { + 'meas': 10, + 'bkg': 20, + 'calc': 30, + 'diff': 40, +} + + +def fit_plot_styles() -> dict[str, dict[str, Any]]: + """Return Plotly-derived series styles for report figures.""" + return { + key: _fit_plot_style(key, source_key) + for key, source_key in _STYLE_SOURCE_KEYS.items() + } + + +def fit_plot_ranges(fit_data: dict[str, Any]) -> dict[str, float]: + """Return explicit x and main-intensity y ranges for fit figures.""" + x_values = _numeric_values(fit_data['x']['values']) + y_series = [ + _numeric_values(fit_data['series']['meas']['values']), + _numeric_values(fit_data['series']['calc']['values']), + ] + bkg = fit_data['series']['bkg'] + if bkg is not None: + y_series.append(_numeric_values(bkg['values'])) + + x_min, x_max = _data_range([x_values]) + y_min, y_max = _data_range(y_series) + y_margin = max(y_max - y_min, 0.0) * MAIN_INTENSITY_RANGE_MARGIN_FRACTION + if y_margin <= 0.0: + y_margin = 1.0 + + main_y_min = y_min - y_margin + main_y_max = y_max + y_margin + residual_limit = _residual_limit(main_y_min=main_y_min, main_y_max=main_y_max) + return { + 'x_min': x_min, + 'x_max': x_max, + 'y_min': main_y_min, + 'y_max': main_y_max, + 'residual_y_min': -residual_limit, + 'residual_y_max': residual_limit, + } + + +def fit_bragg_tick_styles() -> list[dict[str, str]]: + """Return Plotly-derived Bragg tick colors for pgfplots.""" + return [ + { + 'color_name': f'ed_bragg_{idx}', + 'rgb': _rgb_channels(color), + } + for idx, color in enumerate(BRAGG_TICK_COLORS) + ] + + +def _fit_plot_style(key: str, source_key: str) -> dict[str, Any]: + color = DEFAULT_COLORS[source_key] + return { + 'name': SERIES_CONFIG[source_key]['name'], + 'mode': SERIES_CONFIG[source_key]['mode'], + 'plotly_color': color, + 'rgb': _rgb_channels(color), + 'color_name': f'ed_{key}', + 'line_width': _LINE_WIDTHS[key], + 'legend_rank': _LEGEND_RANKS[key], + } + + +def _residual_limit(*, main_y_min: float, main_y_max: float) -> float: + main_y_range = max(main_y_max - main_y_min, 0.0) + residual_limit = 0.5 * main_y_range * DEFAULT_RESIDUAL_HEIGHT_FRACTION + if residual_limit > 0.0: + return residual_limit + return 1.0 + + +def _rgb_channels(color: str) -> str: + match = _COLOR_PATTERN.fullmatch(color) + if match is None: + msg = f"Unsupported Plotly RGB color '{color}'." + raise ValueError(msg) + return ','.join(match.groups()) + + +def _data_range(series_list: list[list[float]]) -> tuple[float, float]: + values = [value for series in series_list for value in series] + if not values: + return 0.0, 1.0 + minimum = float(np.min(values)) + maximum = float(np.max(values)) + if minimum == maximum: + return minimum - 1.0, maximum + 1.0 + return minimum, maximum + + +def _numeric_values(values: object) -> list[float]: + return [float(value) for value in values] diff --git a/src/easydiffraction/report/html_renderer.py b/src/easydiffraction/report/html_renderer.py index 1aeef577f..c9b210293 100644 --- a/src/easydiffraction/report/html_renderer.py +++ b/src/easydiffraction/report/html_renderer.py @@ -9,9 +9,15 @@ from importlib.resources import as_file from importlib.resources import files +import numpy as np from jinja2 import Environment from jinja2 import PackageLoader +from easydiffraction.display.plotters.base import PowderMeasVsCalcSpec +from easydiffraction.display.plotters.plotly import PlotlyPlotter +from easydiffraction.display.plotting import DEFAULT_BRAGG_ROW +from easydiffraction.display.plotting import DEFAULT_RESID_HEIGHT + _TEMPLATE_NAME = 'html/report.html.j2' _MATHJAX_FILENAME = 'mathjax-tex-mml-chtml.js' @@ -166,7 +172,7 @@ def _fit_figure_html_context( if fit_data is None: continue experiment_id = str(experiment.get('id') or 'experiment') - figure = _fit_data_figure(experiment_id, fit_data) + figure = _fit_data_figure(experiment_id, fit_data, experiment) rendered[experiment_id] = _figure_html( figure, include_plotlyjs=include_plotlyjs, @@ -175,9 +181,12 @@ def _fit_figure_html_context( return rendered -def _fit_data_figure(experiment_id: str, fit_data: dict[str, object]) -> object: +def _fit_data_figure( + experiment_id: str, + fit_data: dict[str, object], + experiment: dict[str, object], +) -> object: """Build a Plotly fit figure from one fit-data payload.""" - go = _plotly_go() x_data = fit_data['x'] series = fit_data['series'] x_values = _value_list(x_data['values']) @@ -188,59 +197,87 @@ def _fit_data_figure(experiment_id: str, fit_data: dict[str, object]) -> object: _validate_same_length(experiment_id, 'calc', x_values, y_calc) _validate_same_length(experiment_id, 'diff', x_values, y_diff) - measured_trace = { - 'x': x_values, - 'y': y_meas, - 'mode': 'markers', - 'name': series['meas']['label'], - } - y_meas_su = series['meas']['su'] - if y_meas_su is not None: - su_values = _value_list(y_meas_su) - _validate_same_length(experiment_id, 'meas_su', x_values, su_values) - measured_trace['error_y'] = { - 'type': 'data', - 'array': su_values, - 'visible': True, - } - - fig = go.Figure() - fig.add_trace(go.Scatter(**measured_trace)) - fig.add_trace( - go.Scatter( - x=x_values, - y=y_calc, - mode='lines', - name=series['calc']['label'], - ) - ) + y_meas_su = None + y_meas_su_data = series['meas']['su'] + if y_meas_su_data is not None: + y_meas_su = _value_list(y_meas_su_data) + _validate_same_length(experiment_id, 'meas_su', x_values, y_meas_su) + + y_bkg = None bkg = series['bkg'] if bkg is not None: y_bkg = _value_list(bkg['values']) _validate_same_length(experiment_id, 'bkg', x_values, y_bkg) - fig.add_trace( - go.Scatter( - x=x_values, - y=y_bkg, - mode='lines', - name=bkg['label'], - ) + + if x_data.get('name') == 'intensity_calc': + return _single_crystal_fit_data_figure( + experiment_id=experiment_id, + fit_data=fit_data, + x_values=x_values, + y_meas=y_meas, + y_meas_su=y_meas_su, ) - fig.add_trace( - go.Scatter( - x=x_values, - y=y_diff, - mode='lines', - line={'dash': 'dash'}, - name=series['diff']['label'], + + return PlotlyPlotter().build_powder_meas_vs_calc_figure( + plot_spec=PowderMeasVsCalcSpec( + x=np.asarray(x_values, dtype=float), + y_meas=np.asarray(y_meas, dtype=float), + y_calc=np.asarray(y_calc, dtype=float), + y_resid=np.asarray(y_diff, dtype=float), + bragg_tick_sets=tuple(fit_data.get('bragg_tick_sets') or ()), + axes_labels=list(fit_data.get('axes_labels') or [_axis_title(x_data), 'Intensity']), + title=_fit_figure_title(experiment_id, experiment), + residual_height_fraction=DEFAULT_RESID_HEIGHT, + bragg_peaks_height_fraction=DEFAULT_BRAGG_ROW, + y_bkg=np.asarray(y_bkg, dtype=float) if y_bkg is not None else None, + y_meas_su=( + np.asarray(y_meas_su, dtype=float) + if y_meas_su is not None + else None + ), ) ) - _configure_fit_figure( - fig, - experiment_id=experiment_id, - x_title=_axis_title(x_data), + + +def _single_crystal_fit_data_figure( + *, + experiment_id: str, + fit_data: dict[str, object], + x_values: list[object], + y_meas: list[object], + y_meas_su: list[object] | None, +) -> object: + """Build a single-crystal Plotly report figure.""" + y_meas_array = np.asarray(y_meas, dtype=float) + if y_meas_su is None: + y_meas_su_array = np.zeros_like(y_meas_array) + else: + y_meas_su_array = np.asarray(y_meas_su, dtype=float) + + return PlotlyPlotter().build_single_crystal_figure( + x_calc=np.asarray(x_values, dtype=float), + y_meas=y_meas_array, + y_meas_su=y_meas_su_array, + axes_labels=list(fit_data.get('axes_labels') or ['I²calc', 'I²meas']), + title=f"Measured vs Calculated data for experiment 🔬 '{experiment_id}'", + ) + + +def _fit_figure_title(experiment_id: str, experiment: dict[str, object]) -> str: + """Return a report title matching the direct plotting API.""" + experiment_type = experiment.get('type') + if _is_powder_bragg_context(experiment_type): + return f"Measured vs Calculated data for experiment 🔬 '{experiment_id}'" + return f'Measured vs calculated: {experiment_id}' + + +def _is_powder_bragg_context(experiment_type: object) -> bool: + if not isinstance(experiment_type, dict): + return False + return ( + experiment_type.get('sample_form') == 'powder' + and experiment_type.get('scattering_type') == 'bragg' ) - return fig def _figure_html(figure: object, *, include_plotlyjs: bool | str) -> str: @@ -251,30 +288,12 @@ def _figure_html(figure: object, *, include_plotlyjs: bool | str) -> str: return str(figure) -def _configure_fit_figure( - fig: object, - *, - experiment_id: str, - x_title: str, -) -> None: - """Apply shared report-figure layout.""" - fig.update_layout( - template='plotly_white', - title=f'Measured vs calculated: {experiment_id}', - xaxis_title=x_title, - yaxis_title='Intensity', - height=440, - margin={'l': 64, 'r': 24, 't': 64, 'b': 56}, - legend={'orientation': 'h', 'yanchor': 'bottom', 'y': 1.02}, - ) - - def _axis_title(x_data: dict[str, object]) -> str: """Return a display axis title for fit figures.""" - units = x_data['display_units'] + units = x_data.get('display_units') if units: - return f"{x_data['display_name']} ({units})" - return str(x_data['display_name']) + return f"{x_data.get('display_name')} ({units})" + return str(x_data.get('display_name') or '') def _validate_same_length( @@ -309,10 +328,3 @@ def _experiment_contexts(context: dict[str, object]) -> list[dict[str, object]]: for experiment in experiments if isinstance(experiment, dict) ] - - -def _plotly_go() -> object: - """Return Plotly graph objects for report figures.""" - import plotly.graph_objects as go # noqa: PLC0415 - - return go diff --git a/src/easydiffraction/report/templates/html/report.html.j2 b/src/easydiffraction/report/templates/html/report.html.j2 index d6e92bd3b..b5bdf6ff0 100644 --- a/src/easydiffraction/report/templates/html/report.html.j2 +++ b/src/easydiffraction/report/templates/html/report.html.j2 @@ -1,25 +1,4 @@ {% import "base.j2" as base %} -{% macro metadata_table(title, values) -%} - -{%- endmacro %} {% macro field_header(field, fallback) -%} {{ field.label or fallback }}{% if field.units %} ({{ field.units }}){% endif %} {%- endmacro %} @@ -38,120 +17,124 @@
-
-

EasyDiffraction report

-

{{ project.title or project.name or "Untitled project" }}

- {% if project.description %} -

{{ project.description }}

- {% endif %} -
-
-
Phases
-
{{ project.n_phases }}
-
-
-
Experiments
-
{{ project.n_experiments }}
-
-
-
Generated
-
{{ metadata.generated_at }}
-
-
-
Version
-
{{ metadata.easydiffraction_version or "not available" }}
-
-
+
+

{{ project.title or project.name or "EasyDiffraction report" }}

+ {% if project.description %}
-

Refinement

-
-
- Reduced chi-square - {{ base.format_number(refinement.fit_result.reduced_chi_square) }} -
-
- Parameters - - {{ refinement.parameters.free or 0 }} / - {{ refinement.parameters.total or 0 }} - -
-
- Constraints - {{ refinement.constraints }} -
-
- +

Abstract

+

{{ project.description }}

+ + {% endif %} + +
+

Project Summary

+
- {% for key, value in refinement.fit_result.items() if value is not none %} - - + + + + + + + + + + + + + + + + + + - {% endfor %}
{{ key | replace("_", " ") }}{{ value }}Short name{{ project.name }}
Title{{ project.title }}
Structures{{ project.n_phases }}
Experiments{{ project.n_experiments }}
Generated{{ metadata.generated_at }}

Software

-
- {% for role_name, role in software.items() if role_name != "fit_datetime" %} -
-

{{ role_name | replace("_", " ") }}

-

{{ role.name or "not available" }}

- {% if role.version %}

Version {{ role.version }}

{% endif %} - {% if role.url %}

{{ role.url }}

{% endif %} -
- {% endfor %} -
- {% if software.fit_datetime %} -

Fit timestamp: {{ software.fit_datetime }}

- {% endif %} -
- -
-

Publication

-
- {{ metadata_table("Journal", publication.journal) }} - {{ metadata_table("Contact author", publication.contact_author) }} - {{ metadata_table("Body", publication.body) }} -
- {% if publication.authors %} -

Authors

+ - - + - {% for author in publication.authors %} - - - + + + + + + + + + + + + + + + +
Role NameORCIDIUCrVersion
{{ author.name }}{{ author.id_orcid or "" }}{{ author.id_iucr or "" }}Framework{{ software.framework.name }}{{ software.framework.version }}
Calculator{{ software.calculator.name }}{{ software.calculator.version }}
Minimizer{{ software.minimizer.name }}{{ software.minimizer.version }}
+
+ +
+

Refinement

+ + + + + + + + + + + + + + + + + + + + + + + + + - {% endfor %}
Reduced chi-square{{ base.format_number(refinement.fit_result.reduced_chi_square) }}
Free parameters{{ base.format_number(refinement.parameters.free) }}
Total parameters{{ base.format_number(refinement.parameters.total) }}
Constraints{{ base.format_number(refinement.constraints) }}
R factor{{ base.format_number(refinement.fit_result.r_factor_all) }}
Weighted R factor{{ base.format_number(refinement.fit_result.wr_factor_all) }}
- {% endif %}

Structures

{% for structure in structures %}
-
-

{{ structure.id }}

-

{{ structure.space_group or "space group not available" }}

-
- +

{{ structure.id }}

+ +

Space group and unit cell parameters

+
+ + + + + + + + {% for key, value in structure.cell.items() %} @@ -160,7 +143,9 @@ {% endfor %}
Space group{{ structure.space_group }}
Crystal system{{ structure.crystal_system }}
{{ field_header(structure.cell_display[key], key | replace("_", " ")) }}
+ {% if structure.atom_sites %} +

Atom site parameters

@@ -190,7 +175,9 @@
{% endif %} + {% if structure.atom_site_aniso %} +

Anisotropic atomic displacement parameters

@@ -228,19 +215,30 @@

Experiments

{% for experiment in experiments %}
-
-

{{ experiment.id }}

-

- {{ experiment.type.sample_form or "sample form not available" }}, - {{ experiment.type.radiation_probe or "probe not available" }}, - {{ experiment.type.beam_mode or "beam mode not available" }} -

-
-
+

{{ experiment.id }}

+ +

Experiment details

+
+ + + + + + + + + + + + + + + + - + @@ -252,7 +250,9 @@
Sample form{{ experiment.type.sample_form }}
Probe{{ experiment.type.radiation_probe }}
Beam mode{{ experiment.type.beam_mode }}
Scattering type{{ experiment.type.scattering_type }}
Calculator{{ experiment.calculator.type or "not available" }}{{ experiment.calculator.type }}
{{ field_header(experiment.diffrn_display.ambient_temperature, "Temperature") }}
+ {% if fit_figures[experiment.id] %} +

Fit quality

{{ fit_figures[experiment.id] | safe }}
{% endif %}
diff --git a/src/easydiffraction/report/templates/html/style.css b/src/easydiffraction/report/templates/html/style.css index d5cae344d..f1fcddb74 100644 --- a/src/easydiffraction/report/templates/html/style.css +++ b/src/easydiffraction/report/templates/html/style.css @@ -1,13 +1,11 @@ :root { color-scheme: light; - --bg: #f7f8f6; - --surface: #ffffff; - --ink: #1c2430; - --muted: #657182; - --rule: #d8ded6; - --accent: #1d6f68; - --accent-strong: #124d49; - --table: #eef3f1; + --paper: #ffffff; + --ink: #20242a; + --muted: #5f6874; + --rule: #20242a; + --light-rule: #d7dce2; + --link: #245a9b; } * { @@ -16,159 +14,95 @@ body { margin: 0; - background: var(--bg); + background: #f3f4f6; color: var(--ink); - font: 16px/1.55 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font: 15px/1.55 "Helvetica Neue", Arial, sans-serif; } .report-shell { - width: min(1120px, calc(100% - 32px)); + width: min(960px, calc(100% - 32px)); margin: 0 auto; - padding: 32px 0 56px; + padding: 42px 52px 64px; + background: var(--paper); } -.report-header { - padding: 28px 0 20px; - border-bottom: 2px solid var(--accent); -} - -.eyebrow { - margin: 0 0 8px; - color: var(--accent-strong); - font-size: 0.78rem; - font-weight: 700; - letter-spacing: 0; - text-transform: uppercase; +.report-title { + margin-bottom: 28px; + text-align: center; } h1, h2, h3, +h4, p { margin-top: 0; } h1 { - max-width: 820px; - margin-bottom: 14px; - font-size: 2.25rem; - line-height: 1.1; + margin-bottom: 0; + font-size: 2rem; + font-weight: 600; + line-height: 1.2; } h2 { - margin-bottom: 16px; + margin: 30px 0 12px; font-size: 1.35rem; + font-weight: 600; } h3 { - margin-bottom: 8px; - font-size: 1rem; + margin: 22px 0 10px; + font-size: 1.1rem; + font-weight: 600; } -.lede { - max-width: 780px; - color: var(--muted); +h4 { + margin: 18px 0 8px; + font-size: 0.98rem; + font-weight: 600; } section { - padding: 28px 0; - border-bottom: 1px solid var(--rule); -} - -.summary-grid, -.metrics, -.software-grid, -.publication-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); - gap: 12px; -} - -.summary-grid { - margin: 24px 0 0; -} - -.summary-grid div, -.metrics div, -.software-grid article, -.metadata-card, -.record { - border: 1px solid var(--rule); - border-radius: 8px; - background: var(--surface); -} - -.summary-grid div, -.metrics div { - padding: 14px 16px; -} - -.summary-grid dt, -.metrics span { - color: var(--muted); - font-size: 0.82rem; -} - -.summary-grid dd { - margin: 4px 0 0; - font-size: 1.2rem; - font-weight: 700; -} - -.metrics strong { - display: block; - margin-top: 4px; - font-size: 1.2rem; -} - -.software-grid article, -.metadata-card, -.record { - padding: 16px; + margin-top: 24px; } .record + .record { - margin-top: 16px; -} - -.record header { - display: flex; - flex-wrap: wrap; - align-items: baseline; - justify-content: space-between; - gap: 8px 18px; -} - -.muted, -.record header p { - color: var(--muted); + margin-top: 26px; } table { width: 100%; + margin: 8px 0 16px; border-collapse: collapse; - margin-top: 12px; - font-size: 0.93rem; + border-top: 2px solid var(--rule); + border-bottom: 2px solid var(--rule); + font-size: 0.94rem; +} + +thead th { + border-bottom: 1px solid var(--rule); } th, td { - padding: 8px 10px; - border-bottom: 1px solid var(--rule); + padding: 6px 9px; text-align: left; vertical-align: top; } th { - width: 34%; - background: var(--table); - color: var(--accent-strong); - font-weight: 650; + font-weight: 600; +} + +tbody tr + tr th, +tbody tr + tr td { + border-top: 1px solid var(--light-rule); } -tbody tr:last-child th, -tbody tr:last-child td { - border-bottom: 0; +.key-value th { + width: 32%; } .table-scroll { @@ -176,30 +110,28 @@ tbody tr:last-child td { } .figure { - margin-top: 16px; - min-height: 280px; + margin: 8px 0 18px; +} + +.figure .plotly-graph-div { + width: 100% !important; +} + +a { + color: var(--link); } -@media (max-width: 640px) { +@media (max-width: 700px) { body { - font-size: 15px; + font-size: 14px; } .report-shell { - width: min(100% - 20px, 1120px); - padding-top: 18px; + width: 100%; + padding: 24px 16px 42px; } h1 { - font-size: 1.75rem; - } - - .record header { - display: block; - } - - th, - td { - padding: 7px 8px; + font-size: 1.55rem; } } diff --git a/src/easydiffraction/report/templates/tex/report.tex.j2 b/src/easydiffraction/report/templates/tex/report.tex.j2 index 0a61ccfcb..70abec2d7 100644 --- a/src/easydiffraction/report/templates/tex/report.tex.j2 +++ b/src/easydiffraction/report/templates/tex/report.tex.j2 @@ -1,100 +1,167 @@ -\documentclass{styles/iucrjournals} +\documentclass[11pt,a4paper]{styles/iucrjournals} \usepackage{pgfplots} +\usepgfplotslibrary{groupplots} \pgfplotsset{compat=1.18} +{% for style in tex.fit_plot_styles.values() %} +\definecolor{ {{- style.color_name -}} }{RGB}{ {{- style.rgb -}} } +{% endfor %} +{% for style in tex.fit_bragg_tick_styles %} +\definecolor{ {{- style.color_name -}} }{RGB}{ {{- style.rgb -}} } +{% endfor %} {% macro tex_field_header(field, fallback) -%} {% set label = (field.label or fallback) | tex_markup -%} {% set units = field.units | tex_markup -%} {{ label }}{% if field.units %} ({{ units }}){% endif %} {%- endmacro %} -\title{ {{- (publication.body.title or project.title or project.name or "EasyDiffraction report") | tex -}} } -{% if publication.authors %} -{% for author in publication.authors %} -\author{ {{- author.name | tex -}} } -{% endfor %} -{% else %} -\author{EasyDiffraction user} -{% endif %} +%---------------------------------------------------------- +% Title +%---------------------------------------------------------- +\title{ {{- (project.title or project.name or "EasyDiffraction report") | tex -}} } +\author{} +%---------------------------------------------------------- +% Begin document +%---------------------------------------------------------- \begin{document} \maketitle -{% if publication.body.abstract or publication.body.synopsis %} +{% if project.description %} \begin{abstract} -{{ (publication.body.abstract or publication.body.synopsis) | tex }} +{{ project.description | tex }} \end{abstract} {% endif %} -\section{Project summary} +%---------------------------------------------------------- +% Section +%---------------------------------------------------------- +\section{Project Summary} +\begin{table}[H] \begin{tabular}{ll} -Project & {{ project.name | tex }} \\ +\toprule +Short name & {{ project.name | tex }} \\ Title & {{ project.title | tex }} \\ -Phases & {{ project.n_phases }} \\ +Structures & {{ project.n_phases }} \\ Experiments & {{ project.n_experiments }} \\ Generated & {{ metadata.generated_at | tex }} \\ -EasyDiffraction version & {{ metadata.easydiffraction_version | tex }} \\ +\bottomrule +\end{tabular} +\end{table} + +%---------------------------------------------------------- +% Section +%---------------------------------------------------------- +\section{Software} + +\begin{table}[H] +\begin{tabular}{lll} +\toprule +Role & Name & Version \\ +\midrule +Framework & {{ software.framework.name | tex }} & {{ software.framework.version | tex }} \\ +Calculator & {{ software.calculator.name | tex }} & {{ software.calculator.version | tex }} \\ +Minimizer & {{ software.minimizer.name | tex }} & {{ software.minimizer.version | tex }} \\ +\bottomrule \end{tabular} +\end{table} +%---------------------------------------------------------- +% Section +%---------------------------------------------------------- \section{Refinement} +\begin{table}[H] \begin{tabular}{ll} +\toprule Reduced chi-square & {{ refinement.fit_result.reduced_chi_square | tex_number }} \\ Free parameters & {{ refinement.parameters.free | tex_number }} \\ Total parameters & {{ refinement.parameters.total | tex_number }} \\ Constraints & {{ refinement.constraints | tex_number }} \\ R factor & {{ refinement.fit_result.r_factor_all | tex_number }} \\ Weighted R factor & {{ refinement.fit_result.wr_factor_all | tex_number }} \\ +\bottomrule \end{tabular} +\end{table} -\section{Software} - -\begin{tabular}{lll} -Role & Name & Version \\ -Framework & {{ software.framework.name | tex }} & {{ software.framework.version | tex }} \\ -Calculator & {{ software.calculator.name | tex }} & {{ software.calculator.version | tex }} \\ -Minimizer & {{ software.minimizer.name | tex }} & {{ software.minimizer.version | tex }} \\ -\end{tabular} - +%---------------------------------------------------------- +% Section +%---------------------------------------------------------- \section{Structures} {% for structure in structures %} +%---------- +% Structure +%---------- \subsection{ {{- structure.id | tex -}} } +%------------- +\subsubsection{Space group and unit cell parameters} + +\begin{table}[H] \begin{tabular}{ll} +\toprule Space group & {{ structure.space_group | tex }} \\ Crystal system & {{ structure.crystal_system | tex }} \\ {% for key, value in structure.cell.items() %} {{ tex_field_header(structure.cell_latex[key], key | replace("_", " ")) }} & {{ value | tex_number }} \\ {% endfor %} +\bottomrule \end{tabular} +\end{table} {% if structure.atom_sites %} -\begin{tabular}{lllllll} +%------------- +\subsubsection{Atom site parameters} + +\begin{table}[H] +\begin{tabular}{llccccc} +\toprule Label & Type & $x$ & $y$ & $z$ & Occ. & {{ tex_field_header(structure.atom_site_latex.adp_iso, "ADP") }} \\ +\midrule {% for atom in structure.atom_sites %} {{ atom.label | tex }} & {{ atom.type_symbol | tex }} & {{ atom.fract_x | tex_number }} & {{ atom.fract_y | tex_number }} & {{ atom.fract_z | tex_number }} & {{ atom.occupancy | tex_number }} & {{ atom.adp_iso | tex_number }} \\ {% endfor %} +\bottomrule \end{tabular} - +\end{table} {% endif %} + {% if structure.atom_site_aniso %} -\begin{tabular}{lllllll} +%------------- +\subsubsection{Anisotropic atomic displacement parameters} + +\begin{table}[H] +\begin{tabular}{lcccccc} +\toprule Label & {{ tex_field_header(structure.atom_site_aniso_latex.adp_11, "ADP 11") }} & {{ tex_field_header(structure.atom_site_aniso_latex.adp_22, "ADP 22") }} & {{ tex_field_header(structure.atom_site_aniso_latex.adp_33, "ADP 33") }} & {{ tex_field_header(structure.atom_site_aniso_latex.adp_12, "ADP 12") }} & {{ tex_field_header(structure.atom_site_aniso_latex.adp_13, "ADP 13") }} & {{ tex_field_header(structure.atom_site_aniso_latex.adp_23, "ADP 23") }} \\ +\midrule {% for aniso in structure.atom_site_aniso %} {{ aniso.label | tex }} & {{ aniso.adp_11 | tex_number }} & {{ aniso.adp_22 | tex_number }} & {{ aniso.adp_33 | tex_number }} & {{ aniso.adp_12 | tex_number }} & {{ aniso.adp_13 | tex_number }} & {{ aniso.adp_23 | tex_number }} \\ {% endfor %} +\bottomrule \end{tabular} - +\end{table} {% endif %} {% endfor %} +%---------------------------------------------------------- +% Section +%---------------------------------------------------------- \section{Experiments} {% for experiment in experiments %} +%----------- +% Experiment +%----------- \subsection{ {{- experiment.id | tex -}} } +%------------- +\subsubsection{Experiment details} + +\begin{table}[H] \begin{tabular}{ll} +\toprule Sample form & {{ experiment.type.sample_form | tex }} \\ Probe & {{ experiment.type.radiation_probe | tex }} \\ Beam mode & {{ experiment.type.beam_mode | tex }} \\ @@ -102,47 +169,95 @@ Scattering type & {{ experiment.type.scattering_type | tex }} \\ Calculator & {{ experiment.calculator.type | tex }} \\ {{ tex_field_header(experiment.diffrn_latex.ambient_temperature, "Temperature") }} & {{ experiment.diffrn.ambient_temperature | tex_number }} \\ {{ tex_field_header(experiment.diffrn_latex.ambient_pressure, "Pressure") }} & {{ experiment.diffrn.ambient_pressure | tex_number }} \\ +\bottomrule \end{tabular} +\end{table} {% if experiment.fit_data and tex.fit_csv_paths[experiment.id] %} +%------------- +\subsubsection{Fit quality} + \begin{figure}[H] \centering \begin{tikzpicture} -{% set x_label = experiment.fit_data.x.latex_name | tex_markup -%} -{% set x_units = experiment.fit_data.x.latex_units | tex_markup -%} -\begin{axis}[ +{% set ranges = tex.fit_plot_ranges[experiment.id] -%} +{% set styles = tex.fit_plot_styles -%} +{% set axes_labels = experiment.fit_data.axes_labels | default(["", "Intensity"], true) -%} +{% set x_label = axes_labels[0] | tex_axis_label -%} +{% set y_label = axes_labels[1] | tex_axis_label -%} +{% set bragg_tick_sets = experiment.fit_data.bragg_tick_sets | default([], true) -%} +{% set bragg_styles = tex.fit_bragg_tick_styles -%} +\begin{groupplot}[ +group style={ +group size=1 by {% if bragg_tick_sets %}3{% else %}2{% endif %}, +vertical sep=0.04\linewidth, +xlabels at=edge bottom, +xticklabels at=edge bottom, +}, width=\linewidth, -height=0.48\linewidth, -xlabel={ {{ x_label }}{% if experiment.fit_data.x.latex_units %} ({{ x_units }}){% endif %} }, -ylabel={Intensity}, +xmin={{ ranges.x_min | tex_number }}, +xmax={{ ranges.x_max | tex_number }}, +mark layer=like plot, +tick align=outside, +] +\nextgroupplot[ +height=0.34\linewidth, +ymin={{ ranges.y_min | tex_number }}, +ymax={{ ranges.y_max | tex_number }}, +xticklabels=\empty, +ylabel={ {{ y_label }} }, legend pos=north east, +legend style={draw=none, fill=white, fill opacity=0.5, text opacity=1}, ] -\addplot+[only marks, mark size=0.5pt] table[x=x, y=meas, col sep=comma] { {{- tex.fit_csv_paths[experiment.id] -}} }; -\addlegendentry{ {{- experiment.fit_data.series.meas.label | tex -}} } -\addplot+[no markers] table[x=x, y=calc, col sep=comma] { {{- tex.fit_csv_paths[experiment.id] -}} }; -\addlegendentry{ {{- experiment.fit_data.series.calc.label | tex -}} } -\addplot+[no markers, dashed] table[x=x, y=diff, col sep=comma] { {{- tex.fit_csv_paths[experiment.id] -}} }; -\addlegendentry{ {{- experiment.fit_data.series.diff.label | tex -}} } +{% if experiment.fit_data.series.meas.su is not none %} +\addplot+[color={{ styles.meas.color_name }}, line width={{ styles.meas.line_width | tex_number }}pt, line join=bevel, mark=*, mark size=1pt, error bars/.cd, y dir=both, y explicit] table[x=x, y=meas, y error=meas_su, col sep=comma] { {{- tex.fit_csv_paths[experiment.id] -}} }; +{% else %} +\addplot+[color={{ styles.meas.color_name }}, line width={{ styles.meas.line_width | tex_number }}pt, line join=bevel, mark=*, mark size=1pt] table[x=x, y=meas, col sep=comma] { {{- tex.fit_csv_paths[experiment.id] -}} }; +{% endif %} +\addlegendentry{ {{- styles.meas.name | tex -}} } {% if experiment.fit_data.series.bkg %} -\addplot+[no markers, dotted] table[x=x, y=bkg, col sep=comma] { {{- tex.fit_csv_paths[experiment.id] -}} }; -\addlegendentry{ {{- experiment.fit_data.series.bkg.label | tex -}} } +\addplot+[color={{ styles.bkg.color_name }}, line width={{ styles.bkg.line_width | tex_number }}pt, line join=bevel, no markers] table[x=x, y=bkg, col sep=comma] { {{- tex.fit_csv_paths[experiment.id] -}} }; +\addlegendentry{ {{- styles.bkg.name | tex -}} } +{% endif %} +\addplot+[color={{ styles.calc.color_name }}, line width={{ styles.calc.line_width | tex_number }}pt, line join=bevel, no markers] table[x=x, y=calc, col sep=comma] { {{- tex.fit_csv_paths[experiment.id] -}} }; +\addlegendentry{ {{- styles.calc.name | tex -}} } +{% if bragg_tick_sets %} +\nextgroupplot[ +height=0.10\linewidth, +ymin=0.5, +ymax={{ (bragg_tick_sets | length) + 0.5 }}, +y dir=reverse, +ytick={% for tick_set in bragg_tick_sets %}{{ loop.index }}{% if not loop.last %},{% endif %}{% endfor %}, +yticklabels={% for tick_set in bragg_tick_sets %}{{ tick_set.phase_id | tex }}{% if not loop.last %},{% endif %}{% endfor %}, +xticklabels=\empty, +ylabel={Bragg}, +] +{% for tick_set in bragg_tick_sets %} +{% set bragg_row = loop.index -%} +{% set tick_style = bragg_styles[loop.index0 % (bragg_styles | length)] -%} +\addplot+[color={{ tick_style.color_name }}, only marks, mark=|, mark size=5pt] coordinates { +{% for x_value in tick_set.x %} +({{ x_value | tex_number }},{{ bragg_row }}) +{% endfor %} +}; +{% endfor %} {% endif %} -\end{axis} +\nextgroupplot[ +height=0.12\linewidth, +ymin={{ ranges.residual_y_min | tex_number }}, +ymax={{ ranges.residual_y_max | tex_number }}, +xlabel={ {{ x_label }} }, +ylabel={Residual}, +] +\addplot+[color={{ styles.diff.color_name }}, line width={{ styles.diff.line_width | tex_number }}pt, line join=bevel, no markers] table[x=x, y=diff, col sep=comma] { {{- tex.fit_csv_paths[experiment.id] -}} }; +\end{groupplot} \end{tikzpicture} -\caption{Observed and calculated fit for {{ experiment.id | tex }}.} +\caption{Measured and calculated fit for {{ experiment.id | tex }}.} \end{figure} {% endif %} {% endfor %} -\section{Publication metadata} - -\begin{tabular}{ll} -Journal & {{ publication.journal.name_full | tex }} \\ -Year & {{ publication.journal.year | tex }} \\ -DOI & {{ publication.journal.paper_doi | tex }} \\ -Contact author & {{ publication.contact_author.name | tex }} \\ -Contact email & {{ publication.contact_author.email | tex }} \\ -Keywords & {{ publication.body.keywords | tex }} \\ -\end{tabular} - +%---------------------------------------------------------- +% End document +%---------------------------------------------------------- \end{document} diff --git a/src/easydiffraction/report/tex_renderer.py b/src/easydiffraction/report/tex_renderer.py index 4084e606b..b91fae5be 100644 --- a/src/easydiffraction/report/tex_renderer.py +++ b/src/easydiffraction/report/tex_renderer.py @@ -12,6 +12,10 @@ from jinja2 import Environment from jinja2 import PackageLoader +from easydiffraction.report.fit_plot import fit_bragg_tick_styles +from easydiffraction.report.fit_plot import fit_plot_ranges +from easydiffraction.report.fit_plot import fit_plot_styles + _TEMPLATE_NAME = 'tex/report.tex.j2' _TEX_SPECIAL_CHARS = { '\\': r'\textbackslash{}', @@ -76,7 +80,10 @@ def render_tex_report(context: dict[str, object]) -> str: Complete LaTeX document. """ template_context = dict(context) - template_context['tex'] = {'fit_csv_paths': _fit_csv_paths(context)} + template_context['tex'] = _tex_context( + context, + fit_csv_paths=_fit_csv_paths(context), + ) return _environment().get_template(_TEMPLATE_NAME).render(**template_context) @@ -111,7 +118,10 @@ def save_tex_report( styles_dir.mkdir(parents=True, exist_ok=True) template_context = dict(context) - template_context['tex'] = {'fit_csv_paths': _write_fit_csvs(context, tex_dir)} + template_context['tex'] = _tex_context( + context, + fit_csv_paths=_write_fit_csvs(context, tex_dir), + ) output_path.write_text( _render_prepared_context(template_context), encoding='utf-8', @@ -134,6 +144,7 @@ def _environment() -> Environment: lstrip_blocks=True, ) environment.filters['tex'] = _tex_escape + environment.filters['tex_axis_label'] = _tex_axis_label environment.filters['tex_markup'] = _tex_markup environment.filters['tex_number'] = _tex_number return environment @@ -164,6 +175,32 @@ def _write_fit_csvs( return paths +def _tex_context( + context: dict[str, object], + *, + fit_csv_paths: dict[str, str], +) -> dict[str, object]: + """Return TeX-specific render context.""" + return { + 'fit_csv_paths': fit_csv_paths, + 'fit_bragg_tick_styles': fit_bragg_tick_styles(), + 'fit_plot_ranges': _fit_plot_ranges(context), + 'fit_plot_styles': fit_plot_styles(), + } + + +def _fit_plot_ranges(context: dict[str, object]) -> dict[str, dict[str, float]]: + """Return fit-figure axis ranges by experiment id.""" + ranges = {} + for experiment in _experiment_contexts(context): + fit_data = experiment.get('fit_data') + if fit_data is None: + continue + experiment_id = str(experiment.get('id') or 'experiment') + ranges[experiment_id] = fit_plot_ranges(fit_data) + return ranges + + def _write_fit_csv( expt_id: str, fit_data: dict[str, object], @@ -290,6 +327,20 @@ def _tex_markup(value: object) -> str: return _tex_escape(text) +def _tex_axis_label(value: object) -> str: + """Return a TeX-safe axis label from Plotly display text.""" + if value is None: + return '' + text = str(value) + text = text.replace('⁻¹', '$^{-1}$') + text = text.replace('²', '$^2$') + text = text.replace('θ', r'$\theta$') + text = text.replace('λ', r'$\lambda$') + text = text.replace('μ', r'$\mu$') + text = text.replace('Å', r'\AA{}') + return _tex_markup(text) + + def _tex_escape(value: object) -> str: """Escape user-provided text for TeX output.""" if value is None: diff --git a/tests/unit/easydiffraction/display/plotters/test_plotly.py b/tests/unit/easydiffraction/display/plotters/test_plotly.py index 097d4321f..2d24933b1 100644 --- a/tests/unit/easydiffraction/display/plotters/test_plotly.py +++ b/tests/unit/easydiffraction/display/plotters/test_plotly.py @@ -416,6 +416,7 @@ def fake_show_figure(self, fig): y_meas=np.array([10.0, 12.0, 11.0]), y_calc=np.array([9.0, 11.0, 10.5]), y_resid=np.array([1.0, 1.0, 0.5]), + y_meas_su=np.array([0.2, 0.3, 0.4]), bragg_tick_sets=( BraggTickSet( phase_id='phase-a', @@ -477,6 +478,7 @@ def fake_show_figure(self, fig): assert calc_trace.hovertemplate == expected_hovertemplate assert residual_trace.hovertemplate == expected_hovertemplate assert meas_trace.line.width == pp.MEASURED_LINE_WIDTH + assert list(meas_trace.error_y.array) == pytest.approx([0.2, 0.3, 0.4]) assert calc_trace.line.width == pp.CALCULATED_LINE_WIDTH assert residual_trace.line.width == pp.RESIDUAL_LINE_WIDTH assert list(meas_trace.customdata[0]) == pytest.approx([10.0, 9.0, 1.0]) diff --git a/tests/unit/easydiffraction/display/test_plotting.py b/tests/unit/easydiffraction/display/test_plotting.py index d5b6ae76c..c5e279368 100644 --- a/tests/unit/easydiffraction/display/test_plotting.py +++ b/tests/unit/easydiffraction/display/test_plotting.py @@ -1644,6 +1644,7 @@ class Pattern: two_theta = np.array([0.0, 1.0, 2.0, 3.0]) d_spacing = two_theta intensity_meas = np.array([10.0, 20.0, 30.0, 40.0]) + intensity_meas_su = np.array([0.1, 0.2, 0.3, 0.4]) intensity_bkg = np.array([1.0, 2.0, 3.0, 4.0]) intensity_calc = np.array([9.0, 18.0, 27.0, 39.0]) @@ -1679,6 +1680,7 @@ class Experiment: call = captured['powder_meas_vs_calc'] assert np.allclose(call.x, np.array([1.0, 2.0])) assert np.allclose(call.y_meas, np.array([20.0, 30.0])) + assert np.allclose(call.y_meas_su, np.array([0.2, 0.3])) assert np.allclose(call.y_bkg, np.array([2.0, 3.0])) assert np.allclose(call.y_calc, np.array([18.0, 27.0])) assert np.allclose(call.y_resid, np.array([2.0, 3.0])) diff --git a/tests/unit/easydiffraction/report/test_data_context.py b/tests/unit/easydiffraction/report/test_data_context.py index db2402c14..b3a9f4aff 100644 --- a/tests/unit/easydiffraction/report/test_data_context.py +++ b/tests/unit/easydiffraction/report/test_data_context.py @@ -14,6 +14,36 @@ def __init__(self, value): self.value = value +class _XDescriptor: + name = 'intensity_calc' + units = 'none' + + @staticmethod + def resolve_display_name(context): + del context + return 'I²calc' + + @staticmethod + def resolve_display_units(context): + del context + return '' + + +class _TwoThetaDescriptor: + name = 'two_theta' + units = 'degrees' + + @staticmethod + def resolve_display_name(context): + del context + return '2θ' + + @staticmethod + def resolve_display_units(context): + del context + return 'degree' + + def _parameter(name, value, uncertainty): from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.variable import Parameter @@ -83,6 +113,15 @@ def _experiment() -> SimpleNamespace: calculator=SimpleNamespace(type=_Descriptor('cryspy')), diffrn=SimpleNamespace(), measured_range=None, + x_descriptor=_XDescriptor(), + fit_data_arrays=lambda: { + 'x': np.array([10.0, 20.0]), + 'meas': np.array([11.0, 19.0]), + 'meas_su': np.array([0.5, 0.7]), + 'calc': np.array([10.0, 20.0]), + 'diff': np.array([1.0, -1.0]), + 'bkg': None, + }, refln=SimpleNamespace( intensity_meas=np.array([11.0, 19.0]), intensity_calc=np.array([10.0, 20.0]), @@ -106,16 +145,50 @@ def _project() -> SimpleNamespace: ) -def test_report_data_context_builds_fit_figure(): +def test_report_data_context_builds_fit_data(): from easydiffraction.report.data_context import build_report_data_context context = build_report_data_context(_project()) - figure = context['figures']['fit_per_experiment']['heidi'] - assert figure.data[0].name == 'Measured' - assert list(figure.data[0].error_y.array) == [0.5, 0.7] - assert figure.data[1].name == 'I²meas = I²calc' - assert figure.layout.title.text == 'Measured vs calculated: heidi' + fit_data = context['experiments'][0]['fit_data'] + assert fit_data['axes_labels'] == ['I²calc', 'I²meas'] + assert list(fit_data['series']['meas']['su']) == [0.5, 0.7] + assert list(fit_data['series']['calc']['values']) == [10.0, 20.0] + assert list(fit_data['series']['diff']['values']) == [1.0, -1.0] + assert fit_data['bragg_tick_sets'] == () + + +def test_report_data_context_builds_powder_bragg_tick_sets(): + from easydiffraction.report.data_context import build_report_data_context + + experiment = _experiment() + experiment.type.sample_form = _Descriptor('powder') + experiment.x_descriptor = _TwoThetaDescriptor() + experiment.fit_data_arrays = lambda: { + 'x': np.array([1.0, 2.0]), + 'meas': np.array([11.0, 19.0]), + 'meas_su': np.array([0.5, 0.7]), + 'calc': np.array([10.0, 20.0]), + 'diff': np.array([1.0, -1.0]), + 'bkg': np.array([2.0, 2.5]), + } + experiment.refln = SimpleNamespace( + phase_id=np.array(['phase-a']), + two_theta=np.array([1.5]), + index_h=np.array([1]), + index_k=np.array([0]), + index_l=np.array([1]), + f_squared_calc=np.array([100.0]), + f_calc=np.array([10.0]), + ) + project = _project() + project.experiments = {'hrpt': experiment} + + context = build_report_data_context(project) + + tick_sets = context['experiments'][0]['fit_data']['bragg_tick_sets'] + assert [tick_set.phase_id for tick_set in tick_sets] == ['phase-a'] + assert list(tick_sets[0].x) == [1.5] def test_report_data_context_preserves_structure_uncertainties(): diff --git a/tests/unit/easydiffraction/report/test_fit_plot.py b/tests/unit/easydiffraction/report/test_fit_plot.py new file mode 100644 index 000000000..c041f1948 --- /dev/null +++ b/tests/unit/easydiffraction/report/test_fit_plot.py @@ -0,0 +1,47 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Tests for shared report fit-plot helpers.""" + +from __future__ import annotations + +import pytest + + +def test_fit_plot_styles_reuse_plotly_series_conventions(): + from easydiffraction.report.fit_plot import fit_bragg_tick_styles + from easydiffraction.report.fit_plot import fit_plot_styles + + styles = fit_plot_styles() + bragg_styles = fit_bragg_tick_styles() + + assert styles['meas']['name'] == 'Measured (Imeas)' + assert styles['meas']['mode'] == 'lines+markers' + assert styles['meas']['rgb'] == '31,119,180' + assert styles['meas']['line_width'] == 2.0 + assert styles['meas']['legend_rank'] == 10 + assert styles['diff']['name'] == 'Residual (Imeas - Icalc)' + assert bragg_styles[0]['color_name'] == 'ed_bragg_0' + assert bragg_styles[0]['rgb'] == '255,127,14' + + +def test_fit_plot_ranges_match_plotly_main_intensity_margin(): + from easydiffraction.report.fit_plot import fit_plot_ranges + + fit_data = { + 'x': {'values': [1.0, 2.0]}, + 'series': { + 'meas': {'values': [10.0, 30.0]}, + 'calc': {'values': [12.0, 20.0]}, + 'diff': {'values': [-2.0, 10.0]}, + 'bkg': {'values': [5.0, 15.0]}, + }, + } + + ranges = fit_plot_ranges(fit_data) + + assert ranges['x_min'] == pytest.approx(1.0) + assert ranges['x_max'] == pytest.approx(2.0) + assert ranges['y_min'] == pytest.approx(3.75) + assert ranges['y_max'] == pytest.approx(31.25) + assert ranges['residual_y_min'] == pytest.approx(-3.4375) + assert ranges['residual_y_max'] == pytest.approx(3.4375) diff --git a/tests/unit/easydiffraction/report/test_html_renderer.py b/tests/unit/easydiffraction/report/test_html_renderer.py index afc2d2077..8fa46d5b7 100644 --- a/tests/unit/easydiffraction/report/test_html_renderer.py +++ b/tests/unit/easydiffraction/report/test_html_renderer.py @@ -4,12 +4,19 @@ from __future__ import annotations +import numpy as np + + +def _field(label: str, units: str = '') -> dict[str, str]: + return {'label': label, 'units': units} + def _context() -> dict[str, object]: return { 'project': { 'name': 'report_project', 'title': 'Report Project', + 'description': 'Project description.', 'n_phases': 1, 'n_experiments': 0, }, @@ -50,6 +57,14 @@ def _context() -> dict[str, object]: 'angle_beta': '90.()', 'angle_gamma': '90.()', }, + 'cell_display': { + 'length_a': _field('a', 'A'), + 'length_b': _field('b', 'A'), + 'length_c': _field('c', 'A'), + 'angle_alpha': _field('alpha', 'degree'), + 'angle_beta': _field('beta', 'degree'), + 'angle_gamma': _field('gamma', 'degree'), + }, 'atom_sites': [ { 'label': 'Si1', @@ -61,6 +76,12 @@ def _context() -> dict[str, object]: 'adp_iso': '0.00658(14)', } ], + 'atom_site_display': { + 'fract_x': _field('x'), + 'fract_y': _field('y'), + 'fract_z': _field('z'), + 'adp_iso': _field('Uiso', 'A^2'), + }, 'atom_site_aniso': [ { 'label': 'Si1', @@ -72,6 +93,14 @@ def _context() -> dict[str, object]: 'adp_23': '0.00189(13)', } ], + 'atom_site_aniso_display': { + 'adp_11': _field('U11', 'A^2'), + 'adp_22': _field('U22', 'A^2'), + 'adp_33': _field('U33', 'A^2'), + 'adp_12': _field('U12', 'A^2'), + 'adp_13': _field('U13', 'A^2'), + 'adp_23': _field('U23', 'A^2'), + }, } ], 'experiments': [], @@ -87,3 +116,67 @@ def test_render_html_report_preserves_structure_uncertainty_text(): assert '11.985(31)' in html assert '0.00658(14)' in html assert '-0.00048(25)' in html + assert '

Publication

' not in html + assert '

Abstract

' in html + assert '

Project Summary

' in html + + +def test_render_html_report_uses_plotly_fit_style_order(): + from easydiffraction.display.plotters.base import BraggTickSet + from easydiffraction.report.html_renderer import render_html_report + + context = _context() + context['project']['n_experiments'] = 1 + context['experiments'] = [ + { + 'id': 'hrpt', + 'type': { + 'sample_form': 'powder', + 'radiation_probe': 'neutron', + 'beam_mode': 'constant wavelength', + 'scattering_type': 'bragg', + }, + 'calculator': {'type': 'cryspy'}, + 'diffrn': {'ambient_temperature': '', 'ambient_pressure': ''}, + 'diffrn_display': { + 'ambient_temperature': _field('Temperature', 'K'), + 'ambient_pressure': _field('Pressure', 'kPa'), + }, + 'fit_data': { + 'x': {'values': [1.0, 2.0], 'display_name': '2theta'}, + 'axes_labels': ['2θ (degree)', 'Intensity (arb. units)'], + 'series': { + 'meas': {'values': [10.0, 11.0], 'su': [0.1, 0.2]}, + 'calc': {'values': [10.0, 12.0]}, + 'diff': {'values': [0.0, -1.0]}, + 'bkg': {'values': [5.0, 5.5]}, + }, + 'bragg_tick_sets': ( + BraggTickSet( + phase_id='phase-a', + x=np.array([1.5]), + h=np.array([1]), + k=np.array([0]), + ell=np.array([1]), + f_squared_calc=np.array([100.0]), + f_calc=np.array([10.0]), + ), + ), + }, + } + ] + + html = render_html_report(context) + + measured = html.index('"name":"Measured (Imeas)"') + background = html.index('"name":"Background (Ibkg)"') + calculated = html.index('"name":"Total calculated (Icalc)"') + residual = html.index('"name":"Residual (Imeas - Icalc)"') + assert measured < background < calculated < residual + assert '"legendrank":10' in html + assert '"legendrank":20' in html + assert '"legendrank":30' in html + assert '"legendrank":40' in html + assert '"array":[0.1,0.2]' in html + assert '"name":"Bragg peaks: phase-a"' in html + assert '"yaxis3"' in html diff --git a/tests/unit/easydiffraction/report/test_tex_renderer.py b/tests/unit/easydiffraction/report/test_tex_renderer.py index 5a12b4ac3..144fb8e64 100644 --- a/tests/unit/easydiffraction/report/test_tex_renderer.py +++ b/tests/unit/easydiffraction/report/test_tex_renderer.py @@ -3,6 +3,8 @@ from __future__ import annotations +import numpy as np + def _minimal_context() -> dict[str, object]: """Return the smallest report context accepted by TeX templates.""" @@ -10,6 +12,7 @@ def _minimal_context() -> dict[str, object]: 'project': { 'name': 'report_project', 'title': 'Report Project', + 'description': '', 'n_phases': 0, 'n_experiments': 0, }, @@ -66,10 +69,20 @@ def _latex_field(label: str, units: str = '') -> dict[str, str]: def test_render_tex_report_renders_default_document(): from easydiffraction.report.tex_renderer import render_tex_report - tex = render_tex_report(_minimal_context()) + context = _minimal_context() + context['project']['description'] = 'Project description.' + tex = render_tex_report(context) - assert r'\documentclass{styles/iucrjournals}' in tex - assert r'\section{Project summary}' in tex + assert r'\documentclass[11pt,a4paper]{styles/iucrjournals}' in tex + assert r'\title{Report Project}' in tex + assert r'\author{}' in tex + assert r'\begin{abstract}' in tex + assert 'Project description.' in tex + assert r'\section{Project Summary}' in tex + assert r'\begin{table}[H]' in tex + assert r'\toprule' in tex + assert r'\bottomrule' in tex + assert r'\section{Publication' not in tex def test_render_tex_report_preserves_structure_uncertainty_text(): @@ -139,9 +152,13 @@ def test_render_tex_report_preserves_structure_uncertainty_text(): assert '0.00658(14)' in tex assert '-0.00048(25)' in tex assert ( - r'\end{tabular}' '\n\n' r'\begin{tabular}{lllllll}' - '\nLabel &' + r'\subsubsection{Atom site parameters}' '\n\n' + r'\begin{table}[H]' '\n' + r'\begin{tabular}{llccccc}' '\n' + r'\toprule' '\n' + r'Label &' ) in tex + assert r'\midrule' in tex def test_render_tex_report_escapes_plain_latex_field_labels(): @@ -172,6 +189,7 @@ def test_render_tex_report_escapes_plain_latex_field_labels(): 'latex_name': 'time_of_flight', 'latex_units': 'micro_seconds', }, + 'axes_labels': ['time_of_flight (micro_seconds)', 'intensity_obs'], 'series': { 'meas': {'values': [1.0], 'su': None, 'label': 'Measured'}, 'calc': {'values': [1.0], 'label': 'Calculated'}, @@ -187,6 +205,68 @@ def test_render_tex_report_escapes_plain_latex_field_labels(): assert r'ambient\_temperature (K)' in tex assert r'ambient\_pressure (kPa)' in tex assert r'xlabel={ time\_of\_flight (micro\_seconds) }' in tex + assert r'ylabel={ intensity\_obs }' in tex + assert r'\definecolor{ed_meas}{RGB}{31,119,180}' in tex + assert 'line join=bevel' in tex + + +def test_render_tex_report_uses_composite_pgfplots_with_error_bars(): + from easydiffraction.display.plotters.base import BraggTickSet + from easydiffraction.report.tex_renderer import render_tex_report + + context = _minimal_context() + context['experiments'] = [ + { + 'id': 'hrpt', + 'type': { + 'sample_form': 'powder', + 'radiation_probe': 'neutron', + 'beam_mode': 'constant wavelength', + 'scattering_type': 'bragg', + }, + 'calculator': {'type': 'cryspy'}, + 'diffrn': { + 'ambient_temperature': '', + 'ambient_pressure': '', + }, + 'diffrn_latex': { + 'ambient_temperature': _latex_field('Temperature', 'K'), + 'ambient_pressure': _latex_field('Pressure', 'kPa'), + }, + 'fit_data': { + 'x': {'values': [1.0, 2.0]}, + 'axes_labels': ['2θ (degree)', 'Intensity (arb. units)'], + 'series': { + 'meas': {'values': [10.0, 12.0], 'su': [0.2, 0.3]}, + 'calc': {'values': [9.0, 11.0]}, + 'diff': {'values': [1.0, 1.0]}, + 'bkg': {'values': [2.0, 2.5]}, + }, + 'bragg_tick_sets': ( + BraggTickSet( + phase_id='phase-a', + x=np.array([1.5]), + h=np.array([1]), + k=np.array([0]), + ell=np.array([1]), + f_squared_calc=np.array([100.0]), + f_calc=np.array([10.0]), + ), + ), + }, + } + ] + + tex = render_tex_report(context) + + assert r'\usepgfplotslibrary{groupplots}' in tex + assert r'\begin{groupplot}' in tex + assert 'group size=1 by 3' in tex + assert 'mark layer=like plot' in tex + assert 'y error=meas_su' in tex + assert 'mark=|' in tex + assert 'ylabel={Residual}' in tex + assert tex.index('color=ed_meas') < tex.index('color=ed_calc') def test_save_tex_report_removes_stale_managed_bundle_dirs(tmp_path): From bed21df396e875f15fdbcc0b27d740766dea0cb9 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 28 May 2026 12:49:32 +0200 Subject: [PATCH 057/129] Fix TeX report ytick brace escaping --- src/easydiffraction/report/templates/tex/report.tex.j2 | 4 ++-- tests/unit/easydiffraction/report/test_html_renderer.py | 3 ++- tests/unit/easydiffraction/report/test_tex_renderer.py | 2 ++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/easydiffraction/report/templates/tex/report.tex.j2 b/src/easydiffraction/report/templates/tex/report.tex.j2 index 70abec2d7..de56ac237 100644 --- a/src/easydiffraction/report/templates/tex/report.tex.j2 +++ b/src/easydiffraction/report/templates/tex/report.tex.j2 @@ -227,8 +227,8 @@ height=0.10\linewidth, ymin=0.5, ymax={{ (bragg_tick_sets | length) + 0.5 }}, y dir=reverse, -ytick={% for tick_set in bragg_tick_sets %}{{ loop.index }}{% if not loop.last %},{% endif %}{% endfor %}, -yticklabels={% for tick_set in bragg_tick_sets %}{{ tick_set.phase_id | tex }}{% if not loop.last %},{% endif %}{% endfor %}, +ytick={{ "{" }}{% for tick_set in bragg_tick_sets %}{{ loop.index }}{% if not loop.last %},{% endif %}{% endfor %}{{ "}" }}, +yticklabels={{ "{" }}{% for tick_set in bragg_tick_sets %}{{ "{" }}{{ tick_set.phase_id | tex }}{{ "}" }}{% if not loop.last %},{% endif %}{% endfor %}{{ "}" }}, xticklabels=\empty, ylabel={Bragg}, ] diff --git a/tests/unit/easydiffraction/report/test_html_renderer.py b/tests/unit/easydiffraction/report/test_html_renderer.py index 8fa46d5b7..a61e0f249 100644 --- a/tests/unit/easydiffraction/report/test_html_renderer.py +++ b/tests/unit/easydiffraction/report/test_html_renderer.py @@ -177,6 +177,7 @@ def test_render_html_report_uses_plotly_fit_style_order(): assert '"legendrank":20' in html assert '"legendrank":30' in html assert '"legendrank":40' in html - assert '"array":[0.1,0.2]' in html + assert '"error_y"' in html + assert '"color":"rgb(31, 119, 180)"' in html assert '"name":"Bragg peaks: phase-a"' in html assert '"yaxis3"' in html diff --git a/tests/unit/easydiffraction/report/test_tex_renderer.py b/tests/unit/easydiffraction/report/test_tex_renderer.py index 144fb8e64..a1971720f 100644 --- a/tests/unit/easydiffraction/report/test_tex_renderer.py +++ b/tests/unit/easydiffraction/report/test_tex_renderer.py @@ -265,6 +265,8 @@ def test_render_tex_report_uses_composite_pgfplots_with_error_bars(): assert 'mark layer=like plot' in tex assert 'y error=meas_su' in tex assert 'mark=|' in tex + assert 'ytick={1}' in tex + assert 'yticklabels={{phase-a}}' in tex assert 'ylabel={Residual}' in tex assert tex.index('color=ed_meas') < tex.index('color=ed_calc') From 4fcce30e7a7389b4265c48bf5297086a2747e088 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 28 May 2026 13:27:33 +0200 Subject: [PATCH 058/129] Promote project-summary-rendering ADR to accepted --- .../accepted/project-summary-rendering.md | 364 +++++++++++++----- 1 file changed, 268 insertions(+), 96 deletions(-) diff --git a/docs/dev/adrs/accepted/project-summary-rendering.md b/docs/dev/adrs/accepted/project-summary-rendering.md index 8238bb522..a239e98d5 100644 --- a/docs/dev/adrs/accepted/project-summary-rendering.md +++ b/docs/dev/adrs/accepted/project-summary-rendering.md @@ -855,6 +855,42 @@ minified) and refreshed on the same cadence as `iucrjournals.cls`. No new Python dependency — it's a static JavaScript asset shipped with the wheel. +**Plotly figures: forced light theme + the notebook +modebar.** Two appearance rules pin the report's charts to a +consistent, document-appropriate look: + +- **Light theme only.** Every figure in the HTML report + renders with the `plotly_white` template, regardless of + the author's or reader's system / notebook dark-mode + setting. The interactive notebook path deliberately + switches `plotly_white` ↔ `plotly_dark` by theme + (`PlotlyPlotter._default_template_name` in + `display/plotters/plotly.py`), but a shared or printed + report must not inherit a dark background. The HTML + renderer therefore sets the figure's `template` to + `plotly_white` **explicitly per figure** — not via the + global `pio.templates.default` — so the report's + appearance is independent of the ambient theme at render + time. +- **Notebook modebar, not Plotly's default.** The figure + toolbar (zoom / pan / etc.) reuses the **same notebook + serialization mechanics**, which are two distinct pieces: + (1) the standard Plotly `config` from + `PlotlyPlotter._get_config()` — `displayModeBar=True`, + `displaylogo=False`, the curated `modeBarButtonsToRemove` + set (`select2d`, `lasso2d`, `zoomIn2d`, `zoomOut2d`, + `autoScale2d`) — passed to `fig.to_html(config=...)`; and + (2) the **custom legend-toggle button**, which is *not* a + `config` entry — it is installed by the notebook's + `post_script` plus the `_wrap_html_figure` wrapper. The + report must apply **both** (config *and* post-script + + wrapper) and must **not** fall back to Plotly's default + full modebar. These serialization mechanics have one + source of truth in `display/plotters/plotly.py`, shared by + the notebook and the HTML report; only the forced + `plotly_white` template above is report-specific (the + notebook stays theme-adaptive). + `reports/` is created lazily — only when at least one format is configured (or an ad-hoc method is called). A user iterating on a fit with the default `formats = []` produces no extra files. @@ -888,21 +924,32 @@ flag-based and "auto on every save" positions): LaTeX-math strings on both sides, just rendered by MathJax in the browser and by the TeX engine in the PDF. -**Visual consistency with the PDF.** The HTML template -applies academic-paper CSS — top/middle/bottom table rules -that mimic `booktabs`, a serif body font, narrow margins, -table cells in a tabular sans-serif numeric face — so a -reader scrolling the HTML page sees roughly the same -layout the compiled PDF gives them. Plots stay -format-specific (Plotly interactive in HTML, pgfplots -static in PDF), but every label, header, and unit string -matches because both renderers consume the same -`DisplayHandler` data per §1.5. +**Visual consistency with the PDF.** The HTML template mirrors +the TeX/PDF report structure rather than presenting a separate +dashboard-style design. Section order, headings, project-title +treatment, abstract handling, table labels, and `booktabs`-like +top/middle/bottom rules match the PDF as closely as browser CSS +allows. Publication metadata is intentionally not rendered in +HTML or TeX/PDF reports; it remains source data for the IUCr CIF +path. Plots stay format-specific (Plotly interactive in HTML, +pgfplots static in PDF), but the HTML renderer reuses the same +Plotly figure builder as normal EasyDiffraction plotting for the +corresponding plot kind. For powder Bragg measured-vs-calculated +plots that means the report HTML is the same composite layout as +the direct Plotly view: main intensity row, Bragg tick row, and +residual row. The TeX renderer mirrors that structure with native +pgfplots group panels and maps the same names, colors, line +widths, axis labels, and axis ranges where pgfplots has an +equivalent. Measured uncertainty is **not** drawn as per-point +error bars in the PDF (they exhaust TeX's fixed memory pool — +see §3.3); the PDF intensity panel is measured line+markers +and calculated line only. Content (one HTML page per project — per-project granularity matches the IUCr "one CIF per article" convention): -- Project info — title, description, phase count, experiment count. +- Project info — title, phase count, experiment count. The project + description is rendered as the report abstract when non-empty. - Crystal data per phase — phase id, space group, cell parameters, atom-sites table. - Data collection per experiment — experiment id, type fields, @@ -918,17 +965,20 @@ the IUCr "one CIF per article" convention): LaTeX label / units, all four label forms pre-resolved at builder time per §1.5) and a `series` sub-dict (`meas`, `calc`, `diff`, optional `bkg`, each carrying values + label - + optional `su` uncertainty array). The descriptor-driven + + optional `su` uncertainty array). Powder Bragg fit data also + carries Bragg tick sets extracted from the same reflection data + used by the interactive plotter. The descriptor-driven `x` payload covers Bragg powder CWL `two_theta`, TOF `time_of_flight`, and total-scattering `r` uniformly — any experiment whose x-axis descriptor exposes a - `DisplayHandler` drops in without further ADR changes. The - Jinja template feeds the dict to - `display/plotters/plotly.py` and embeds the resulting figure - via `fig.to_html(include_plotlyjs=)`. The same - `fit_data` series feeds the pgfplots CSV emitter for the - LaTeX renderer — one source of truth. -- Footer — EasyDiffraction version, save timestamp. + `DisplayHandler` drops in without further ADR changes. The HTML + renderer converts this dict into the same `PowderMeasVsCalcSpec` + consumed by `PlotlyPlotter` for direct plotting, then embeds the + resulting figure via `fig.to_html(include_plotlyjs=)`. + The same `fit_data` series feeds the pgfplots CSV emitter for + the LaTeX renderer, while shared report-plot helpers reuse the + Plotly display constants for style — one source of truth for + both data and visual conventions. ### 3. LaTeX + PDF — config-driven via `project.report.formats` @@ -969,15 +1019,23 @@ Future `project.report.html_style` (dark mode, journal-mimicking HTML layout) can land separately without collision because it lives in the config category, not in a method signature. -**Plots use `pgfplots` with external CSV data, not pre-rendered -images.** The figure-rendering pipeline produces CSV files -under `reports/tex/data/`; the `.tex` document references them -with `\addplot table {data/fit_.csv};`. This removes the +**Plots use `pgfplots`, emitted as one standalone `.tex` +figure per experiment, compiled independently to PDF, and +included in the main report via `\includegraphics`.** The +figure pipeline writes, per experiment, a CSV +(`data/fit_.csv`), a standalone pgfplots document +(`data/fit_.tex`), and the compiled figure +(`data/fit_.pdf`); `.tex` then does +`\includegraphics{data/fit_.pdf}`. This removes the Plotly + kaleido + headless-Chromium dependency chain entirely -— the LaTeX bundle compiles to PDF using only `tectonic` (or -another local TeX engine) plus the `pgfplots` package, which -`tectonic` resolves on demand. See §3.3 for the figure -emission detail. +— figures compile to PDF using only `tectonic` (or another +local TeX engine) plus the `pgfplots` package, which `tectonic` +resolves on demand. Building each figure in its own compile +also isolates its memory cost (see §3.3 — TeX's fixed main- +memory pool caps the coordinate count a *single* document can +hold, so per-figure isolation prevents accumulation across +experiments). See §3.3 for the figure composition and build +detail. #### 3.1 Folder layout @@ -995,9 +1053,11 @@ Single style (`iucrjournals`) — no multi-style infrastructure. .html # ← 'html' in project.report.formats (this ADR §2) .pdf # ← 'pdf' in project.report.formats (this ADR §3.4) tex/ # ← 'tex' or 'pdf' in project.report.formats (this ADR §3) - .tex # main document; tables + pgfplots figures + .tex # main document; tables + \includegraphics of figure PDFs data/ - fit_.csv # one per experiment, plotted via pgfplots + fit_.csv # profile data: x, meas, calc, diff (+ meas_su) + fit_.tex # standalone pgfplots figure document (one per experiment) + fit_.pdf # built independently from the .tex; included by .tex styles/ # vendored — required to compile the TeX iucrjournals.cls # IUCr unified class (CC0 1.0) harvard.sty # IUCr companion bibliography style @@ -1115,6 +1175,13 @@ Multi-style support (REVTeX, Elsevier `elsarticle`, …) is adds a style selector when there is a concrete second style to ship. +The generated document uses the project title directly in +`\title{...}` and emits an empty `\author{}`. If +`project.info.description` is non-empty, that text becomes the +document abstract; if it is empty, the abstract environment is +omitted. Publication metadata (`project.publication.*`) is not +included in the human-readable HTML or TeX/PDF reports. + #### 3.2.1 Source provenance and bundled files The IUCr source is vendored under @@ -1149,78 +1216,172 @@ REVTeX and other styles are **not** vendored — only `iucrjournals.cls` ships. See "Deferred Work" for multi-style addition. -#### 3.3 Plot generation — `pgfplots` with external CSV data +#### 3.3 Plot generation — standalone `pgfplots` figures, built independently, included as PDF -Plots inside the LaTeX output are rendered by the +Plots inside the LaTeX output are drawn by the [`pgfplots`](https://www.overleaf.com/learn/latex/Pgfplots_package) -package directly, not by a pre-rendered raster or vector -image. Each fit plot becomes a `\begin{tikzpicture}` block in -`.tex` that loads its data from a sibling CSV file: +package — vector, not pre-rendered raster — but **each fit +figure is its own standalone `.tex` document, compiled +independently to a PDF, and pulled into the report with +`\includegraphics`.** The main `.tex` carries no +inline pgfplots; it only includes the pre-built figure PDFs. + +**Per-experiment files** (`reports/tex/data/`): + +- `fit_.csv` — profile data, one column per series: + `x`, `meas`, `calc`, `diff` (and `meas_su` when measured + standard uncertainties exist). +- `fit_.tex` — a `\documentclass{standalone}` pgfplots + document that reads `fit_.csv`. +- `fit_.pdf` — produced by compiling `fit_.tex` + on its own; this is what `.tex` includes. ```latex -\begin{figure}[H] -\centering +% ---- data/fit_.tex (standalone, built on its own) ---- +\documentclass[border=2pt]{standalone} +\usepackage{pgfplots} +\usepgfplotslibrary{groupplots} +\pgfplotsset{compat=1.18} +\pgfplotsset{set layers} % REQUIRED — see "Marker z-order" below +\definecolor{ed_meas}{RGB}{31,119,180} +\definecolor{ed_calc}{RGB}{214,39,40} +\definecolor{ed_bragg_0}{RGB}{255,127,14} +\definecolor{ed_diff}{RGB}{44,160,44} +\begin{document} \begin{tikzpicture} -\begin{axis}[width=\linewidth, xlabel={$2\theta$ (deg)}, - ylabel={Intensity (arb. units)}, - legend pos=north east] - \addplot[only marks, mark size=0.5pt] - table[x=two_theta, y=meas, col sep=comma] - {data/fit_.csv}; - \addlegendentry{Measured}; - \addplot[no markers] - table[x=two_theta, y=calc, col sep=comma] - {data/fit_.csv}; - \addlegendentry{Calculated}; - \addplot[no markers, dashed] - table[x=two_theta, y=diff, col sep=comma] - {data/fit_.csv}; - \addlegendentry{Difference}; -\end{axis} +\begin{groupplot}[group style={group size=1 by 3, vertical sep=4pt, + x descriptions at=edge bottom}, + width=12cm, xmin=, xmax=, + mark layer=like plot] + % Intensity panel — exactly TWO plots + \nextgroupplot[ylabel={Intensity (arb. units)}, xticklabels=\empty, + legend pos=north east] + % (1) measured: connecting line + markers + \addplot+[mark=*, mark size=0.75pt, color=ed_meas, line width=0.5pt, + line join=bevel, mark options={line width=0pt}] + table[x=x, y=meas, col sep=comma] {fit_.csv}; + \addlegendentry{Measured} + % (2) calculated: line, declared LAST so it draws on top + \addplot+[color=ed_calc, line width=0.75pt, line join=bevel, no markers] + table[x=x, y=calc, col sep=comma] {fit_.csv}; + \addlegendentry{Calculated} + % Bragg tick row + \nextgroupplot[ymin=0.5, ymax=1.5, ytick=1, yticklabels={}, + xticklabels=\empty, ylabel={Bragg}] + \addplot+[color=ed_bragg_0, only marks, mark=|, mark size=5pt] + coordinates {(,1) ...}; + % Residual panel + \nextgroupplot[xlabel={$2\theta$ (degree)}, ylabel={Residual}] + \addplot+[color=ed_diff, line width=0.5pt, line join=bevel, no markers] + table[x=x, y=diff, col sep=comma] {fit_.csv}; +\end{groupplot} \end{tikzpicture} +\end{document} +``` + +```latex +% ---- .tex includes the pre-built PDF ---- +\begin{figure}[H]\centering +\includegraphics[width=\linewidth]{data/fit_.pdf} \caption{Fit quality for experiment .} \end{figure} ``` -Data files at `reports/tex/data/fit_.csv` carry one -column per series (`x`, `meas`, `calc`, `diff`, optionally -`background`). Powder profiles with thousands of points stay -in CSV; pgfplots reads them at compile time. - -**Why pgfplots and not pre-rendered images.** - +**Intensity panel — exactly two plots: measured and +calculated.** Measured is a connecting line with markers; +calculated is a line drawn last (on top). The figure carries +**no measured–calculated difference band, no per-point error +bars, and no background curve** on the intensity axis. The +difference is shown in its own residual panel; the background +is omitted from report charts. Bragg tick coordinates are +written directly into the figure `.tex` (sparse, categorical +annotations, not a dense profile series). + +**Why no per-point error bars (the core constraint).** TeX +engines (pdfTeX/XeTeX — and therefore `tectonic`, which is +XeTeX-based) allocate a *fixed* main-memory pool +(`main memory size=5000000`); `tectonic` does not expose a +knob to raise it, and the only engine that grows memory +dynamically (LuaTeX) is not available cross-platform on +conda-forge (no `texlive-core` for `win-64`). pgfplots holds +an in-memory structure per plotted primitive, and per-point +error bars (`error bars/.cd, y dir=both, y explicit`) cost a +path *per data point* — empirically the dominant consumer. +A measured series with markers + error bars overflowed the +pool at ~1600 points; the same series as a line + markers, +without error bars, compiles at full resolution (3098 points) +in the same pool. **Measured uncertainty is therefore not +shown as per-point error bars in the PDF report.** (An +exploration of a single ±Nσ fill polygon as a cheaper +uncertainty cue was prototyped and then dropped in favour of +the simpler two-plot figure; it is not part of this decision.) + +**Marker z-order — `set layers` is mandatory.** pgfplots' +default is a two-pass render: all lines first, then all +markers on a foreground pass, so markers always paint over +lines regardless of `\addplot` order. `mark layer=like plot` +restores plot-order layering **but only takes effect when +`\pgfplotsset{set layers}` is active**. Both are required so +the calculated line (declared last) draws over the measured +markers. With `mark layer=like plot` alone (no `set layers`) +the calculated line renders *under* the measured markers. + +**Simplified measured-marker options.** A filled `mark=*` +inherits both its fill and its border colour from the plot's +`color=`, so `mark options={fill=…, draw=…}` is redundant +when they match `color`. The one non-redundant piece is +`mark options={line width=0pt}`: without it the marker border +inherits the plot's `line width` and visibly fattens the dot. +The canonical measured plot is therefore +`mark=*, mark size=0.75pt, color=ed_meas, line width=0.5pt, +line join=bevel, mark options={line width=0pt}`. + +**Data resolution — full up to a cap, peak-preserving +downsample above it.** Each figure compiles in its own +process with a fresh pool, so a single experiment's chart is +the unit that must fit. HRPT/typical CWL (~3k points) renders +every point; a dense CWL (~15k) or TOF bank (~30k) would +overflow even a plain line, so above a safe cap the renderer +downsamples with a **peak-preserving** method (min/max-per-bin +or LTTB — never naive every-Nth striding, which can step over +a sharp Bragg apex and flatten it). The full-fidelity data +always remains in the CIF/CSV; the figure is a view. + +**Why standalone figures included as PDF, not inline +pgfplots.** + +- **Memory isolation.** Each figure's pgfplots load lives in + its own compile; the main report never accumulates every + experiment's coordinates in one pool. A five-experiment + report that would overflow if inlined compiles fine as five + independent figures + a light main document. - **No Python image renderer needed.** Removes `kaleido` and - the headless-Chromium dependency chain that earlier drafts - carried, plus the cross-platform `chromium` packaging - problem on conda-forge. -- **Editable.** A user opening the PDF source can tweak - axis labels, colours, legend, or marker size by editing - the `\begin{axis}[...]` options directly in - `.tex`. The CSV stays untouched. -- **Native LaTeX typography.** Axis labels, legends, and - captions render in the same font family as the surrounding - document. No font-hinting mismatch the way there would be - with a Plotly-rendered PNG/PDF. -- **`pgfplots` is on every modern TeX distribution.** - `tectonic` resolves it on demand from CTAN; TeX Live and - MiKTeX ship it in their default sets. No extra - vendoring. + the headless-Chromium chain plus the cross-platform + `chromium` packaging problem on conda-forge. Vector + throughout — no rasterization. +- **Independently rebuildable.** A figure can be regenerated + or hand-tweaked without recompiling the whole report; the + CSV and figure `.tex` stay editable. +- **Native LaTeX typography.** Axis labels, legends, captions + render in the document font; no font-hinting mismatch. +- **Shared visual conventions.** The figure `.tex` consumes + the same display colours and range helpers as the + HTML/Plotly path, mapped to TeX-native `\definecolor`, + `line width`, legend, and `groupplot` options. +- **`pgfplots` and `standalone` are on every modern TeX + distribution.** `tectonic` resolves both on demand from + CTAN; TeX Live and MiKTeX ship them. No extra vendoring. **Caveats.** -- Compile-time scales with the data-point count. Powder - patterns with ~50K points compile in seconds, not - milliseconds; the implementer can downsample for very - large patterns via a `pgfplots` `each nth point=N` option - if compile time becomes noticeable. This is a tuning knob - for the renderer, not an ADR-level decision. -- The HTML output remains Plotly-based (interactive in the - browser); the LaTeX output is pgfplots-based (static in - the PDF). The two have **different visual styling** by - design — there is no shared figure-rendering library and - no "pixel-identical" claim. Sharing the same source data - (the project state via the data context) is the only - consistency guarantee. +- The build now compiles N+1 documents (one per experiment + figure, plus the main report). The PDF compiler (§3.4) + drives the per-figure builds before the main document. +- The HTML output stays Plotly-based (interactive in the + browser); the LaTeX output is pgfplots-based (static PDF). + They share the two-plot measured/calculated composition, + series colours, and source data, but are not pixel- + identical. Font-family parity is deferred. **Dependencies named by this ADR.** The implementation plan must name one dependency before any `/draft-impl-1` or @@ -1837,7 +1998,8 @@ benefits every renderer (HTML, PDF, terminal, GUI) simultaneously. directly. The HTML output remains Plotly-based (interactive in the browser); the LaTeX output is pgfplots-based (static in the PDF). The two renderings share the underlying data - but not the visual styling — see §3 and §3.3 for the + and reuse Plotly's report-relevant styling constants wherever + pgfplots has an equivalent — see §3 and §3.3 for the rationale. - PDF compilation is opportunistic — works when `tectonic`, `latexmk`, or `pdflatex` is on `PATH`; otherwise the `.tex` @@ -2141,15 +2303,25 @@ Refinement, Structures, Experiments) rather than imitating an IUCr journal-submission manuscript — "typeset Python state", not a ready-to-submit manuscript. -Plots inside the LaTeX output are rendered by `pgfplots` -reading the CSV data at compile time. The HTML output stays -Plotly-based (interactive in the browser); the LaTeX output -is pgfplots-based (static in the PDF). The two share the -underlying data but not the visual styling — there is no -shared figure-rendering library, no pixel-equality claim, and +Plots inside the LaTeX output are rendered by `pgfplots` as one +standalone figure document per experiment, compiled +independently to PDF and included in the report via +`\includegraphics` (§3.3). The HTML output stays Plotly-based +(interactive in the browser) and reuses the direct +EasyDiffraction Plotly figure builder for powder Bragg +measured-vs-calculated plots. The LaTeX output is pgfplots-based +(static in the PDF) and mirrors the same main-intensity / Bragg +tick / residual row structure with native groupplots. The two +share the underlying data and the Plotly-derived visual +conventions that map cleanly to both backends (series names, +colors, line widths, axis labels, ranges, and legend +placement). Measured uncertainty is **not** drawn as per-point +error bars in the PDF intensity panel — they exhaust TeX's +fixed memory pool (§3.3); the PDF shows measured line+markers +and calculated line only. There is no pixel-equality claim and no Python image-rendering dependency in the LaTeX path -(`kaleido` and the browser dependency from earlier drafts are -both gone). +(`kaleido` and the browser dependency from earlier drafts are both +gone). Adds an `analysis.software` Python category — three-role triple (framework / calculator / minimizer) matching the alignment ADR's From ab7659eb0f6fc29d405f9126996503682ca88e5a Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 28 May 2026 13:27:56 +0200 Subject: [PATCH 059/129] Add project-summary-rendering implementation plan --- docs/dev/plans/project-summary-rendering.md | 335 ++++++++++++++++---- 1 file changed, 281 insertions(+), 54 deletions(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 1e42726c7..f13920976 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -70,11 +70,19 @@ plan does not re-litigate them: (`iucrjournals`). The vendored TeX bundle drops from 12 files (previous design) to 2: `iucrjournals.cls` and `harvard.sty`, both CC0 1.0 from the IUCr upstream. -- **No `kaleido`, no `chromium`** (§3.3): fit-quality - figures are emitted as `pgfplots` blocks reading external - CSV files at `reports/tex/data/.csv`. The TeX - engine (Tectonic / TeX Live / MiKTeX) supplies - `pgfplots` + its TikZ deps from CTAN or its default sets. +- **No `kaleido`, no `chromium`** (§3.3): each fit-quality + figure is a **standalone `pgfplots` `.tex` document** + (`reports/tex/data/fit_.tex`, reading its sibling + `fit_.csv`), compiled independently to + `fit_.pdf` and pulled into the main report via + `\includegraphics` — no inline pgfplots in `.tex`, + and per-figure compiles isolate the TeX memory pool. The + intensity panel carries exactly two plots (measured + line+markers, calculated line) with **no band, no + per-point error bars, no background** (error bars are the + fixed-memory-pool killer; see §3.3). The TeX engine + (Tectonic / TeX Live / MiKTeX) supplies `pgfplots` + + `standalone` + TikZ deps from CTAN or its default sets. - **`DisplayHandler` value object** (§1.5): new `@dataclass(frozen=True, slots=True)` at `src/easydiffraction/core/display_handler.py` carrying @@ -139,11 +147,13 @@ plan does not re-litigate them: release for `tex-mml-chtml.js` and pin its source URL in `LICENSES.md` for traceability. Assume the latest 3.x release tagged on jsdelivr at vendoring time. -- **`reports/tex/data/.csv` schema.** The CSV +- **`reports/tex/data/fit_.csv` schema.** The CSV emitter writes one row per data point with columns - `x`, `meas`, optional `meas_su`, `calc`, `diff`, optional - `bkg`. Confirm the pgfplots template's column references - match this schema exactly during P1. + `x`, `meas`, optional `meas_su`, `calc`, `diff`. + Background is **not** plotted in the report figure (§3.3), + so no `bkg` column is required for the figure; emit it only + if another consumer needs it. Confirm `figure.tex.j2`'s + column references match this schema exactly during P1. - **Style-bundle cleanup vs. wheel size.** The 10 REVTeX files dropped from the bundle reduce the wheel by ~350 KB. The Phase 2 verification step covers the actual @@ -268,22 +278,43 @@ plan does not re-litigate them: `vendor/` subtree). **`fit_data` shape + pgfplots CSV emitter:** +- `src/easydiffraction/report/downsample.py` (new — + `downsample_min_max(x, y, max_points)`; `MAX_FIGURE_POINTS + = 5000`; peak-preserving min/max-per-bin; see P1.16). - `src/easydiffraction/report/data_context.py` (existing — replace the previous `figures.fit_per_experiment` payload with the descriptor-driven `experiments[i].fit_data` dict per ADR §6). - `src/easydiffraction/report/tex_renderer.py` (existing - — add a `_write_fit_csv(expt_id, fit_data, out_dir)` - helper writing `reports/tex/data/.csv` with - the schema from §3.3). + — add `_write_fit_csv(expt_id, fit_data, out_dir)` + writing `reports/tex/data/fit_.csv`, and render + a standalone `data/fit_.tex` per experiment from + the new `figure.tex.j2`; see §3.3 and P1.16). +- `src/easydiffraction/report/templates/tex/figure.tex.j2` + (new — `\documentclass{standalone}` pgfplots figure: two + intensity plots (measured line+markers, calculated line), + Bragg row, residual panel. No band, no per-point error + bars, no background. `\pgfplotsset{set layers}` + + `mark layer=like plot` mandatory; see P1.16). - `src/easydiffraction/report/templates/tex/report.tex.j2` - (renamed from `iucr.tex.j2`) — replace - `\includegraphics{figures/fit_.pdf}` with a - `pgfplots` block reading the matching CSV. Single - template, no style branching. + (renamed from `iucr.tex.j2`) — the **main** document: + tables + `\includegraphics{data/fit_.pdf}` per + experiment. No inline pgfplots. Single template, no style + branching. +- `src/easydiffraction/report/pdf_compiler.py` (existing — + compile each `data/fit_.tex` first, then the main + `.tex`, **each using the same discovered-engine + fallback** `_ENGINE_ORDER` = `tectonic` → `latexmk` → + `pdflatex` (not tectonic only); per-figure separate + documents provide the memory isolation; see P1.16). - `src/easydiffraction/report/html_renderer.py` (existing — the Plotly builder now consumes `fit_data` directly - rather than expecting pre-rendered HTML). + rather than expecting pre-rendered HTML; serializes through + the shared `PlotlyPlotter` helper that applies + `_get_config()` **and** the legend-toggle `post_script` + + `_wrap_html_figure` wrapper, with a **report-only** + `force_template='plotly_white'` override (notebook stays + theme-adaptive); see ADR §2 and P1.18). - `src/easydiffraction/report/templates/html/report.html.j2` (existing — feed the dict to the Plotly builder at render time). @@ -755,30 +786,77 @@ exceptions. - Commit: `Replace fit_data payload with descriptor-driven shape`. -- [x] **P1.16 — pgfplots CSV emitter + TeX template** +- [ ] **P1.16 — Standalone per-figure pgfplots `.tex` → PDF, included by the report** + (See ADR §3.3 — "standalone pgfplots figures, built + independently, included as PDF" — for the full decision + this step implements.) - Files: existing - `src/easydiffraction/report/tex_renderer.py`; - renamed - `src/easydiffraction/report/templates/tex/report.tex.j2` - (from P1.4). - - Add a `_write_fit_csv(expt_id, fit_data, out_dir)` - helper writing `/data/.csv` with - columns `x, meas, meas_su, calc, diff, bkg` (the - `meas_su` and `bkg` columns are present only when - `fit_data['series']['meas']['su']` / - `fit_data['series']['bkg']` are non-None). Use Python - `csv` from stdlib — no new dependency. - - Replace every `\includegraphics{figures/fit_.pdf}` - block in `report.tex.j2` with a `pgfplots` block - pointing at `data/.csv` — series labels read - from `fit_data['series'][...]['label']`, axis labels - from `fit_data['x']['latex_name']` and - `fit_data['x']['latex_units']`. - - `Report.save_tex()` now invokes `_write_fit_csv` for - each experiment and writes the rendered `.tex` to - `reports/.tex`. The `figures/` directory and - every kaleido call site are gone. - - Commit: `Emit pgfplots CSV and replace includegraphics with pgfplots blocks`. + `src/easydiffraction/report/tex_renderer.py`, + `src/easydiffraction/report/pdf_compiler.py`; + renamed `src/easydiffraction/report/templates/tex/report.tex.j2` + (from P1.4); new + `src/easydiffraction/report/templates/tex/figure.tex.j2` + (standalone figure document). + - Add `_write_fit_csv(expt_id, fit_data, out_dir)` writing + `/data/fit_.csv` with columns + `x, meas, calc, diff` (+ `meas_su` when present). Python + stdlib `csv`, no new dependency. + - Add `figure.tex.j2`: a `\documentclass{standalone}` + pgfplots document, one per experiment, rendered to + `data/fit_.tex`. Mandatory preamble: + `\pgfplotsset{set layers}` **and** axis option + `mark layer=like plot` (both required — `mark layer` + is inert without `set layers`; together they let the + calculated line draw over the measured markers). + - **Intensity panel: exactly two plots.** + - Measured — connecting line + markers: + `\addplot+[mark=*, mark size=0.75pt, color=ed_meas, + line width=0.5pt, line join=bevel, + mark options={line width=0pt}]`. (Do **not** add + `mark options={fill=…, draw=…}` — a filled `mark=*` + already inherits both from `color`; only the + `line width=0pt` is load-bearing, keeping the dot at + its nominal size.) + - Calculated — line, declared **last** so it draws on + top: `\addplot+[color=ed_calc, line width=0.75pt, + line join=bevel, no markers]`. + - **No measured–calculated band, no per-point error + bars, no background curve** on the intensity panel. + Per-point error bars are the memory killer (ADR + §3.3); measured uncertainty is not shown as error + bars in the PDF. + - Bragg tick row + residual panel as before (residual in + its own panel; difference is not overlaid on intensity). + - `report.tex.j2` (main document) includes each figure as a + pre-built PDF: `\includegraphics[width=\linewidth]{data/fit_.pdf}` + — **no inline pgfplots in the main report**. + - `Report.save_tex()` writes, per experiment, the CSV + + the standalone `fit_.tex`, and writes the main + `.tex`. `pdf_compiler.py` is extended so + `save_pdf()` compiles **each figure `.tex` first, then + the main `.tex`** (which `\includegraphics` the + figure PDFs) — an N+1-document build. Each compile (figure + and main) uses the **same discovered-engine fallback the + compiler already implements** (`tectonic` → `latexmk` → + `pdflatex`, `_ENGINE_ORDER` in `pdf_compiler.py`) — not + `tectonic` only — so TeX Live / MiKTeX users keep PDF + output. Compiling each figure as its own document is what + isolates the memory pool; the engine choice is unchanged. + When only `tex` is configured (no `pdf`), the figure + `.tex` + CSV are written but not compiled. + - **Data resolution — concrete contract.** Plot all points + up to **`MAX_FIGURE_POINTS = 5000`** per series; above + that, downsample with **min/max-per-bin** (peak- + preserving — keeps each bin's min and max so a sharp Bragg + apex survives; **never** naive every-Nth striding, which + can step over an apex and flatten it). Owned by a new + helper `src/easydiffraction/report/downsample.py`, + `downsample_min_max(x, y, max_points) -> (x2, y2)`, called + from `tex_renderer.py` when writing `fit_.csv`. HRPT + / typical CWL (~3k points) is under the cap and renders + every point; dense CWL / TOF banks downsample. Full- + fidelity data always stays in the CIF/CSV. + - Commit: `Emit standalone pgfplots figures included as PDF`. - [x] **P1.17 — HTML Plotly builder consumes `fit_data` directly** - Files: existing @@ -801,7 +879,61 @@ exceptions. - Commit: `Build Plotly fit figures from fit_data context`. -- [x] **P1.18 — Clean up stale `figures/` references in code and docs** +- [ ] **P1.18 — HTML serialization parity: forced light theme + notebook modebar (ADR §2)** + - Files: + `src/easydiffraction/report/html_renderer.py`; + `src/easydiffraction/display/plotters/plotly.py` (extract + a shared serialization helper — see below). + - **Problem this fixes.** The current report HTML path + serializes with only `full_html=False` + + `include_plotlyjs=...` (`html_renderer.py:283`); it does + **not** pass the notebook `config`, does **not** pass the + notebook `post_script`, and does **not** force the figure + template after `PlotlyPlotter()` has set the global + default from the ambient theme (`html_renderer.py:221`). + So the report can inherit a dark theme and shows Plotly's + default modebar. ADR §2 requires the report to match the + notebook. + - **Forced light theme — report only.** For the report, set + each figure's `template` to `plotly_white` **on the figure + object** (e.g. `fig.update_layout(template='plotly_white')`) + right before serialization — not via the global + `pio.templates.default`, which `PlotlyPlotter.__init__` + already set from the ambient theme. This makes the report + independent of notebook/system dark mode. **The notebook + path must NOT be forced** — it stays theme-adaptive per + `_default_template_name` (ADR §1 / §2). So forcing + `plotly_white` is a report-only override, not part of the + shared mechanics. + - **Notebook modebar — reuse the notebook serialization + mechanics (two pieces).** The curated buttons come from + `PlotlyPlotter._get_config()` + (`plotly.py:567` — `displaylogo=False`, + `modeBarButtonsToRemove`), **but the custom legend-toggle + button is NOT in `_get_config()`** — it is installed via + `post_script` plus the `_wrap_html_figure` wrapper in + `_show_figure` (`plotly.py:954`). To match the notebook, + the report must pass **both** `config=_get_config()` and + the same `post_script`, and apply the same wrapper. + - **Implementation: one shared low-level helper + a + report-only template override.** Extract the notebook's + `pio.to_html(..., config=_get_config(), post_script=...)` + + `_wrap_html_figure` sequence into a single classmethod + on `PlotlyPlotter` that takes the template as a parameter + — e.g. `serialize_html(fig, *, offline, + force_template: str | None = None)`: it applies + `_get_config()`, the legend-toggle `post_script`, and the + wrapper, and when `force_template` is given does + `fig.update_layout(template=force_template)` first. The + **notebook** path (`_show_figure`) calls it with + `force_template=None` (keeps its theme-adaptive template); + the **report** renderer calls it with + `force_template='plotly_white'`. One source of truth for + the modebar/post-script/wrapper; the light-theme override + lives only at the report call site. + - Commit: `Serialize report Plotly figures via shared notebook path`. + +- [x] **P1.19 — Clean up stale `figures/` references in code and docs** - Files: `src/easydiffraction/project/categories/report/default.py`, `src/easydiffraction/report/tex_renderer.py`, @@ -821,7 +953,7 @@ exceptions. - Commit: `Replace stale figures/ references with data/`. -- [x] **P1.19 — Update tutorials and user-guide for the new surface** +- [x] **P1.20 — Update tutorials and user-guide for the new surface** - Files: `docs/docs/tutorials/ed-3.py`, `docs/docs/tutorials/ed-14.py`, any other tutorial `.py` referencing `style=` or `--style` (grep at step @@ -843,7 +975,61 @@ exceptions. - Commit: `Update tutorials and docs for single-style report surface`. -- [x] **P1.20 — Reach Phase 1 review gate** +- [ ] **P1.21 — Visual parity check — PDF figure vs Plotly figure** + - Files: `src/easydiffraction/report/templates/tex/figure.tex.j2` + (geometry fixes); read-only reference to the Plotly + builder in `src/easydiffraction/display/plotters/plotly.py`. + - The pgfplots PDF figure and the Plotly HTML figure must + share the **same geometry** even though fonts and exact + line rendering differ. This is a build-and-eyeball check + on a real project (the `lbco_hrpt` tutorial), **not** an + automated pixel test — fonts differ, so pixel diffing + would false-fail. It is a Phase 1 step because it drives + geometry corrections to `figure.tex.j2`; it produces + template commits, not test files. + - Procedure (per representative experiment): + 1. **Build the PDF figure.** Compile the standalone + `reports/tex/data/fit_.tex` to `fit_.pdf` + (and/or the full `reports/.pdf`). + 2. **Rasterize to PNG.** Any available rasteriser — + `pdftoppm -png -r 150 fit_.pdf out`, + `magick -density 150 fit_.pdf out.png`, or macOS + `sips -s format png fit_.pdf --out out.png`. + 3. **Capture the Plotly reference.** Generate + `reports/.html` (or the notebook figure) for + the same experiment and capture its chart as an image + — open the HTML in a browser and screenshot, or use a + browser-automation tool. Plotly→static image has **no** + headless path here (`kaleido` was dropped), so the + reference comes from the rendered HTML / notebook, not + a Python export. + 4. **Compare geometry side by side.** The two images must + match on: + - overall figure aspect ratio / box dimensions; + - number of stacked panels and their **relative + heights** (intensity : Bragg : residual); + - **vertical spacing between panels** (no oversized or + uneven gaps); + - which panels carry x-axis ticks/labels (**only the + bottom panel**) and which carry a y-axis title + (**each panel its own, once** — no missing titles, + no stray/overlapping titles across panels); + - x-range (`xmin`/`xmax`) and per-panel y-range; + - legend position. + Fonts, line antialiasing, and marker rendering may + differ — out of scope. + - **Known defects to fix in `figure.tex.j2`:** uneven/wrong + inter-panel spacing (`vertical sep` / per-panel `height`), + and spurious or overlapping y-axis titles on the short + Bragg / residual panels (tune each panel's `ylabel`, + `ylabel style`, `yticklabel` placement so labels don't + collide — the demo symptom where "Bragg", "lbco", and + "Residual" overprinted). Re-run steps 1–4 until the + geometry matches the Plotly figure. + - Commit one geometry fix per logical change, e.g. + `Match pgfplots figure geometry to Plotly layout`. + +- [ ] **P1.22 — Reach Phase 1 review gate** - No-code step. Mark every `[ ]` above as `[x]`; commit the plan-file update alone. - Commit: `Reach Phase 1 review gate`. @@ -945,17 +1131,57 @@ running the verification commands below, add or update: pre-rendered HTML keys are absent; single-crystal experiments emit `fit_data: None`. P1.15 surface. - [ ] **`tests/unit/easydiffraction/report/test_tex_renderer.py`** - (extend) — `_write_fit_csv` writes the right schema; - rendered `.tex` contains a `\begin{axis}` block per - experiment with `table[col sep=comma] {data/.csv}` - pointing at the matching CSV; `\includegraphics{figures/...}` - is absent. P1.16 surface. + (extend) — `_write_fit_csv` writes the right schema + (`x, meas, calc, diff` + optional `meas_su`); each + standalone `data/fit_.tex` contains exactly two + intensity-panel `\addplot` calls (measured `mark=*` line, + calculated `no markers` line) and **no** `error bars`, + **no** band/fill-between, **no** background plot; the + preamble contains both `\pgfplotsset{set layers}` and + `mark layer=like plot`; the main `report.tex` uses + `\includegraphics{data/fit_.pdf}` and contains **no** + inline `\begin{axis}`. P1.16 surface. - [ ] **`tests/unit/easydiffraction/report/test_html_renderer.py`** (further extend) — Plotly figures appear in the rendered HTML and are built from the `fit_data` payload rather than read from a context key; the resulting HTML body contains a `
` per experiment. P1.17 surface. +- [ ] **`tests/unit/easydiffraction/report/test_html_renderer.py`** + (further extend) — **HTML serialization parity, with the + report-only light override.** With `pio.templates.default` + set to `plotly_dark` (simulating notebook/system dark + mode): + - **Report** serialization (`force_template='plotly_white'`) + carries `plotly_white`, not `plotly_dark`. + - **Notebook** serialization (`force_template=None`) keeps + the theme-adaptive template (here `plotly_dark`) — i.e. + the shared helper does **not** force light for the + notebook path. This guards the regression where forcing + light leaks into the interactive path. + - Both paths emit a `config` matching + `PlotlyPlotter._get_config()` (`displaylogo:false`, the + curated `modeBarButtonsToRemove` set) **and** the legend- + toggle behaviour from the shared `post_script` / + `_wrap_html_figure`, since both call the single + serialization helper. P1.18 surface. +- [ ] **`tests/unit/easydiffraction/report/test_downsample.py`** + (new) — `downsample_min_max(x, y, max_points)`: arrays at + or under `max_points` pass through unchanged; arrays above + it shrink to ≈`max_points`; **a synthetic single-bin-wide + spike at full height on a flat baseline is preserved** (the + spike's max y value appears in the output) — the + peak-preservation guarantee that naive striding would + violate. P1.16 surface. +- [ ] **`tests/unit/easydiffraction/report/test_pdf_compiler.py`** + (extend) — **N+1 build + engine fallback.** Each figure + `data/fit_.tex` is compiled **before** the main + `.tex` (assert call ordering); figure and main + compiles use the **same discovered engine** (`_ENGINE_ORDER` + fallback `tectonic` → `latexmk` → `pdflatex`), not tectonic + only; the no-engine branch writes the `.tex` + CSV bundle + and returns with the install hint **without raising**; an + engine non-zero exit raises a clear error. P1.16 surface. - [ ] **Wheel-packaging verification** — run `pixi run dist-build` and then `unzip -l dist/*.whl | grep -E 'mathjax|iucrjournals.cls|harvard.sty'` @@ -1018,12 +1244,13 @@ changes for users: support (REVTeX, Elsevier, …) is deferred to a follow-up ADR. The vendored TeX bundle shrinks from 12 files to 2 (`iucrjournals.cls` + `harvard.sty`, both CC0 1.0). -- **No `kaleido`, no Chrome bootstrap.** Fit-quality figures - in the PDF / TeX bundle are emitted as `pgfplots` blocks - reading external CSV files under `reports/tex/data/`. The - TeX engine (Tectonic / TeX Live / MiKTeX) handles the - plotting; users no longer need a system browser to - generate a PDF report. +- **No `kaleido`, no Chrome bootstrap.** Each fit-quality + figure in the PDF / TeX bundle is a standalone `pgfplots` + document under `reports/tex/data/`, compiled to PDF and + included in the report — vector throughout, no inline + pgfplots in the main document. The TeX engine (Tectonic / + TeX Live / MiKTeX) handles the plotting; users no longer + need a system browser to generate a PDF report. - **Offline HTML is now actually offline.** When `project.report.html_offline = True`, both Plotly and MathJax are inline-bundled — the resulting `.html` opens From 4f28bb442b50d8b8965a58fcaf86ec59d836b756 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 28 May 2026 13:36:58 +0200 Subject: [PATCH 060/129] Emit standalone pgfplots figures included as PDF --- docs/dev/plans/project-summary-rendering.md | 2 +- src/easydiffraction/report/downsample.py | 102 +++++++++++++++++ src/easydiffraction/report/fit_plot.py | 3 - src/easydiffraction/report/pdf_compiler.py | 30 ++++- .../report/templates/tex/figure.tex.j2 | 74 +++++++++++++ .../report/templates/tex/report.tex.j2 | 86 +-------------- src/easydiffraction/report/tex_renderer.py | 104 +++++++++++++++--- 7 files changed, 299 insertions(+), 102 deletions(-) create mode 100644 src/easydiffraction/report/downsample.py create mode 100644 src/easydiffraction/report/templates/tex/figure.tex.j2 diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index f13920976..c6c7854ad 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -786,7 +786,7 @@ exceptions. - Commit: `Replace fit_data payload with descriptor-driven shape`. -- [ ] **P1.16 — Standalone per-figure pgfplots `.tex` → PDF, included by the report** +- [x] **P1.16 — Standalone per-figure pgfplots `.tex` → PDF, included by the report** (See ADR §3.3 — "standalone pgfplots figures, built independently, included as PDF" — for the full decision this step implements.) diff --git a/src/easydiffraction/report/downsample.py b/src/easydiffraction/report/downsample.py new file mode 100644 index 000000000..ba9c07a99 --- /dev/null +++ b/src/easydiffraction/report/downsample.py @@ -0,0 +1,102 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Downsample report plot data while preserving extrema.""" + +from __future__ import annotations + +from collections.abc import Sequence + +import numpy as np + +MAX_FIGURE_POINTS = 5000 + + +def downsample_min_max( + x: Sequence[object], + y: Sequence[object], + max_points: int = MAX_FIGURE_POINTS, +) -> tuple[list[object], list[object]]: + """ + Downsample paired data by keeping each bin's extrema. + + Parameters + ---------- + x : Sequence[object] + X-axis values. + y : Sequence[object] + Y-axis values used to select each bin's extrema. + max_points : int, default=MAX_FIGURE_POINTS + Maximum approximate number of output points. + + Returns + ------- + tuple[list[object], list[object]] + Downsampled x and y values in original order. + + Raises + ------ + ValueError + If the inputs have different lengths or ``max_points`` is + smaller than two. + """ + x_values = list(x) + y_values = list(y) + if len(x_values) != len(y_values): + msg = ( + f"Cannot downsample arrays with different lengths: " + f"x={len(x_values)}, y={len(y_values)}." + ) + raise ValueError(msg) + indices = downsample_min_max_indices(y_values, max_points) + return ( + [x_values[index] for index in indices], + [y_values[index] for index in indices], + ) + + +def downsample_min_max_indices( + y: Sequence[object], + max_points: int = MAX_FIGURE_POINTS, +) -> list[int]: + """ + Return peak-preserving min/max indices for one series. + + Parameters + ---------- + y : Sequence[object] + Values whose bin minima and maxima select retained points. + max_points : int, default=MAX_FIGURE_POINTS + Maximum approximate number of output points. + + Returns + ------- + list[int] + Original indices retained in ascending order. + + Raises + ------ + ValueError + If ``max_points`` is smaller than two. + """ + if max_points < 2: + msg = 'max_points must be at least 2.' + raise ValueError(msg) + + values = list(y) + if len(values) <= max_points: + return list(range(len(values))) + + numeric = np.asarray(values, dtype=float) + bin_count = max_points // 2 + edges = np.linspace(0, len(values), num=bin_count + 1, dtype=int) + indices: list[int] = [] + for start, stop in zip(edges[:-1], edges[1:], strict=True): + if start == stop: + continue + segment = numeric[start:stop] + bin_indices = { + start + int(np.argmin(segment)), + start + int(np.argmax(segment)), + } + indices.extend(sorted(bin_indices)) + return indices diff --git a/src/easydiffraction/report/fit_plot.py b/src/easydiffraction/report/fit_plot.py index 53b9bb6b0..207bd7596 100644 --- a/src/easydiffraction/report/fit_plot.py +++ b/src/easydiffraction/report/fit_plot.py @@ -55,9 +55,6 @@ def fit_plot_ranges(fit_data: dict[str, Any]) -> dict[str, float]: _numeric_values(fit_data['series']['meas']['values']), _numeric_values(fit_data['series']['calc']['values']), ] - bkg = fit_data['series']['bkg'] - if bkg is not None: - y_series.append(_numeric_values(bkg['values'])) x_min, x_max = _data_range([x_values]) y_min, y_max = _data_range(y_series) diff --git a/src/easydiffraction/report/pdf_compiler.py b/src/easydiffraction/report/pdf_compiler.py index 083377319..cd886a1f0 100644 --- a/src/easydiffraction/report/pdf_compiler.py +++ b/src/easydiffraction/report/pdf_compiler.py @@ -86,7 +86,7 @@ def compile_pdf_report(tex_path: pathlib.Path) -> pathlib.Path: runtime_failures = [] for engine in engines: - runtime_failure = _compile_pdf(engine, tex_path, pdf_path) + runtime_failure = _compile_report_bundle(engine, tex_path, pdf_path) if runtime_failure is None: return pdf_path runtime_failures.append(runtime_failure) @@ -113,6 +113,31 @@ def _find_engine() -> tuple[str, str] | None: return engines[0] +def _compile_report_bundle( + engine: tuple[str, str], + tex_path: pathlib.Path, + pdf_path: pathlib.Path, +) -> str | None: + """Compile figure documents first, then the main report.""" + for figure_tex_path in _figure_tex_paths(tex_path): + runtime_failure = _compile_pdf( + engine, + figure_tex_path, + figure_tex_path.with_suffix('.pdf'), + ) + if runtime_failure is not None: + return runtime_failure + return _compile_pdf(engine, tex_path, pdf_path) + + +def _figure_tex_paths(tex_path: pathlib.Path) -> list[pathlib.Path]: + """Return standalone figure TeX files for a report bundle.""" + data_dir = tex_path.parent / 'data' + if not data_dir.is_dir(): + return [] + return sorted(data_dir.glob('fit_*.tex')) + + def _is_engine_runtime_failure( engine_name: str, result: subprocess.CompletedProcess[str], @@ -133,6 +158,7 @@ def _compile_pdf( engine_name, executable = engine compile_tex_path = tex_path.resolve() compile_pdf_path = pdf_path.resolve() + compile_pdf_path.unlink(missing_ok=True) command = _compile_command( engine_name, executable, @@ -206,6 +232,8 @@ def _compile_environment(tex_path: pathlib.Path) -> dict[str, str]: """Return a TeX subprocess environment with vendored styles.""" environment = os.environ.copy() styles_dir = tex_path.parent / 'styles' + if not styles_dir.is_dir(): + styles_dir = tex_path.parent.parent / 'styles' texinputs = environment.get('TEXINPUTS', '') environment['TEXINPUTS'] = f'{styles_dir}{os.pathsep}{texinputs}' return environment diff --git a/src/easydiffraction/report/templates/tex/figure.tex.j2 b/src/easydiffraction/report/templates/tex/figure.tex.j2 new file mode 100644 index 000000000..6635d1c7f --- /dev/null +++ b/src/easydiffraction/report/templates/tex/figure.tex.j2 @@ -0,0 +1,74 @@ +\documentclass[border=2pt]{standalone} +\usepackage{pgfplots} +\usepgfplotslibrary{groupplots} +\pgfplotsset{compat=1.18} +\pgfplotsset{set layers} +{% for style in styles.values() %} +\definecolor{ {{- style.color_name -}} }{RGB}{ {{- style.rgb -}} } +{% endfor %} +{% for style in bragg_styles %} +\definecolor{ {{- style.color_name -}} }{RGB}{ {{- style.rgb -}} } +{% endfor %} +{% set axes_labels = fit_data.axes_labels | default(["", "Intensity"], true) -%} +{% set x_label = axes_labels[0] | tex_axis_label -%} +{% set y_label = axes_labels[1] | tex_axis_label -%} +{% set bragg_tick_sets = fit_data.bragg_tick_sets | default([], true) -%} +\begin{document} +\begin{tikzpicture} +\begin{groupplot}[ +group style={ +group size=1 by {% if bragg_tick_sets %}3{% else %}2{% endif %}, +vertical sep=4pt, +x descriptions at=edge bottom, +}, +width=12cm, +xmin={{ ranges.x_min | tex_number }}, +xmax={{ ranges.x_max | tex_number }}, +mark layer=like plot, +tick align=outside, +] +\nextgroupplot[ +height=4.2cm, +ymin={{ ranges.y_min | tex_number }}, +ymax={{ ranges.y_max | tex_number }}, +xticklabels=\empty, +ylabel={ {{ y_label }} }, +legend pos=north east, +legend style={draw=none, fill=white, fill opacity=0.5, text opacity=1}, +] +\addplot+[mark=*, mark size=0.75pt, color={{ styles.meas.color_name }}, line width=0.5pt, line join=bevel, mark options={line width=0pt}] table[x=x, y=meas, col sep=comma] { {{- csv_filename -}} }; +\addlegendentry{ {{- styles.meas.name | tex -}} } +\addplot+[color={{ styles.calc.color_name }}, line width=0.75pt, line join=bevel, no markers] table[x=x, y=calc, col sep=comma] { {{- csv_filename -}} }; +\addlegendentry{ {{- styles.calc.name | tex -}} } +{% if bragg_tick_sets %} +\nextgroupplot[ +height=1.05cm, +ymin=0.5, +ymax={{ (bragg_tick_sets | length) + 0.5 }}, +y dir=reverse, +ytick={{ "{" }}{% for tick_set in bragg_tick_sets %}{{ loop.index }}{% if not loop.last %},{% endif %}{% endfor %}{{ "}" }}, +yticklabels={{ "{" }}{% for tick_set in bragg_tick_sets %}{{ "{" }}{{ tick_set.phase_id | tex }}{{ "}" }}{% if not loop.last %},{% endif %}{% endfor %}{{ "}" }}, +xticklabels=\empty, +ylabel={Bragg}, +] +{% for tick_set in bragg_tick_sets %} +{% set bragg_row = loop.index -%} +{% set tick_style = bragg_styles[loop.index0 % (bragg_styles | length)] -%} +\addplot+[color={{ tick_style.color_name }}, only marks, mark=|, mark size=5pt] coordinates { +{% for x_value in tick_set.x %} +({{ x_value | tex_number }},{{ bragg_row }}) +{% endfor %} +}; +{% endfor %} +{% endif %} +\nextgroupplot[ +height=1.45cm, +ymin={{ ranges.residual_y_min | tex_number }}, +ymax={{ ranges.residual_y_max | tex_number }}, +xlabel={ {{ x_label }} }, +ylabel={Residual}, +] +\addplot+[color={{ styles.diff.color_name }}, line width=0.5pt, line join=bevel, no markers] table[x=x, y=diff, col sep=comma] { {{- csv_filename -}} }; +\end{groupplot} +\end{tikzpicture} +\end{document} diff --git a/src/easydiffraction/report/templates/tex/report.tex.j2 b/src/easydiffraction/report/templates/tex/report.tex.j2 index de56ac237..15b40d571 100644 --- a/src/easydiffraction/report/templates/tex/report.tex.j2 +++ b/src/easydiffraction/report/templates/tex/report.tex.j2 @@ -1,13 +1,5 @@ \documentclass[11pt,a4paper]{styles/iucrjournals} -\usepackage{pgfplots} -\usepgfplotslibrary{groupplots} -\pgfplotsset{compat=1.18} -{% for style in tex.fit_plot_styles.values() %} -\definecolor{ {{- style.color_name -}} }{RGB}{ {{- style.rgb -}} } -{% endfor %} -{% for style in tex.fit_bragg_tick_styles %} -\definecolor{ {{- style.color_name -}} }{RGB}{ {{- style.rgb -}} } -{% endfor %} +\usepackage{graphicx} {% macro tex_field_header(field, fallback) -%} {% set label = (field.label or fallback) | tex_markup -%} {% set units = field.units | tex_markup -%} @@ -173,85 +165,13 @@ Calculator & {{ experiment.calculator.type | tex }} \\ \end{tabular} \end{table} -{% if experiment.fit_data and tex.fit_csv_paths[experiment.id] %} +{% if experiment.fit_data and tex.fit_figure_paths[experiment.id] %} %------------- \subsubsection{Fit quality} \begin{figure}[H] \centering -\begin{tikzpicture} -{% set ranges = tex.fit_plot_ranges[experiment.id] -%} -{% set styles = tex.fit_plot_styles -%} -{% set axes_labels = experiment.fit_data.axes_labels | default(["", "Intensity"], true) -%} -{% set x_label = axes_labels[0] | tex_axis_label -%} -{% set y_label = axes_labels[1] | tex_axis_label -%} -{% set bragg_tick_sets = experiment.fit_data.bragg_tick_sets | default([], true) -%} -{% set bragg_styles = tex.fit_bragg_tick_styles -%} -\begin{groupplot}[ -group style={ -group size=1 by {% if bragg_tick_sets %}3{% else %}2{% endif %}, -vertical sep=0.04\linewidth, -xlabels at=edge bottom, -xticklabels at=edge bottom, -}, -width=\linewidth, -xmin={{ ranges.x_min | tex_number }}, -xmax={{ ranges.x_max | tex_number }}, -mark layer=like plot, -tick align=outside, -] -\nextgroupplot[ -height=0.34\linewidth, -ymin={{ ranges.y_min | tex_number }}, -ymax={{ ranges.y_max | tex_number }}, -xticklabels=\empty, -ylabel={ {{ y_label }} }, -legend pos=north east, -legend style={draw=none, fill=white, fill opacity=0.5, text opacity=1}, -] -{% if experiment.fit_data.series.meas.su is not none %} -\addplot+[color={{ styles.meas.color_name }}, line width={{ styles.meas.line_width | tex_number }}pt, line join=bevel, mark=*, mark size=1pt, error bars/.cd, y dir=both, y explicit] table[x=x, y=meas, y error=meas_su, col sep=comma] { {{- tex.fit_csv_paths[experiment.id] -}} }; -{% else %} -\addplot+[color={{ styles.meas.color_name }}, line width={{ styles.meas.line_width | tex_number }}pt, line join=bevel, mark=*, mark size=1pt] table[x=x, y=meas, col sep=comma] { {{- tex.fit_csv_paths[experiment.id] -}} }; -{% endif %} -\addlegendentry{ {{- styles.meas.name | tex -}} } -{% if experiment.fit_data.series.bkg %} -\addplot+[color={{ styles.bkg.color_name }}, line width={{ styles.bkg.line_width | tex_number }}pt, line join=bevel, no markers] table[x=x, y=bkg, col sep=comma] { {{- tex.fit_csv_paths[experiment.id] -}} }; -\addlegendentry{ {{- styles.bkg.name | tex -}} } -{% endif %} -\addplot+[color={{ styles.calc.color_name }}, line width={{ styles.calc.line_width | tex_number }}pt, line join=bevel, no markers] table[x=x, y=calc, col sep=comma] { {{- tex.fit_csv_paths[experiment.id] -}} }; -\addlegendentry{ {{- styles.calc.name | tex -}} } -{% if bragg_tick_sets %} -\nextgroupplot[ -height=0.10\linewidth, -ymin=0.5, -ymax={{ (bragg_tick_sets | length) + 0.5 }}, -y dir=reverse, -ytick={{ "{" }}{% for tick_set in bragg_tick_sets %}{{ loop.index }}{% if not loop.last %},{% endif %}{% endfor %}{{ "}" }}, -yticklabels={{ "{" }}{% for tick_set in bragg_tick_sets %}{{ "{" }}{{ tick_set.phase_id | tex }}{{ "}" }}{% if not loop.last %},{% endif %}{% endfor %}{{ "}" }}, -xticklabels=\empty, -ylabel={Bragg}, -] -{% for tick_set in bragg_tick_sets %} -{% set bragg_row = loop.index -%} -{% set tick_style = bragg_styles[loop.index0 % (bragg_styles | length)] -%} -\addplot+[color={{ tick_style.color_name }}, only marks, mark=|, mark size=5pt] coordinates { -{% for x_value in tick_set.x %} -({{ x_value | tex_number }},{{ bragg_row }}) -{% endfor %} -}; -{% endfor %} -{% endif %} -\nextgroupplot[ -height=0.12\linewidth, -ymin={{ ranges.residual_y_min | tex_number }}, -ymax={{ ranges.residual_y_max | tex_number }}, -xlabel={ {{ x_label }} }, -ylabel={Residual}, -] -\addplot+[color={{ styles.diff.color_name }}, line width={{ styles.diff.line_width | tex_number }}pt, line join=bevel, no markers] table[x=x, y=diff, col sep=comma] { {{- tex.fit_csv_paths[experiment.id] -}} }; -\end{groupplot} -\end{tikzpicture} +\includegraphics[width=\linewidth]{ {{- tex.fit_figure_paths[experiment.id] -}} } \caption{Measured and calculated fit for {{ experiment.id | tex }}.} \end{figure} {% endif %} diff --git a/src/easydiffraction/report/tex_renderer.py b/src/easydiffraction/report/tex_renderer.py index b91fae5be..562d62811 100644 --- a/src/easydiffraction/report/tex_renderer.py +++ b/src/easydiffraction/report/tex_renderer.py @@ -12,11 +12,14 @@ from jinja2 import Environment from jinja2 import PackageLoader +from easydiffraction.report.downsample import MAX_FIGURE_POINTS +from easydiffraction.report.downsample import downsample_min_max_indices from easydiffraction.report.fit_plot import fit_bragg_tick_styles from easydiffraction.report.fit_plot import fit_plot_ranges from easydiffraction.report.fit_plot import fit_plot_styles _TEMPLATE_NAME = 'tex/report.tex.j2' +_FIGURE_TEMPLATE_NAME = 'tex/figure.tex.j2' _TEX_SPECIAL_CHARS = { '\\': r'\textbackslash{}', '&': r'\&', @@ -29,6 +32,8 @@ '~': r'\textasciitilde{}', '^': r'\textasciicircum{}', } + + def tex_report_path( project: object, path: str | pathlib.Path | None = None, @@ -83,6 +88,7 @@ def render_tex_report(context: dict[str, object]) -> str: template_context['tex'] = _tex_context( context, fit_csv_paths=_fit_csv_paths(context), + fit_figure_paths=_fit_figure_paths(context), ) return _environment().get_template(_TEMPLATE_NAME).render(**template_context) @@ -118,9 +124,11 @@ def save_tex_report( styles_dir.mkdir(parents=True, exist_ok=True) template_context = dict(context) + fit_asset_paths = _write_fit_assets(context, tex_dir) template_context['tex'] = _tex_context( context, - fit_csv_paths=_write_fit_csvs(context, tex_dir), + fit_csv_paths=fit_asset_paths['csv'], + fit_figure_paths=fit_asset_paths['figure'], ) output_path.write_text( _render_prepared_context(template_context), @@ -159,30 +167,39 @@ def _prepare_tex_bundle(tex_dir: pathlib.Path) -> None: shutil.rmtree(path) -def _write_fit_csvs( +def _write_fit_assets( context: dict[str, object], out_dir: pathlib.Path, -) -> dict[str, str]: - """Write fit-data CSV files and return paths for TeX.""" - paths = {} +) -> dict[str, dict[str, str]]: + """Write fit-data CSV and figure TeX files.""" + csv_paths: dict[str, str] = {} + figure_paths: dict[str, str] = {} for experiment in _experiment_contexts(context): fit_data = experiment.get('fit_data') if fit_data is None: continue experiment_id = str(experiment.get('id') or 'experiment') csv_path = _write_fit_csv(experiment_id, fit_data, out_dir) - paths[experiment_id] = f'data/{csv_path.name}' - return paths + figure_path = _write_fit_figure_tex( + experiment=experiment, + csv_path=csv_path, + out_dir=out_dir, + ) + csv_paths[experiment_id] = f'data/{csv_path.name}' + figure_paths[experiment_id] = f'data/{figure_path.stem}.pdf' + return {'csv': csv_paths, 'figure': figure_paths} def _tex_context( context: dict[str, object], *, fit_csv_paths: dict[str, str], + fit_figure_paths: dict[str, str], ) -> dict[str, object]: """Return TeX-specific render context.""" return { 'fit_csv_paths': fit_csv_paths, + 'fit_figure_paths': fit_figure_paths, 'fit_bragg_tick_styles': fit_bragg_tick_styles(), 'fit_plot_ranges': _fit_plot_ranges(context), 'fit_plot_styles': fit_plot_styles(), @@ -210,7 +227,7 @@ def _write_fit_csv( data_dir = out_dir / 'data' data_dir.mkdir(parents=True, exist_ok=True) csv_path = data_dir / _fit_csv_filename(expt_id) - columns = _fit_csv_columns(fit_data) + columns = _fit_csv_columns(expt_id, fit_data) _validate_fit_csv_columns(expt_id, columns) with csv_path.open('w', newline='', encoding='utf-8') as handle: writer = csv.writer(handle) @@ -219,6 +236,35 @@ def _write_fit_csv( return csv_path +def _write_fit_figure_tex( + *, + experiment: dict[str, object], + csv_path: pathlib.Path, + out_dir: pathlib.Path, +) -> pathlib.Path: + """Write one standalone pgfplots TeX figure.""" + data_dir = out_dir / 'data' + data_dir.mkdir(parents=True, exist_ok=True) + experiment_id = str(experiment.get('id') or 'experiment') + fit_data = experiment['fit_data'] + figure_path = data_dir / f'{_fit_asset_stem(experiment_id)}.tex' + template_context = { + 'experiment': experiment, + 'fit_data': fit_data, + 'csv_filename': csv_path.name, + 'ranges': fit_plot_ranges(fit_data), + 'styles': fit_plot_styles(), + 'bragg_styles': fit_bragg_tick_styles(), + } + figure_path.write_text( + _environment().get_template(_FIGURE_TEMPLATE_NAME).render( + **template_context, + ), + encoding='utf-8', + ) + return figure_path + + def _fit_csv_paths(context: dict[str, object]) -> dict[str, str]: """Return expected fit-data CSV paths for TeX rendering.""" paths = {} @@ -230,6 +276,17 @@ def _fit_csv_paths(context: dict[str, object]) -> dict[str, str]: return paths +def _fit_figure_paths(context: dict[str, object]) -> dict[str, str]: + """Return expected fit-figure PDF paths for TeX rendering.""" + paths = {} + for experiment in _experiment_contexts(context): + if experiment.get('fit_data') is None: + continue + experiment_id = str(experiment.get('id') or 'experiment') + paths[experiment_id] = f'data/{_fit_asset_stem(experiment_id)}.pdf' + return paths + + def _experiment_contexts(context: dict[str, object]) -> list[dict[str, object]]: """Return experiment contexts from a report context.""" experiments = context.get('experiments') @@ -244,23 +301,30 @@ def _experiment_contexts(context: dict[str, object]) -> list[dict[str, object]]: def _fit_csv_filename(expt_id: str) -> str: """Return a filesystem-safe fit-data CSV filename.""" + return f'{_fit_asset_stem(expt_id)}.csv' + + +def _fit_asset_stem(expt_id: str) -> str: + """Return a filesystem-safe fit-data asset stem.""" safe_id = ''.join( char if char.isascii() and (char.isalnum() or char in {'-', '_'}) else '_' for char in expt_id ).strip('_') if not safe_id: safe_id = 'experiment' - return f'{safe_id}.csv' + return f'fit_{safe_id}' -def _fit_csv_columns(fit_data: dict[str, object]) -> list[tuple[str, list[object]]]: +def _fit_csv_columns( + expt_id: str, + fit_data: dict[str, object], +) -> list[tuple[str, list[object]]]: """Return ordered CSV columns for one fit-data payload.""" x_data = fit_data['x'] series = fit_data['series'] meas = series['meas'] calc = series['calc'] diff = series['diff'] - bkg = series['bkg'] columns = [ ('x', list(x_data['values'])), @@ -274,9 +338,21 @@ def _fit_csv_columns(fit_data: dict[str, object]) -> list[tuple[str, list[object ('diff', list(diff['values'])), ] ) - if bkg is not None: - columns.append(('bkg', list(bkg['values']))) - return columns + return _downsample_fit_csv_columns(expt_id, columns) + + +def _downsample_fit_csv_columns( + expt_id: str, + columns: list[tuple[str, list[object]]], +) -> list[tuple[str, list[object]]]: + """Return columns selected by measured-intensity extrema.""" + _validate_fit_csv_columns(expt_id, columns) + meas_values = dict(columns)['meas'] + indices = downsample_min_max_indices(meas_values, MAX_FIGURE_POINTS) + return [ + (name, [values[index] for index in indices]) + for name, values in columns + ] def _validate_fit_csv_columns( From a454de1ff7c7324f8031ef6bdf2da9e4c015e84e Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 28 May 2026 13:38:01 +0200 Subject: [PATCH 061/129] Serialize report Plotly figures via shared notebook path --- docs/dev/plans/project-summary-rendering.md | 2 +- .../display/plotters/plotly.py | 43 ++++++++++++++++--- src/easydiffraction/report/html_renderer.py | 6 ++- 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index c6c7854ad..59ae79a3e 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -879,7 +879,7 @@ exceptions. - Commit: `Build Plotly fit figures from fit_data context`. -- [ ] **P1.18 — HTML serialization parity: forced light theme + notebook modebar (ADR §2)** +- [x] **P1.18 — HTML serialization parity: forced light theme + notebook modebar (ADR §2)** - Files: `src/easydiffraction/report/html_renderer.py`; `src/easydiffraction/display/plotters/plotly.py` (extract diff --git a/src/easydiffraction/display/plotters/plotly.py b/src/easydiffraction/display/plotters/plotly.py index 5430dc05d..5977e2369 100644 --- a/src/easydiffraction/display/plotters/plotly.py +++ b/src/easydiffraction/display/plotters/plotly.py @@ -956,17 +956,48 @@ def _show_figure( if in_pycharm() or display is None or HTML is None: fig.show(config=config) else: - post_script = self._html_post_script(fig) - html_fig = pio.to_html( + html_fig = self.serialize_html( fig, include_plotlyjs='cdn', - full_html=False, - config=config, - post_script=post_script, ) - html_fig = self._wrap_html_figure(fig, html_fig) display(HTML(html_fig)) + @classmethod + def serialize_html( + cls, + fig: object, + *, + include_plotlyjs: bool | str, + force_template: str | None = None, + ) -> str: + """ + Serialize a Plotly figure with EasyDiffraction controls. + + Parameters + ---------- + fig : object + Plotly figure to serialize. + include_plotlyjs : bool | str + Plotly JavaScript inclusion mode passed to Plotly. + force_template : str | None, default=None + Optional template name applied before serialization. + + Returns + ------- + str + Inline HTML containing the figure and helper scripts. + """ + if force_template is not None: + fig.update_layout(template=force_template) + html_fig = pio.to_html( + fig, + include_plotlyjs=include_plotlyjs, + full_html=False, + config=cls._get_config(), + post_script=cls._html_post_script(fig), + ) + return cls._wrap_html_figure(fig, html_fig) + @classmethod def _get_layout( cls, diff --git a/src/easydiffraction/report/html_renderer.py b/src/easydiffraction/report/html_renderer.py index c9b210293..6c80f8b14 100644 --- a/src/easydiffraction/report/html_renderer.py +++ b/src/easydiffraction/report/html_renderer.py @@ -284,7 +284,11 @@ def _figure_html(figure: object, *, include_plotlyjs: bool | str) -> str: """Return an HTML snippet for one figure-like object.""" to_html = getattr(figure, 'to_html', None) if callable(to_html): - return to_html(full_html=False, include_plotlyjs=include_plotlyjs) + return PlotlyPlotter.serialize_html( + figure, + include_plotlyjs=include_plotlyjs, + force_template='plotly_white', + ) return str(figure) From 8175f60e014d851982661536ea8147e752d5e1f7 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 28 May 2026 15:25:37 +0200 Subject: [PATCH 062/129] Match pgfplots figure geometry to Plotly layout --- docs/dev/plans/project-summary-rendering.md | 2 +- src/easydiffraction/report/fit_plot.py | 16 ++++++++++++++++ .../report/templates/tex/figure.tex.j2 | 15 +++++++++------ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 59ae79a3e..b3bea3747 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -975,7 +975,7 @@ exceptions. - Commit: `Update tutorials and docs for single-style report surface`. -- [ ] **P1.21 — Visual parity check — PDF figure vs Plotly figure** +- [x] **P1.21 — Visual parity check — PDF figure vs Plotly figure** - Files: `src/easydiffraction/report/templates/tex/figure.tex.j2` (geometry fixes); read-only reference to the Plotly builder in `src/easydiffraction/display/plotters/plotly.py`. diff --git a/src/easydiffraction/report/fit_plot.py b/src/easydiffraction/report/fit_plot.py index 207bd7596..a24994461 100644 --- a/src/easydiffraction/report/fit_plot.py +++ b/src/easydiffraction/report/fit_plot.py @@ -14,6 +14,7 @@ from easydiffraction.display.plotters.plotly import BRAGG_TICK_COLORS from easydiffraction.display.plotters.plotly import CALCULATED_LINE_WIDTH from easydiffraction.display.plotters.plotly import DEFAULT_COLORS +from easydiffraction.display.plotters.plotly import DISPLAY_TICK_FRACTIONS from easydiffraction.display.plotters.plotly import MAIN_INTENSITY_RANGE_MARGIN_FRACTION from easydiffraction.display.plotters.plotly import MEASURED_LINE_WIDTH from easydiffraction.display.plotters.plotly import RESIDUAL_LINE_WIDTH @@ -72,6 +73,7 @@ def fit_plot_ranges(fit_data: dict[str, Any]) -> dict[str, float]: 'y_max': main_y_max, 'residual_y_min': -residual_limit, 'residual_y_max': residual_limit, + 'residual_y_tick': _display_tick_limit(residual_limit), } @@ -107,6 +109,20 @@ def _residual_limit(*, main_y_min: float, main_y_max: float) -> float: return 1.0 +def _display_tick_limit(raw_limit: float) -> float: + if raw_limit <= 0: + return 1.0 + + exponent = float(np.floor(np.log10(raw_limit))) + base = 10.0**exponent + fraction = raw_limit / base + + for nice_fraction in reversed(DISPLAY_TICK_FRACTIONS): + if fraction >= nice_fraction: + return nice_fraction * base + return DISPLAY_TICK_FRACTIONS[0] * base + + def _rgb_channels(color: str) -> str: match = _COLOR_PATTERN.fullmatch(color) if match is None: diff --git a/src/easydiffraction/report/templates/tex/figure.tex.j2 b/src/easydiffraction/report/templates/tex/figure.tex.j2 index 6635d1c7f..67f0fc6f8 100644 --- a/src/easydiffraction/report/templates/tex/figure.tex.j2 +++ b/src/easydiffraction/report/templates/tex/figure.tex.j2 @@ -18,7 +18,7 @@ \begin{groupplot}[ group style={ group size=1 by {% if bragg_tick_sets %}3{% else %}2{% endif %}, -vertical sep=4pt, +vertical sep=8pt, x descriptions at=edge bottom, }, width=12cm, @@ -26,6 +26,8 @@ xmin={{ ranges.x_min | tex_number }}, xmax={{ ranges.x_max | tex_number }}, mark layer=like plot, tick align=outside, +label style={font=\footnotesize}, +tick label style={font=\footnotesize}, ] \nextgroupplot[ height=4.2cm, @@ -34,7 +36,7 @@ ymax={{ ranges.y_max | tex_number }}, xticklabels=\empty, ylabel={ {{ y_label }} }, legend pos=north east, -legend style={draw=none, fill=white, fill opacity=0.5, text opacity=1}, +legend style={draw=none, fill=white, fill opacity=0.5, text opacity=1, font=\footnotesize}, ] \addplot+[mark=*, mark size=0.75pt, color={{ styles.meas.color_name }}, line width=0.5pt, line join=bevel, mark options={line width=0pt}] table[x=x, y=meas, col sep=comma] { {{- csv_filename -}} }; \addlegendentry{ {{- styles.meas.name | tex -}} } @@ -42,14 +44,14 @@ legend style={draw=none, fill=white, fill opacity=0.5, text opacity=1}, \addlegendentry{ {{- styles.calc.name | tex -}} } {% if bragg_tick_sets %} \nextgroupplot[ -height=1.05cm, +height=1.6cm, ymin=0.5, ymax={{ (bragg_tick_sets | length) + 0.5 }}, y dir=reverse, ytick={{ "{" }}{% for tick_set in bragg_tick_sets %}{{ loop.index }}{% if not loop.last %},{% endif %}{% endfor %}{{ "}" }}, yticklabels={{ "{" }}{% for tick_set in bragg_tick_sets %}{{ "{" }}{{ tick_set.phase_id | tex }}{{ "}" }}{% if not loop.last %},{% endif %}{% endfor %}{{ "}" }}, +yticklabel style={font=\footnotesize}, xticklabels=\empty, -ylabel={Bragg}, ] {% for tick_set in bragg_tick_sets %} {% set bragg_row = loop.index -%} @@ -62,11 +64,12 @@ ylabel={Bragg}, {% endfor %} {% endif %} \nextgroupplot[ -height=1.45cm, +height=1.8cm, ymin={{ ranges.residual_y_min | tex_number }}, ymax={{ ranges.residual_y_max | tex_number }}, +ytick={-{{ ranges.residual_y_tick | tex_number }},0,{{ ranges.residual_y_tick | tex_number }}}, xlabel={ {{ x_label }} }, -ylabel={Residual}, +yticklabel style={font=\footnotesize}, ] \addplot+[color={{ styles.diff.color_name }}, line width=0.5pt, line join=bevel, no markers] table[x=x, y=diff, col sep=comma] { {{- csv_filename -}} }; \end{groupplot} From 07383714e0446c6c9fe0450362bf17ac2c1d3bf3 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 28 May 2026 15:25:57 +0200 Subject: [PATCH 063/129] Reach Phase 1 review gate --- docs/dev/plans/project-summary-rendering.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index b3bea3747..1feba5a96 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -1029,7 +1029,7 @@ exceptions. - Commit one geometry fix per logical change, e.g. `Match pgfplots figure geometry to Plotly layout`. -- [ ] **P1.22 — Reach Phase 1 review gate** +- [x] **P1.22 — Reach Phase 1 review gate** - No-code step. Mark every `[ ]` above as `[x]`; commit the plan-file update alone. - Commit: `Reach Phase 1 review gate`. From 058e240377a7d44b565eb383d2d823675f791f71 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 28 May 2026 15:34:16 +0200 Subject: [PATCH 064/129] Scale pgfplots axes to Plotly panel ratios --- docs/dev/plans/project-summary-rendering.md | 5 + src/easydiffraction/report/fit_plot.py | 98 +++++++++++++++++++ .../report/templates/tex/figure.tex.j2 | 11 ++- src/easydiffraction/report/tex_renderer.py | 2 + 4 files changed, 111 insertions(+), 5 deletions(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 1feba5a96..482c6360e 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -1026,6 +1026,11 @@ exceptions. collide — the demo symptom where "Bragg", "lbco", and "Residual" overprinted). Re-run steps 1–4 until the geometry matches the Plotly figure. + - Follow-up adjustment: derive pgfplots panel heights and + vertical spacing from the Plotly row-height constants. Use + `scale only axis` so the axes rectangles preserve Plotly's + main/Bragg/residual ratios, with total axis height equal to + the axis width. - Commit one geometry fix per logical change, e.g. `Match pgfplots figure geometry to Plotly layout`. diff --git a/src/easydiffraction/report/fit_plot.py b/src/easydiffraction/report/fit_plot.py index a24994461..7adddd3bf 100644 --- a/src/easydiffraction/report/fit_plot.py +++ b/src/easydiffraction/report/fit_plot.py @@ -9,18 +9,27 @@ import numpy as np +from easydiffraction.display.plotters.base import DEFAULT_HEIGHT from easydiffraction.display.plotters.base import SERIES_CONFIG from easydiffraction.display.plotters.plotly import BACKGROUND_LINE_WIDTH from easydiffraction.display.plotters.plotly import BRAGG_TICK_COLORS +from easydiffraction.display.plotters.plotly import BRAGG_TICK_MARKER_LINE_WIDTH +from easydiffraction.display.plotters.plotly import BRAGG_TICK_MARKER_SIZE +from easydiffraction.display.plotters.plotly import BRAGG_TICK_SYMBOL_HEIGHT_SCALE from easydiffraction.display.plotters.plotly import CALCULATED_LINE_WIDTH +from easydiffraction.display.plotters.plotly import COMPOSITE_MARGIN_BOTTOM +from easydiffraction.display.plotters.plotly import COMPOSITE_MARGIN_TOP +from easydiffraction.display.plotters.plotly import COMPOSITE_VERTICAL_SPACING from easydiffraction.display.plotters.plotly import DEFAULT_COLORS from easydiffraction.display.plotters.plotly import DISPLAY_TICK_FRACTIONS from easydiffraction.display.plotters.plotly import MAIN_INTENSITY_RANGE_MARGIN_FRACTION from easydiffraction.display.plotters.plotly import MEASURED_LINE_WIDTH +from easydiffraction.display.plotters.plotly import PLOTLY_HEIGHT_PER_UNIT from easydiffraction.display.plotters.plotly import RESIDUAL_LINE_WIDTH from easydiffraction.display.plotting import DEFAULT_RESIDUAL_HEIGHT_FRACTION _COLOR_PATTERN = re.compile(r'rgb\((\d+),\s*(\d+),\s*(\d+)\)') +_FIGURE_AXIS_WIDTH_CM = 12.0 _STYLE_SOURCE_KEYS = { 'meas': 'meas', 'bkg': 'bkg', @@ -77,6 +86,38 @@ def fit_plot_ranges(fit_data: dict[str, Any]) -> dict[str, float]: } +def fit_plot_geometry(fit_data: dict[str, Any]) -> dict[str, float]: + """Return Plotly-matched pgfplots axis geometry.""" + bragg_tick_sets = fit_data.get('bragg_tick_sets') or [] + has_bragg_ticks = bool(bragg_tick_sets) + has_residual = _has_residual(fit_data) + row_count = 1 + int(has_bragg_ticks) + int(has_residual) + main_pixels, residual_pixels = _non_bragg_row_heights( + row_count=row_count, + has_bragg_ticks=has_bragg_ticks, + has_residual=has_residual, + ) + + row_heights = [main_pixels] + if has_bragg_ticks: + row_heights.append(_bragg_row_height_pixels(len(bragg_tick_sets))) + if has_residual and residual_pixels is not None: + row_heights.append(residual_pixels) + + height_sum = sum(row_heights) + scaled_heights = [ + _FIGURE_AXIS_WIDTH_CM * row_height / height_sum + for row_height in row_heights + ] + return { + 'axis_width_cm': _FIGURE_AXIS_WIDTH_CM, + 'main_height_cm': scaled_heights[0], + 'bragg_height_cm': scaled_heights[1] if has_bragg_ticks else 0.0, + 'residual_height_cm': scaled_heights[-1] if has_residual else 0.0, + 'vertical_sep_cm': _vertical_sep_cm(row_count), + } + + def fit_bragg_tick_styles() -> list[dict[str, str]]: """Return Plotly-derived Bragg tick colors for pgfplots.""" return [ @@ -109,6 +150,63 @@ def _residual_limit(*, main_y_min: float, main_y_max: float) -> float: return 1.0 +def _has_residual(fit_data: dict[str, Any]) -> bool: + series = fit_data.get('series') + return isinstance(series, dict) and 'diff' in series + + +def _non_bragg_row_heights( + *, + row_count: int, + has_bragg_ticks: bool, + has_residual: bool, +) -> tuple[float, float | None]: + plot_area_height = _composite_plot_area_height() + available_row_pixels = plot_area_height * _subplot_available_height_fraction(row_count) + baseline_bragg_pixels = ( + _bragg_tick_symbol_height_pixels() if has_bragg_ticks else 0.0 + ) + non_bragg_pixels = max(available_row_pixels - baseline_bragg_pixels, 1.0) + + if not has_residual: + return non_bragg_pixels, None + + main_pixels = non_bragg_pixels / (1.0 + DEFAULT_RESIDUAL_HEIGHT_FRACTION) + residual_pixels = main_pixels * DEFAULT_RESIDUAL_HEIGHT_FRACTION + return main_pixels, residual_pixels + + +def _composite_plot_area_height() -> float: + full_height = float(DEFAULT_HEIGHT * PLOTLY_HEIGHT_PER_UNIT) + return max(full_height - COMPOSITE_MARGIN_TOP - COMPOSITE_MARGIN_BOTTOM, 1.0) + + +def _subplot_available_height_fraction(row_count: int) -> float: + return 1.0 - COMPOSITE_VERTICAL_SPACING * max(row_count - 1, 0) + + +def _bragg_tick_symbol_height_pixels() -> float: + return ( + BRAGG_TICK_MARKER_SIZE * BRAGG_TICK_SYMBOL_HEIGHT_SCALE + + BRAGG_TICK_MARKER_LINE_WIDTH + ) + + +def _bragg_row_height_pixels(tick_set_count: int) -> float: + return float(tick_set_count) * _bragg_tick_symbol_height_pixels() + + +def _vertical_sep_cm(row_count: int) -> float: + available_fraction = _subplot_available_height_fraction(row_count) + if available_fraction <= 0.0: + return 0.0 + return ( + _FIGURE_AXIS_WIDTH_CM + * COMPOSITE_VERTICAL_SPACING + / available_fraction + ) + + def _display_tick_limit(raw_limit: float) -> float: if raw_limit <= 0: return 1.0 diff --git a/src/easydiffraction/report/templates/tex/figure.tex.j2 b/src/easydiffraction/report/templates/tex/figure.tex.j2 index 67f0fc6f8..f4bfdae30 100644 --- a/src/easydiffraction/report/templates/tex/figure.tex.j2 +++ b/src/easydiffraction/report/templates/tex/figure.tex.j2 @@ -18,10 +18,11 @@ \begin{groupplot}[ group style={ group size=1 by {% if bragg_tick_sets %}3{% else %}2{% endif %}, -vertical sep=8pt, +vertical sep={{ geometry.vertical_sep_cm | tex_number }}cm, x descriptions at=edge bottom, }, -width=12cm, +width={{ geometry.axis_width_cm | tex_number }}cm, +scale only axis, xmin={{ ranges.x_min | tex_number }}, xmax={{ ranges.x_max | tex_number }}, mark layer=like plot, @@ -30,7 +31,7 @@ label style={font=\footnotesize}, tick label style={font=\footnotesize}, ] \nextgroupplot[ -height=4.2cm, +height={{ geometry.main_height_cm | tex_number }}cm, ymin={{ ranges.y_min | tex_number }}, ymax={{ ranges.y_max | tex_number }}, xticklabels=\empty, @@ -44,7 +45,7 @@ legend style={draw=none, fill=white, fill opacity=0.5, text opacity=1, font=\foo \addlegendentry{ {{- styles.calc.name | tex -}} } {% if bragg_tick_sets %} \nextgroupplot[ -height=1.6cm, +height={{ geometry.bragg_height_cm | tex_number }}cm, ymin=0.5, ymax={{ (bragg_tick_sets | length) + 0.5 }}, y dir=reverse, @@ -64,7 +65,7 @@ xticklabels=\empty, {% endfor %} {% endif %} \nextgroupplot[ -height=1.8cm, +height={{ geometry.residual_height_cm | tex_number }}cm, ymin={{ ranges.residual_y_min | tex_number }}, ymax={{ ranges.residual_y_max | tex_number }}, ytick={-{{ ranges.residual_y_tick | tex_number }},0,{{ ranges.residual_y_tick | tex_number }}}, diff --git a/src/easydiffraction/report/tex_renderer.py b/src/easydiffraction/report/tex_renderer.py index 562d62811..3ced981c8 100644 --- a/src/easydiffraction/report/tex_renderer.py +++ b/src/easydiffraction/report/tex_renderer.py @@ -15,6 +15,7 @@ from easydiffraction.report.downsample import MAX_FIGURE_POINTS from easydiffraction.report.downsample import downsample_min_max_indices from easydiffraction.report.fit_plot import fit_bragg_tick_styles +from easydiffraction.report.fit_plot import fit_plot_geometry from easydiffraction.report.fit_plot import fit_plot_ranges from easydiffraction.report.fit_plot import fit_plot_styles @@ -252,6 +253,7 @@ def _write_fit_figure_tex( 'experiment': experiment, 'fit_data': fit_data, 'csv_filename': csv_path.name, + 'geometry': fit_plot_geometry(fit_data), 'ranges': fit_plot_ranges(fit_data), 'styles': fit_plot_styles(), 'bragg_styles': fit_bragg_tick_styles(), From beb611472686c810ceae7e57c2b7afab359c7546 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 28 May 2026 16:06:49 +0200 Subject: [PATCH 065/129] Align report plot styling with Plotly output --- docs/dev/plans/project-summary-rendering.md | 146 ++++++++++++------ .../display/plotters/plotly.py | 50 +++++- src/easydiffraction/report/fit_plot.py | 60 +++++-- .../report/templates/tex/figure.tex.j2 | 37 ++++- src/easydiffraction/report/tex_renderer.py | 69 +++++++++ 5 files changed, 297 insertions(+), 65 deletions(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 482c6360e..ec81baf64 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -77,11 +77,15 @@ plan does not re-litigate them: `fit_.pdf` and pulled into the main report via `\includegraphics` — no inline pgfplots in `.tex`, and per-figure compiles isolate the TeX memory pool. The - intensity panel carries exactly two plots (measured - line+markers, calculated line) with **no band, no - per-point error bars, no background** (error bars are the - fixed-memory-pool killer; see §3.3). The TeX engine - (Tectonic / TeX Live / MiKTeX) supplies `pgfplots` + + fit-quality figure reuses the Plotly styling contract as + far as `pgfplots` can support it: measured line + sampled + markers/error bars, background line, calculated line, + Bragg tick row, and residual panel. The TeX renderer caps + the measured marker/error-bar overlay at 300 + peak-preserving points to stay inside Tectonic's fixed + memory pool; the full measured/background/calculated/ + residual traces still use the regular figure CSV. The TeX + engine (Tectonic / TeX Live / MiKTeX) supplies `pgfplots` + `standalone` + TikZ deps from CTAN or its default sets. - **`DisplayHandler` value object** (§1.5): new `@dataclass(frozen=True, slots=True)` at @@ -149,11 +153,12 @@ plan does not re-litigate them: release tagged on jsdelivr at vendoring time. - **`reports/tex/data/fit_.csv` schema.** The CSV emitter writes one row per data point with columns - `x`, `meas`, optional `meas_su`, `calc`, `diff`. - Background is **not** plotted in the report figure (§3.3), - so no `bkg` column is required for the figure; emit it only - if another consumer needs it. Confirm `figure.tex.j2`'s - column references match this schema exactly during P1. + `x`, `meas`, optional `meas_su`, optional `bkg`, `calc`, + `diff`. When `meas_su` is present, the renderer also writes + `fit__errors.csv` with capped `x`, `meas`, + `meas_su` columns for the PGFPlots error-bar overlay. + Confirm `figure.tex.j2`'s column references match these + schemas exactly during P1. - **Style-bundle cleanup vs. wheel size.** The 10 REVTeX files dropped from the bundle reduce the wheel by ~350 KB. The Phase 2 verification step covers the actual @@ -291,11 +296,12 @@ plan does not re-litigate them: a standalone `data/fit_.tex` per experiment from the new `figure.tex.j2`; see §3.3 and P1.16). - `src/easydiffraction/report/templates/tex/figure.tex.j2` - (new — `\documentclass{standalone}` pgfplots figure: two - intensity plots (measured line+markers, calculated line), - Bragg row, residual panel. No band, no per-point error - bars, no background. `\pgfplotsset{set layers}` + - `mark layer=like plot` mandatory; see P1.16). + (new — `\documentclass{standalone}` pgfplots figure: + Plotly-derived measured/background/calculated intensity + styling, sampled measured uncertainty overlay, Bragg row, + residual panel, light inner grids, no outside tick marks. + No band. `\pgfplotsset{set layers}` + + `mark layer=like plot` mandatory; see P1.16 and P1.23). - `src/easydiffraction/report/templates/tex/report.tex.j2` (renamed from `iucr.tex.j2`) — the **main** document: tables + `\includegraphics{data/fit_.pdf}` per @@ -799,8 +805,11 @@ exceptions. (standalone figure document). - Add `_write_fit_csv(expt_id, fit_data, out_dir)` writing `/data/fit_.csv` with columns - `x, meas, calc, diff` (+ `meas_su` when present). Python - stdlib `csv`, no new dependency. + `x, meas, calc, diff` (+ `meas_su` and `bkg` when + present). When `meas_su` is present, also write + `fit__errors.csv` with capped `x`, `meas`, + `meas_su` values for the PGFPlots uncertainty overlay. + Python stdlib `csv`, no new dependency. - Add `figure.tex.j2`: a `\documentclass{standalone}` pgfplots document, one per experiment, rendered to `data/fit_.tex`. Mandatory preamble: @@ -808,23 +817,25 @@ exceptions. `mark layer=like plot` (both required — `mark layer` is inert without `set layers`; together they let the calculated line draw over the measured markers). - - **Intensity panel: exactly two plots.** - - Measured — connecting line + markers: - `\addplot+[mark=*, mark size=0.75pt, color=ed_meas, - line width=0.5pt, line join=bevel, - mark options={line width=0pt}]`. (Do **not** add - `mark options={fill=…, draw=…}` — a filled `mark=*` - already inherits both from `color`; only the - `line width=0pt` is load-bearing, keeping the dot at - its nominal size.) - - Calculated — line, declared **last** so it draws on - top: `\addplot+[color=ed_calc, line width=0.75pt, - line join=bevel, no markers]`. - - **No measured–calculated band, no per-point error - bars, no background curve** on the intensity panel. - Per-point error bars are the memory killer (ADR - §3.3); measured uncertainty is not shown as error - bars in the PDF. + - **Intensity panel: Plotly-derived series order.** + - Measured — full-resolution connecting line first + (`no markers`) so the calculated line can remain on + top. + - Measured uncertainty — sampled overlay from + `fit__errors.csv`: circular markers use the + Plotly measured marker size converted from pixels to + points; error-bar line thickness and cap size come + from the same Plotly constants. The overlay is capped + at 300 peak-preserving points because drawing capped + error bars for every point exhausts Tectonic's fixed + memory pool on HRPT-sized data. + - Background — grey line when `fit_data.series.bkg` + exists. + - Calculated — red line, declared after measured / + background so it draws above the measured data. + - **No measured–calculated band.** Residual and Bragg + peaks stay in their own panels, but appear in the + main legend like Plotly. - Bragg tick row + residual panel as before (residual in its own panel; difference is not overlaid on intensity). - `report.tex.j2` (main document) includes each figure as a @@ -1029,8 +1040,9 @@ exceptions. - Follow-up adjustment: derive pgfplots panel heights and vertical spacing from the Plotly row-height constants. Use `scale only axis` so the axes rectangles preserve Plotly's - main/Bragg/residual ratios, with total axis height equal to - the axis width. + main/Bragg/residual ratios, with total axis height matching + the current Plotly chart aspect ratio (about 70% of the + axis width for the representative 856x600 HTML chart). - Commit one geometry fix per logical change, e.g. `Match pgfplots figure geometry to Plotly layout`. @@ -1039,6 +1051,41 @@ exceptions. the plan-file update alone. - Commit: `Reach Phase 1 review gate`. +- [x] **P1.23 — Align report plot styling with Plotly output** + - Files: + `src/easydiffraction/display/plotters/plotly.py`, + `src/easydiffraction/report/fit_plot.py`, + `src/easydiffraction/report/tex_renderer.py`, + `src/easydiffraction/report/templates/tex/figure.tex.j2`. + - HTML reports force report-only light styling after + applying `plotly_white`: legend background + `rgba(255, 255, 255, 0.5)` and light axis-frame colors + are applied even when the figure object was built under + a dark notebook/system theme. Notebook rendering stays + theme-adaptive. + - The measured powder trace exposes reusable style + constants: marker size 6 px, marker outline 0 px, line + width 2 px, error-bar thickness 2 px, and error-bar cap + width 4 px. The PDF renderer converts px → pt with + `1 px = 0.75 pt`, using marker radius 2.25 pt, + error-bar line width 1.5 pt, and cap mark size 3 pt. + - PDF figures use Plotly-like inner grids: vertical grid + lines on major x ticks in all panels, horizontal grid + lines in the intensity and residual panels, no outside + tick marks, and light Plotly-derived axis/grid colors. + - PDF legends include measured, background, calculated, + residual, and Bragg-peak entries. Residual and Bragg + peaks remain in their own panels even though their legend + entries live with the main legend, matching Plotly. + - Compile and visually inspect the standalone + `fit_.pdf` plus the full report PDF on the + representative `lbco_hrpt` project. The standalone figure + should match the Plotly chart's 70% height-to-width ratio + and keep the same main/Bragg/residual panel ratios and + vertical spacing. + - Commit: + `Align report plot styling with Plotly output`. + ## Test plan (Phase 2) Per AGENTS.md §Testing, every new module, class, and bug fix @@ -1137,15 +1184,18 @@ running the verification commands below, add or update: experiments emit `fit_data: None`. P1.15 surface. - [ ] **`tests/unit/easydiffraction/report/test_tex_renderer.py`** (extend) — `_write_fit_csv` writes the right schema - (`x, meas, calc, diff` + optional `meas_su`); each - standalone `data/fit_.tex` contains exactly two - intensity-panel `\addplot` calls (measured `mark=*` line, - calculated `no markers` line) and **no** `error bars`, - **no** band/fill-between, **no** background plot; the - preamble contains both `\pgfplotsset{set layers}` and - `mark layer=like plot`; the main `report.tex` uses + (`x, meas, calc, diff` + optional `meas_su` and `bkg`); + `_write_fit_errorbar_csv` writes the capped + `x, meas, meas_su` uncertainty schema only when `meas_su` + exists; each standalone `data/fit_.tex` contains + measured line, sampled measured error-bar overlay, + optional background line, calculated line, residual and + Bragg legend entries, light inner grid settings, and no + band/fill-between; the preamble contains both + `\pgfplotsset{set layers}` and `mark layer=like plot`; + the main `report.tex` uses `\includegraphics{data/fit_.pdf}` and contains **no** - inline `\begin{axis}`. P1.16 surface. + inline `\begin{axis}`. P1.16 + P1.23 surface. - [ ] **`tests/unit/easydiffraction/report/test_html_renderer.py`** (further extend) — Plotly figures appear in the rendered HTML and are built from the `fit_data` payload @@ -1170,6 +1220,10 @@ running the verification commands below, add or update: toggle behaviour from the shared `post_script` / `_wrap_html_figure`, since both call the single serialization helper. P1.18 surface. + - The report path also forces light legend background and + light axis-frame colors after applying `plotly_white`, + preventing dark-theme colors from leaking into report + HTML. P1.23 surface. - [ ] **`tests/unit/easydiffraction/report/test_downsample.py`** (new) — `downsample_min_max(x, y, max_points)`: arrays at or under `max_points` pass through unchanged; arrays above @@ -1256,6 +1310,10 @@ changes for users: pgfplots in the main document. The TeX engine (Tectonic / TeX Live / MiKTeX) handles the plotting; users no longer need a system browser to generate a PDF report. +- **Fit plots look consistent across HTML and PDF.** Report + HTML always uses the light Plotly report theme, while the + PDF figure uses Plotly-derived colors, line widths, marker + sizes, grids, panel proportions, and legend entries. - **Offline HTML is now actually offline.** When `project.report.html_offline = True`, both Plotly and MathJax are inline-bundled — the resulting `.html` opens diff --git a/src/easydiffraction/display/plotters/plotly.py b/src/easydiffraction/display/plotters/plotly.py index 5977e2369..337682f6e 100644 --- a/src/easydiffraction/display/plotters/plotly.py +++ b/src/easydiffraction/display/plotters/plotly.py @@ -46,6 +46,14 @@ BACKGROUND_LINE_WIDTH = 1.0 CALCULATED_LINE_WIDTH = 2.0 RESIDUAL_LINE_WIDTH = 2.0 +MEASURED_MARKER_SIZE = 6 +MEASURED_MARKER_LINE_WIDTH = 0 +MEASURED_ERROR_BAR_THICKNESS = 2 +MEASURED_ERROR_BAR_WIDTH = 4 +LIGHT_AXIS_FRAME_COLOR = 'rgba(120, 140, 160, 0.28)' +DARK_AXIS_FRAME_COLOR = 'rgba(110, 145, 190, 0.35)' +LIGHT_LEGEND_BACKGROUND_COLOR = 'rgba(255, 255, 255, 0.5)' +DARK_LEGEND_BACKGROUND_COLOR = 'rgba(0, 0, 0, 0.5)' BRAGG_TICK_COLORS = ( 'rgb(255, 127, 14)', @@ -165,8 +173,8 @@ def _correlation_grid_color(cls) -> str: RGBA color string tuned for the active theme. """ if cls._is_dark_mode(): - return 'rgba(110, 145, 190, 0.35)' - return 'rgba(120, 140, 160, 0.28)' + return DARK_AXIS_FRAME_COLOR + return LIGHT_AXIS_FRAME_COLOR @classmethod def _axis_frame_color(cls) -> str: @@ -177,8 +185,24 @@ def _axis_frame_color(cls) -> str: def _legend_background_color(cls) -> str: """Return a half-transparent legend background color.""" if cls._is_dark_mode(): - return 'rgba(0, 0, 0, 0.5)' - return 'rgba(255, 255, 255, 0.5)' + return DARK_LEGEND_BACKGROUND_COLOR + return LIGHT_LEGEND_BACKGROUND_COLOR + + @staticmethod + def _axis_frame_color_for_template(template: str) -> str | None: + if template == 'plotly_white': + return LIGHT_AXIS_FRAME_COLOR + if template == 'plotly_dark': + return DARK_AXIS_FRAME_COLOR + return None + + @staticmethod + def _legend_background_color_for_template(template: str) -> str | None: + if template == 'plotly_white': + return LIGHT_LEGEND_BACKGROUND_COLOR + if template == 'plotly_dark': + return DARK_LEGEND_BACKGROUND_COLOR + return None def plot_correlation_heatmap( self, @@ -428,6 +452,14 @@ def _get_powder_trace( 'resid': RESIDUAL_LINE_WIDTH, }[label] line = {'color': color, 'width': line_width} + marker = None + if label == 'meas': + marker = { + 'symbol': 'circle', + 'size': MEASURED_MARKER_SIZE, + 'line': {'width': MEASURED_MARKER_LINE_WIDTH}, + 'color': color, + } legend_rank = { 'meas': 10, 'bkg': 20, @@ -442,6 +474,7 @@ def _get_powder_trace( mode=mode, name=name, legendrank=legend_rank, + marker=marker, customdata=customdata, hovertemplate=( hovertemplate @@ -989,6 +1022,13 @@ def serialize_html( """ if force_template is not None: fig.update_layout(template=force_template) + axis_frame_color = cls._axis_frame_color_for_template(force_template) + if axis_frame_color is not None: + fig.update_xaxes(linecolor=axis_frame_color) + fig.update_yaxes(linecolor=axis_frame_color) + legend_bgcolor = cls._legend_background_color_for_template(force_template) + if legend_bgcolor is not None: + fig.update_layout(legend={'bgcolor': legend_bgcolor}) html_fig = pio.to_html( fig, include_plotlyjs=include_plotlyjs, @@ -1533,6 +1573,8 @@ def _add_main_intensity_traces( 'array': plot_spec.y_meas_su, 'visible': True, 'color': DEFAULT_COLORS['meas'], + 'thickness': MEASURED_ERROR_BAR_THICKNESS, + 'width': MEASURED_ERROR_BAR_WIDTH, } fig.add_trace(meas_trace, row=1, col=1) diff --git a/src/easydiffraction/report/fit_plot.py b/src/easydiffraction/report/fit_plot.py index 7adddd3bf..11d44cb20 100644 --- a/src/easydiffraction/report/fit_plot.py +++ b/src/easydiffraction/report/fit_plot.py @@ -22,7 +22,11 @@ from easydiffraction.display.plotters.plotly import COMPOSITE_VERTICAL_SPACING from easydiffraction.display.plotters.plotly import DEFAULT_COLORS from easydiffraction.display.plotters.plotly import DISPLAY_TICK_FRACTIONS +from easydiffraction.display.plotters.plotly import MEASURED_ERROR_BAR_THICKNESS +from easydiffraction.display.plotters.plotly import MEASURED_ERROR_BAR_WIDTH from easydiffraction.display.plotters.plotly import MAIN_INTENSITY_RANGE_MARGIN_FRACTION +from easydiffraction.display.plotters.plotly import MEASURED_MARKER_LINE_WIDTH +from easydiffraction.display.plotters.plotly import MEASURED_MARKER_SIZE from easydiffraction.display.plotters.plotly import MEASURED_LINE_WIDTH from easydiffraction.display.plotters.plotly import PLOTLY_HEIGHT_PER_UNIT from easydiffraction.display.plotters.plotly import RESIDUAL_LINE_WIDTH @@ -30,6 +34,10 @@ _COLOR_PATTERN = re.compile(r'rgb\((\d+),\s*(\d+),\s*(\d+)\)') _FIGURE_AXIS_WIDTH_CM = 12.0 +_FIGURE_AXIS_HEIGHT_TO_WIDTH = 0.70 +_PLOTLY_GRID_RGB = '235,240,248' +_PLOTLY_AXIS_RGB = '217,223,228' +_PIXEL_TO_POINT = 0.75 _STYLE_SOURCE_KEYS = { 'meas': 'meas', 'bkg': 'bkg', @@ -105,8 +113,10 @@ def fit_plot_geometry(fit_data: dict[str, Any]) -> dict[str, float]: row_heights.append(residual_pixels) height_sum = sum(row_heights) + stack_height = _FIGURE_AXIS_WIDTH_CM * _FIGURE_AXIS_HEIGHT_TO_WIDTH + row_area_height = stack_height * _subplot_available_height_fraction(row_count) scaled_heights = [ - _FIGURE_AXIS_WIDTH_CM * row_height / height_sum + row_area_height * row_height / height_sum for row_height in row_heights ] return { @@ -114,7 +124,7 @@ def fit_plot_geometry(fit_data: dict[str, Any]) -> dict[str, float]: 'main_height_cm': scaled_heights[0], 'bragg_height_cm': scaled_heights[1] if has_bragg_ticks else 0.0, 'residual_height_cm': scaled_heights[-1] if has_residual else 0.0, - 'vertical_sep_cm': _vertical_sep_cm(row_count), + 'vertical_sep_cm': _vertical_sep_cm(stack_height), } @@ -129,17 +139,38 @@ def fit_bragg_tick_styles() -> list[dict[str, str]]: ] +def fit_plot_axis_styles() -> dict[str, str]: + """Return Plotly-derived axis colors for report figures.""" + return { + 'axis_rgb': _PLOTLY_AXIS_RGB, + 'grid_rgb': _PLOTLY_GRID_RGB, + } + + def _fit_plot_style(key: str, source_key: str) -> dict[str, Any]: color = DEFAULT_COLORS[source_key] - return { + style = { 'name': SERIES_CONFIG[source_key]['name'], 'mode': SERIES_CONFIG[source_key]['mode'], 'plotly_color': color, 'rgb': _rgb_channels(color), 'color_name': f'ed_{key}', 'line_width': _LINE_WIDTHS[key], + 'line_width_pt': _plotly_px_to_pt(_LINE_WIDTHS[key]), 'legend_rank': _LEGEND_RANKS[key], } + if key == 'meas': + style.update({ + 'marker_size': MEASURED_MARKER_SIZE, + 'marker_size_pt': _plotly_marker_size_pt(MEASURED_MARKER_SIZE), + 'marker_line_width': MEASURED_MARKER_LINE_WIDTH, + 'marker_line_width_pt': _plotly_px_to_pt(MEASURED_MARKER_LINE_WIDTH), + 'error_bar_thickness': MEASURED_ERROR_BAR_THICKNESS, + 'error_bar_thickness_pt': _plotly_px_to_pt(MEASURED_ERROR_BAR_THICKNESS), + 'error_bar_width': MEASURED_ERROR_BAR_WIDTH, + 'error_bar_cap_size_pt': _plotly_px_to_pt(MEASURED_ERROR_BAR_WIDTH), + }) + return style def _residual_limit(*, main_y_min: float, main_y_max: float) -> float: @@ -162,7 +193,9 @@ def _non_bragg_row_heights( has_residual: bool, ) -> tuple[float, float | None]: plot_area_height = _composite_plot_area_height() - available_row_pixels = plot_area_height * _subplot_available_height_fraction(row_count) + available_row_pixels = ( + plot_area_height * _subplot_available_height_fraction(row_count) + ) baseline_bragg_pixels = ( _bragg_tick_symbol_height_pixels() if has_bragg_ticks else 0.0 ) @@ -196,15 +229,16 @@ def _bragg_row_height_pixels(tick_set_count: int) -> float: return float(tick_set_count) * _bragg_tick_symbol_height_pixels() -def _vertical_sep_cm(row_count: int) -> float: - available_fraction = _subplot_available_height_fraction(row_count) - if available_fraction <= 0.0: - return 0.0 - return ( - _FIGURE_AXIS_WIDTH_CM - * COMPOSITE_VERTICAL_SPACING - / available_fraction - ) +def _vertical_sep_cm(stack_height: float) -> float: + return stack_height * COMPOSITE_VERTICAL_SPACING + + +def _plotly_px_to_pt(value: float) -> float: + return value * _PIXEL_TO_POINT + + +def _plotly_marker_size_pt(value: float) -> float: + return 0.5 * _plotly_px_to_pt(value) def _display_tick_limit(raw_limit: float) -> float: diff --git a/src/easydiffraction/report/templates/tex/figure.tex.j2 b/src/easydiffraction/report/templates/tex/figure.tex.j2 index f4bfdae30..aa5d125a9 100644 --- a/src/easydiffraction/report/templates/tex/figure.tex.j2 +++ b/src/easydiffraction/report/templates/tex/figure.tex.j2 @@ -9,10 +9,15 @@ {% for style in bragg_styles %} \definecolor{ {{- style.color_name -}} }{RGB}{ {{- style.rgb -}} } {% endfor %} +\definecolor{ed_axis}{RGB}{ {{- axis_styles.axis_rgb -}} } +\definecolor{ed_grid}{RGB}{ {{- axis_styles.grid_rgb -}} } {% set axes_labels = fit_data.axes_labels | default(["", "Intensity"], true) -%} {% set x_label = axes_labels[0] | tex_axis_label -%} {% set y_label = axes_labels[1] | tex_axis_label -%} {% set bragg_tick_sets = fit_data.bragg_tick_sets | default([], true) -%} +{% set has_bkg = fit_data.series.bkg is not none -%} +{% set has_meas_su = fit_data.series.meas.su is not none -%} +{% set has_error_bars = has_meas_su and errorbar_csv_filename is not none -%} \begin{document} \begin{tikzpicture} \begin{groupplot}[ @@ -26,23 +31,45 @@ scale only axis, xmin={{ ranges.x_min | tex_number }}, xmax={{ ranges.x_max | tex_number }}, mark layer=like plot, -tick align=outside, +axis line style={draw=ed_axis}, +tick style={draw=none}, label style={font=\footnotesize}, tick label style={font=\footnotesize}, +major grid style={draw=ed_grid, line width=0.4pt}, +xmajorgrids=true, ] \nextgroupplot[ height={{ geometry.main_height_cm | tex_number }}cm, ymin={{ ranges.y_min | tex_number }}, ymax={{ ranges.y_max | tex_number }}, +ymajorgrids=true, xticklabels=\empty, ylabel={ {{ y_label }} }, legend pos=north east, legend style={draw=none, fill=white, fill opacity=0.5, text opacity=1, font=\footnotesize}, ] -\addplot+[mark=*, mark size=0.75pt, color={{ styles.meas.color_name }}, line width=0.5pt, line join=bevel, mark options={line width=0pt}] table[x=x, y=meas, col sep=comma] { {{- csv_filename -}} }; +{% if has_error_bars %} +\addplot+[forget plot, color={{ styles.meas.color_name }}, line width={{ styles.meas.line_width_pt | tex_number }}pt, line join=bevel, no markers] table[x=x, y=meas, col sep=comma] { {{- csv_filename -}} }; +\addlegendimage{mark=*, mark size={{ styles.meas.marker_size_pt | tex_number }}pt, color={{ styles.meas.color_name }}, line width={{ styles.meas.line_width_pt | tex_number }}pt, mark options={line width={{ styles.meas.marker_line_width_pt | tex_number }}pt}} \addlegendentry{ {{- styles.meas.name | tex -}} } -\addplot+[color={{ styles.calc.color_name }}, line width=0.75pt, line join=bevel, no markers] table[x=x, y=calc, col sep=comma] { {{- csv_filename -}} }; +\addplot+[forget plot, only marks, mark=*, mark size={{ styles.meas.marker_size_pt | tex_number }}pt, color={{ styles.meas.color_name }}, mark options={line width={{ styles.meas.marker_line_width_pt | tex_number }}pt}, error bars/.cd, y dir=both, y explicit, error bar style={line width={{ styles.meas.error_bar_thickness_pt | tex_number }}pt}, error mark=|, error mark options={mark size={{ styles.meas.error_bar_cap_size_pt | tex_number }}pt}] table[x=x, y=meas, y error=meas_su, col sep=comma] { {{- errorbar_csv_filename -}} }; +{% else %} +\addplot+[mark=*, mark size={{ styles.meas.marker_size_pt | tex_number }}pt, color={{ styles.meas.color_name }}, line width={{ styles.meas.line_width_pt | tex_number }}pt, line join=bevel, mark options={line width={{ styles.meas.marker_line_width_pt | tex_number }}pt}] table[x=x, y=meas, col sep=comma] { {{- csv_filename -}} }; +\addlegendentry{ {{- styles.meas.name | tex -}} } +{% endif %} +{% if has_bkg %} +\addplot+[color={{ styles.bkg.color_name }}, line width={{ styles.bkg.line_width_pt | tex_number }}pt, line join=bevel, no markers] table[x=x, y=bkg, col sep=comma] { {{- csv_filename -}} }; +\addlegendentry{ {{- styles.bkg.name | tex -}} } +{% endif %} +\addplot+[color={{ styles.calc.color_name }}, line width={{ styles.calc.line_width_pt | tex_number }}pt, line join=bevel, no markers] table[x=x, y=calc, col sep=comma] { {{- csv_filename -}} }; \addlegendentry{ {{- styles.calc.name | tex -}} } +\addlegendimage{color={{ styles.diff.color_name }}, line width={{ styles.diff.line_width_pt | tex_number }}pt, line join=bevel, no markers} +\addlegendentry{ {{- styles.diff.name | tex -}} } +{% for tick_set in bragg_tick_sets %} +{% set tick_style = bragg_styles[loop.index0 % (bragg_styles | length)] -%} +\addlegendimage{color={{ tick_style.color_name }}, only marks, mark=|, mark size=5pt} +\addlegendentry{Bragg peaks: {{ tick_set.phase_id | tex }}} +{% endfor %} {% if bragg_tick_sets %} \nextgroupplot[ height={{ geometry.bragg_height_cm | tex_number }}cm, @@ -52,6 +79,7 @@ y dir=reverse, ytick={{ "{" }}{% for tick_set in bragg_tick_sets %}{{ loop.index }}{% if not loop.last %},{% endif %}{% endfor %}{{ "}" }}, yticklabels={{ "{" }}{% for tick_set in bragg_tick_sets %}{{ "{" }}{{ tick_set.phase_id | tex }}{{ "}" }}{% if not loop.last %},{% endif %}{% endfor %}{{ "}" }}, yticklabel style={font=\footnotesize}, +ymajorgrids=false, xticklabels=\empty, ] {% for tick_set in bragg_tick_sets %} @@ -69,10 +97,11 @@ height={{ geometry.residual_height_cm | tex_number }}cm, ymin={{ ranges.residual_y_min | tex_number }}, ymax={{ ranges.residual_y_max | tex_number }}, ytick={-{{ ranges.residual_y_tick | tex_number }},0,{{ ranges.residual_y_tick | tex_number }}}, +ymajorgrids=true, xlabel={ {{ x_label }} }, yticklabel style={font=\footnotesize}, ] -\addplot+[color={{ styles.diff.color_name }}, line width=0.5pt, line join=bevel, no markers] table[x=x, y=diff, col sep=comma] { {{- csv_filename -}} }; +\addplot+[color={{ styles.diff.color_name }}, line width={{ styles.diff.line_width_pt | tex_number }}pt, line join=bevel, no markers] table[x=x, y=diff, col sep=comma] { {{- csv_filename -}} }; \end{groupplot} \end{tikzpicture} \end{document} diff --git a/src/easydiffraction/report/tex_renderer.py b/src/easydiffraction/report/tex_renderer.py index 3ced981c8..7eaabd245 100644 --- a/src/easydiffraction/report/tex_renderer.py +++ b/src/easydiffraction/report/tex_renderer.py @@ -15,12 +15,14 @@ from easydiffraction.report.downsample import MAX_FIGURE_POINTS from easydiffraction.report.downsample import downsample_min_max_indices from easydiffraction.report.fit_plot import fit_bragg_tick_styles +from easydiffraction.report.fit_plot import fit_plot_axis_styles from easydiffraction.report.fit_plot import fit_plot_geometry from easydiffraction.report.fit_plot import fit_plot_ranges from easydiffraction.report.fit_plot import fit_plot_styles _TEMPLATE_NAME = 'tex/report.tex.j2' _FIGURE_TEMPLATE_NAME = 'tex/figure.tex.j2' +_MAX_ERROR_BAR_POINTS = 300 _TEX_SPECIAL_CHARS = { '\\': r'\textbackslash{}', '&': r'\&', @@ -181,9 +183,15 @@ def _write_fit_assets( continue experiment_id = str(experiment.get('id') or 'experiment') csv_path = _write_fit_csv(experiment_id, fit_data, out_dir) + errorbar_csv_path = _write_fit_errorbar_csv( + experiment_id, + fit_data, + out_dir, + ) figure_path = _write_fit_figure_tex( experiment=experiment, csv_path=csv_path, + errorbar_csv_path=errorbar_csv_path, out_dir=out_dir, ) csv_paths[experiment_id] = f'data/{csv_path.name}' @@ -241,6 +249,7 @@ def _write_fit_figure_tex( *, experiment: dict[str, object], csv_path: pathlib.Path, + errorbar_csv_path: pathlib.Path | None, out_dir: pathlib.Path, ) -> pathlib.Path: """Write one standalone pgfplots TeX figure.""" @@ -253,8 +262,12 @@ def _write_fit_figure_tex( 'experiment': experiment, 'fit_data': fit_data, 'csv_filename': csv_path.name, + 'errorbar_csv_filename': ( + None if errorbar_csv_path is None else errorbar_csv_path.name + ), 'geometry': fit_plot_geometry(fit_data), 'ranges': fit_plot_ranges(fit_data), + 'axis_styles': fit_plot_axis_styles(), 'styles': fit_plot_styles(), 'bragg_styles': fit_bragg_tick_styles(), } @@ -267,6 +280,26 @@ def _write_fit_figure_tex( return figure_path +def _write_fit_errorbar_csv( + expt_id: str, + fit_data: dict[str, object], + out_dir: pathlib.Path, +) -> pathlib.Path | None: + """Write capped uncertainty data for one fit figure.""" + columns = _fit_errorbar_csv_columns(expt_id, fit_data) + if columns is None: + return None + + data_dir = out_dir / 'data' + data_dir.mkdir(parents=True, exist_ok=True) + csv_path = data_dir / _fit_errorbar_csv_filename(expt_id) + with csv_path.open('w', newline='', encoding='utf-8') as handle: + writer = csv.writer(handle) + writer.writerow([name for name, _values in columns]) + writer.writerows(zip(*(values for _name, values in columns), strict=True)) + return csv_path + + def _fit_csv_paths(context: dict[str, object]) -> dict[str, str]: """Return expected fit-data CSV paths for TeX rendering.""" paths = {} @@ -306,6 +339,11 @@ def _fit_csv_filename(expt_id: str) -> str: return f'{_fit_asset_stem(expt_id)}.csv' +def _fit_errorbar_csv_filename(expt_id: str) -> str: + """Return a filesystem-safe error-bar CSV filename.""" + return f'{_fit_asset_stem(expt_id)}_errors.csv' + + def _fit_asset_stem(expt_id: str) -> str: """Return a filesystem-safe fit-data asset stem.""" safe_id = ''.join( @@ -334,6 +372,9 @@ def _fit_csv_columns( ] if meas['su'] is not None: columns.append(('meas_su', list(meas['su']))) + bkg = series.get('bkg') + if bkg is not None: + columns.append(('bkg', list(bkg['values']))) columns.extend( [ ('calc', list(calc['values'])), @@ -343,6 +384,26 @@ def _fit_csv_columns( return _downsample_fit_csv_columns(expt_id, columns) +def _fit_errorbar_csv_columns( + expt_id: str, + fit_data: dict[str, object], +) -> list[tuple[str, list[object]]] | None: + """Return capped CSV columns for measured uncertainties.""" + x_data = fit_data['x'] + meas = fit_data['series']['meas'] + if meas['su'] is None: + return None + + columns = [ + ('x', list(x_data['values'])), + ('meas', list(meas['values'])), + ('meas_su', list(meas['su'])), + ] + _validate_fit_csv_columns(expt_id, columns) + indices = downsample_min_max_indices(meas['values'], _MAX_ERROR_BAR_POINTS) + return _select_fit_csv_rows(columns, indices) + + def _downsample_fit_csv_columns( expt_id: str, columns: list[tuple[str, list[object]]], @@ -351,6 +412,14 @@ def _downsample_fit_csv_columns( _validate_fit_csv_columns(expt_id, columns) meas_values = dict(columns)['meas'] indices = downsample_min_max_indices(meas_values, MAX_FIGURE_POINTS) + return _select_fit_csv_rows(columns, indices) + + +def _select_fit_csv_rows( + columns: list[tuple[str, list[object]]], + indices: list[int], +) -> list[tuple[str, list[object]]]: + """Return fit CSV columns selected by row indices.""" return [ (name, [values[index] for index in indices]) for name, values in columns From e9447f6f02d26250d9d7f5381da6344c5886dc71 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 28 May 2026 16:17:11 +0200 Subject: [PATCH 066/129] Keep all points in pgfplots figures --- docs/dev/plans/project-summary-rendering.md | 132 +++++++----------- src/easydiffraction/report/downsample.py | 102 -------------- src/easydiffraction/report/fit_plot.py | 20 +-- .../report/templates/tex/figure.tex.j2 | 14 -- src/easydiffraction/report/tex_renderer.py | 84 +---------- 5 files changed, 56 insertions(+), 296 deletions(-) delete mode 100644 src/easydiffraction/report/downsample.py diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index ec81baf64..2caff645c 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -77,16 +77,15 @@ plan does not re-litigate them: `fit_.pdf` and pulled into the main report via `\includegraphics` — no inline pgfplots in `.tex`, and per-figure compiles isolate the TeX memory pool. The - fit-quality figure reuses the Plotly styling contract as - far as `pgfplots` can support it: measured line + sampled - markers/error bars, background line, calculated line, - Bragg tick row, and residual panel. The TeX renderer caps - the measured marker/error-bar overlay at 300 - peak-preserving points to stay inside Tectonic's fixed - memory pool; the full measured/background/calculated/ - residual traces still use the regular figure CSV. The TeX - engine (Tectonic / TeX Live / MiKTeX) supplies `pgfplots` + - `standalone` + TikZ deps from CTAN or its default sets. + fit-quality figure uses the Plotly geometry, colors, + legend structure, grid colors, and line widths where + `pgfplots` can support them without exceeding TeX memory. + The PDF figure deliberately does **not** include the + background curve or measured error bars, and it keeps every + measured point with the small pgfplots marker size from the + accepted design. The TeX engine (Tectonic / TeX Live / + MiKTeX) supplies `pgfplots` + `standalone` + TikZ deps from + CTAN or its default sets. - **`DisplayHandler` value object** (§1.5): new `@dataclass(frozen=True, slots=True)` at `src/easydiffraction/core/display_handler.py` carrying @@ -153,12 +152,10 @@ plan does not re-litigate them: release tagged on jsdelivr at vendoring time. - **`reports/tex/data/fit_.csv` schema.** The CSV emitter writes one row per data point with columns - `x`, `meas`, optional `meas_su`, optional `bkg`, `calc`, - `diff`. When `meas_su` is present, the renderer also writes - `fit__errors.csv` with capped `x`, `meas`, - `meas_su` columns for the PGFPlots error-bar overlay. - Confirm `figure.tex.j2`'s column references match these - schemas exactly during P1. + `x`, `meas`, optional `meas_su`, `calc`, `diff`. + Background is not emitted to the pgfplots CSV because the + PDF figure does not plot it. Confirm `figure.tex.j2`'s + column references match this schema exactly during P1. - **Style-bundle cleanup vs. wheel size.** The 10 REVTeX files dropped from the bundle reduce the wheel by ~350 KB. The Phase 2 verification step covers the actual @@ -283,9 +280,6 @@ plan does not re-litigate them: `vendor/` subtree). **`fit_data` shape + pgfplots CSV emitter:** -- `src/easydiffraction/report/downsample.py` (new — - `downsample_min_max(x, y, max_points)`; `MAX_FIGURE_POINTS - = 5000`; peak-preserving min/max-per-bin; see P1.16). - `src/easydiffraction/report/data_context.py` (existing — replace the previous `figures.fit_per_experiment` payload with the descriptor-driven @@ -297,10 +291,12 @@ plan does not re-litigate them: the new `figure.tex.j2`; see §3.3 and P1.16). - `src/easydiffraction/report/templates/tex/figure.tex.j2` (new — `\documentclass{standalone}` pgfplots figure: - Plotly-derived measured/background/calculated intensity - styling, sampled measured uncertainty overlay, Bragg row, - residual panel, light inner grids, no outside tick marks. - No band. `\pgfplotsset{set layers}` + + Plotly-derived geometry, colors, line widths, legend + structure, Bragg row, residual panel, light inner grids, + no outside tick marks. Measured points are all plotted; + background and measured error bars are intentionally + omitted from the PDF figure. No band. + `\pgfplotsset{set layers}` + `mark layer=like plot` mandatory; see P1.16 and P1.23). - `src/easydiffraction/report/templates/tex/report.tex.j2` (renamed from `iucr.tex.j2`) — the **main** document: @@ -805,11 +801,11 @@ exceptions. (standalone figure document). - Add `_write_fit_csv(expt_id, fit_data, out_dir)` writing `/data/fit_.csv` with columns - `x, meas, calc, diff` (+ `meas_su` and `bkg` when - present). When `meas_su` is present, also write - `fit__errors.csv` with capped `x`, `meas`, - `meas_su` values for the PGFPlots uncertainty overlay. - Python stdlib `csv`, no new dependency. + `x, meas, calc, diff` (+ `meas_su` when present). + Write all rows; do not downsample the pgfplots data. + Background is omitted from this CSV because the PDF + figure does not plot it. Python stdlib `csv`, no new + dependency. - Add `figure.tex.j2`: a `\documentclass{standalone}` pgfplots document, one per experiment, rendered to `data/fit_.tex`. Mandatory preamble: @@ -817,24 +813,16 @@ exceptions. `mark layer=like plot` (both required — `mark layer` is inert without `set layers`; together they let the calculated line draw over the measured markers). - - **Intensity panel: Plotly-derived series order.** - - Measured — full-resolution connecting line first - (`no markers`) so the calculated line can remain on - top. - - Measured uncertainty — sampled overlay from - `fit__errors.csv`: circular markers use the - Plotly measured marker size converted from pixels to - points; error-bar line thickness and cap size come - from the same Plotly constants. The overlay is capped - at 300 peak-preserving points because drawing capped - error bars for every point exhausts Tectonic's fixed - memory pool on HRPT-sized data. - - Background — grey line when `fit_data.series.bkg` - exists. - - Calculated — red line, declared after measured / - background so it draws above the measured data. - - **No measured–calculated band.** Residual and Bragg - peaks stay in their own panels, but appear in the + - **Intensity panel: two plotted data series.** + - Measured — connecting line + markers for **all** + points: `mark=*`, small `mark size=0.75pt`, Plotly- + derived line width, `line join=bevel`, and marker + outline width `0pt`. + - Calculated — red line, declared after measured so it + draws above the measured data. + - **No measured–calculated band, no background curve, + and no measured error bars in the PDF.** Residual and + Bragg peaks stay in their own panels, but appear in the main legend like Plotly. - Bragg tick row + residual panel as before (residual in its own panel; difference is not overlaid on intensity). @@ -855,18 +843,9 @@ exceptions. isolates the memory pool; the engine choice is unchanged. When only `tex` is configured (no `pdf`), the figure `.tex` + CSV are written but not compiled. - - **Data resolution — concrete contract.** Plot all points - up to **`MAX_FIGURE_POINTS = 5000`** per series; above - that, downsample with **min/max-per-bin** (peak- - preserving — keeps each bin's min and max so a sharp Bragg - apex survives; **never** naive every-Nth striding, which - can step over an apex and flatten it). Owned by a new - helper `src/easydiffraction/report/downsample.py`, - `downsample_min_max(x, y, max_points) -> (x2, y2)`, called - from `tex_renderer.py` when writing `fit_.csv`. HRPT - / typical CWL (~3k points) is under the cap and renders - every point; dense CWL / TOF banks downsample. Full- - fidelity data always stays in the CIF/CSV. + - **Data resolution — concrete contract.** Plot every row + from `fit_data` in the pgfplots CSV. Do not downsample + PDF plots or use a sampled marker/error-bar overlay. - Commit: `Emit standalone pgfplots figures included as PDF`. - [x] **P1.17 — HTML Plotly builder consumes `fit_data` directly** @@ -1066,17 +1045,17 @@ exceptions. - The measured powder trace exposes reusable style constants: marker size 6 px, marker outline 0 px, line width 2 px, error-bar thickness 2 px, and error-bar cap - width 4 px. The PDF renderer converts px → pt with - `1 px = 0.75 pt`, using marker radius 2.25 pt, - error-bar line width 1.5 pt, and cap mark size 3 pt. + width 4 px for Plotly/HTML. The PDF deliberately keeps + the smaller accepted pgfplots measured marker size + (`0.75pt`) and does not draw measured error bars. - PDF figures use Plotly-like inner grids: vertical grid lines on major x ticks in all panels, horizontal grid lines in the intensity and residual panels, no outside tick marks, and light Plotly-derived axis/grid colors. - - PDF legends include measured, background, calculated, - residual, and Bragg-peak entries. Residual and Bragg - peaks remain in their own panels even though their legend - entries live with the main legend, matching Plotly. + - PDF legends include measured, calculated, residual, and + Bragg-peak entries. Residual and Bragg peaks remain in + their own panels even though their legend entries live + with the main legend, matching Plotly where practical. - Compile and visually inspect the standalone `fit_.pdf` plus the full report PDF on the representative `lbco_hrpt` project. The standalone figure @@ -1184,14 +1163,13 @@ running the verification commands below, add or update: experiments emit `fit_data: None`. P1.15 surface. - [ ] **`tests/unit/easydiffraction/report/test_tex_renderer.py`** (extend) — `_write_fit_csv` writes the right schema - (`x, meas, calc, diff` + optional `meas_su` and `bkg`); - `_write_fit_errorbar_csv` writes the capped - `x, meas, meas_su` uncertainty schema only when `meas_su` - exists; each standalone `data/fit_.tex` contains - measured line, sampled measured error-bar overlay, - optional background line, calculated line, residual and - Bragg legend entries, light inner grid settings, and no - band/fill-between; the preamble contains both + (`x, meas, calc, diff` + optional `meas_su`) with every + row from `fit_data`; each standalone `data/fit_.tex` + contains measured line+markers for all points, calculated + line, residual and Bragg legend entries, light inner grid + settings, and no band/fill-between, no background plot, no + error-bar overlay, and no sampled marker path; the preamble + contains both `\pgfplotsset{set layers}` and `mark layer=like plot`; the main `report.tex` uses `\includegraphics{data/fit_.pdf}` and contains **no** @@ -1224,14 +1202,6 @@ running the verification commands below, add or update: light axis-frame colors after applying `plotly_white`, preventing dark-theme colors from leaking into report HTML. P1.23 surface. -- [ ] **`tests/unit/easydiffraction/report/test_downsample.py`** - (new) — `downsample_min_max(x, y, max_points)`: arrays at - or under `max_points` pass through unchanged; arrays above - it shrink to ≈`max_points`; **a synthetic single-bin-wide - spike at full height on a flat baseline is preserved** (the - spike's max y value appears in the output) — the - peak-preservation guarantee that naive striding would - violate. P1.16 surface. - [ ] **`tests/unit/easydiffraction/report/test_pdf_compiler.py`** (extend) — **N+1 build + engine fallback.** Each figure `data/fit_.tex` is compiled **before** the main diff --git a/src/easydiffraction/report/downsample.py b/src/easydiffraction/report/downsample.py deleted file mode 100644 index ba9c07a99..000000000 --- a/src/easydiffraction/report/downsample.py +++ /dev/null @@ -1,102 +0,0 @@ -# SPDX-FileCopyrightText: 2026 EasyScience contributors -# SPDX-License-Identifier: BSD-3-Clause -"""Downsample report plot data while preserving extrema.""" - -from __future__ import annotations - -from collections.abc import Sequence - -import numpy as np - -MAX_FIGURE_POINTS = 5000 - - -def downsample_min_max( - x: Sequence[object], - y: Sequence[object], - max_points: int = MAX_FIGURE_POINTS, -) -> tuple[list[object], list[object]]: - """ - Downsample paired data by keeping each bin's extrema. - - Parameters - ---------- - x : Sequence[object] - X-axis values. - y : Sequence[object] - Y-axis values used to select each bin's extrema. - max_points : int, default=MAX_FIGURE_POINTS - Maximum approximate number of output points. - - Returns - ------- - tuple[list[object], list[object]] - Downsampled x and y values in original order. - - Raises - ------ - ValueError - If the inputs have different lengths or ``max_points`` is - smaller than two. - """ - x_values = list(x) - y_values = list(y) - if len(x_values) != len(y_values): - msg = ( - f"Cannot downsample arrays with different lengths: " - f"x={len(x_values)}, y={len(y_values)}." - ) - raise ValueError(msg) - indices = downsample_min_max_indices(y_values, max_points) - return ( - [x_values[index] for index in indices], - [y_values[index] for index in indices], - ) - - -def downsample_min_max_indices( - y: Sequence[object], - max_points: int = MAX_FIGURE_POINTS, -) -> list[int]: - """ - Return peak-preserving min/max indices for one series. - - Parameters - ---------- - y : Sequence[object] - Values whose bin minima and maxima select retained points. - max_points : int, default=MAX_FIGURE_POINTS - Maximum approximate number of output points. - - Returns - ------- - list[int] - Original indices retained in ascending order. - - Raises - ------ - ValueError - If ``max_points`` is smaller than two. - """ - if max_points < 2: - msg = 'max_points must be at least 2.' - raise ValueError(msg) - - values = list(y) - if len(values) <= max_points: - return list(range(len(values))) - - numeric = np.asarray(values, dtype=float) - bin_count = max_points // 2 - edges = np.linspace(0, len(values), num=bin_count + 1, dtype=int) - indices: list[int] = [] - for start, stop in zip(edges[:-1], edges[1:], strict=True): - if start == stop: - continue - segment = numeric[start:stop] - bin_indices = { - start + int(np.argmin(segment)), - start + int(np.argmax(segment)), - } - indices.extend(sorted(bin_indices)) - return indices diff --git a/src/easydiffraction/report/fit_plot.py b/src/easydiffraction/report/fit_plot.py index 11d44cb20..bdce009da 100644 --- a/src/easydiffraction/report/fit_plot.py +++ b/src/easydiffraction/report/fit_plot.py @@ -22,11 +22,7 @@ from easydiffraction.display.plotters.plotly import COMPOSITE_VERTICAL_SPACING from easydiffraction.display.plotters.plotly import DEFAULT_COLORS from easydiffraction.display.plotters.plotly import DISPLAY_TICK_FRACTIONS -from easydiffraction.display.plotters.plotly import MEASURED_ERROR_BAR_THICKNESS -from easydiffraction.display.plotters.plotly import MEASURED_ERROR_BAR_WIDTH from easydiffraction.display.plotters.plotly import MAIN_INTENSITY_RANGE_MARGIN_FRACTION -from easydiffraction.display.plotters.plotly import MEASURED_MARKER_LINE_WIDTH -from easydiffraction.display.plotters.plotly import MEASURED_MARKER_SIZE from easydiffraction.display.plotters.plotly import MEASURED_LINE_WIDTH from easydiffraction.display.plotters.plotly import PLOTLY_HEIGHT_PER_UNIT from easydiffraction.display.plotters.plotly import RESIDUAL_LINE_WIDTH @@ -38,6 +34,8 @@ _PLOTLY_GRID_RGB = '235,240,248' _PLOTLY_AXIS_RGB = '217,223,228' _PIXEL_TO_POINT = 0.75 +_PGFPLOTS_MEASURED_MARKER_SIZE_PT = 0.75 +_PGFPLOTS_MEASURED_MARKER_LINE_WIDTH_PT = 0.0 _STYLE_SOURCE_KEYS = { 'meas': 'meas', 'bkg': 'bkg', @@ -161,14 +159,8 @@ def _fit_plot_style(key: str, source_key: str) -> dict[str, Any]: } if key == 'meas': style.update({ - 'marker_size': MEASURED_MARKER_SIZE, - 'marker_size_pt': _plotly_marker_size_pt(MEASURED_MARKER_SIZE), - 'marker_line_width': MEASURED_MARKER_LINE_WIDTH, - 'marker_line_width_pt': _plotly_px_to_pt(MEASURED_MARKER_LINE_WIDTH), - 'error_bar_thickness': MEASURED_ERROR_BAR_THICKNESS, - 'error_bar_thickness_pt': _plotly_px_to_pt(MEASURED_ERROR_BAR_THICKNESS), - 'error_bar_width': MEASURED_ERROR_BAR_WIDTH, - 'error_bar_cap_size_pt': _plotly_px_to_pt(MEASURED_ERROR_BAR_WIDTH), + 'marker_size_pt': _PGFPLOTS_MEASURED_MARKER_SIZE_PT, + 'marker_line_width_pt': _PGFPLOTS_MEASURED_MARKER_LINE_WIDTH_PT, }) return style @@ -237,10 +229,6 @@ def _plotly_px_to_pt(value: float) -> float: return value * _PIXEL_TO_POINT -def _plotly_marker_size_pt(value: float) -> float: - return 0.5 * _plotly_px_to_pt(value) - - def _display_tick_limit(raw_limit: float) -> float: if raw_limit <= 0: return 1.0 diff --git a/src/easydiffraction/report/templates/tex/figure.tex.j2 b/src/easydiffraction/report/templates/tex/figure.tex.j2 index aa5d125a9..a88ca46ff 100644 --- a/src/easydiffraction/report/templates/tex/figure.tex.j2 +++ b/src/easydiffraction/report/templates/tex/figure.tex.j2 @@ -15,9 +15,6 @@ {% set x_label = axes_labels[0] | tex_axis_label -%} {% set y_label = axes_labels[1] | tex_axis_label -%} {% set bragg_tick_sets = fit_data.bragg_tick_sets | default([], true) -%} -{% set has_bkg = fit_data.series.bkg is not none -%} -{% set has_meas_su = fit_data.series.meas.su is not none -%} -{% set has_error_bars = has_meas_su and errorbar_csv_filename is not none -%} \begin{document} \begin{tikzpicture} \begin{groupplot}[ @@ -48,19 +45,8 @@ ylabel={ {{ y_label }} }, legend pos=north east, legend style={draw=none, fill=white, fill opacity=0.5, text opacity=1, font=\footnotesize}, ] -{% if has_error_bars %} -\addplot+[forget plot, color={{ styles.meas.color_name }}, line width={{ styles.meas.line_width_pt | tex_number }}pt, line join=bevel, no markers] table[x=x, y=meas, col sep=comma] { {{- csv_filename -}} }; -\addlegendimage{mark=*, mark size={{ styles.meas.marker_size_pt | tex_number }}pt, color={{ styles.meas.color_name }}, line width={{ styles.meas.line_width_pt | tex_number }}pt, mark options={line width={{ styles.meas.marker_line_width_pt | tex_number }}pt}} -\addlegendentry{ {{- styles.meas.name | tex -}} } -\addplot+[forget plot, only marks, mark=*, mark size={{ styles.meas.marker_size_pt | tex_number }}pt, color={{ styles.meas.color_name }}, mark options={line width={{ styles.meas.marker_line_width_pt | tex_number }}pt}, error bars/.cd, y dir=both, y explicit, error bar style={line width={{ styles.meas.error_bar_thickness_pt | tex_number }}pt}, error mark=|, error mark options={mark size={{ styles.meas.error_bar_cap_size_pt | tex_number }}pt}] table[x=x, y=meas, y error=meas_su, col sep=comma] { {{- errorbar_csv_filename -}} }; -{% else %} \addplot+[mark=*, mark size={{ styles.meas.marker_size_pt | tex_number }}pt, color={{ styles.meas.color_name }}, line width={{ styles.meas.line_width_pt | tex_number }}pt, line join=bevel, mark options={line width={{ styles.meas.marker_line_width_pt | tex_number }}pt}] table[x=x, y=meas, col sep=comma] { {{- csv_filename -}} }; \addlegendentry{ {{- styles.meas.name | tex -}} } -{% endif %} -{% if has_bkg %} -\addplot+[color={{ styles.bkg.color_name }}, line width={{ styles.bkg.line_width_pt | tex_number }}pt, line join=bevel, no markers] table[x=x, y=bkg, col sep=comma] { {{- csv_filename -}} }; -\addlegendentry{ {{- styles.bkg.name | tex -}} } -{% endif %} \addplot+[color={{ styles.calc.color_name }}, line width={{ styles.calc.line_width_pt | tex_number }}pt, line join=bevel, no markers] table[x=x, y=calc, col sep=comma] { {{- csv_filename -}} }; \addlegendentry{ {{- styles.calc.name | tex -}} } \addlegendimage{color={{ styles.diff.color_name }}, line width={{ styles.diff.line_width_pt | tex_number }}pt, line join=bevel, no markers} diff --git a/src/easydiffraction/report/tex_renderer.py b/src/easydiffraction/report/tex_renderer.py index 7eaabd245..dc39f6057 100644 --- a/src/easydiffraction/report/tex_renderer.py +++ b/src/easydiffraction/report/tex_renderer.py @@ -12,8 +12,6 @@ from jinja2 import Environment from jinja2 import PackageLoader -from easydiffraction.report.downsample import MAX_FIGURE_POINTS -from easydiffraction.report.downsample import downsample_min_max_indices from easydiffraction.report.fit_plot import fit_bragg_tick_styles from easydiffraction.report.fit_plot import fit_plot_axis_styles from easydiffraction.report.fit_plot import fit_plot_geometry @@ -22,7 +20,6 @@ _TEMPLATE_NAME = 'tex/report.tex.j2' _FIGURE_TEMPLATE_NAME = 'tex/figure.tex.j2' -_MAX_ERROR_BAR_POINTS = 300 _TEX_SPECIAL_CHARS = { '\\': r'\textbackslash{}', '&': r'\&', @@ -183,15 +180,9 @@ def _write_fit_assets( continue experiment_id = str(experiment.get('id') or 'experiment') csv_path = _write_fit_csv(experiment_id, fit_data, out_dir) - errorbar_csv_path = _write_fit_errorbar_csv( - experiment_id, - fit_data, - out_dir, - ) figure_path = _write_fit_figure_tex( experiment=experiment, csv_path=csv_path, - errorbar_csv_path=errorbar_csv_path, out_dir=out_dir, ) csv_paths[experiment_id] = f'data/{csv_path.name}' @@ -249,7 +240,6 @@ def _write_fit_figure_tex( *, experiment: dict[str, object], csv_path: pathlib.Path, - errorbar_csv_path: pathlib.Path | None, out_dir: pathlib.Path, ) -> pathlib.Path: """Write one standalone pgfplots TeX figure.""" @@ -262,9 +252,6 @@ def _write_fit_figure_tex( 'experiment': experiment, 'fit_data': fit_data, 'csv_filename': csv_path.name, - 'errorbar_csv_filename': ( - None if errorbar_csv_path is None else errorbar_csv_path.name - ), 'geometry': fit_plot_geometry(fit_data), 'ranges': fit_plot_ranges(fit_data), 'axis_styles': fit_plot_axis_styles(), @@ -280,26 +267,6 @@ def _write_fit_figure_tex( return figure_path -def _write_fit_errorbar_csv( - expt_id: str, - fit_data: dict[str, object], - out_dir: pathlib.Path, -) -> pathlib.Path | None: - """Write capped uncertainty data for one fit figure.""" - columns = _fit_errorbar_csv_columns(expt_id, fit_data) - if columns is None: - return None - - data_dir = out_dir / 'data' - data_dir.mkdir(parents=True, exist_ok=True) - csv_path = data_dir / _fit_errorbar_csv_filename(expt_id) - with csv_path.open('w', newline='', encoding='utf-8') as handle: - writer = csv.writer(handle) - writer.writerow([name for name, _values in columns]) - writer.writerows(zip(*(values for _name, values in columns), strict=True)) - return csv_path - - def _fit_csv_paths(context: dict[str, object]) -> dict[str, str]: """Return expected fit-data CSV paths for TeX rendering.""" paths = {} @@ -339,11 +306,6 @@ def _fit_csv_filename(expt_id: str) -> str: return f'{_fit_asset_stem(expt_id)}.csv' -def _fit_errorbar_csv_filename(expt_id: str) -> str: - """Return a filesystem-safe error-bar CSV filename.""" - return f'{_fit_asset_stem(expt_id)}_errors.csv' - - def _fit_asset_stem(expt_id: str) -> str: """Return a filesystem-safe fit-data asset stem.""" safe_id = ''.join( @@ -372,58 +334,14 @@ def _fit_csv_columns( ] if meas['su'] is not None: columns.append(('meas_su', list(meas['su']))) - bkg = series.get('bkg') - if bkg is not None: - columns.append(('bkg', list(bkg['values']))) columns.extend( [ ('calc', list(calc['values'])), ('diff', list(diff['values'])), ] ) - return _downsample_fit_csv_columns(expt_id, columns) - - -def _fit_errorbar_csv_columns( - expt_id: str, - fit_data: dict[str, object], -) -> list[tuple[str, list[object]]] | None: - """Return capped CSV columns for measured uncertainties.""" - x_data = fit_data['x'] - meas = fit_data['series']['meas'] - if meas['su'] is None: - return None - - columns = [ - ('x', list(x_data['values'])), - ('meas', list(meas['values'])), - ('meas_su', list(meas['su'])), - ] _validate_fit_csv_columns(expt_id, columns) - indices = downsample_min_max_indices(meas['values'], _MAX_ERROR_BAR_POINTS) - return _select_fit_csv_rows(columns, indices) - - -def _downsample_fit_csv_columns( - expt_id: str, - columns: list[tuple[str, list[object]]], -) -> list[tuple[str, list[object]]]: - """Return columns selected by measured-intensity extrema.""" - _validate_fit_csv_columns(expt_id, columns) - meas_values = dict(columns)['meas'] - indices = downsample_min_max_indices(meas_values, MAX_FIGURE_POINTS) - return _select_fit_csv_rows(columns, indices) - - -def _select_fit_csv_rows( - columns: list[tuple[str, list[object]]], - indices: list[int], -) -> list[tuple[str, list[object]]]: - """Return fit CSV columns selected by row indices.""" - return [ - (name, [values[index] for index in indices]) - for name, values in columns - ] + return columns def _validate_fit_csv_columns( From 49ab34bfaac80ff6f002ec60f56526edfb38da97 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 28 May 2026 16:21:45 +0200 Subject: [PATCH 067/129] Align numeric report table columns --- docs/dev/plans/project-summary-rendering.md | 22 +++++++++++++ .../report/templates/tex/report.tex.j2 | 33 +++++++++++-------- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 2caff645c..ab37c5092 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -1065,6 +1065,28 @@ exceptions. - Commit: `Align report plot styling with Plotly output`. +- [x] **P1.24 — Disable draft line numbers and align numeric tables** + - Files: + `src/easydiffraction/report/templates/tex/report.tex.j2`. + - Disable the `iucrjournals.cls` draft line numbering in + generated reports by emitting `\nolinenumbers` at the + start of the document. The vendored class still loads + `lineno`, but reports should not show line numbers by + default. + - Add `siunitx` to the report template and use `S` + columns for numeric report tables: refinement, unit-cell + numeric rows, atom-site fractional coordinates / + occupancies / isotropic ADPs, anisotropic ADPs, and + experiment temperature / pressure. Non-numeric rows in + mixed key-value tables are wrapped in `\multicolumn` + cells so `siunitx` does not parse text as a number. + - Parenthesized uncertainty notation such as `0.584(20)` + is left unwrapped in `S` cells; `siunitx` parses it as + numeric uncertainty notation and aligns on the decimal + marker. + - Commit: + `Align numeric report table columns`. + ## Test plan (Phase 2) Per AGENTS.md §Testing, every new module, class, and bug fix diff --git a/src/easydiffraction/report/templates/tex/report.tex.j2 b/src/easydiffraction/report/templates/tex/report.tex.j2 index 15b40d571..1a189d934 100644 --- a/src/easydiffraction/report/templates/tex/report.tex.j2 +++ b/src/easydiffraction/report/templates/tex/report.tex.j2 @@ -1,5 +1,7 @@ \documentclass[11pt,a4paper]{styles/iucrjournals} \usepackage{graphicx} +\usepackage{siunitx} +\sisetup{detect-all} {% macro tex_field_header(field, fallback) -%} {% set label = (field.label or fallback) | tex_markup -%} {% set units = field.units | tex_markup -%} @@ -16,6 +18,7 @@ % Begin document %---------------------------------------------------------- \begin{document} +\nolinenumbers \maketitle {% if project.description %} @@ -64,8 +67,10 @@ Minimizer & {{ software.minimizer.name | tex }} & {{ software.minimizer.version \section{Refinement} \begin{table}[H] -\begin{tabular}{ll} +\begin{tabular}{lS} \toprule +\multicolumn{1}{l}{Quantity} & \multicolumn{1}{c}{Value} \\ +\midrule Reduced chi-square & {{ refinement.fit_result.reduced_chi_square | tex_number }} \\ Free parameters & {{ refinement.parameters.free | tex_number }} \\ Total parameters & {{ refinement.parameters.total | tex_number }} \\ @@ -91,10 +96,10 @@ Weighted R factor & {{ refinement.fit_result.wr_factor_all | tex_number }} \\ \subsubsection{Space group and unit cell parameters} \begin{table}[H] -\begin{tabular}{ll} +\begin{tabular}{lS} \toprule -Space group & {{ structure.space_group | tex }} \\ -Crystal system & {{ structure.crystal_system | tex }} \\ +Space group & \multicolumn{1}{l}{ {{- structure.space_group | tex -}} } \\ +Crystal system & \multicolumn{1}{l}{ {{- structure.crystal_system | tex -}} } \\ {% for key, value in structure.cell.items() %} {{ tex_field_header(structure.cell_latex[key], key | replace("_", " ")) }} & {{ value | tex_number }} \\ {% endfor %} @@ -107,9 +112,9 @@ Crystal system & {{ structure.crystal_system | tex }} \\ \subsubsection{Atom site parameters} \begin{table}[H] -\begin{tabular}{llccccc} +\begin{tabular}{llSSSSS} \toprule -Label & Type & $x$ & $y$ & $z$ & Occ. & {{ tex_field_header(structure.atom_site_latex.adp_iso, "ADP") }} \\ +\multicolumn{1}{l}{Label} & \multicolumn{1}{l}{Type} & \multicolumn{1}{c}{$x$} & \multicolumn{1}{c}{$y$} & \multicolumn{1}{c}{$z$} & \multicolumn{1}{c}{Occ.} & \multicolumn{1}{c}{ {{- tex_field_header(structure.atom_site_latex.adp_iso, "ADP") -}} } \\ \midrule {% for atom in structure.atom_sites %} {{ atom.label | tex }} & {{ atom.type_symbol | tex }} & {{ atom.fract_x | tex_number }} & {{ atom.fract_y | tex_number }} & {{ atom.fract_z | tex_number }} & {{ atom.occupancy | tex_number }} & {{ atom.adp_iso | tex_number }} \\ @@ -124,9 +129,9 @@ Label & Type & $x$ & $y$ & $z$ & Occ. & {{ tex_field_header(structure.atom_site_ \subsubsection{Anisotropic atomic displacement parameters} \begin{table}[H] -\begin{tabular}{lcccccc} +\begin{tabular}{lSSSSSS} \toprule -Label & {{ tex_field_header(structure.atom_site_aniso_latex.adp_11, "ADP 11") }} & {{ tex_field_header(structure.atom_site_aniso_latex.adp_22, "ADP 22") }} & {{ tex_field_header(structure.atom_site_aniso_latex.adp_33, "ADP 33") }} & {{ tex_field_header(structure.atom_site_aniso_latex.adp_12, "ADP 12") }} & {{ tex_field_header(structure.atom_site_aniso_latex.adp_13, "ADP 13") }} & {{ tex_field_header(structure.atom_site_aniso_latex.adp_23, "ADP 23") }} \\ +\multicolumn{1}{l}{Label} & \multicolumn{1}{c}{ {{- tex_field_header(structure.atom_site_aniso_latex.adp_11, "ADP 11") -}} } & \multicolumn{1}{c}{ {{- tex_field_header(structure.atom_site_aniso_latex.adp_22, "ADP 22") -}} } & \multicolumn{1}{c}{ {{- tex_field_header(structure.atom_site_aniso_latex.adp_33, "ADP 33") -}} } & \multicolumn{1}{c}{ {{- tex_field_header(structure.atom_site_aniso_latex.adp_12, "ADP 12") -}} } & \multicolumn{1}{c}{ {{- tex_field_header(structure.atom_site_aniso_latex.adp_13, "ADP 13") -}} } & \multicolumn{1}{c}{ {{- tex_field_header(structure.atom_site_aniso_latex.adp_23, "ADP 23") -}} } \\ \midrule {% for aniso in structure.atom_site_aniso %} {{ aniso.label | tex }} & {{ aniso.adp_11 | tex_number }} & {{ aniso.adp_22 | tex_number }} & {{ aniso.adp_33 | tex_number }} & {{ aniso.adp_12 | tex_number }} & {{ aniso.adp_13 | tex_number }} & {{ aniso.adp_23 | tex_number }} \\ @@ -152,13 +157,13 @@ Label & {{ tex_field_header(structure.atom_site_aniso_latex.adp_11, "ADP 11") }} \subsubsection{Experiment details} \begin{table}[H] -\begin{tabular}{ll} +\begin{tabular}{lS} \toprule -Sample form & {{ experiment.type.sample_form | tex }} \\ -Probe & {{ experiment.type.radiation_probe | tex }} \\ -Beam mode & {{ experiment.type.beam_mode | tex }} \\ -Scattering type & {{ experiment.type.scattering_type | tex }} \\ -Calculator & {{ experiment.calculator.type | tex }} \\ +Sample form & \multicolumn{1}{l}{ {{- experiment.type.sample_form | tex -}} } \\ +Probe & \multicolumn{1}{l}{ {{- experiment.type.radiation_probe | tex -}} } \\ +Beam mode & \multicolumn{1}{l}{ {{- experiment.type.beam_mode | tex -}} } \\ +Scattering type & \multicolumn{1}{l}{ {{- experiment.type.scattering_type | tex -}} } \\ +Calculator & \multicolumn{1}{l}{ {{- experiment.calculator.type | tex -}} } \\ {{ tex_field_header(experiment.diffrn_latex.ambient_temperature, "Temperature") }} & {{ experiment.diffrn.ambient_temperature | tex_number }} \\ {{ tex_field_header(experiment.diffrn_latex.ambient_pressure, "Pressure") }} & {{ experiment.diffrn.ambient_pressure | tex_number }} \\ \bottomrule From 318d202503d62b5584c6a275fbf25968e46eec19 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 28 May 2026 16:25:03 +0200 Subject: [PATCH 068/129] Restore pgfplots report line widths --- docs/dev/plans/project-summary-rendering.md | 10 ++++++++++ src/easydiffraction/report/fit_plot.py | 13 +++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index ab37c5092..2162f0bc8 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -1087,6 +1087,16 @@ exceptions. - Commit: `Align numeric report table columns`. +- [x] **P1.25 — Restore accepted pgfplots line widths** + - Files: + `src/easydiffraction/report/fit_plot.py`. + - Keep Plotly/HTML line-width settings unchanged, but use + the earlier accepted pgfplots widths for PDF figures: + measured line `0.5pt`, calculated line `0.75pt`, and + residual line `0.5pt`. + - Commit: + `Restore pgfplots report line widths`. + ## Test plan (Phase 2) Per AGENTS.md §Testing, every new module, class, and bug fix diff --git a/src/easydiffraction/report/fit_plot.py b/src/easydiffraction/report/fit_plot.py index bdce009da..935ba9537 100644 --- a/src/easydiffraction/report/fit_plot.py +++ b/src/easydiffraction/report/fit_plot.py @@ -33,7 +33,6 @@ _FIGURE_AXIS_HEIGHT_TO_WIDTH = 0.70 _PLOTLY_GRID_RGB = '235,240,248' _PLOTLY_AXIS_RGB = '217,223,228' -_PIXEL_TO_POINT = 0.75 _PGFPLOTS_MEASURED_MARKER_SIZE_PT = 0.75 _PGFPLOTS_MEASURED_MARKER_LINE_WIDTH_PT = 0.0 _STYLE_SOURCE_KEYS = { @@ -48,6 +47,12 @@ 'calc': CALCULATED_LINE_WIDTH, 'diff': RESIDUAL_LINE_WIDTH, } +_PGFPLOTS_LINE_WIDTHS_PT = { + 'meas': 0.5, + 'bkg': 0.75, + 'calc': 0.75, + 'diff': 0.5, +} _LEGEND_RANKS = { 'meas': 10, 'bkg': 20, @@ -154,7 +159,7 @@ def _fit_plot_style(key: str, source_key: str) -> dict[str, Any]: 'rgb': _rgb_channels(color), 'color_name': f'ed_{key}', 'line_width': _LINE_WIDTHS[key], - 'line_width_pt': _plotly_px_to_pt(_LINE_WIDTHS[key]), + 'line_width_pt': _PGFPLOTS_LINE_WIDTHS_PT[key], 'legend_rank': _LEGEND_RANKS[key], } if key == 'meas': @@ -225,10 +230,6 @@ def _vertical_sep_cm(stack_height: float) -> float: return stack_height * COMPOSITE_VERTICAL_SPACING -def _plotly_px_to_pt(value: float) -> float: - return value * _PIXEL_TO_POINT - - def _display_tick_limit(raw_limit: float) -> float: if raw_limit <= 0: return 1.0 From fdf17ec8c35c93b00663395706e3e9a9b2132c68 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 28 May 2026 19:38:44 +0200 Subject: [PATCH 069/129] Render category-driven reports with aligned tables --- .../accepted/project-summary-rendering.md | 46 ++ docs/dev/plans/project-summary-rendering.md | 93 ++++ .../experiment/categories/background/base.py | 5 + .../categories/background/line_segment.py | 13 + .../categories/calculator/default.py | 5 + .../experiment/categories/data/bragg_pd.py | 4 +- .../experiment/categories/diffrn/default.py | 12 +- .../categories/experiment_type/default.py | 17 + .../experiment/categories/instrument/cwl.py | 8 +- .../experiment/categories/instrument/tof.py | 14 +- .../categories/linked_crystal/default.py | 9 + .../categories/linked_phases/default.py | 9 + .../experiment/categories/peak/base.py | 5 + .../experiment/categories/peak/cwl_mixins.py | 32 +- .../experiment/categories/refln/bragg_pd.py | 4 +- .../categories/atom_sites/default.py | 34 +- .../structure/categories/cell/default.py | 14 +- .../categories/space_group/default.py | 9 + src/easydiffraction/display/plotters/base.py | 2 +- src/easydiffraction/report/data_context.py | 514 +++++++++++++++++- .../report/templates/html/report.html.j2 | 225 ++++---- .../report/templates/html/style.css | 90 ++- .../report/templates/tex/figure.tex.j2 | 2 +- .../report/templates/tex/report.tex.j2 | 165 ++++-- src/easydiffraction/report/tex_renderer.py | 44 +- 25 files changed, 1156 insertions(+), 219 deletions(-) diff --git a/docs/dev/adrs/accepted/project-summary-rendering.md b/docs/dev/adrs/accepted/project-summary-rendering.md index a239e98d5..307e3b5c0 100644 --- a/docs/dev/adrs/accepted/project-summary-rendering.md +++ b/docs/dev/adrs/accepted/project-summary-rendering.md @@ -891,6 +891,52 @@ consistent, document-appropriate look: `plotly_white` template above is report-specific (the notebook stays theme-adaptive). +**Table contents and heading styling — category-driven, +consistent HTML and PDF.** The reports mirror the project's own +datablock categories rather than maintaining hand-written summary +tables: + +- **Category-driven sections.** `ReportDataContext` iterates each + structure and experiment owner's public `categories` in order. + Each rendered category gets its own sub-subsection. Item + categories render as two-column key-value tables. Collection + categories render scalar descriptors first as key-value tables + and loop items as loop tables with headers. Experiment data + categories (`pd_data`, `total_data`, `refln`) are skipped because + they are plotted or too large for report tables. The + fit-quality plot remains the first experiment sub-subsection, and + publication metadata remains source data only — it is not added + to HTML, TeX, or PDF reports. +- **DisplayHandler names and units.** All table labels and units + use the per-context `DisplayHandler` resolution chain, so TeX + sees LaTeX names (`$2\theta$ offset`, + `$U_{\mathrm{iso}}$`), HTML sees MathJax-capable equivalents, + and plain values keep the readable labels (`H-M symbol`, + `Wavelength`, `Scale`). Units use `deg` rather than a degree + symbol in report labels. +- **Normal-weight headings and table headers.** Section headings + (`h1`–`h4`, the document title and section / subsection / + sub-subsection headers) render at normal weight. Table headers + are normal weight as well; hierarchy comes from size, spacing, + and rule lines rather than bold text. +- **Decimal-point-aligned number columns.** TeX tables use + `siunitx` `S` columns. HTML has no browser-native equivalent, so + report data carries numeric split metadata (`left`, decimal + marker, `right`, and per-column widths). The HTML template emits + `` with `number-left`, `number-dot`, and + `number-right` children, using tabular digits so values such as + `0.584(20)` and `3.89086937` align visually on the decimal + marker without changing the original value text. +- **Automatic section numbering via CSS counters.** HTML sections + are numbered like the PDF (`1`, `1.1`, `1.1.1`) using CSS + counters. Numbers are presentation-only, so they stay correct if + sections are added or reordered. The document title and Abstract + are unnumbered, matching the LaTeX report. +- **Three-rule tables with light zebra striping.** HTML and TeX + tables use only top, middle, and bottom rules. Body rows alternate + with a very light grey background starting at the first body row; + no per-row horizontal rules are drawn. + `reports/` is created lazily — only when at least one format is configured (or an ad-hoc method is called). A user iterating on a fit with the default `formats = []` produces no extra files. diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 2162f0bc8..65e95e1de 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -1097,6 +1097,77 @@ exceptions. - Commit: `Restore pgfplots report line widths`. +- [x] **P1.26 — Render category-driven reports with aligned tables (ADR §2)** + - Files: + `docs/dev/adrs/accepted/project-summary-rendering.md`, + `src/easydiffraction/datablocks/experiment/categories/background/base.py`, + `src/easydiffraction/datablocks/experiment/categories/background/line_segment.py`, + `src/easydiffraction/datablocks/experiment/categories/calculator/default.py`, + `src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py`, + `src/easydiffraction/datablocks/experiment/categories/diffrn/default.py`, + `src/easydiffraction/datablocks/experiment/categories/experiment_type/default.py`, + `src/easydiffraction/datablocks/experiment/categories/instrument/cwl.py`, + `src/easydiffraction/datablocks/experiment/categories/instrument/tof.py`, + `src/easydiffraction/datablocks/experiment/categories/linked_crystal/default.py`, + `src/easydiffraction/datablocks/experiment/categories/linked_phases/default.py`, + `src/easydiffraction/datablocks/experiment/categories/peak/base.py`, + `src/easydiffraction/datablocks/experiment/categories/peak/cwl_mixins.py`, + `src/easydiffraction/datablocks/experiment/categories/refln/bragg_pd.py`, + `src/easydiffraction/datablocks/structure/categories/atom_sites/default.py`, + `src/easydiffraction/datablocks/structure/categories/cell/default.py`, + `src/easydiffraction/datablocks/structure/categories/space_group/default.py`, + `src/easydiffraction/display/plotters/base.py`, + `src/easydiffraction/report/data_context.py`, + `src/easydiffraction/report/templates/html/report.html.j2`, + `src/easydiffraction/report/templates/html/style.css`, + `src/easydiffraction/report/templates/tex/figure.tex.j2`, + `src/easydiffraction/report/templates/tex/report.tex.j2`, + `src/easydiffraction/report/tex_renderer.py`. + - **Category-driven report content.** Replace the last + hand-written structure and experiment summary tables with + `ReportDataContext` category traversal: every public + structure category and every public experiment category + gets its own sub-subsection, item categories render as + two-column key-value tables, and collection loops render + as one-to-one loop tables. Experiment data categories + (`pd_data`, `total_data`, `refln`) stay out of the tabular + report because they are already represented by plots or + are too large for summary tables. The fit-quality chart + remains the first experiment sub-subsection. + - **Shared readable labels and units.** Add the missing + `DisplayHandler` names and units used by report tables so + HTML and TeX resolve the same domain labels (`H-M symbol`, + `$2\theta$ offset`, `Wavelength`, `Scale`, + `$U_{\mathrm{iso}}$`, etc.) rather than raw CIF field names + where a better report label exists. Normalise report angle + units to `deg` and avoid degree-symbol / `degree_squared` + labels. + - **Normal-weight headings and headers.** Make HTML and TeX + document titles, section headings, subheadings, and table + headers normal weight. Table hierarchy comes from spacing, + sizing, and rules instead of bold headers. + - **Decimal-point-aligned numeric columns.** Keep TeX numeric + columns on `siunitx` `S` columns and add HTML-side numeric + metadata (`left`, decimal marker, `right`, and per-column + widths) so `report.html.j2` emits `number-left`, + `number-dot`, and `number-right` spans with tabular digits. + This aligns values such as `0.584(20)` and `3.89086937` + on the decimal marker without changing the displayed text. + - **Matching table layout.** HTML tables shrink to their + content width instead of filling the report page. HTML and + TeX both use only top, middle, and bottom rules with very + light alternating body-row backgrounds starting at the first + body row. + - **Automatic HTML section numbering.** Add CSS counters so + HTML sections mirror the PDF numbering (`1`, `1.1`, + `1.1.1`) while keeping the document title and Abstract + unnumbered. + - **PDF plot legend polish.** Explicitly set the measured + marker fill and draw color in the pgfplots legend so the + legend marker matches the plotted measured points. + - Commit: + `Render category-driven reports with aligned tables`. + ## Test plan (Phase 2) Per AGENTS.md §Testing, every new module, class, and bug fix @@ -1243,6 +1314,28 @@ running the verification commands below, add or update: only; the no-engine branch writes the `.tex` + CSV bundle and returns with the install hint **without raising**; an engine non-zero exit raises a clear error. P1.16 surface. +- [ ] **`tests/unit/easydiffraction/report/test_html_renderer.py`** + (further extend) — **HTML category tables and CSS polish + (P1.26).** Rendered fixtures include category-driven + structure and experiment tables, with experiment data + categories skipped and the fit-quality chart first in the + experiment block. Numeric cells render split into + `` with `number-left`, `number-dot`, + and `number-right` children while preserving the original + `aria-label`; non-numeric cells stay plain. The emitted CSS + sets `h1`–`h4` and `th` to `font-weight: 400`, fits tables + to content width, stripes the first body row, and defines + section-numbering counters on `h2`/`h3`/`h4`; the title + (`h1`) and Abstract carry no counter. P1.26 surface. +- [ ] **`tests/unit/easydiffraction/report/test_tex_renderer.py`** + (further extend) — **TeX category tables and styling + (P1.26).** The main report renders structure and experiment + categories as separate sub-subsections, skips experiment data + categories, uses normal-weight titles/headings/table headers, + applies the first-row table striping commands, keeps numeric + columns on `S` alignment, emits readable labels/units, and + sets the measured pgfplots legend marker color explicitly. + P1.26 surface. - [ ] **Wheel-packaging verification** — run `pixi run dist-build` and then `unzip -l dist/*.whl | grep -E 'mathjax|iucrjournals.cls|harvard.sty'` diff --git a/src/easydiffraction/datablocks/experiment/categories/background/base.py b/src/easydiffraction/datablocks/experiment/categories/background/base.py index 02c682289..819eca145 100644 --- a/src/easydiffraction/datablocks/experiment/categories/background/base.py +++ b/src/easydiffraction/datablocks/experiment/categories/background/base.py @@ -6,6 +6,7 @@ from abc import abstractmethod from easydiffraction.core.category import CategoryCollection +from easydiffraction.core.display_handler import DisplayHandler from easydiffraction.core.switchable import SwitchableCategoryBase from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.validation import MembershipValidator @@ -35,6 +36,10 @@ def __init__(self, item_type: type) -> None: self._type: StringDescriptor = StringDescriptor( name='type', description='Active background type tag', + display_handler=DisplayHandler( + display_name='Type', + latex_name='Type', + ), value_spec=AttributeSpec( default=default_tag, validator=MembershipValidator( diff --git a/src/easydiffraction/datablocks/experiment/categories/background/line_segment.py b/src/easydiffraction/datablocks/experiment/categories/background/line_segment.py index 431abb317..0e48ddb91 100644 --- a/src/easydiffraction/datablocks/experiment/categories/background/line_segment.py +++ b/src/easydiffraction/datablocks/experiment/categories/background/line_segment.py @@ -12,6 +12,7 @@ from scipy.interpolate import interp1d from easydiffraction.core.category import CategoryItem +from easydiffraction.core.display_handler import DisplayHandler from easydiffraction.core.metadata import CalculatorSupport from easydiffraction.core.metadata import Compatibility from easydiffraction.core.metadata import TypeInfo @@ -51,6 +52,10 @@ def __init__(self) -> None: validator=RegexValidator(pattern=r'^[A-Za-z0-9_]*$'), ), cif_handler=CifHandler(names=['_pd_background.id']), + display_handler=DisplayHandler( + display_name='ID', + latex_name='ID', + ), ) self._x = NumericDescriptor( name='x', @@ -65,6 +70,10 @@ def __init__(self) -> None: '_pd_background_line_segment_X', ] ), + display_handler=DisplayHandler( + display_name='x', + latex_name='$x$', + ), ) self._y = Parameter( name='y', # TODO: rename to intensity @@ -79,6 +88,10 @@ def __init__(self) -> None: '_pd_background_line_segment_intensity', ] ), + display_handler=DisplayHandler( + display_name='Intensity', + latex_name='Intensity', + ), ) # ------------------------------------------------------------------ diff --git a/src/easydiffraction/datablocks/experiment/categories/calculator/default.py b/src/easydiffraction/datablocks/experiment/categories/calculator/default.py index b7c0871ef..25e430bff 100644 --- a/src/easydiffraction/datablocks/experiment/categories/calculator/default.py +++ b/src/easydiffraction/datablocks/experiment/categories/calculator/default.py @@ -5,6 +5,7 @@ from __future__ import annotations from easydiffraction.core.category import CategoryItem +from easydiffraction.core.display_handler import DisplayHandler from easydiffraction.core.metadata import TypeInfo from easydiffraction.core.switchable import SwitchableCategoryBase from easydiffraction.core.validation import AttributeSpec @@ -51,6 +52,10 @@ def __init__( names=['_calculator.type'], iucr_name='_easydiffraction_calculator.type', ), + display_handler=DisplayHandler( + display_name='Type', + latex_name='Type', + ), ) @property diff --git a/src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py b/src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py index 6fa8e1c70..da62ddab6 100644 --- a/src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py +++ b/src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py @@ -9,10 +9,10 @@ from easydiffraction.core.category import CategoryCollection from easydiffraction.core.category import CategoryItem +from easydiffraction.core.display_handler import DisplayHandler from easydiffraction.core.metadata import CalculatorSupport from easydiffraction.core.metadata import Compatibility from easydiffraction.core.metadata import TypeInfo -from easydiffraction.core.display_handler import DisplayHandler from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.validation import MembershipValidator from easydiffraction.core.validation import RangeValidator @@ -216,7 +216,7 @@ def __init__(self) -> None: display_name='2θ', display_units='deg', latex_name=r'$2\theta$', - latex_units=r'$^\circ$', + latex_units=r'\mathrm{deg}', ), value_spec=AttributeSpec( default=0.0, diff --git a/src/easydiffraction/datablocks/experiment/categories/diffrn/default.py b/src/easydiffraction/datablocks/experiment/categories/diffrn/default.py index c5319c02d..13396af9d 100644 --- a/src/easydiffraction/datablocks/experiment/categories/diffrn/default.py +++ b/src/easydiffraction/datablocks/experiment/categories/diffrn/default.py @@ -5,8 +5,8 @@ from __future__ import annotations from easydiffraction.core.category import CategoryItem -from easydiffraction.core.metadata import TypeInfo from easydiffraction.core.display_handler import DisplayHandler +from easydiffraction.core.metadata import TypeInfo from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.validation import RangeValidator from easydiffraction.core.variable import NumericDescriptor @@ -36,7 +36,7 @@ def __init__(self) -> None: display_name='Temperature', display_units='K', latex_name='Temperature', - latex_units='K', + latex_units=r'\mathrm{K}', ), value_spec=AttributeSpec( default=None, @@ -54,7 +54,7 @@ def __init__(self) -> None: display_name='Pressure', display_units='kPa', latex_name='Pressure', - latex_units='kPa', + latex_units=r'\mathrm{kPa}', ), value_spec=AttributeSpec( default=None, @@ -69,8 +69,10 @@ def __init__(self) -> None: description='Mean magnetic field during measurement', units='teslas', display_handler=DisplayHandler( + display_name='Magnetic field', display_units='T', - latex_units='T', + latex_name='Magnetic field', + latex_units=r'\mathrm{T}', ), value_spec=AttributeSpec( default=None, @@ -88,7 +90,9 @@ def __init__(self) -> None: description='Mean electric field during measurement', units='volts_per_metre', display_handler=DisplayHandler( + display_name='Electric field', display_units='V/m', + latex_name='Electric field', latex_units=r'\mathrm{V}/\mathrm{m}', ), value_spec=AttributeSpec( diff --git a/src/easydiffraction/datablocks/experiment/categories/experiment_type/default.py b/src/easydiffraction/datablocks/experiment/categories/experiment_type/default.py index c4e540eb6..8616ed08c 100644 --- a/src/easydiffraction/datablocks/experiment/categories/experiment_type/default.py +++ b/src/easydiffraction/datablocks/experiment/categories/experiment_type/default.py @@ -11,6 +11,7 @@ from __future__ import annotations from easydiffraction.core.category import CategoryItem +from easydiffraction.core.display_handler import DisplayHandler from easydiffraction.core.metadata import TypeInfo from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.validation import MembershipValidator @@ -50,6 +51,10 @@ def __init__(self) -> None: names=['_expt_type.sample_form'], iucr_name='_easydiffraction_experiment_type.sample_form', ), + display_handler=DisplayHandler( + display_name='Sample form', + latex_name='Sample form', + ), ) self._beam_mode = StringDescriptor( @@ -63,6 +68,10 @@ def __init__(self) -> None: names=['_expt_type.beam_mode'], iucr_name='_easydiffraction_experiment_type.beam_mode', ), + display_handler=DisplayHandler( + display_name='Beam mode', + latex_name='Beam mode', + ), ) self._radiation_probe = StringDescriptor( name='radiation_probe', @@ -77,6 +86,10 @@ def __init__(self) -> None: names=['_expt_type.radiation_probe'], iucr_name='_easydiffraction_experiment_type.radiation_probe', ), + display_handler=DisplayHandler( + display_name='Probe', + latex_name='Probe', + ), ) self._scattering_type = StringDescriptor( name='scattering_type', @@ -91,6 +104,10 @@ def __init__(self) -> None: names=['_expt_type.scattering_type'], iucr_name='_easydiffraction_experiment_type.scattering_type', ), + display_handler=DisplayHandler( + display_name='Scattering type', + latex_name='Scattering type', + ), ) # ------------------------------------------------------------------ diff --git a/src/easydiffraction/datablocks/experiment/categories/instrument/cwl.py b/src/easydiffraction/datablocks/experiment/categories/instrument/cwl.py index cbfb58b93..04c9f9a01 100644 --- a/src/easydiffraction/datablocks/experiment/categories/instrument/cwl.py +++ b/src/easydiffraction/datablocks/experiment/categories/instrument/cwl.py @@ -1,10 +1,10 @@ # SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause +from easydiffraction.core.display_handler import DisplayHandler from easydiffraction.core.metadata import CalculatorSupport from easydiffraction.core.metadata import Compatibility from easydiffraction.core.metadata import TypeInfo -from easydiffraction.core.display_handler import DisplayHandler from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.validation import RangeValidator from easydiffraction.core.variable import Parameter @@ -28,7 +28,9 @@ def __init__(self) -> None: description='Incident neutron or X-ray wavelength', units='angstroms', display_handler=DisplayHandler( + display_name='Wavelength', display_units='Å', + latex_name='Wavelength', latex_units=r'\AA', ), value_spec=AttributeSpec( @@ -107,8 +109,10 @@ def __init__(self) -> None: description='Instrument misalignment offset', units='degrees', display_handler=DisplayHandler( + display_name='2θ offset', display_units='deg', - latex_units=r'$^\circ$', + latex_name=r'$2\theta$ offset', + latex_units=r'\mathrm{deg}', ), value_spec=AttributeSpec( default=0.0, diff --git a/src/easydiffraction/datablocks/experiment/categories/instrument/tof.py b/src/easydiffraction/datablocks/experiment/categories/instrument/tof.py index 7b27b804b..00f99e952 100644 --- a/src/easydiffraction/datablocks/experiment/categories/instrument/tof.py +++ b/src/easydiffraction/datablocks/experiment/categories/instrument/tof.py @@ -1,10 +1,10 @@ # SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause +from easydiffraction.core.display_handler import DisplayHandler from easydiffraction.core.metadata import CalculatorSupport from easydiffraction.core.metadata import Compatibility from easydiffraction.core.metadata import TypeInfo -from easydiffraction.core.display_handler import DisplayHandler from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.validation import RangeValidator from easydiffraction.core.variable import Parameter @@ -63,8 +63,10 @@ def __init__(self) -> None: description='Detector bank position', units='degrees', display_handler=DisplayHandler( + display_name='2θ bank', display_units='deg', - latex_units=r'$^\circ$', + latex_name=r'$2\theta$ bank', + latex_units=r'\mathrm{deg}', ), value_spec=AttributeSpec( default=150.0, @@ -77,7 +79,9 @@ def __init__(self) -> None: description='TOF offset', units='microseconds', display_handler=DisplayHandler( + display_name='TOF offset', display_units='μs', + latex_name='TOF offset', latex_units=r'$\mu\mathrm{s}$', ), value_spec=AttributeSpec( @@ -91,7 +95,9 @@ def __init__(self) -> None: description='TOF linear conversion', units='microseconds_per_angstrom', display_handler=DisplayHandler( + display_name='TOF linear', display_units='μs/Å', + latex_name='TOF linear', latex_units=r'$\mu\mathrm{s}/\mathrm{\AA}$', ), value_spec=AttributeSpec( @@ -105,7 +111,9 @@ def __init__(self) -> None: description='TOF quadratic correction', units='microseconds_per_angstrom_squared', display_handler=DisplayHandler( + display_name='TOF quadratic', display_units='μs/Ų', + latex_name='TOF quadratic', latex_units=r'$\mu\mathrm{s}/\mathrm{\AA}^2$', ), value_spec=AttributeSpec( @@ -119,7 +127,9 @@ def __init__(self) -> None: description='TOF reciprocal velocity correction', units='microsecond_angstroms', display_handler=DisplayHandler( + display_name='TOF reciprocal', display_units='μs·Å', + latex_name='TOF reciprocal', latex_units=r'$\mu\mathrm{s}\,\mathrm{\AA}$', ), value_spec=AttributeSpec( diff --git a/src/easydiffraction/datablocks/experiment/categories/linked_crystal/default.py b/src/easydiffraction/datablocks/experiment/categories/linked_crystal/default.py index 5340e199f..88ab4e887 100644 --- a/src/easydiffraction/datablocks/experiment/categories/linked_crystal/default.py +++ b/src/easydiffraction/datablocks/experiment/categories/linked_crystal/default.py @@ -5,6 +5,7 @@ from __future__ import annotations from easydiffraction.core.category import CategoryItem +from easydiffraction.core.display_handler import DisplayHandler from easydiffraction.core.metadata import Compatibility from easydiffraction.core.metadata import TypeInfo from easydiffraction.core.validation import AttributeSpec @@ -47,6 +48,10 @@ def __init__(self) -> None: names=['_sc_crystal_block.id'], iucr_name='_easydiffraction_sc_crystal_block.id', ), + display_handler=DisplayHandler( + display_name='Crystal', + latex_name='Crystal', + ), ) self._scale = Parameter( name='scale', @@ -59,6 +64,10 @@ def __init__(self) -> None: names=['_sc_crystal_block.scale'], iucr_name='_easydiffraction_sc_crystal_block.scale', ), + display_handler=DisplayHandler( + display_name='Scale', + latex_name='Scale', + ), ) # ------------------------------------------------------------------ diff --git a/src/easydiffraction/datablocks/experiment/categories/linked_phases/default.py b/src/easydiffraction/datablocks/experiment/categories/linked_phases/default.py index fca0fb3b2..c1106f45a 100644 --- a/src/easydiffraction/datablocks/experiment/categories/linked_phases/default.py +++ b/src/easydiffraction/datablocks/experiment/categories/linked_phases/default.py @@ -6,6 +6,7 @@ from easydiffraction.core.category import CategoryCollection from easydiffraction.core.category import CategoryItem +from easydiffraction.core.display_handler import DisplayHandler from easydiffraction.core.metadata import Compatibility from easydiffraction.core.metadata import TypeInfo from easydiffraction.core.validation import AttributeSpec @@ -37,6 +38,10 @@ def __init__(self) -> None: validator=RegexValidator(pattern=r'^[A-Za-z_][A-Za-z0-9_]*$'), ), cif_handler=CifHandler(names=['_pd_phase_block.id']), + display_handler=DisplayHandler( + display_name='Phase', + latex_name='Phase', + ), ) self._scale = Parameter( name='scale', @@ -46,6 +51,10 @@ def __init__(self) -> None: validator=RangeValidator(ge=0.0), ), cif_handler=CifHandler(names=['_pd_phase_block.scale']), + display_handler=DisplayHandler( + display_name='Scale', + latex_name='Scale', + ), ) # ------------------------------------------------------------------ diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/base.py b/src/easydiffraction/datablocks/experiment/categories/peak/base.py index 563d45dff..4569b041e 100644 --- a/src/easydiffraction/datablocks/experiment/categories/peak/base.py +++ b/src/easydiffraction/datablocks/experiment/categories/peak/base.py @@ -5,6 +5,7 @@ from __future__ import annotations from easydiffraction.core.category import CategoryItem +from easydiffraction.core.display_handler import DisplayHandler from easydiffraction.core.switchable import SwitchableCategoryBase from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.validation import MembershipValidator @@ -31,6 +32,10 @@ def __init__(self) -> None: self._type: StringDescriptor = StringDescriptor( name='type', description='Active peak profile type tag', + display_handler=DisplayHandler( + display_name='Type', + latex_name='Type', + ), value_spec=AttributeSpec( default=default_tag, validator=MembershipValidator( diff --git a/src/easydiffraction/datablocks/experiment/categories/peak/cwl_mixins.py b/src/easydiffraction/datablocks/experiment/categories/peak/cwl_mixins.py index 73d3f8c47..2285b31b7 100644 --- a/src/easydiffraction/datablocks/experiment/categories/peak/cwl_mixins.py +++ b/src/easydiffraction/datablocks/experiment/categories/peak/cwl_mixins.py @@ -26,8 +26,10 @@ def __init__(self) -> None: description='Gaussian broadening from sample size and resolution', units='degrees_squared', display_handler=DisplayHandler( - display_units='deg²', - latex_units=r'$^\circ{}^2$', + display_name='U', + display_units='deg^2', + latex_name=r'$U$', + latex_units=r'\mathrm{deg}^2', ), value_spec=AttributeSpec( default=0.01, @@ -43,8 +45,10 @@ def __init__(self) -> None: description='Gaussian broadening instrumental contribution', units='degrees_squared', display_handler=DisplayHandler( - display_units='deg²', - latex_units=r'$^\circ{}^2$', + display_name='V', + display_units='deg^2', + latex_name=r'$V$', + latex_units=r'\mathrm{deg}^2', ), value_spec=AttributeSpec( default=-0.01, @@ -60,8 +64,10 @@ def __init__(self) -> None: description='Gaussian broadening instrumental contribution', units='degrees_squared', display_handler=DisplayHandler( - display_units='deg²', - latex_units=r'$^\circ{}^2$', + display_name='W', + display_units='deg^2', + latex_name=r'$W$', + latex_units=r'\mathrm{deg}^2', ), value_spec=AttributeSpec( default=0.02, @@ -77,8 +83,10 @@ def __init__(self) -> None: description='Lorentzian broadening from sample strain effects', units='degrees', display_handler=DisplayHandler( + display_name='X', display_units='deg', - latex_units=r'$^\circ$', + latex_name=r'$X$', + latex_units=r'\mathrm{deg}', ), value_spec=AttributeSpec( default=0.0, @@ -94,8 +102,10 @@ def __init__(self) -> None: description='Lorentzian broadening from microstructural defects', units='degrees', display_handler=DisplayHandler( + display_name='Y', display_units='deg', - latex_units=r'$^\circ$', + latex_name=r'$Y$', + latex_units=r'\mathrm{deg}', ), value_spec=AttributeSpec( default=0.0, @@ -114,7 +124,7 @@ def __init__(self) -> None: @property def broad_gauss_u(self) -> Parameter: """ - Gaussian broadening from sample size and resolution (deg²). + Gaussian broadening from sample size and resolution (deg^2). Reading this property returns the underlying ``Parameter`` object. Assigning to it updates the parameter value. @@ -128,7 +138,7 @@ def broad_gauss_u(self, value: float) -> None: @property def broad_gauss_v(self) -> Parameter: """ - Gaussian broadening instrumental contribution (deg²). + Gaussian broadening instrumental contribution (deg^2). Reading this property returns the underlying ``Parameter`` object. Assigning to it updates the parameter value. @@ -142,7 +152,7 @@ def broad_gauss_v(self, value: float) -> None: @property def broad_gauss_w(self) -> Parameter: """ - Gaussian broadening instrumental contribution (deg²). + Gaussian broadening instrumental contribution (deg^2). Reading this property returns the underlying ``Parameter`` object. Assigning to it updates the parameter value. diff --git a/src/easydiffraction/datablocks/experiment/categories/refln/bragg_pd.py b/src/easydiffraction/datablocks/experiment/categories/refln/bragg_pd.py index 553fb1c9d..f70b2cb8e 100644 --- a/src/easydiffraction/datablocks/experiment/categories/refln/bragg_pd.py +++ b/src/easydiffraction/datablocks/experiment/categories/refln/bragg_pd.py @@ -8,10 +8,10 @@ import numpy as np from easydiffraction.core.category import CategoryCollection +from easydiffraction.core.display_handler import DisplayHandler from easydiffraction.core.metadata import CalculatorSupport from easydiffraction.core.metadata import Compatibility from easydiffraction.core.metadata import TypeInfo -from easydiffraction.core.display_handler import DisplayHandler from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.validation import RangeValidator from easydiffraction.core.variable import NumericDescriptor @@ -108,7 +108,7 @@ def __init__(self) -> None: display_name='2θ', display_units='deg', latex_name=r'$2\theta$', - latex_units=r'$^\circ$', + latex_units=r'\mathrm{deg}', ), value_spec=AttributeSpec( default=0.0, diff --git a/src/easydiffraction/datablocks/structure/categories/atom_sites/default.py b/src/easydiffraction/datablocks/structure/categories/atom_sites/default.py index 57a0bd259..ec47a19f2 100644 --- a/src/easydiffraction/datablocks/structure/categories/atom_sites/default.py +++ b/src/easydiffraction/datablocks/structure/categories/atom_sites/default.py @@ -15,8 +15,8 @@ from easydiffraction.core.category import CategoryCollection from easydiffraction.core.category import CategoryItem -from easydiffraction.core.metadata import TypeInfo from easydiffraction.core.display_handler import DisplayHandler +from easydiffraction.core.metadata import TypeInfo from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.validation import MembershipValidator from easydiffraction.core.validation import RangeValidator @@ -47,6 +47,10 @@ def __init__(self) -> None: self._label = StringDescriptor( name='label', description='Unique identifier for the atom site.', + display_handler=DisplayHandler( + display_name='Label', + latex_name='Label', + ), value_spec=AttributeSpec( default='Si', # TODO: the following pattern is valid for dict key @@ -59,6 +63,10 @@ def __init__(self) -> None: self._type_symbol = StringDescriptor( name='type_symbol', description='Chemical symbol of the atom at this site.', + display_handler=DisplayHandler( + display_name='Type', + latex_name='Type', + ), value_spec=AttributeSpec( default='Tb', validator=MembershipValidator(allowed=self._type_symbol_allowed_values), @@ -68,6 +76,10 @@ def __init__(self) -> None: self._fract_x = Parameter( name='fract_x', description='Fractional x-coordinate of the atom site within the unit cell.', + display_handler=DisplayHandler( + display_name='x', + latex_name=r'$x$', + ), value_spec=AttributeSpec( default=0.0, validator=RangeValidator(), @@ -77,6 +89,10 @@ def __init__(self) -> None: self._fract_y = Parameter( name='fract_y', description='Fractional y-coordinate of the atom site within the unit cell.', + display_handler=DisplayHandler( + display_name='y', + latex_name=r'$y$', + ), value_spec=AttributeSpec( default=0.0, validator=RangeValidator(), @@ -86,6 +102,10 @@ def __init__(self) -> None: self._fract_z = Parameter( name='fract_z', description='Fractional z-coordinate of the atom site within the unit cell.', + display_handler=DisplayHandler( + display_name='z', + latex_name=r'$z$', + ), value_spec=AttributeSpec( default=0.0, validator=RangeValidator(), @@ -96,6 +116,10 @@ def __init__(self) -> None: name='wyckoff_letter', description='Wyckoff letter indicating the symmetry of the ' 'atom site within the space group.', + display_handler=DisplayHandler( + display_name='Wyckoff', + latex_name='Wyckoff', + ), value_spec=AttributeSpec( default=self._wyckoff_letter_default_value, validator=MembershipValidator(allowed=self._wyckoff_letter_allowed_values), @@ -112,6 +136,10 @@ def __init__(self) -> None: name='occupancy', description='Occupancy of the atom site, representing the ' 'fraction of the site occupied by the atom type.', + display_handler=DisplayHandler( + display_name='Occ.', + latex_name='Occ.', + ), value_spec=AttributeSpec( default=1.0, validator=RangeValidator(ge=0.0, le=1.0), @@ -143,6 +171,10 @@ def __init__(self) -> None: name='adp_type', description='Type of atomic displacement parameter (ADP) ' 'used (e.g., Biso, Uiso, Uani, Bani).', + display_handler=DisplayHandler( + display_name='ADP type', + latex_name='ADP type', + ), value_spec=AttributeSpec( default=AdpTypeEnum.default(), validator=MembershipValidator(allowed=[m.value for m in AdpTypeEnum]), diff --git a/src/easydiffraction/datablocks/structure/categories/cell/default.py b/src/easydiffraction/datablocks/structure/categories/cell/default.py index 1e0df818e..7a735df00 100644 --- a/src/easydiffraction/datablocks/structure/categories/cell/default.py +++ b/src/easydiffraction/datablocks/structure/categories/cell/default.py @@ -5,8 +5,8 @@ from __future__ import annotations from easydiffraction.core.category import CategoryItem -from easydiffraction.core.metadata import TypeInfo from easydiffraction.core.display_handler import DisplayHandler +from easydiffraction.core.metadata import TypeInfo from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.validation import RangeValidator from easydiffraction.core.variable import Parameter @@ -88,10 +88,10 @@ def __init__(self) -> None: description='Angle between edges b and c', units='degrees', display_handler=DisplayHandler( - display_name='α', + display_name='\N{GREEK SMALL LETTER ALPHA}', display_units='deg', latex_name=r'$\alpha$', - latex_units=r'$^\circ$', + latex_units=r'\mathrm{deg}', ), value_spec=AttributeSpec( default=90.0, @@ -104,10 +104,10 @@ def __init__(self) -> None: description='Angle between edges a and c', units='degrees', display_handler=DisplayHandler( - display_name='β', + display_name='\N{GREEK SMALL LETTER BETA}', display_units='deg', latex_name=r'$\beta$', - latex_units=r'$^\circ$', + latex_units=r'\mathrm{deg}', ), value_spec=AttributeSpec( default=90.0, @@ -120,10 +120,10 @@ def __init__(self) -> None: description='Angle between edges a and b', units='degrees', display_handler=DisplayHandler( - display_name='γ', + display_name='\N{GREEK SMALL LETTER GAMMA}', display_units='deg', latex_name=r'$\gamma$', - latex_units=r'$^\circ$', + latex_units=r'\mathrm{deg}', ), value_spec=AttributeSpec( default=90.0, diff --git a/src/easydiffraction/datablocks/structure/categories/space_group/default.py b/src/easydiffraction/datablocks/structure/categories/space_group/default.py index 59ae7facb..a8c34b548 100644 --- a/src/easydiffraction/datablocks/structure/categories/space_group/default.py +++ b/src/easydiffraction/datablocks/structure/categories/space_group/default.py @@ -11,6 +11,7 @@ from cryspy.A_functions_base.function_2_space_group import get_it_number_by_name_hm_short from easydiffraction.core.category import CategoryItem +from easydiffraction.core.display_handler import DisplayHandler from easydiffraction.core.metadata import TypeInfo from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.validation import MembershipValidator @@ -54,6 +55,10 @@ def __init__(self) -> None: self._name_h_m = StringDescriptor( name='name_h_m', description='Hermann-Mauguin symbol of the space group.', + display_handler=DisplayHandler( + display_name='H-M symbol', + latex_name='H-M symbol', + ), value_spec=AttributeSpec( default='P 1', validator=MembershipValidator( @@ -74,6 +79,10 @@ def __init__(self) -> None: self._it_coordinate_system_code = StringDescriptor( name='it_coordinate_system_code', description='A qualifier identifying which setting in IT is used.', + display_handler=DisplayHandler( + display_name='IT code', + latex_name='IT code', + ), value_spec=AttributeSpec( default=lambda: self._it_coordinate_system_code_default_value, validator=MembershipValidator( diff --git a/src/easydiffraction/display/plotters/base.py b/src/easydiffraction/display/plotters/base.py index e013bee1b..5a02c37b8 100644 --- a/src/easydiffraction/display/plotters/base.py +++ b/src/easydiffraction/display/plotters/base.py @@ -132,7 +132,7 @@ class XAxisType(StrEnum): ScatteringTypeEnum.BRAGG, XAxisType.TWO_THETA, ): [ - '2θ (degree)', + '2θ (deg)', 'Intensity (arb. units)', ], ( diff --git a/src/easydiffraction/report/data_context.py b/src/easydiffraction/report/data_context.py index 96c562fb2..e6176a13f 100644 --- a/src/easydiffraction/report/data_context.py +++ b/src/easydiffraction/report/data_context.py @@ -4,10 +4,15 @@ from __future__ import annotations +import re from collections.abc import Iterable from datetime import UTC from datetime import datetime +from easydiffraction.core.category import CategoryCollection +from easydiffraction.core.variable import GenericDescriptorBase +from easydiffraction.core.variable import IntegerDescriptor +from easydiffraction.core.variable import NumericDescriptor from easydiffraction.core.variable import Parameter from easydiffraction.display.plotters.base import DEFAULT_AXES_LABELS from easydiffraction.display.plotters.base import DEFAULT_X_AXIS @@ -114,6 +119,18 @@ 'id_orcid', 'id_iucr', ) +_EXPERIMENT_DATA_CATEGORY_CODES = frozenset({'pd_data', 'total_data', 'refln'}) +_NUMERIC_TEXT_RE = re.compile( + r'^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:\(\d+\))?(?:[eE][+-]?\d+)?$' +) +_NUMBER_PARTS_RE = re.compile( + r'^(?P[+-]?)' + r'(?:(?P\d+)(?:\.(?P\d*))?' + r'|\.(?P\d+))' + r'(?P\(\d+\))?' + r'(?P[eE][+-]?\d+)?$' +) +_MATH_FRAGMENT_RE = re.compile(r'\$([^$]+)\$') class ReportDataContext: @@ -225,9 +242,11 @@ def _structure_context(self, structure: object) -> dict[str, object]: _ATOM_SITE_ANISO_FIELDS, context='latex', ), + 'categories': _category_contexts(structure), } - def _atom_site_context(self, atom_site: object) -> dict[str, object]: + @staticmethod + def _atom_site_context(atom_site: object) -> dict[str, object]: """Return one atom-site row.""" return { 'label': _attr_value(atom_site, 'label'), @@ -240,7 +259,8 @@ def _atom_site_context(self, atom_site: object) -> dict[str, object]: 'adp_iso': _attr_display_value(atom_site, 'adp_iso'), } - def _atom_site_aniso_context(self, aniso_site: object) -> dict[str, object]: + @staticmethod + def _atom_site_aniso_context(aniso_site: object) -> dict[str, object]: """Return one atom-site-aniso row.""" return { 'label': _attr_value(aniso_site, 'label'), @@ -252,7 +272,8 @@ def _atom_site_aniso_context(self, aniso_site: object) -> dict[str, object]: 'adp_23': _attr_display_value(aniso_site, 'adp_23'), } - def _experiment_context(self, experiment: object) -> dict[str, object]: + @staticmethod + def _experiment_context(experiment: object) -> dict[str, object]: """Return one experiment summary.""" calculator = _safe_attr(experiment, 'calculator') diffrn = _safe_attr(experiment, 'diffrn') @@ -287,6 +308,10 @@ def _experiment_context(self, experiment: object) -> dict[str, object]: ), 'measured_range': _value(_safe_attr(experiment, 'measured_range')), 'fit_data': _fit_data_context(experiment), + 'categories': _category_contexts( + experiment, + skip_codes=_EXPERIMENT_DATA_CATEGORY_CODES, + ), } def _refinement_context(self) -> dict[str, object]: @@ -297,6 +322,7 @@ def _refinement_context(self) -> dict[str, object]: total = fields.get('n_parameters') free = fields.get('n_free_parameters') fixed = total - free if isinstance(total, int) and isinstance(free, int) else None + constraints = len(list(_collection_values(_safe_attr(analysis, 'constraints')))) return { 'fit_result': fields, 'parameters': { @@ -304,7 +330,13 @@ def _refinement_context(self) -> dict[str, object]: 'free': free, 'fixed': fixed, }, - 'constraints': len(list(_collection_values(_safe_attr(analysis, 'constraints')))), + 'constraints': constraints, + 'rows': _refinement_rows( + fields=fields, + total=total, + free=free, + constraints=constraints, + ), } def _software_context(self) -> dict[str, object]: @@ -428,10 +460,478 @@ def _display_field_metadata( def _display_metadata(value: object, *, context: str) -> dict[str, str]: """Return display label and units for one descriptor.""" name_resolver = getattr(value, 'resolve_display_name', None) - units_resolver = getattr(value, 'resolve_display_units', None) label = name_resolver(context) if callable(name_resolver) else '' - units = units_resolver(context) if callable(units_resolver) else '' - return {'label': label, 'units': units} + units = _descriptor_units(value, context=context) + return {'label': label, 'units': _display_units(units)} + + +def _refinement_rows( + *, + fields: dict[str, object], + total: object, + free: object, + constraints: int, +) -> list[dict[str, object]]: + """Return refinement rows with HTML numeric-alignment metadata.""" + rows = [ + _value_row('Reduced chi-square', fields.get('reduced_chi_square')), + _value_row('Free parameters', free), + _value_row('Total parameters', total), + _value_row('Constraints', constraints), + _value_row('R factor', fields.get('r_factor_all')), + _value_row('Weighted R factor', fields.get('wr_factor_all')), + ] + _apply_row_number_alignment(rows) + return rows + + +def _value_row(label: str, value: object) -> dict[str, object]: + """Return one label-value report row.""" + return { + 'label': label, + 'value': value, + 'numeric': _is_numeric_value(value), + 'number': None, + } + + +def _category_contexts( + owner: object, + *, + skip_codes: frozenset[str] | None = None, +) -> list[dict[str, object]]: + """Return report rows for each public category on an owner.""" + if skip_codes is None: + skip_codes = frozenset() + categories = getattr(owner, 'categories', ()) + contexts = [] + for category in categories: + if _skip_category(category, skip_codes): + continue + context = _category_context(category) + if _category_has_content(context): + contexts.append(context) + return contexts + + +def _skip_category(category: object, skip_codes: frozenset[str]) -> bool: + """Return whether a category should be omitted from reports.""" + category_code = _category_code(category) + if category_code in skip_codes: + return True + + skip = getattr(category, '_skip_cif_serialization', None) + return bool(callable(skip) and skip()) + + +def _category_context(category: object) -> dict[str, object]: + """Return one generic category-rendering context.""" + if isinstance(category, CategoryCollection): + return _collection_category_context(category) + return _item_category_context(category) + + +def _item_category_context(category: object) -> dict[str, object]: + """Return a non-loop category context.""" + rows = _descriptor_rows(_category_parameters(category)) + has_numeric_values = _rows_have_numeric_values(rows) + return { + 'kind': 'item', + 'code': _category_code(category), + 'title': _category_title(category), + 'rows': rows, + 'has_numeric_values': has_numeric_values, + 'value_column_numeric': has_numeric_values, + } + + +def _collection_category_context(category: CategoryCollection) -> dict[str, object]: + """Return a loop-category context.""" + items = list(category.values()) + columns = _collection_columns(category, items) + rows = [_collection_row(category, item, columns) for item in items] + scalar_rows = _descriptor_rows(category.scalar_descriptors) + _mark_numeric_columns(columns, rows) + _apply_cell_number_alignment(columns, rows) + return { + 'kind': 'loop', + 'code': _category_code(category), + 'title': _category_title(category), + 'scalar_rows': scalar_rows, + 'scalar_has_numeric_values': _rows_have_numeric_values(scalar_rows), + 'columns': columns, + 'rows': rows, + 'colspec': ''.join('S' if column['numeric'] else 'l' for column in columns), + } + + +def _category_has_content(context: dict[str, object]) -> bool: + """Return whether a category context has renderable content.""" + if context['kind'] == 'item': + return bool(context['rows']) + return bool(context['rows'] or context['scalar_rows']) + + +def _collection_columns( + category: CategoryCollection, + items: list[object], +) -> list[dict[str, object]]: + """Return loop column metadata from the first row item.""" + if not items: + return [] + return [ + _column_context(parameter) + for parameter in _collection_loop_parameters(category, items[0]) + ] + + +def _collection_row( + category: CategoryCollection, + item: object, + columns: list[dict[str, object]], +) -> dict[str, object]: + """Return one loop row.""" + parameters = _collection_loop_parameters(category, item) + values = [_display_value(parameter) for parameter in parameters] + cells = [ + {'value': value, 'numeric': column['numeric'], 'number': None} + for value, column in zip(values, columns, strict=True) + ] + return {'cells': cells} + + +def _collection_loop_parameters( + category: CategoryCollection, + item: object, +) -> list[GenericDescriptorBase]: + """Return the descriptors that define a collection row.""" + loop_parameters = getattr(category, '_cif_loop_parameters', None) + if callable(loop_parameters): + return list(loop_parameters(item)) + return list(item.parameters) + + +def _category_parameters(category: object) -> list[GenericDescriptorBase]: + """Return descriptors that define a non-loop category.""" + parameters = getattr(category, 'parameters', ()) + return list(parameters) + + +def _descriptor_rows( + parameters: Iterable[GenericDescriptorBase], +) -> list[dict[str, object]]: + """Return key-value table rows from descriptors.""" + rows = [] + for parameter in parameters: + value = _display_value(parameter) + rows.append( + { + 'name': parameter.name, + 'label': _display_label(parameter, context='html'), + 'latex_label': _display_label(parameter, context='latex'), + 'html_label': _html_label(parameter), + 'units': _display_units(_descriptor_units(parameter, context='html')), + 'latex_units': _display_units( + _descriptor_units(parameter, context='latex') + ), + 'html_units': _html_units(parameter), + 'value': value, + 'numeric': _descriptor_is_numeric(parameter) + and _is_numeric_value(value), + 'number': None, + } + ) + _apply_row_number_alignment(rows) + return rows + + +def _column_context(parameter: GenericDescriptorBase) -> dict[str, object]: + """Return loop-column metadata from one descriptor.""" + return { + 'name': parameter.name, + 'label': _display_label(parameter, context='html'), + 'latex_label': _display_label(parameter, context='latex'), + 'html_label': _html_label(parameter), + 'units': _display_units(_descriptor_units(parameter, context='html')), + 'latex_units': _display_units(_descriptor_units(parameter, context='latex')), + 'html_units': _html_units(parameter), + 'numeric': False, + 'numeric_candidate': _descriptor_is_numeric(parameter), + } + + +def _mark_numeric_columns( + columns: list[dict[str, object]], + rows: list[dict[str, object]], +) -> None: + """Mark each loop column that can use numeric alignment.""" + for index, column in enumerate(columns): + column_values = [row['cells'][index]['value'] for row in rows] + is_numeric = bool(column['numeric_candidate']) and _values_are_numeric( + column_values + ) + column['numeric'] = is_numeric + column.pop('numeric_candidate', None) + for row in rows: + row['cells'][index]['numeric'] = is_numeric + + +def _apply_row_number_alignment(rows: list[dict[str, object]]) -> None: + """Add HTML decimal-alignment metadata to key-value rows.""" + number_parts = [ + _number_parts(row['value']) if row['numeric'] else None for row in rows + ] + left_ch, right_ch = _number_widths(number_parts) + for row, parts in zip(rows, number_parts, strict=True): + row['number'] = _number_context(parts, left_ch, right_ch) + + +def _apply_cell_number_alignment( + columns: list[dict[str, object]], + rows: list[dict[str, object]], +) -> None: + """Add HTML decimal-alignment metadata to loop cells.""" + for index, column in enumerate(columns): + if not column['numeric']: + continue + number_parts = [ + _number_parts(row['cells'][index]['value']) for row in rows + ] + left_ch, right_ch = _number_widths(number_parts) + column['number_left_ch'] = left_ch + column['number_right_ch'] = right_ch + for row, parts in zip(rows, number_parts, strict=True): + row['cells'][index]['number'] = _number_context( + parts, + left_ch, + right_ch, + ) + + +def _number_widths( + number_parts: Iterable[dict[str, object] | None], +) -> tuple[int, int]: + """Return left and right character widths for numeric cells.""" + populated = [parts for parts in number_parts if parts is not None] + if not populated: + return 0, 0 + left_ch = max(len(str(parts['left'])) for parts in populated) + right_ch = max(len(str(parts['right'])) for parts in populated) + return left_ch, right_ch + + +def _number_context( + parts: dict[str, object] | None, + left_ch: int, + right_ch: int, +) -> dict[str, object] | None: + """Return one number context with column widths attached.""" + if parts is None: + return None + return { + **parts, + 'left_ch': max(left_ch, 1), + 'right_ch': max(right_ch, 1), + } + + +def _number_parts(value: object) -> dict[str, object] | None: + """Split one numeric display value around its decimal marker.""" + text = _number_text(value) + match = _NUMBER_PARTS_RE.match(text) + if match is None: + return None + + sign = match.group('sign') or '' + integer = match.group('integer') + leading_fraction = match.group('leading_fraction') + fraction = match.group('fraction') + if integer is None: + left = f'{sign}0' + right_fraction = leading_fraction or '' + has_decimal = True + else: + left = f'{sign}{integer}' + right_fraction = fraction or '' + has_decimal = fraction is not None + + uncertainty = match.group('uncertainty') or '' + exponent = match.group('exponent') or '' + return { + 'left': left, + 'right': f'{right_fraction}{uncertainty}{exponent}', + 'has_decimal': has_decimal, + } + + +def _number_text(value: object) -> str: + """Return a compact text representation for alignment.""" + if isinstance(value, (float, int)) and not isinstance(value, bool): + return f'{value:.6g}' + return str(value).strip() + + +def _values_are_numeric(values: Iterable[object]) -> bool: + """Return whether all populated values are numeric.""" + populated_values = [value for value in values if not _is_empty_value(value)] + return bool(populated_values) and all( + _is_numeric_value(value) for value in populated_values + ) + + +def _rows_have_numeric_values(rows: Iterable[dict[str, object]]) -> bool: + """Return whether any descriptor row has a numeric value.""" + return any(row['numeric'] for row in rows) + + +def _is_empty_value(value: object) -> bool: + """Return whether a table cell should be treated as empty.""" + return value is None or (isinstance(value, str) and not value) + + +def _is_numeric_value(value: object) -> bool: + """Return whether a value can be typeset as a number.""" + if isinstance(value, bool): + return False + if isinstance(value, (float, int)): + return True + return isinstance(value, str) and bool(_NUMERIC_TEXT_RE.match(value.strip())) + + +def _descriptor_is_numeric(parameter: GenericDescriptorBase) -> bool: + """Return whether a descriptor semantically stores numeric data.""" + return isinstance(parameter, (IntegerDescriptor, NumericDescriptor, Parameter)) + + +def _display_label(parameter: GenericDescriptorBase, *, context: str) -> str: + """Return a display label for a descriptor.""" + label = parameter.resolve_display_name(context) + return label or parameter.name + + +def _display_units(units: object) -> str: + """Return display units, suppressing placeholder unit labels.""" + if units is None: + return '' + units_text = str(units) + if units_text.lower() == 'none': + return '' + return units_text + + +def _html_label(parameter: GenericDescriptorBase) -> str: + """Return a MathJax-capable HTML label for one descriptor.""" + latex_label = _display_label(parameter, context='latex') + if _has_explicit_latex(parameter) and _is_latex_markup(latex_label): + return _html_markup(latex_label) + return _display_label(parameter, context='html') + + +def _html_units(parameter: GenericDescriptorBase) -> str: + """Return MathJax-capable HTML units for one descriptor.""" + latex_units = _display_units(_descriptor_units(parameter, context='latex')) + if not latex_units: + return '' + if _is_latex_markup(latex_units): + return _mathjax_text(latex_units) + + html_units = _display_units(_descriptor_units(parameter, context='html')) + return _plain_unit_text(html_units or latex_units) + + +def _has_explicit_latex(parameter: GenericDescriptorBase) -> bool: + """Return whether a descriptor declares LaTeX display metadata.""" + display_handler = _safe_attr(parameter, 'display_handler') + return display_handler is not None and display_handler.latex_name is not None + + +def _is_latex_markup(value: object) -> bool: + """Return whether a display string contains TeX markup.""" + text = str(value) + return '$' in text or '\\' in text + + +def _mathjax_text(value: object) -> str: + """Return inline MathJax text from a LaTeX fragment.""" + text = _mathjax_markup(str(value).replace('$', '')) + return rf'\({text}\)' + + +def _html_markup(value: object) -> str: + """Return HTML text with inline LaTeX fragments as MathJax.""" + text = str(value) + if '$' in text: + return _MATH_FRAGMENT_RE.sub( + lambda match: _mathjax_text(match.group(1)), + text, + ) + if '\\' in text: + return _mathjax_text(text) + return text + + +def _mathjax_markup(value: str) -> str: + """Return LaTeX markup normalized for MathJax rendering.""" + placeholder = '__EASYDIFFRACTION_ANGSTROM__' + text = _degree_unit_math(value) + text = text.replace(r'\mathrm{\AA}', placeholder) + text = text.replace(r'\AA', r'\mathring{\mathrm{A}}') + return text.replace(placeholder, r'\mathring{\mathrm{A}}') + + +def _degree_unit_math(value: str) -> str: + """Return TeX unit markup with degree symbols named as deg.""" + text = value + markers = (r'^\circ{}^2', r'^\circ{}^{2}', r'^\circ^2', r'^\circ^{2}') + for marker in markers: + text = text.replace(marker, r'\mathrm{deg}^2') + return text.replace(r'^\circ{}', r'\mathrm{deg}').replace( + r'^\circ', + r'\mathrm{deg}', + ) + + +def _plain_unit_text(value: str) -> str: + """Return plain unit text normalized for report display.""" + return ( + value.replace('degrees_squared', 'deg^2') + .replace('degree_squared', 'deg^2') + .replace('degrees squared', 'deg^2') + .replace('degree squared', 'deg^2') + .replace('degrees', 'deg') + .replace('degree', 'deg') + .replace('deg²', 'deg^2') + .replace('°²', 'deg^2') + .replace('°', 'deg') + ) + + +def _descriptor_units(parameter: object, *, context: str) -> str: + """Return descriptor units without probing missing attributes.""" + display_handler = _safe_attr(parameter, 'display_handler') + if display_handler is not None: + if context == 'latex' and display_handler.latex_units is not None: + return display_handler.latex_units + if context != 'latex' and display_handler.display_units is not None: + return display_handler.display_units + + units = _safe_attr(parameter, 'units') + return '' if units is None else str(units) + + +def _category_title(category: object) -> str: + """Return the report title for a category.""" + return _category_code(category) or type(category).__name__ + + +def _category_code(category: object) -> str | None: + """Return the CIF-like category code for item or collection.""" + identity = getattr(category, '_identity', None) + category_code = getattr(identity, 'category_code', None) + if category_code is not None: + return category_code + item_type = getattr(category, '_item_type', None) + return getattr(item_type, '_category_code', None) def _software_role_context(role: object) -> dict[str, object]: diff --git a/src/easydiffraction/report/templates/html/report.html.j2 b/src/easydiffraction/report/templates/html/report.html.j2 index b5bdf6ff0..87c140329 100644 --- a/src/easydiffraction/report/templates/html/report.html.j2 +++ b/src/easydiffraction/report/templates/html/report.html.j2 @@ -1,6 +1,19 @@ {% import "base.j2" as base %} -{% macro field_header(field, fallback) -%} -{{ field.label or fallback }}{% if field.units %} ({{ field.units }}){% endif %} +{% macro category_label(row) -%} +{{ row.html_label or row.label or row.name }}{% if row.html_units %} ({{ row.html_units }}){% endif %} +{%- endmacro %} +{% macro aligned_number(number, value) -%} +{% if number %} + + {{ number.left }}.{{ number.right }} + +{% else %}{{ base.format_number(value) }}{% endif %} +{%- endmacro %} +{% macro category_cell(cell) -%} +{% if cell.numeric %}{{ aligned_number(cell.number, cell.value) }}{% elif cell.value is none %}{% else %}{{ cell.value }}{% endif %} +{%- endmacro %} +{% macro category_value(row) -%} +{% if row.numeric %}{{ aligned_number(row.number, row.value) }}{% elif row.value is none %}{% else %}{{ row.value }}{% endif %} {%- endmacro %} @@ -22,41 +35,41 @@
{% if project.description %} -
+

Abstract

{{ project.description }}

{% endif %} -
+

Project Summary

- + - + - + - + - +
Short nameShort name {{ project.name }}
TitleTitle {{ project.title }}
StructuresStructures {{ project.n_phases }}
ExperimentsExperiments {{ project.n_experiments }}
GeneratedGenerated {{ metadata.generated_at }}
-
+

Software

@@ -86,175 +99,141 @@
-
+

Refinement

+ {% for row in refinement.rows %} - - - - - - - - - - - - - - - - - - - - - - + + {{ category_value(row) }} + {% endfor %}
Reduced chi-square{{ base.format_number(refinement.fit_result.reduced_chi_square) }}
Free parameters{{ base.format_number(refinement.parameters.free) }}
Total parameters{{ base.format_number(refinement.parameters.total) }}
Constraints{{ base.format_number(refinement.constraints) }}
R factor{{ base.format_number(refinement.fit_result.r_factor_all) }}
Weighted R factor{{ base.format_number(refinement.fit_result.wr_factor_all) }}{{ row.label }}
-
+

Structures

{% for structure in structures %}

{{ structure.id }}

-

Space group and unit cell parameters

+ {% for category in structure.categories %} +

{{ category.title }}

+ {% if category.kind == "item" %} + {% for row in category.rows %} - - - - - - + + {{ category_value(row) }} - {% for key, value in structure.cell.items() %} + {% endfor %} + +
Space group{{ structure.space_group }}
Crystal system{{ structure.crystal_system }}{{ category_label(row) }}
+ {% else %} + {% if category.scalar_rows %} + + + {% for row in category.scalar_rows %} - - + + {{ category_value(row) }} {% endfor %}
{{ field_header(structure.cell_display[key], key | replace("_", " ")) }}{{ base.format_number(value) }}{{ category_label(row) }}
- - {% if structure.atom_sites %} -

Atom site parameters

-
- - - - - - - - - - - - - - {% for atom in structure.atom_sites %} - - - - - - - - - - {% endfor %} - -
LabelType{{ field_header(structure.atom_site_display.fract_x, "x") }}{{ field_header(structure.atom_site_display.fract_y, "y") }}{{ field_header(structure.atom_site_display.fract_z, "z") }}Occ.{{ field_header(structure.atom_site_display.adp_iso, "ADP") }}
{{ atom.label }}{{ atom.type_symbol }}{{ base.format_number(atom.fract_x) }}{{ base.format_number(atom.fract_y) }}{{ base.format_number(atom.fract_z) }}{{ base.format_number(atom.occupancy) }}{{ base.format_number(atom.adp_iso) }}
-
{% endif %} - - {% if structure.atom_site_aniso %} -

Anisotropic atomic displacement parameters

+ {% if category.rows %}
- - - - - - - + {% for column in category.columns %} + {{ category_label(column) }} + {% endfor %} - {% for aniso in structure.atom_site_aniso %} + {% for row in category.rows %} - - - - - - - + {% for cell in row.cells %} + {{ category_cell(cell) }} + {% endfor %} {% endfor %}
Label{{ field_header(structure.atom_site_aniso_display.adp_11, "ADP 11") }}{{ field_header(structure.atom_site_aniso_display.adp_22, "ADP 22") }}{{ field_header(structure.atom_site_aniso_display.adp_33, "ADP 33") }}{{ field_header(structure.atom_site_aniso_display.adp_12, "ADP 12") }}{{ field_header(structure.atom_site_aniso_display.adp_13, "ADP 13") }}{{ field_header(structure.atom_site_aniso_display.adp_23, "ADP 23") }}
{{ aniso.label }}{{ base.format_number(aniso.adp_11) }}{{ base.format_number(aniso.adp_22) }}{{ base.format_number(aniso.adp_33) }}{{ base.format_number(aniso.adp_12) }}{{ base.format_number(aniso.adp_13) }}{{ base.format_number(aniso.adp_23) }}
{% endif %} + {% endif %} + {% endfor %}
{% endfor %}
-
+

Experiments

{% for experiment in experiments %}

{{ experiment.id }}

-

Experiment details

+ {% if fit_figures[experiment.id] %} +

Fit quality

+
{{ fit_figures[experiment.id] | safe }}
+ {% endif %} + + {% for category in experiment.categories %} +

{{ category.title }}

+ {% if category.kind == "item" %} + {% for row in category.rows %} - - - - - - - - - - - - - - - - - - - - - - + + {{ category_value(row) }} + {% endfor %} + +
Sample form{{ experiment.type.sample_form }}
Probe{{ experiment.type.radiation_probe }}
Beam mode{{ experiment.type.beam_mode }}
Scattering type{{ experiment.type.scattering_type }}
Calculator{{ experiment.calculator.type }}
{{ field_header(experiment.diffrn_display.ambient_temperature, "Temperature") }}{{ base.format_number(experiment.diffrn.ambient_temperature) }}{{ category_label(row) }}
+ {% else %} + {% if category.scalar_rows %} + + + {% for row in category.scalar_rows %} - - + + {{ category_value(row) }} + {% endfor %}
{{ field_header(experiment.diffrn_display.ambient_pressure, "Pressure") }}{{ base.format_number(experiment.diffrn.ambient_pressure) }}{{ category_label(row) }}
- - {% if fit_figures[experiment.id] %} -

Fit quality

-
{{ fit_figures[experiment.id] | safe }}
{% endif %} + {% if category.rows %} +
+ + + + {% for column in category.columns %} + {{ category_label(column) }} + {% endfor %} + + + + {% for row in category.rows %} + + {% for cell in row.cells %} + {{ category_cell(cell) }} + {% endfor %} + + {% endfor %} + +
+
+ {% endif %} + {% endif %} + {% endfor %}
{% endfor %}
diff --git a/src/easydiffraction/report/templates/html/style.css b/src/easydiffraction/report/templates/html/style.css index f1fcddb74..c70fef813 100644 --- a/src/easydiffraction/report/templates/html/style.css +++ b/src/easydiffraction/report/templates/html/style.css @@ -4,7 +4,7 @@ --ink: #20242a; --muted: #5f6874; --rule: #20242a; - --light-rule: #d7dce2; + --row-shade: #f8f9fa; --link: #245a9b; } @@ -24,6 +24,7 @@ body { margin: 0 auto; padding: 42px 52px 64px; background: var(--paper); + counter-reset: report-section; } .report-title { @@ -42,38 +43,66 @@ p { h1 { margin-bottom: 0; font-size: 2rem; - font-weight: 600; + font-weight: 400; line-height: 1.2; } h2 { margin: 30px 0 12px; font-size: 1.35rem; - font-weight: 600; + font-weight: 400; } h3 { margin: 22px 0 10px; font-size: 1.1rem; - font-weight: 600; + font-weight: 400; } h4 { margin: 18px 0 8px; font-size: 0.98rem; - font-weight: 600; + font-weight: 400; } section { margin-top: 24px; } +.numbered-section { + counter-increment: report-section; + counter-reset: report-subsection; +} + +.numbered-section > h2::before { + content: counter(report-section) " "; +} + +.numbered-section .record { + counter-increment: report-subsection; + counter-reset: report-subsubsection; +} + +.numbered-section .record > h3::before { + content: counter(report-section) "." counter(report-subsection) " "; +} + +.numbered-section .record > h4 { + counter-increment: report-subsubsection; +} + +.numbered-section .record > h4::before { + content: counter(report-section) "." counter(report-subsection) "." + counter(report-subsubsection) " "; +} + .record + .record { margin-top: 26px; } table { - width: 100%; + width: max-content; + max-width: 100%; margin: 8px 0 16px; border-collapse: collapse; border-top: 2px solid var(--rule); @@ -93,22 +122,59 @@ td { } th { - font-weight: 600; + font-weight: 400; +} + +tbody tr:nth-child(odd) { + background: var(--row-shade); } -tbody tr + tr th, -tbody tr + tr td { - border-top: 1px solid var(--light-rule); +.numeric, +.numeric-header { + font-variant-numeric: tabular-nums; + text-align: right; } -.key-value th { - width: 32%; +.number { + display: inline-grid; + grid-template-columns: + var(--number-left, 1ch) 0.35ch + var(--number-right, 1ch); + white-space: nowrap; +} + +.number-left { + text-align: right; +} + +.number-dot { + text-align: center; +} + +.number-dot-placeholder { + visibility: hidden; +} + +.number-right { + text-align: left; +} + +.key-value .key { + width: auto; + padding-right: 28px; + font-weight: 400; } .table-scroll { + width: fit-content; + max-width: 100%; overflow-x: auto; } +.table-scroll table { + max-width: none; +} + .figure { margin: 8px 0 18px; } diff --git a/src/easydiffraction/report/templates/tex/figure.tex.j2 b/src/easydiffraction/report/templates/tex/figure.tex.j2 index a88ca46ff..792bc18b0 100644 --- a/src/easydiffraction/report/templates/tex/figure.tex.j2 +++ b/src/easydiffraction/report/templates/tex/figure.tex.j2 @@ -45,7 +45,7 @@ ylabel={ {{ y_label }} }, legend pos=north east, legend style={draw=none, fill=white, fill opacity=0.5, text opacity=1, font=\footnotesize}, ] -\addplot+[mark=*, mark size={{ styles.meas.marker_size_pt | tex_number }}pt, color={{ styles.meas.color_name }}, line width={{ styles.meas.line_width_pt | tex_number }}pt, line join=bevel, mark options={line width={{ styles.meas.marker_line_width_pt | tex_number }}pt}] table[x=x, y=meas, col sep=comma] { {{- csv_filename -}} }; +\addplot+[mark=*, mark size={{ styles.meas.marker_size_pt | tex_number }}pt, color={{ styles.meas.color_name }}, line width={{ styles.meas.line_width_pt | tex_number }}pt, line join=bevel, mark options={fill={{ styles.meas.color_name }}, draw={{ styles.meas.color_name }}, fill opacity=1, draw opacity=1, line width={{ styles.meas.marker_line_width_pt | tex_number }}pt}] table[x=x, y=meas, col sep=comma] { {{- csv_filename -}} }; \addlegendentry{ {{- styles.meas.name | tex -}} } \addplot+[color={{ styles.calc.color_name }}, line width={{ styles.calc.line_width_pt | tex_number }}pt, line join=bevel, no markers] table[x=x, y=calc, col sep=comma] { {{- csv_filename -}} }; \addlegendentry{ {{- styles.calc.name | tex -}} } diff --git a/src/easydiffraction/report/templates/tex/report.tex.j2 b/src/easydiffraction/report/templates/tex/report.tex.j2 index 1a189d934..d03191d78 100644 --- a/src/easydiffraction/report/templates/tex/report.tex.j2 +++ b/src/easydiffraction/report/templates/tex/report.tex.j2 @@ -1,12 +1,58 @@ +\PassOptionsToPackage{table}{xcolor} \documentclass[11pt,a4paper]{styles/iucrjournals} \usepackage{graphicx} \usepackage{siunitx} -\sisetup{detect-all} +\sisetup{detect-all, group-digits=false} +\definecolor{edTableStripe}{RGB}{248,249,250} +\newcommand{\edKeyValueRows}{\rowcolors{1}{edTableStripe}{white}} +\newcommand{\edHeaderRows}{\rowcolors{2}{edTableStripe}{white}} +\makeatletter +\let\title\origtitle +\renewcommand\section{\@startsection {section}{1}{\z@}% + {-3.5ex \@plus -1ex \@minus -.2ex}% + {2.3ex \@plus .2ex}% + {\normalfont\Large}} +\renewcommand\subsection{\@startsection{subsection}{2}{\z@}% + {-3.25ex\@plus -1ex \@minus -.2ex}% + {1.5ex \@plus .2ex}% + {\normalfont\large}} +\renewcommand\subsubsection{\@startsection{subsubsection}{3}{\z@}% + {-3.25ex\@plus -1ex \@minus -.2ex}% + {1.5ex \@plus .2ex}% + {\normalfont\normalsize}} +\renewenvironment{abstract} + {\begin{center} + \abstractname\vspace{-.5em}\vspace{0pt} + \end{center} + \list{}{ + \setlength{\leftmargin}{1cm}% + \setlength{\rightmargin}{\leftmargin}% + }% + \item\relax} + {\endlist\bigskip} +\makeatother {% macro tex_field_header(field, fallback) -%} {% set label = (field.label or fallback) | tex_markup -%} -{% set units = field.units | tex_markup -%} +{% set units = field.units | tex_unit -%} {{ label }}{% if field.units %} ({{ units }}){% endif %} {%- endmacro %} +{% macro tex_category_label(row) -%} +{% set label = (row.latex_label or row.name) | tex_markup -%} +{% set units = row.latex_units | tex_unit -%} +{{ label }}{% if row.latex_units %} ({{ units }}){% endif %} +{%- endmacro %} +{% macro tex_category_header(column) -%} +{% set label = (column.latex_label or column.name) | tex_markup -%} +{% set units = column.latex_units | tex_unit -%} +{% set header %}{{ label }}{% if column.latex_units %} ({{ units }}){% endif %}{% endset -%} +{% if column.numeric %}\multicolumn{1}{c}{ {{- header -}} }{% else %}\multicolumn{1}{l}{ {{- header -}} }{% endif %} +{%- endmacro %} +{% macro tex_category_cell(cell) -%} +{% if cell.numeric %}{{ cell.value | tex_number }}{% else %}{{ cell.value | tex }}{% endif %} +{%- endmacro %} +{% macro tex_category_value(row, numeric_column) -%} +{% if row.numeric %}{{ row.value | tex_number }}{% elif numeric_column %}\multicolumn{1}{l}{ {{- row.value | tex -}} }{% else %}{{ row.value | tex }}{% endif %} +{%- endmacro %} %---------------------------------------------------------- % Title @@ -33,6 +79,7 @@ \section{Project Summary} \begin{table}[H] +\edKeyValueRows \begin{tabular}{ll} \toprule Short name & {{ project.name | tex }} \\ @@ -50,6 +97,7 @@ Generated & {{ metadata.generated_at | tex }} \\ \section{Software} \begin{table}[H] +\edHeaderRows \begin{tabular}{lll} \toprule Role & Name & Version \\ @@ -67,6 +115,7 @@ Minimizer & {{ software.minimizer.name | tex }} & {{ software.minimizer.version \section{Refinement} \begin{table}[H] +\edHeaderRows \begin{tabular}{lS} \toprule \multicolumn{1}{l}{Quantity} & \multicolumn{1}{c}{Value} \\ @@ -92,54 +141,54 @@ Weighted R factor & {{ refinement.fit_result.wr_factor_all | tex_number }} \\ %---------- \subsection{ {{- structure.id | tex -}} } +{% for category in structure.categories %} %------------- -\subsubsection{Space group and unit cell parameters} +\subsubsection{ {{- category.title | tex -}} } +{% if category.kind == "item" %} \begin{table}[H] -\begin{tabular}{lS} +\edKeyValueRows +\begin{tabular}{l{% if category.has_numeric_values %}S{% else %}l{% endif %}} \toprule -Space group & \multicolumn{1}{l}{ {{- structure.space_group | tex -}} } \\ -Crystal system & \multicolumn{1}{l}{ {{- structure.crystal_system | tex -}} } \\ -{% for key, value in structure.cell.items() %} -{{ tex_field_header(structure.cell_latex[key], key | replace("_", " ")) }} & {{ value | tex_number }} \\ +{% for row in category.rows %} +{{ tex_category_label(row) }} & {{ tex_category_value(row, category.has_numeric_values) }} \\ {% endfor %} \bottomrule \end{tabular} \end{table} +{% else %} -{% if structure.atom_sites %} -%------------- -\subsubsection{Atom site parameters} - +{% if category.scalar_rows %} \begin{table}[H] -\begin{tabular}{llSSSSS} +\edKeyValueRows +\begin{tabular}{l{% if category.scalar_has_numeric_values %}S{% else %}l{% endif %}} \toprule -\multicolumn{1}{l}{Label} & \multicolumn{1}{l}{Type} & \multicolumn{1}{c}{$x$} & \multicolumn{1}{c}{$y$} & \multicolumn{1}{c}{$z$} & \multicolumn{1}{c}{Occ.} & \multicolumn{1}{c}{ {{- tex_field_header(structure.atom_site_latex.adp_iso, "ADP") -}} } \\ -\midrule -{% for atom in structure.atom_sites %} -{{ atom.label | tex }} & {{ atom.type_symbol | tex }} & {{ atom.fract_x | tex_number }} & {{ atom.fract_y | tex_number }} & {{ atom.fract_z | tex_number }} & {{ atom.occupancy | tex_number }} & {{ atom.adp_iso | tex_number }} \\ +{% for row in category.scalar_rows %} +{{ tex_category_label(row) }} & {{ tex_category_value(row, category.scalar_has_numeric_values) }} \\ {% endfor %} \bottomrule \end{tabular} \end{table} {% endif %} -{% if structure.atom_site_aniso %} -%------------- -\subsubsection{Anisotropic atomic displacement parameters} - +{% if category.rows %} \begin{table}[H] -\begin{tabular}{lSSSSSS} +\edHeaderRows +\resizebox{\ifdim\width>\linewidth\linewidth\else\width\fi}{!}{ +\begin{tabular}{ {{- category.colspec -}} } \toprule -\multicolumn{1}{l}{Label} & \multicolumn{1}{c}{ {{- tex_field_header(structure.atom_site_aniso_latex.adp_11, "ADP 11") -}} } & \multicolumn{1}{c}{ {{- tex_field_header(structure.atom_site_aniso_latex.adp_22, "ADP 22") -}} } & \multicolumn{1}{c}{ {{- tex_field_header(structure.atom_site_aniso_latex.adp_33, "ADP 33") -}} } & \multicolumn{1}{c}{ {{- tex_field_header(structure.atom_site_aniso_latex.adp_12, "ADP 12") -}} } & \multicolumn{1}{c}{ {{- tex_field_header(structure.atom_site_aniso_latex.adp_13, "ADP 13") -}} } & \multicolumn{1}{c}{ {{- tex_field_header(structure.atom_site_aniso_latex.adp_23, "ADP 23") -}} } \\ +{% for column in category.columns %}{{ tex_category_header(column) }}{% if not loop.last %} & {% endif %}{% endfor %} \\ \midrule -{% for aniso in structure.atom_site_aniso %} -{{ aniso.label | tex }} & {{ aniso.adp_11 | tex_number }} & {{ aniso.adp_22 | tex_number }} & {{ aniso.adp_33 | tex_number }} & {{ aniso.adp_12 | tex_number }} & {{ aniso.adp_13 | tex_number }} & {{ aniso.adp_23 | tex_number }} \\ +{% for row in category.rows %} +{% for cell in row.cells %}{{ tex_category_cell(cell) }}{% if not loop.last %} & {% endif %}{% endfor %} \\ {% endfor %} \bottomrule \end{tabular} +} \end{table} {% endif %} +{% endif %} +{% endfor %} {% endfor %} %---------------------------------------------------------- @@ -153,23 +202,6 @@ Crystal system & \multicolumn{1}{l}{ {{- structure.crystal_system | tex -}} } \\ %----------- \subsection{ {{- experiment.id | tex -}} } -%------------- -\subsubsection{Experiment details} - -\begin{table}[H] -\begin{tabular}{lS} -\toprule -Sample form & \multicolumn{1}{l}{ {{- experiment.type.sample_form | tex -}} } \\ -Probe & \multicolumn{1}{l}{ {{- experiment.type.radiation_probe | tex -}} } \\ -Beam mode & \multicolumn{1}{l}{ {{- experiment.type.beam_mode | tex -}} } \\ -Scattering type & \multicolumn{1}{l}{ {{- experiment.type.scattering_type | tex -}} } \\ -Calculator & \multicolumn{1}{l}{ {{- experiment.calculator.type | tex -}} } \\ -{{ tex_field_header(experiment.diffrn_latex.ambient_temperature, "Temperature") }} & {{ experiment.diffrn.ambient_temperature | tex_number }} \\ -{{ tex_field_header(experiment.diffrn_latex.ambient_pressure, "Pressure") }} & {{ experiment.diffrn.ambient_pressure | tex_number }} \\ -\bottomrule -\end{tabular} -\end{table} - {% if experiment.fit_data and tex.fit_figure_paths[experiment.id] %} %------------- \subsubsection{Fit quality} @@ -180,6 +212,55 @@ Calculator & \multicolumn{1}{l}{ {{- experiment.calculator.type | tex -}} } \\ \caption{Measured and calculated fit for {{ experiment.id | tex }}.} \end{figure} {% endif %} + +{% for category in experiment.categories %} +%------------- +\subsubsection{ {{- category.title | tex -}} } + +{% if category.kind == "item" %} +\begin{table}[H] +\edKeyValueRows +\begin{tabular}{l{% if category.has_numeric_values %}S{% else %}l{% endif %}} +\toprule +{% for row in category.rows %} +{{ tex_category_label(row) }} & {{ tex_category_value(row, category.has_numeric_values) }} \\ +{% endfor %} +\bottomrule +\end{tabular} +\end{table} +{% else %} + +{% if category.scalar_rows %} +\begin{table}[H] +\edKeyValueRows +\begin{tabular}{l{% if category.scalar_has_numeric_values %}S{% else %}l{% endif %}} +\toprule +{% for row in category.scalar_rows %} +{{ tex_category_label(row) }} & {{ tex_category_value(row, category.scalar_has_numeric_values) }} \\ +{% endfor %} +\bottomrule +\end{tabular} +\end{table} +{% endif %} + +{% if category.rows %} +\begin{table}[H] +\edHeaderRows +\resizebox{\ifdim\width>\linewidth\linewidth\else\width\fi}{!}{ +\begin{tabular}{ {{- category.colspec -}} } +\toprule +{% for column in category.columns %}{{ tex_category_header(column) }}{% if not loop.last %} & {% endif %}{% endfor %} \\ +\midrule +{% for row in category.rows %} +{% for cell in row.cells %}{{ tex_category_cell(cell) }}{% if not loop.last %} & {% endif %}{% endfor %} \\ +{% endfor %} +\bottomrule +\end{tabular} +} +\end{table} +{% endif %} +{% endif %} +{% endfor %} {% endfor %} %---------------------------------------------------------- diff --git a/src/easydiffraction/report/tex_renderer.py b/src/easydiffraction/report/tex_renderer.py index dc39f6057..e21f89122 100644 --- a/src/easydiffraction/report/tex_renderer.py +++ b/src/easydiffraction/report/tex_renderer.py @@ -11,6 +11,7 @@ from jinja2 import Environment from jinja2 import PackageLoader +from jinja2 import select_autoescape from easydiffraction.report.fit_plot import fit_bragg_tick_styles from easydiffraction.report.fit_plot import fit_plot_axis_styles @@ -147,7 +148,11 @@ def _environment() -> Environment: """Return the Jinja environment for TeX report templates.""" environment = Environment( loader=PackageLoader('easydiffraction.report', 'templates'), - autoescape=False, + autoescape=select_autoescape( + enabled_extensions=(), + default_for_string=False, + default=False, + ), trim_blocks=True, lstrip_blocks=True, ) @@ -155,11 +160,12 @@ def _environment() -> Environment: environment.filters['tex_axis_label'] = _tex_axis_label environment.filters['tex_markup'] = _tex_markup environment.filters['tex_number'] = _tex_number + environment.filters['tex_unit'] = _tex_unit return environment def _prepare_tex_bundle(tex_dir: pathlib.Path) -> None: - """Remove managed bundle subdirectories before writing TeX assets.""" + """Remove managed bundle directories before writing TeX assets.""" tex_dir.mkdir(parents=True, exist_ok=True) for dirname in ('data', 'styles', 'figures'): path = tex_dir / dirname @@ -392,11 +398,45 @@ def _tex_markup(value: object) -> str: return _tex_escape(text) +def _tex_unit(value: object) -> str: + """Return TeX-safe unit text for table labels.""" + if value is None: + return '' + text = str(value) + if not text: + return '' + if '\\' in text or '$' in text: + return f'${_tex_unit_math(text.replace("$", ""))}$' + return _tex_escape(text) + + +def _tex_unit_math(value: str) -> str: + """Return unit TeX normalized for math-mode rendering.""" + placeholder = '__EASYDIFFRACTION_ANGSTROM__' + text = _tex_degree_unit_math(value) + text = text.replace(r'\mathrm{\AA}', placeholder) + text = text.replace(r'\AA', r'\mathring{\mathrm{A}}') + return text.replace(placeholder, r'\mathring{\mathrm{A}}') + + +def _tex_degree_unit_math(value: str) -> str: + """Return TeX unit markup with degree symbols named as deg.""" + text = value + markers = (r'^\circ{}^2', r'^\circ{}^{2}', r'^\circ^2', r'^\circ^{2}') + for marker in markers: + text = text.replace(marker, r'\mathrm{deg}^2') + return text.replace(r'^\circ{}', r'\mathrm{deg}').replace( + r'^\circ', + r'\mathrm{deg}', + ) + + def _tex_axis_label(value: object) -> str: """Return a TeX-safe axis label from Plotly display text.""" if value is None: return '' text = str(value) + text = text.replace('degree', 'deg') text = text.replace('⁻¹', '$^{-1}$') text = text.replace('²', '$^2$') text = text.replace('θ', r'$\theta$') From 3450eb81d78aa3397406c19b9a79d0b0cfbaaf2f Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Fri, 29 May 2026 17:00:05 +0200 Subject: [PATCH 070/129] Harmonize report styling --- .../accepted/project-summary-rendering.md | 45 +- docs/dev/plans/project-summary-rendering.md | 67 +++ .../user-guide/analysis-workflow/report.md | 4 +- src/easydiffraction/analysis/analysis.py | 8 +- src/easydiffraction/core/datablock.py | 7 +- .../experiment/categories/data/bragg_pd.py | 33 ++ .../experiment/categories/refln/bragg_pd.py | 14 + .../experiment/categories/refln/bragg_sc.py | 36 +- .../display/plotters/plotly.py | 19 +- .../project/categories/info/default.py | 13 + src/easydiffraction/report/data_context.py | 219 ++++++++- src/easydiffraction/report/fit_plot.py | 13 +- src/easydiffraction/report/html_renderer.py | 13 +- src/easydiffraction/report/pdf_compiler.py | 17 +- src/easydiffraction/report/style.py | 42 ++ .../report/templates/html/report.html.j2 | 40 +- .../report/templates/html/style.css | 189 +++++--- .../report/templates/tex/figure.tex.j2 | 41 +- .../report/templates/tex/report.tex.j2 | 316 ++++++------ .../report/templates/tex/styles/LICENSES.md | 142 ------ .../report/templates/tex/styles/harvard.sty | 270 ----------- .../templates/tex/styles/iucrjournals.cls | 385 --------------- src/easydiffraction/report/tex_renderer.py | 451 ++++++++++++++++-- .../display/plotters/test_ascii.py | 2 +- .../display/plotters/test_plotly.py | 26 +- .../easydiffraction/display/test_plotting.py | 4 +- .../test_serialize_category_owner_baseline.py | 41 ++ .../report/test_data_context.py | 249 +++++++++- .../report/test_html_renderer.py | 171 ++++++- .../report/test_pdf_compiler.py | 15 + .../report/test_tex_renderer.py | 324 +++++++++---- 31 files changed, 1993 insertions(+), 1223 deletions(-) create mode 100644 src/easydiffraction/report/style.py delete mode 100644 src/easydiffraction/report/templates/tex/styles/LICENSES.md delete mode 100644 src/easydiffraction/report/templates/tex/styles/harvard.sty delete mode 100644 src/easydiffraction/report/templates/tex/styles/iucrjournals.cls diff --git a/docs/dev/adrs/accepted/project-summary-rendering.md b/docs/dev/adrs/accepted/project-summary-rendering.md index 307e3b5c0..d95222089 100644 --- a/docs/dev/adrs/accepted/project-summary-rendering.md +++ b/docs/dev/adrs/accepted/project-summary-rendering.md @@ -928,14 +928,37 @@ tables: `0.584(20)` and `3.89086937` align visually on the decimal marker without changing the original value text. - **Automatic section numbering via CSS counters.** HTML sections - are numbered like the PDF (`1`, `1.1`, `1.1.1`) using CSS + are numbered like the PDF (`1.`, `1.1.`, `1.1.1.`) using CSS counters. Numbers are presentation-only, so they stay correct if - sections are added or reordered. The document title and Abstract - are unnumbered, matching the LaTeX report. -- **Three-rule tables with light zebra striping.** HTML and TeX - tables use only top, middle, and bottom rules. Body rows alternate - with a very light grey background starting at the first body row; - no per-row horizontal rules are drawn. + sections are added or reordered. The document title and Description + section are unnumbered, matching the LaTeX report. +- **Framed tables with shared report colors.** HTML and TeX tables + have an outer frame and, for header tables, one rule below the + header. They do not draw separators between body rows or between + columns. The outer frame and the rule below header rows use the same + darker color as the fit-plot axis rectangle. Fit-plot inner grid + lines use the lighter Plotly-like grid color. The alternating row + background remains a separate, lighter report color. These colors are + defined once in report styling code and passed to HTML CSS, TeX + tables, and Plotly/pgfplots figures. Body rows alternate with the + first body row filled, regardless of whether the table has a header. +- **Predictable table widths.** HTML and TeX key-value tables use at + least half of the available text width. Loop tables are classified + from their rendered content: compact loops use half width, while + wider loops use the full text width. This keeps small tables aligned + with each other while giving wide category loops enough room for + scientific values. +- **Left-aligned report title, subtitle, and description.** Reports + render the project title as a left-aligned title, followed by a + smaller subtitle (`EasyDiffraction report`). The project + description is rendered as an unnumbered `Description` section, + not as publication metadata and not as a centered abstract block. +- **Configurable free report font.** Report styling defines a single + font configuration. HTML uses a non-embedded local-font stack headed + by Nunito. TeX uses `fontspec` when the engine supports it, tries + Nunito for text and Fira Math for math, and otherwise falls back to + the TeX engine's bundled Latin Modern defaults. The PDF engine + embeds the fonts it uses. `reports/` is created lazily — only when at least one format is configured (or an ad-hoc method is called). A user iterating on @@ -1415,8 +1438,8 @@ pgfplots.** HTML/Plotly path, mapped to TeX-native `\definecolor`, `line width`, legend, and `groupplot` options. - **`pgfplots` and `standalone` are on every modern TeX - distribution.** `tectonic` resolves both on demand from - CTAN; TeX Live and MiKTeX ship them. No extra vendoring. + distribution.** TeX Live and MiKTeX ship them. `tectonic` can + resolve them into its user cache when needed. No extra vendoring. **Caveats.** @@ -1436,8 +1459,8 @@ or `pixi.lock`: - `tectonic` — pixi/conda package, lightweight TeX engine for §3.4 PDF compilation in the project dev environment. - `tectonic` auto-resolves `pgfplots` and any other - TeX-package dependency from CTAN on first use. + `tectonic` can resolve `pgfplots` and any other TeX-package + dependency from CTAN into its user cache. Neither `kaleido` nor a browser is a dependency. The earlier draft's `kaleido` + `chromium` chain is dropped wholesale. diff --git a/docs/dev/plans/project-summary-rendering.md b/docs/dev/plans/project-summary-rendering.md index 65e95e1de..4c40c55be 100644 --- a/docs/dev/plans/project-summary-rendering.md +++ b/docs/dev/plans/project-summary-rendering.md @@ -1168,6 +1168,56 @@ exceptions. - Commit: `Render category-driven reports with aligned tables`. +- [x] **P1.27 — Harmonize report tables, colors, title, and fonts (ADR §2)** + - Files: + `docs/dev/adrs/accepted/project-summary-rendering.md`, + `docs/dev/plans/project-summary-rendering.md`, + `src/easydiffraction/display/plotters/plotly.py`, + `src/easydiffraction/report/data_context.py`, + `src/easydiffraction/report/fit_plot.py`, + `src/easydiffraction/report/html_renderer.py`, + `src/easydiffraction/report/style.py`, + `src/easydiffraction/report/templates/html/report.html.j2`, + `src/easydiffraction/report/templates/html/style.css`, + `src/easydiffraction/report/templates/tex/figure.tex.j2`, + `src/easydiffraction/report/templates/tex/report.tex.j2`, + `src/easydiffraction/report/tex_renderer.py`. + - Add report styling constants for the axis-frame color, + table-inner color, chart-grid color, row-fill color, subtitle, + HTML font stack, and TeX font choices. Feed those constants + into HTML CSS, Plotly report figure serialization, pgfplots + figures, and TeX table styles. + - Render HTML and TeX tables with an outer frame and, for header + tables, one header rule. The outer frame and header rule use the + darker shared axis-frame color; filled rows use the shared + row-fill color. Tables do not draw separators between columns or + between body rows. + - Keep the HTML and TeX row striping consistent: the first + body row is filled for both header and key-value tables. + Remove the extra TeX spacing around the line below header + rows by using framed tables plus a single colored header + rule instead of booktabs rules. + - Force key-value tables to occupy half of the text line. Classify + loop tables from their rendered content: compact loops occupy + half of the text line, and wider loops occupy the full text line. + Add `report_style` to the saved TeX report context so project + saves can render the configured font and color settings. + - Tighten HTML table line height to match the denser PDF + table rhythm. + - Add terminal dots to generated section numbers in both HTML + and TeX (`1.`, `1.1.`, `1.1.1.`). + - Render the report title and project description left-aligned. + Show the project description as an unnumbered `Description` + section, and show `EasyDiffraction report` as a smaller + subtitle under the main title. + - Configure free report fonts centrally: HTML uses a + non-embedded local font stack headed by Nunito; TeX uses + `fontspec` with Nunito and Fira Math when available, otherwise + leaving the engine's bundled Latin Modern defaults in place so + the generated PDF embeds the fonts it actually uses. + - Commit: + `Harmonize report table and title styling`. + ## Test plan (Phase 2) Per AGENTS.md §Testing, every new module, class, and bug fix @@ -1336,6 +1386,23 @@ running the verification commands below, add or update: columns on `S` alignment, emits readable labels/units, and sets the measured pgfplots legend marker color explicitly. P1.26 surface. +- [ ] **`tests/unit/easydiffraction/report/test_html_renderer.py`** + (further extend) — **shared report styling (P1.27).** + Assert that the HTML report injects shared axis/grid/row color + CSS variables, uses dotted section-number counters, renders a + left-aligned subtitle and unnumbered `Description` section, emits + framed tables without column or body-row separators, sets compact + table line-height, and applies content-driven half/full table width + classes. +- [ ] **`tests/unit/easydiffraction/report/test_tex_renderer.py`** + (further extend) — **shared report styling (P1.27).** + Assert that the TeX report defines the shared axis/grid/row + colors, uses framed half-width and full-width tables without inner + column separators, emits a colored header rule instead of booktabs or + body-row rules, adds terminal dots to section numbers, renders the + custom left-aligned title/subtitle and `Description` section, + includes the configured font setup, and passes `report_style` + through saved TeX report rendering. - [ ] **Wheel-packaging verification** — run `pixi run dist-build` and then `unzip -l dist/*.whl | grep -E 'mathjax|iucrjournals.cls|harvard.sty'` diff --git a/docs/docs/user-guide/analysis-workflow/report.md b/docs/docs/user-guide/analysis-workflow/report.md index 12cc58a71..7626dd2f0 100644 --- a/docs/docs/user-guide/analysis-workflow/report.md +++ b/docs/docs/user-guide/analysis-workflow/report.md @@ -72,8 +72,8 @@ project.report.save_pdf() ``` `save_pdf()` always writes the TeX bundle first. If no TeX engine is on -`PATH`, EasyDiffraction leaves the `.tex`, `data/`, and `styles/` files -under `reports/tex/`, prints a short install hint, and does not raise. +`PATH`, EasyDiffraction leaves the `.tex` and `data/` files under +`reports/tex/`, prints a short install hint, and does not raise. HTML reports load Plotly and MathJax from CDNs by default. Set `project.report.html_offline = True` to make the HTML report usable diff --git a/src/easydiffraction/analysis/analysis.py b/src/easydiffraction/analysis/analysis.py index 56f6fe890..62c474380 100644 --- a/src/easydiffraction/analysis/analysis.py +++ b/src/easydiffraction/analysis/analysis.py @@ -500,7 +500,7 @@ def fit_parameter_correlations(self) -> FitParameterCorrelations: @property def software(self) -> Software: - """Software-provenance snapshot for the latest successful fit.""" + """Software snapshot for the latest successful fit.""" return self._software @@ -1172,9 +1172,15 @@ def _serializable_categories(self) -> list: categories.extend(self._fit_parameter_state_categories()) if self._has_persisted_fit_state(): categories.extend(self._fit_result_state_categories()) + if self._has_software_provenance(): + categories.append(self.software) return categories + def _has_software_provenance(self) -> bool: + """Return True when software provenance has been stamped.""" + return any(parameter.value is not None for parameter in self.software.parameters) + # ------------------------------------------------------------------ # Parameter helpers # ------------------------------------------------------------------ diff --git a/src/easydiffraction/core/datablock.py b/src/easydiffraction/core/datablock.py index 4eb18866a..7dca0d4d1 100644 --- a/src/easydiffraction/core/datablock.py +++ b/src/easydiffraction/core/datablock.py @@ -7,6 +7,8 @@ from easydiffraction.core.collection import CollectionBase from easydiffraction.core.variable import Parameter +DEFAULT_LOOP_DISPLAY_LIMIT = 20 + class DatablockItem(CategoryOwner): """Base class for items in a datablock collection.""" @@ -38,7 +40,10 @@ def as_cif(self) -> str: self._update_categories() return datablock_item_to_cif(self) - def _cif_for_display(self, max_loop_display: int = 20) -> str: + def _cif_for_display( + self, + max_loop_display: int = DEFAULT_LOOP_DISPLAY_LIMIT, + ) -> str: """ Return CIF text with loop categories truncated for display. diff --git a/src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py b/src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py index da62ddab6..d31c5db7d 100644 --- a/src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py +++ b/src/easydiffraction/datablocks/experiment/categories/data/bragg_pd.py @@ -45,6 +45,10 @@ def __init__(self) -> None: self._point_id = StringDescriptor( name='point_id', description='Identifier for this data point in the dataset', + display_handler=DisplayHandler( + display_name='ID', + latex_name='ID', + ), value_spec=AttributeSpec( default='0', # TODO: the following pattern is valid for dict key @@ -61,6 +65,13 @@ def __init__(self) -> None: self._d_spacing = NumericDescriptor( name='d_spacing', description='d-spacing value corresponding to this data point', + units='angstroms', + display_handler=DisplayHandler( + display_name='d', + display_units='Å', + latex_name=r'$d$', + latex_units=r'\AA', + ), value_spec=AttributeSpec( default=0.0, validator=RangeValidator(ge=0), @@ -70,6 +81,10 @@ def __init__(self) -> None: self._intensity_meas = NumericDescriptor( name='intensity_meas', description='Intensity recorded at each measurement point (angle/time)', + display_handler=DisplayHandler( + display_name='Imeas', + latex_name=r'$I_{\mathrm{meas}}$', + ), value_spec=AttributeSpec( default=0.0, validator=RangeValidator(ge=0), @@ -84,6 +99,10 @@ def __init__(self) -> None: self._intensity_meas_su = NumericDescriptor( name='intensity_meas_su', description='Standard uncertainty of the measured intensity at this point', + display_handler=DisplayHandler( + display_name='s.u.(Imeas)', + latex_name=r'$\sigma(I_{\mathrm{meas}})$', + ), value_spec=AttributeSpec( default=1.0, validator=RangeValidator(ge=0), @@ -98,6 +117,10 @@ def __init__(self) -> None: self._intensity_calc = NumericDescriptor( name='intensity_calc', description='Intensity of a computed diffractogram at this point', + display_handler=DisplayHandler( + display_name='Icalc', + latex_name=r'$I_{\mathrm{calc}}$', + ), value_spec=AttributeSpec( default=0.0, validator=RangeValidator(ge=0), @@ -107,6 +130,10 @@ def __init__(self) -> None: self._intensity_bkg = NumericDescriptor( name='intensity_bkg', description='Intensity of a computed background at this point', + display_handler=DisplayHandler( + display_name='Ibkg', + latex_name=r'$I_{\mathrm{bkg}}$', + ), value_spec=AttributeSpec( default=0.0, validator=RangeValidator(ge=0), @@ -116,6 +143,10 @@ def __init__(self) -> None: self._calc_status = StringDescriptor( name='calc_status', description='Status code of the data point in the calculation process', + display_handler=DisplayHandler( + display_name='Status', + latex_name='Status', + ), value_spec=AttributeSpec( default='incl', # TODO: Make Enum validator=MembershipValidator(allowed=['incl', 'excl']), @@ -256,6 +287,8 @@ def __init__(self) -> None: description='Measured time for time-of-flight neutron measurement.', units='microseconds', display_handler=DisplayHandler( + display_name='TOF', + latex_name='TOF', display_units='μs', latex_units=r'$\mu\mathrm{s}$', ), diff --git a/src/easydiffraction/datablocks/experiment/categories/refln/bragg_pd.py b/src/easydiffraction/datablocks/experiment/categories/refln/bragg_pd.py index f70b2cb8e..581e00221 100644 --- a/src/easydiffraction/datablocks/experiment/categories/refln/bragg_pd.py +++ b/src/easydiffraction/datablocks/experiment/categories/refln/bragg_pd.py @@ -41,12 +41,20 @@ def __init__(self) -> None: self._phase_id = StringDescriptor( name='phase_id', description='Identifier of the linked phase for this reflection', + display_handler=DisplayHandler( + display_name='Phase', + latex_name='Phase', + ), value_spec=AttributeSpec(default=''), cif_handler=CifHandler(names=['_refln.phase_id']), ) self._f_calc = NumericDescriptor( name='f_calc', description='Calculated structure-factor amplitude for this reflection', + display_handler=DisplayHandler( + display_name='Fcalc', + latex_name=r'$F_{\mathrm{calc}}$', + ), value_spec=AttributeSpec( default=0.0, validator=RangeValidator(ge=0), @@ -56,6 +64,10 @@ def __init__(self) -> None: self._f_squared_calc = NumericDescriptor( name='f_squared_calc', description='Calculated structure-factor amplitude squared for this reflection', + display_handler=DisplayHandler( + display_name='F^2calc', + latex_name=r'$F^2_{\mathrm{calc}}$', + ), value_spec=AttributeSpec( default=0.0, validator=RangeValidator(ge=0), @@ -139,6 +151,8 @@ def __init__(self) -> None: description='Calculated time-of-flight position for this reflection', units='microseconds', display_handler=DisplayHandler( + display_name='TOF', + latex_name='TOF', display_units='μs', latex_units=r'$\mu\mathrm{s}$', ), diff --git a/src/easydiffraction/datablocks/experiment/categories/refln/bragg_sc.py b/src/easydiffraction/datablocks/experiment/categories/refln/bragg_sc.py index 28b1fad1b..26d498c41 100644 --- a/src/easydiffraction/datablocks/experiment/categories/refln/bragg_sc.py +++ b/src/easydiffraction/datablocks/experiment/categories/refln/bragg_sc.py @@ -7,10 +7,10 @@ from easydiffraction.core.category import CategoryCollection from easydiffraction.core.category import CategoryItem +from easydiffraction.core.display_handler import DisplayHandler from easydiffraction.core.metadata import CalculatorSupport from easydiffraction.core.metadata import Compatibility from easydiffraction.core.metadata import TypeInfo -from easydiffraction.core.display_handler import DisplayHandler from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.validation import RangeValidator from easydiffraction.core.validation import RegexValidator @@ -38,6 +38,10 @@ def __init__(self) -> None: self._id = StringDescriptor( name='id', description='Identifier of the reflection', + display_handler=DisplayHandler( + display_name='ID', + latex_name='ID', + ), value_spec=AttributeSpec( default='0', # TODO: the following pattern is valid for dict key @@ -52,7 +56,9 @@ def __init__(self) -> None: description='Distance between lattice planes for this reflection', units='angstroms', display_handler=DisplayHandler( + display_name='d', display_units='Å', + latex_name=r'$d$', latex_units=r'\AA', ), value_spec=AttributeSpec( @@ -66,7 +72,9 @@ def __init__(self) -> None: description='The sin(θ)/λ value for this reflection', units='reciprocal_angstroms', display_handler=DisplayHandler( + display_name='sinθ/λ', display_units='Å⁻¹', + latex_name=r'$\sin\theta/\lambda$', latex_units=r'\AA$^{-1}$', ), value_spec=AttributeSpec( @@ -78,6 +86,10 @@ def __init__(self) -> None: self._index_h = NumericDescriptor( name='index_h', description='Miller index h of a measured reflection', + display_handler=DisplayHandler( + display_name='h', + latex_name=r'$h$', + ), value_spec=AttributeSpec( default=0.0, validator=RangeValidator(), @@ -87,6 +99,10 @@ def __init__(self) -> None: self._index_k = NumericDescriptor( name='index_k', description='Miller index k of a measured reflection', + display_handler=DisplayHandler( + display_name='k', + latex_name=r'$k$', + ), value_spec=AttributeSpec( default=0.0, validator=RangeValidator(), @@ -96,6 +112,10 @@ def __init__(self) -> None: self._index_l = NumericDescriptor( name='index_l', description='Miller index l of a measured reflection', + display_handler=DisplayHandler( + display_name='l', + latex_name=r'$l$', + ), value_spec=AttributeSpec( default=0.0, validator=RangeValidator(), @@ -105,6 +125,10 @@ def __init__(self) -> None: self._intensity_meas = NumericDescriptor( name='intensity_meas', description=' The intensity of the reflection derived from the measurements.', + display_handler=DisplayHandler( + display_name='Imeas', + latex_name=r'$I_{\mathrm{meas}}$', + ), value_spec=AttributeSpec( default=0.0, validator=RangeValidator(ge=0), @@ -114,6 +138,10 @@ def __init__(self) -> None: self._intensity_meas_su = NumericDescriptor( name='intensity_meas_su', description='Standard uncertainty of the measured intensity.', + display_handler=DisplayHandler( + display_name='s.u.(Imeas)', + latex_name=r'$\sigma(I_{\mathrm{meas}})$', + ), value_spec=AttributeSpec( default=0.0, validator=RangeValidator(ge=0), @@ -123,6 +151,10 @@ def __init__(self) -> None: self._intensity_calc = NumericDescriptor( name='intensity_calc', description='Intensity of the reflection calculated from atom site data', + display_handler=DisplayHandler( + display_name='Icalc', + latex_name=r'$I_{\mathrm{calc}}$', + ), value_spec=AttributeSpec( default=0.0, validator=RangeValidator(ge=0), @@ -134,7 +166,9 @@ def __init__(self) -> None: description='Mean wavelength of radiation for this reflection', units='angstroms', display_handler=DisplayHandler( + display_name='λ', display_units='Å', + latex_name=r'$\lambda$', latex_units=r'\AA', ), value_spec=AttributeSpec( diff --git a/src/easydiffraction/display/plotters/plotly.py b/src/easydiffraction/display/plotters/plotly.py index 337682f6e..f16437184 100644 --- a/src/easydiffraction/display/plotters/plotly.py +++ b/src/easydiffraction/display/plotters/plotly.py @@ -1002,6 +1002,8 @@ def serialize_html( *, include_plotlyjs: bool | str, force_template: str | None = None, + axis_frame_color: str | None = None, + grid_color: str | None = None, ) -> str: """ Serialize a Plotly figure with EasyDiffraction controls. @@ -1014,6 +1016,10 @@ def serialize_html( Plotly JavaScript inclusion mode passed to Plotly. force_template : str | None, default=None Optional template name applied before serialization. + axis_frame_color : str | None, default=None + Optional explicit axis-frame color. + grid_color : str | None, default=None + Optional explicit major-grid color. Returns ------- @@ -1022,10 +1028,15 @@ def serialize_html( """ if force_template is not None: fig.update_layout(template=force_template) - axis_frame_color = cls._axis_frame_color_for_template(force_template) - if axis_frame_color is not None: - fig.update_xaxes(linecolor=axis_frame_color) - fig.update_yaxes(linecolor=axis_frame_color) + resolved_axis_color = axis_frame_color + if resolved_axis_color is None: + resolved_axis_color = cls._axis_frame_color_for_template(force_template) + if resolved_axis_color is not None: + fig.update_xaxes(linecolor=resolved_axis_color) + fig.update_yaxes(linecolor=resolved_axis_color) + if grid_color is not None: + fig.update_xaxes(gridcolor=grid_color) + fig.update_yaxes(gridcolor=grid_color) legend_bgcolor = cls._legend_background_color_for_template(force_template) if legend_bgcolor is not None: fig.update_layout(legend={'bgcolor': legend_bgcolor}) diff --git a/src/easydiffraction/project/categories/info/default.py b/src/easydiffraction/project/categories/info/default.py index ff730dcee..e18a0632f 100644 --- a/src/easydiffraction/project/categories/info/default.py +++ b/src/easydiffraction/project/categories/info/default.py @@ -15,6 +15,7 @@ from easydiffraction.io.cif.serialize import project_info_to_cif from easydiffraction.project.categories.info.factory import ProjectInfoFactory from easydiffraction.utils.logging import console +from easydiffraction.utils.logging import log from easydiffraction.utils.utils import render_cif _PROJECT_TIMESTAMP_FORMAT = '%d %b %Y %H:%M:%S' @@ -39,6 +40,8 @@ def __init__( ) -> None: super().__init__() + self._validate_name(name) + created = datetime.datetime.now(tz=datetime.UTC) last_modified = datetime.datetime.now(tz=datetime.UTC) @@ -74,6 +77,15 @@ def __init__( ) self._path: pathlib.Path | None = None + @staticmethod + def _validate_name(value: str) -> None: + """Reject project names containing a path separator.""" + if '/' in value or '\\' in value: + log.error( + f"Project name {value!r} must not contain a path separator ('/' or '\\').", + exc_type=ValueError, + ) + @staticmethod def _parse_timestamp(value: str) -> datetime.datetime: """Parse project timestamp text from CIF storage format.""" @@ -105,6 +117,7 @@ def name(self) -> str: @name.setter def name(self, value: str) -> None: + self._validate_name(value) self._project_id.value = value @property diff --git a/src/easydiffraction/report/data_context.py b/src/easydiffraction/report/data_context.py index e6176a13f..79417fa8c 100644 --- a/src/easydiffraction/report/data_context.py +++ b/src/easydiffraction/report/data_context.py @@ -10,6 +10,7 @@ from datetime import datetime from easydiffraction.core.category import CategoryCollection +from easydiffraction.core.datablock import DEFAULT_LOOP_DISPLAY_LIMIT from easydiffraction.core.variable import GenericDescriptorBase from easydiffraction.core.variable import IntegerDescriptor from easydiffraction.core.variable import NumericDescriptor @@ -119,7 +120,9 @@ 'id_orcid', 'id_iucr', ) -_EXPERIMENT_DATA_CATEGORY_CODES = frozenset({'pd_data', 'total_data', 'refln'}) +_REPORT_LOOP_DISPLAY_LIMIT = DEFAULT_LOOP_DISPLAY_LIMIT +_FULL_WIDTH_TABLE_CHAR_LIMIT = 40 +_TRUNCATED_DATA_CATEGORY_CODES = frozenset({'pd_data', 'total_data'}) _NUMERIC_TEXT_RE = re.compile( r'^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:\(\d+\))?(?:[eE][+-]?\d+)?$' ) @@ -310,7 +313,7 @@ def _experiment_context(experiment: object) -> dict[str, object]: 'fit_data': _fit_data_context(experiment), 'categories': _category_contexts( experiment, - skip_codes=_EXPERIMENT_DATA_CATEGORY_CODES, + truncate_codes=_TRUNCATED_DATA_CATEGORY_CODES, ), } @@ -323,6 +326,12 @@ def _refinement_context(self) -> dict[str, object]: free = fields.get('n_free_parameters') fixed = total - free if isinstance(total, int) and isinstance(free, int) else None constraints = len(list(_collection_values(_safe_attr(analysis, 'constraints')))) + rows = _refinement_rows( + fields=fields, + total=total, + free=free, + constraints=constraints, + ) return { 'fit_result': fields, 'parameters': { @@ -331,12 +340,8 @@ def _refinement_context(self) -> dict[str, object]: 'fixed': fixed, }, 'constraints': constraints, - 'rows': _refinement_rows( - fields=fields, - total=total, - free=free, - constraints=constraints, - ), + 'rows': rows, + 'colspec': _key_value_colspec(rows), } def _software_context(self) -> dict[str, object]: @@ -499,16 +504,22 @@ def _category_contexts( owner: object, *, skip_codes: frozenset[str] | None = None, + truncate_codes: frozenset[str] | None = None, ) -> list[dict[str, object]]: """Return report rows for each public category on an owner.""" if skip_codes is None: skip_codes = frozenset() + if truncate_codes is None: + truncate_codes = frozenset() categories = getattr(owner, 'categories', ()) contexts = [] for category in categories: if _skip_category(category, skip_codes): continue - context = _category_context(category) + context = _category_context( + category, + truncate=_category_code(category) in truncate_codes, + ) if _category_has_content(context): contexts.append(context) return contexts @@ -524,16 +535,16 @@ def _skip_category(category: object, skip_codes: frozenset[str]) -> bool: return bool(callable(skip) and skip()) -def _category_context(category: object) -> dict[str, object]: +def _category_context(category: object, *, truncate: bool = False) -> dict[str, object]: """Return one generic category-rendering context.""" if isinstance(category, CategoryCollection): - return _collection_category_context(category) + return _collection_category_context(category, truncate=truncate) return _item_category_context(category) def _item_category_context(category: object) -> dict[str, object]: """Return a non-loop category context.""" - rows = _descriptor_rows(_category_parameters(category)) + rows = _defined_descriptor_rows(_descriptor_rows(_category_parameters(category))) has_numeric_values = _rows_have_numeric_values(rows) return { 'kind': 'item', @@ -542,15 +553,25 @@ def _item_category_context(category: object) -> dict[str, object]: 'rows': rows, 'has_numeric_values': has_numeric_values, 'value_column_numeric': has_numeric_values, + 'colspec': _key_value_colspec(rows), } -def _collection_category_context(category: CategoryCollection) -> dict[str, object]: +def _collection_category_context( + category: CategoryCollection, + *, + truncate: bool = False, +) -> dict[str, object]: """Return a loop-category context.""" items = list(category.values()) columns = _collection_columns(category, items) rows = [_collection_row(category, item, columns) for item in items] - scalar_rows = _descriptor_rows(category.scalar_descriptors) + rows = [row for row in rows if _loop_row_has_report_values(row)] + if truncate: + rows = _truncate_loop_rows(rows) + scalar_rows = _defined_descriptor_rows( + _descriptor_rows(category.scalar_descriptors) + ) _mark_numeric_columns(columns, rows) _apply_cell_number_alignment(columns, rows) return { @@ -559,9 +580,11 @@ def _collection_category_context(category: CategoryCollection) -> dict[str, obje 'title': _category_title(category), 'scalar_rows': scalar_rows, 'scalar_has_numeric_values': _rows_have_numeric_values(scalar_rows), + 'scalar_colspec': _key_value_colspec(scalar_rows), 'columns': columns, 'rows': rows, - 'colspec': ''.join('S' if column['numeric'] else 'l' for column in columns), + 'colspec': _loop_colspec(columns), + 'table_width': _loop_table_width(columns, rows), } @@ -572,6 +595,97 @@ def _category_has_content(context: dict[str, object]) -> bool: return bool(context['rows'] or context['scalar_rows']) +def _defined_descriptor_rows( + rows: list[dict[str, object]], +) -> list[dict[str, object]]: + """Return descriptor rows whose value column is populated.""" + return [row for row in rows if not _is_empty_value(row['value'])] + + +def _loop_row_has_report_values(row: dict[str, object]) -> bool: + """Return whether a loop row has more than an identifier value.""" + return _loop_row_value_count(row) > 1 + + +def _loop_row_value_count(row: dict[str, object]) -> int: + """Return the number of populated cells in a loop row.""" + return sum( + 1 + for cell in row['cells'] + if not _is_empty_value(cell['value']) + ) + + +def _truncate_loop_rows( + rows: list[dict[str, object]], +) -> list[dict[str, object]]: + """Return display-truncated loop rows with an ellipsis row.""" + if len(rows) <= _REPORT_LOOP_DISPLAY_LIMIT: + return rows + + half_limit = _REPORT_LOOP_DISPLAY_LIMIT // 2 + return [ + *rows[:half_limit], + _ellipsis_loop_row(rows[0]), + *rows[-half_limit:], + ] + + +def _ellipsis_loop_row(reference_row: dict[str, object]) -> dict[str, object]: + """Return an ellipsis row matching a loop row shape.""" + cells = [{'value': '...', 'numeric': False, 'number': None}] + cells.extend( + {'value': '', 'numeric': False, 'number': None} + for _ in reference_row['cells'][1:] + ) + return {'cells': cells} + + +def _loop_table_width( + columns: list[dict[str, object]], + rows: list[dict[str, object]], +) -> str: + """Return the report width bucket for a loop table.""" + if _estimated_loop_table_chars(columns, rows) > _FULL_WIDTH_TABLE_CHAR_LIMIT: + return 'full' + return 'half' + + +def _estimated_loop_table_chars( + columns: list[dict[str, object]], + rows: list[dict[str, object]], +) -> int: + """Return an approximate monospace width for loop content.""" + widths = [] + for index, column in enumerate(columns): + label_width = len(_plain_table_text(_column_display_label(column))) + value_width = max( + ( + len(_plain_table_text(row['cells'][index]['value'])) + for row in rows + ), + default=0, + ) + widths.append(max(label_width, value_width) + 4) + return sum(widths) + + +def _column_display_label(column: dict[str, object]) -> str: + """Return the preferred display label for width estimation.""" + label = column.get('html_label') or column.get('label') or column.get('name') + units = column.get('html_units') or column.get('units') + if units: + return f'{label} ({units})' + return str(label) + + +def _plain_table_text(value: object) -> str: + """Return approximate plain text for table width estimates.""" + if value is None: + return '' + return str(value) + + def _collection_columns( category: CategoryCollection, items: list[object], @@ -671,11 +785,86 @@ def _mark_numeric_columns( column_values ) column['numeric'] = is_numeric + if is_numeric: + column['table_format'] = _siunitx_table_format(column_values) column.pop('numeric_candidate', None) for row in rows: row['cells'][index]['numeric'] = is_numeric +def _loop_colspec(columns: list[dict[str, object]]) -> str: + """Return a TeX tabular column spec for loop-category tables.""" + return ''.join(_loop_column_colspec(column) for column in columns) + + +def _key_value_colspec(rows: list[dict[str, object]]) -> str: + """Return a TeX tabular column spec for key-value tables.""" + if not _rows_have_numeric_values(rows): + return 'll' + values = [row['value'] for row in rows if row['numeric']] + return f'l{_numeric_colspec(values)}' + + +def _loop_column_colspec(column: dict[str, object]) -> str: + """Return one TeX tabular column spec.""" + if not column['numeric']: + return 'l' + table_format = column.get('table_format') + if table_format: + return _numeric_colspec_from_format(str(table_format)) + return _numeric_colspec([]) + + +def _numeric_colspec(values: Iterable[object]) -> str: + """Return one compact siunitx numeric column spec.""" + return _numeric_colspec_from_format(_siunitx_table_format(values)) + + +def _numeric_colspec_from_format(table_format: str) -> str: + """Return one siunitx column spec from a table-format value.""" + return f'S[table-format={table_format}]' + + +def _siunitx_table_format(values: Iterable[object]) -> str: + """Return a compact siunitx table-format for numeric values.""" + parts = [ + parts + for value in values + if not _is_empty_value(value) + for parts in [_siunitx_number_parts(value)] + if parts is not None + ] + if not parts: + return '1.0' + + sign = '+' if any(part['has_sign'] for part in parts) else '' + integer_digits = max(int(part['integer_digits']) for part in parts) + fraction_digits = max(int(part['fraction_digits']) for part in parts) + uncertainty_digits = max(int(part['uncertainty_digits']) for part in parts) + table_format = f'{sign}{integer_digits}.{fraction_digits}' + if uncertainty_digits: + table_format = f'{table_format}({uncertainty_digits})' + return table_format + + +def _siunitx_number_parts(value: object) -> dict[str, object] | None: + """Return table-format parts for one numeric value.""" + match = _NUMBER_PARTS_RE.match(_number_text(value)) + if match is None: + return None + + integer = match.group('integer') + leading_fraction = match.group('leading_fraction') + fraction = match.group('fraction') + uncertainty = match.group('uncertainty') or '' + return { + 'has_sign': bool(match.group('sign')), + 'integer_digits': len(integer or '0'), + 'fraction_digits': len(leading_fraction or fraction or ''), + 'uncertainty_digits': len(uncertainty.strip('()')), + } + + def _apply_row_number_alignment(rows: list[dict[str, object]]) -> None: """Add HTML decimal-alignment metadata to key-value rows.""" number_parts = [ diff --git a/src/easydiffraction/report/fit_plot.py b/src/easydiffraction/report/fit_plot.py index 935ba9537..a9d821af3 100644 --- a/src/easydiffraction/report/fit_plot.py +++ b/src/easydiffraction/report/fit_plot.py @@ -27,12 +27,12 @@ from easydiffraction.display.plotters.plotly import PLOTLY_HEIGHT_PER_UNIT from easydiffraction.display.plotters.plotly import RESIDUAL_LINE_WIDTH from easydiffraction.display.plotting import DEFAULT_RESIDUAL_HEIGHT_FRACTION +from easydiffraction.report.style import REPORT_AXIS_RGB +from easydiffraction.report.style import REPORT_CHART_GRID_RGB _COLOR_PATTERN = re.compile(r'rgb\((\d+),\s*(\d+),\s*(\d+)\)') _FIGURE_AXIS_WIDTH_CM = 12.0 _FIGURE_AXIS_HEIGHT_TO_WIDTH = 0.70 -_PLOTLY_GRID_RGB = '235,240,248' -_PLOTLY_AXIS_RGB = '217,223,228' _PGFPLOTS_MEASURED_MARKER_SIZE_PT = 0.75 _PGFPLOTS_MEASURED_MARKER_LINE_WIDTH_PT = 0.0 _STYLE_SOURCE_KEYS = { @@ -145,8 +145,8 @@ def fit_bragg_tick_styles() -> list[dict[str, str]]: def fit_plot_axis_styles() -> dict[str, str]: """Return Plotly-derived axis colors for report figures.""" return { - 'axis_rgb': _PLOTLY_AXIS_RGB, - 'grid_rgb': _PLOTLY_GRID_RGB, + 'axis_rgb': _style_rgb_channels(REPORT_AXIS_RGB), + 'grid_rgb': _style_rgb_channels(REPORT_CHART_GRID_RGB), } @@ -252,6 +252,11 @@ def _rgb_channels(color: str) -> str: return ','.join(match.groups()) +def _style_rgb_channels(rgb: tuple[int, int, int]) -> str: + """Return comma-separated channels for report style RGB colors.""" + return ','.join(str(channel) for channel in rgb) + + def _data_range(series_list: list[list[float]]) -> tuple[float, float]: values = [value for series in series_list for value in series] if not values: diff --git a/src/easydiffraction/report/html_renderer.py b/src/easydiffraction/report/html_renderer.py index 6c80f8b14..9e20bbdb4 100644 --- a/src/easydiffraction/report/html_renderer.py +++ b/src/easydiffraction/report/html_renderer.py @@ -17,6 +17,7 @@ from easydiffraction.display.plotters.plotly import PlotlyPlotter from easydiffraction.display.plotting import DEFAULT_BRAGG_ROW from easydiffraction.display.plotting import DEFAULT_RESID_HEIGHT +from easydiffraction.report.style import report_style_context _TEMPLATE_NAME = 'html/report.html.j2' _MATHJAX_FILENAME = 'mathjax-tex-mml-chtml.js' @@ -80,6 +81,7 @@ def render_html_report( """ template_context = dict(context) template_context['html_offline'] = offline + template_context['report_style'] = report_style_context() template_context['stylesheet'] = _stylesheet_text() template_context['fit_figures'] = _fit_figure_html_context( context, @@ -166,6 +168,7 @@ def _fit_figure_html_context( ) -> dict[str, str]: """Return fit figure HTML snippets by experiment id.""" include_plotlyjs: bool | str = True if offline else 'cdn' + report_style = report_style_context() rendered: dict[str, str] = {} for experiment in _experiment_contexts(context): fit_data = experiment.get('fit_data') @@ -176,6 +179,7 @@ def _fit_figure_html_context( rendered[experiment_id] = _figure_html( figure, include_plotlyjs=include_plotlyjs, + report_style=report_style, ) include_plotlyjs = False return rendered @@ -280,7 +284,12 @@ def _is_powder_bragg_context(experiment_type: object) -> bool: ) -def _figure_html(figure: object, *, include_plotlyjs: bool | str) -> str: +def _figure_html( + figure: object, + *, + include_plotlyjs: bool | str, + report_style: dict[str, object], +) -> str: """Return an HTML snippet for one figure-like object.""" to_html = getattr(figure, 'to_html', None) if callable(to_html): @@ -288,6 +297,8 @@ def _figure_html(figure: object, *, include_plotlyjs: bool | str) -> str: figure, include_plotlyjs=include_plotlyjs, force_template='plotly_white', + axis_frame_color=str(report_style['axis_hex']), + grid_color=str(report_style['chart_grid_hex']), ) return str(figure) diff --git a/src/easydiffraction/report/pdf_compiler.py b/src/easydiffraction/report/pdf_compiler.py index cd886a1f0..ee13b9120 100644 --- a/src/easydiffraction/report/pdf_compiler.py +++ b/src/easydiffraction/report/pdf_compiler.py @@ -25,7 +25,7 @@ # or any TeX Live distribution (latexmk / pdflatex) Then re-run project.save() with 'pdf' in project.report.formats or project.report.save_pdf(). -The .tex, data/, and styles/ bundle remains under reports/tex/.""" +The .tex and data/ bundle remains under reports/tex/.""" def save_pdf_report( @@ -135,7 +135,7 @@ def _figure_tex_paths(tex_path: pathlib.Path) -> list[pathlib.Path]: data_dir = tex_path.parent / 'data' if not data_dir.is_dir(): return [] - return sorted(data_dir.glob('fit_*.tex')) + return sorted(data_dir.glob('*.tex')) def _is_engine_runtime_failure( @@ -194,7 +194,7 @@ def _warn_engine_runtime_failure( details = '\n\n'.join(failures) msg = ( 'PDF skipped: the TeX engine failed before LaTeX compilation. ' - 'The .tex, data/, and styles/ bundle remains under reports/tex/.\n' + 'The .tex and data/ bundle remains under reports/tex/.\n' f'{details}' ) log.warning(msg) @@ -229,14 +229,9 @@ def _compile_command( def _compile_environment(tex_path: pathlib.Path) -> dict[str, str]: - """Return a TeX subprocess environment with vendored styles.""" - environment = os.environ.copy() - styles_dir = tex_path.parent / 'styles' - if not styles_dir.is_dir(): - styles_dir = tex_path.parent.parent / 'styles' - texinputs = environment.get('TEXINPUTS', '') - environment['TEXINPUTS'] = f'{styles_dir}{os.pathsep}{texinputs}' - return environment + """Return a TeX subprocess environment.""" + del tex_path + return os.environ.copy() def _compiler_error_message( diff --git a/src/easydiffraction/report/style.py b/src/easydiffraction/report/style.py new file mode 100644 index 000000000..01327ed64 --- /dev/null +++ b/src/easydiffraction/report/style.py @@ -0,0 +1,42 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Shared visual constants for generated reports.""" + +from __future__ import annotations + +REPORT_AXIS_RGB = (190, 199, 208) +REPORT_TABLE_INNER_RGB = (217, 223, 228) +REPORT_CHART_GRID_RGB = (235, 240, 248) +REPORT_ROW_RGB = (235, 240, 248) +REPORT_SUBTITLE = 'EasyDiffraction Report' +REPORT_HTML_FONT_FAMILY = '"PT Sans", "Trebuchet MS", "Segoe UI", sans-serif' +REPORT_TEX_FONT_FAMILY = 'Nunito' +REPORT_TEX_MATH_FONT_FAMILY = 'Fira Math' + + +def report_style_context() -> dict[str, object]: + """Return shared visual constants for report templates.""" + return { + 'axis_hex': _rgb_hex(REPORT_AXIS_RGB), + 'axis_rgb': _rgb_channels(REPORT_AXIS_RGB), + 'grid_hex': _rgb_hex(REPORT_TABLE_INNER_RGB), + 'grid_rgb': _rgb_channels(REPORT_TABLE_INNER_RGB), + 'chart_grid_hex': _rgb_hex(REPORT_CHART_GRID_RGB), + 'chart_grid_rgb': _rgb_channels(REPORT_CHART_GRID_RGB), + 'row_hex': _rgb_hex(REPORT_ROW_RGB), + 'row_rgb': _rgb_channels(REPORT_ROW_RGB), + 'html_font_family': REPORT_HTML_FONT_FAMILY, + 'tex_font_family': REPORT_TEX_FONT_FAMILY, + 'tex_math_font_family': REPORT_TEX_MATH_FONT_FAMILY, + 'subtitle': REPORT_SUBTITLE, + } + + +def _rgb_hex(rgb: tuple[int, int, int]) -> str: + """Return CSS hex notation for an RGB triple.""" + return f'#{rgb[0]:02x}{rgb[1]:02x}{rgb[2]:02x}' + + +def _rgb_channels(rgb: tuple[int, int, int]) -> str: + """Return comma-separated channels for TeX RGB definitions.""" + return ','.join(str(channel) for channel in rgb) diff --git a/src/easydiffraction/report/templates/html/report.html.j2 b/src/easydiffraction/report/templates/html/report.html.j2 index 87c140329..d60052ea2 100644 --- a/src/easydiffraction/report/templates/html/report.html.j2 +++ b/src/easydiffraction/report/templates/html/report.html.j2 @@ -20,8 +20,21 @@ - {{ project.title or project.name or "EasyDiffraction report" }} - + {{ project.title or project.name or "EasyDiffraction Report" }} + {% if not html_offline %} + + + + {% endif %} + {% if html_offline %} {% else %} @@ -29,14 +42,15 @@ {% endif %} -
-
-

{{ project.title or project.name or "EasyDiffraction report" }}

+
+
+
{{ report_style.subtitle }}
+
{{ project.title or project.name or "EasyDiffraction Report" }}
{% if project.description %} -
-

Abstract

+
+

Project Description

{{ project.description }}

{% endif %} @@ -101,7 +115,13 @@

Refinement

- +
+ + + + + + {% for row in refinement.rows %} @@ -147,7 +167,7 @@ {% endif %} {% if category.rows %}
-
QuantityValue
+ {% for column in category.columns %} @@ -212,7 +232,7 @@ {% endif %} {% if category.rows %}
-
+ {% for column in category.columns %} diff --git a/src/easydiffraction/report/templates/html/style.css b/src/easydiffraction/report/templates/html/style.css index c70fef813..e5f6951b8 100644 --- a/src/easydiffraction/report/templates/html/style.css +++ b/src/easydiffraction/report/templates/html/style.css @@ -1,72 +1,102 @@ :root { color-scheme: light; --paper: #ffffff; - --ink: #20242a; - --muted: #5f6874; - --rule: #20242a; - --row-shade: #f8f9fa; + --page-bg: #8a8a8a; + --ink: #000000; --link: #245a9b; + --rowshade: var(--ed-row-color); + --tableborder: var(--ed-axis-color); + --bw: 0.4pt; + --body-font: "Utopia", "Charter", "Bitstream Charter", Georgia, + "Times New Roman", serif; + --head-font: var(--ed-font-family); + --colsep: 6pt; + --wide-colsep: 3pt; + --lead: 20.4pt; } * { box-sizing: border-box; } +html { + background: var(--page-bg); +} + body { margin: 0; - background: #f3f4f6; + padding: 24px 0; + background: var(--page-bg); color: var(--ink); - font: 15px/1.55 "Helvetica Neue", Arial, sans-serif; + font-family: var(--body-font); + font-size: 10.04pt; + font-variant-numeric: lining-nums; + line-height: var(--lead); } .report-shell { - width: min(960px, calc(100% - 32px)); + width: 21.59cm; + min-height: 27.94cm; margin: 0 auto; - padding: 42px 52px 64px; + padding: 2.5cm; background: var(--paper); + box-shadow: 0 2px 14px rgb(0 0 0 / 45%); counter-reset: report-section; + display: flex; + flex-direction: column; + align-items: stretch; +} + +.title, +h2, +h3, +h4 { + margin: 0; + font-family: var(--head-font); + font-weight: 400; + line-height: 1.2; } .report-title { - margin-bottom: 28px; - text-align: center; + margin: 0 0 22.8pt; + text-align: left; } -h1, -h2, -h3, -h4, -p { - margin-top: 0; +.report-title .t1 { + margin: 0 0 14pt; + font-size: 14.35pt; + line-height: 1.15; } -h1 { - margin-bottom: 0; - font-size: 2rem; - font-weight: 400; - line-height: 1.2; +.report-title .t2 { + margin: 0; + font-size: 17.22pt; + line-height: 1.15; } h2 { - margin: 30px 0 12px; - font-size: 1.35rem; - font-weight: 400; + margin: 16.8pt 0 7pt; + font-size: 14.35pt; } h3 { - margin: 22px 0 10px; - font-size: 1.1rem; - font-weight: 400; + margin: 17.5pt 0 10.8pt; + font-size: 11.96pt; } h4 { - margin: 18px 0 8px; - font-size: 0.98rem; - font-weight: 400; + margin: 13.2pt 0 6.2pt; + font-size: 10.91pt; } -section { - margin-top: 24px; +p { + margin: 11.9pt 0 20.4pt; + hyphens: auto; + text-align: justify; +} + +.description-section h2 { + margin-top: 0; } .numbered-section { @@ -75,25 +105,17 @@ section { } .numbered-section > h2::before { - content: counter(report-section) " "; + margin-right: 1em; + content: counter(report-section) "."; } .numbered-section .record { counter-increment: report-subsection; - counter-reset: report-subsubsection; } .numbered-section .record > h3::before { - content: counter(report-section) "." counter(report-subsection) " "; -} - -.numbered-section .record > h4 { - counter-increment: report-subsubsection; -} - -.numbered-section .record > h4::before { - content: counter(report-section) "." counter(report-subsection) "." - counter(report-subsubsection) " "; + margin-right: 1em; + content: counter(report-section) "." counter(report-subsection) "."; } .record + .record { @@ -101,40 +123,51 @@ section { } table { - width: max-content; + width: auto; max-width: 100%; - margin: 8px 0 16px; + margin: 1.55pt 0 9.5pt; + border: var(--bw) solid var(--tableborder); border-collapse: collapse; - border-top: 2px solid var(--rule); - border-bottom: 2px solid var(--rule); - font-size: 0.94rem; + align-self: flex-start; + font-variant-numeric: lining-nums tabular-nums; +} + +thead tr { + background: var(--rowshade); +} + +tbody tr:nth-child(even) { + background: var(--rowshade); +} + +tbody tr:nth-child(odd) { + background: var(--paper); } thead th { - border-bottom: 1px solid var(--rule); + border-bottom: var(--bw) solid var(--tableborder); } th, td { - padding: 6px 9px; + padding: 0 var(--colsep); text-align: left; - vertical-align: top; -} - -th { + vertical-align: baseline; font-weight: 400; -} - -tbody tr:nth-child(odd) { - background: var(--row-shade); + line-height: var(--lead); + white-space: nowrap; } .numeric, .numeric-header { - font-variant-numeric: tabular-nums; + font-variant-numeric: lining-nums tabular-nums; text-align: right; } +.numeric-header { + text-align: center; +} + .number { display: inline-grid; grid-template-columns: @@ -166,17 +199,31 @@ tbody tr:nth-child(odd) { } .table-scroll { - width: fit-content; + width: 100%; max-width: 100%; + margin: 1.55pt 0 9.5pt; overflow-x: auto; } .table-scroll table { + margin: 0; +} + +.wide-table { + width: max-content; max-width: none; } +.wide-table th, +.wide-table td { + padding-right: var(--wide-colsep); + padding-left: var(--wide-colsep); +} + .figure { - margin: 8px 0 18px; + align-self: stretch; + margin: 0 0 11.3pt; + text-align: center; } .figure .plotly-graph-div { @@ -187,17 +234,29 @@ a { color: var(--link); } +sub, +sup { + font-variant-numeric: normal; +} + @media (max-width: 700px) { + html, + body { + background: var(--paper); + } + body { - font-size: 14px; + padding: 0; } .report-shell { width: 100%; + min-height: 0; padding: 24px 16px 42px; + box-shadow: none; } - h1 { - font-size: 1.55rem; + .table-scroll { + overflow-x: auto; } } diff --git a/src/easydiffraction/report/templates/tex/figure.tex.j2 b/src/easydiffraction/report/templates/tex/figure.tex.j2 index 792bc18b0..85e4dea98 100644 --- a/src/easydiffraction/report/templates/tex/figure.tex.j2 +++ b/src/easydiffraction/report/templates/tex/figure.tex.j2 @@ -1,4 +1,9 @@ \documentclass[border=2pt]{standalone} +\usepackage{fourier} +\let\utopiarm\rmdefault +\usepackage{paratype} +\let\rmdefault\utopiarm +\renewcommand{\familydefault}{\rmdefault} \usepackage{pgfplots} \usepgfplotslibrary{groupplots} \pgfplotsset{compat=1.18} @@ -9,17 +14,17 @@ {% for style in bragg_styles %} \definecolor{ {{- style.color_name -}} }{RGB}{ {{- style.rgb -}} } {% endfor %} -\definecolor{ed_axis}{RGB}{ {{- axis_styles.axis_rgb -}} } -\definecolor{ed_grid}{RGB}{ {{- axis_styles.grid_rgb -}} } +\definecolor{edAxis}{RGB}{ {{- axis_styles.axis_rgb -}} } +\definecolor{edGrid}{RGB}{ {{- axis_styles.grid_rgb -}} } {% set axes_labels = fit_data.axes_labels | default(["", "Intensity"], true) -%} {% set x_label = axes_labels[0] | tex_axis_label -%} {% set y_label = axes_labels[1] | tex_axis_label -%} -{% set bragg_tick_sets = fit_data.bragg_tick_sets | default([], true) -%} +{% set bragg_tick_sources = bragg_tick_sources | default([], true) -%} \begin{document} \begin{tikzpicture} \begin{groupplot}[ group style={ -group size=1 by {% if bragg_tick_sets %}3{% else %}2{% endif %}, +group size=1 by {% if bragg_tick_sources %}3{% else %}2{% endif %}, vertical sep={{ geometry.vertical_sep_cm | tex_number }}cm, x descriptions at=edge bottom, }, @@ -28,11 +33,11 @@ scale only axis, xmin={{ ranges.x_min | tex_number }}, xmax={{ ranges.x_max | tex_number }}, mark layer=like plot, -axis line style={draw=ed_axis}, +axis line style={draw=edAxis}, tick style={draw=none}, label style={font=\footnotesize}, tick label style={font=\footnotesize}, -major grid style={draw=ed_grid, line width=0.4pt}, +major grid style={draw=edGrid, line width=0.4pt}, xmajorgrids=true, ] \nextgroupplot[ @@ -45,37 +50,33 @@ ylabel={ {{ y_label }} }, legend pos=north east, legend style={draw=none, fill=white, fill opacity=0.5, text opacity=1, font=\footnotesize}, ] -\addplot+[mark=*, mark size={{ styles.meas.marker_size_pt | tex_number }}pt, color={{ styles.meas.color_name }}, line width={{ styles.meas.line_width_pt | tex_number }}pt, line join=bevel, mark options={fill={{ styles.meas.color_name }}, draw={{ styles.meas.color_name }}, fill opacity=1, draw opacity=1, line width={{ styles.meas.marker_line_width_pt | tex_number }}pt}] table[x=x, y=meas, col sep=comma] { {{- csv_filename -}} }; +\addplot+[mark=*, mark size={{ styles.meas.marker_size_pt | tex_number }}pt, color={{ styles.meas.color_name }}, line width={{ styles.meas.line_width_pt | tex_number }}pt, line join=bevel, mark options={fill={{ styles.meas.color_name }}, draw={{ styles.meas.color_name }}, fill opacity=1, draw opacity=1, line width={{ styles.meas.marker_line_width_pt | tex_number }}pt}] table[x={ {{- fit_csv.x -}} }, y={ {{- fit_csv.meas -}} }, col sep=comma] { {{- csv_filename -}} }; \addlegendentry{ {{- styles.meas.name | tex -}} } -\addplot+[color={{ styles.calc.color_name }}, line width={{ styles.calc.line_width_pt | tex_number }}pt, line join=bevel, no markers] table[x=x, y=calc, col sep=comma] { {{- csv_filename -}} }; +\addplot+[color={{ styles.calc.color_name }}, line width={{ styles.calc.line_width_pt | tex_number }}pt, line join=bevel, no markers] table[x={ {{- fit_csv.x -}} }, y={ {{- fit_csv.calc -}} }, col sep=comma] { {{- csv_filename -}} }; \addlegendentry{ {{- styles.calc.name | tex -}} } \addlegendimage{color={{ styles.diff.color_name }}, line width={{ styles.diff.line_width_pt | tex_number }}pt, line join=bevel, no markers} \addlegendentry{ {{- styles.diff.name | tex -}} } -{% for tick_set in bragg_tick_sets %} +{% for tick_set in bragg_tick_sources %} {% set tick_style = bragg_styles[loop.index0 % (bragg_styles | length)] -%} \addlegendimage{color={{ tick_style.color_name }}, only marks, mark=|, mark size=5pt} \addlegendentry{Bragg peaks: {{ tick_set.phase_id | tex }}} {% endfor %} -{% if bragg_tick_sets %} +{% if bragg_tick_sources %} \nextgroupplot[ height={{ geometry.bragg_height_cm | tex_number }}cm, ymin=0.5, -ymax={{ (bragg_tick_sets | length) + 0.5 }}, +ymax={{ (bragg_tick_sources | length) + 0.5 }}, y dir=reverse, -ytick={{ "{" }}{% for tick_set in bragg_tick_sets %}{{ loop.index }}{% if not loop.last %},{% endif %}{% endfor %}{{ "}" }}, -yticklabels={{ "{" }}{% for tick_set in bragg_tick_sets %}{{ "{" }}{{ tick_set.phase_id | tex }}{{ "}" }}{% if not loop.last %},{% endif %}{% endfor %}{{ "}" }}, +ytick={{ "{" }}{% for tick_set in bragg_tick_sources %}{{ loop.index }}{% if not loop.last %},{% endif %}{% endfor %}{{ "}" }}, +yticklabels={{ "{" }}{% for tick_set in bragg_tick_sources %}{{ "{" }}{{ tick_set.phase_id | tex }}{{ "}" }}{% if not loop.last %},{% endif %}{% endfor %}{{ "}" }}, yticklabel style={font=\footnotesize}, ymajorgrids=false, xticklabels=\empty, ] -{% for tick_set in bragg_tick_sets %} +{% for tick_set in bragg_tick_sources %} {% set bragg_row = loop.index -%} {% set tick_style = bragg_styles[loop.index0 % (bragg_styles | length)] -%} -\addplot+[color={{ tick_style.color_name }}, only marks, mark=|, mark size=5pt] coordinates { -{% for x_value in tick_set.x %} -({{ x_value | tex_number }},{{ bragg_row }}) -{% endfor %} -}; +\addplot+[color={{ tick_style.color_name }}, only marks, mark=|, mark size=5pt] table[x={ {{- tick_set.x_column -}} }, y expr={{ bragg_row }}, col sep=comma] { {{- tick_set.csv_filename -}} }; {% endfor %} {% endif %} \nextgroupplot[ @@ -87,7 +88,7 @@ ymajorgrids=true, xlabel={ {{ x_label }} }, yticklabel style={font=\footnotesize}, ] -\addplot+[color={{ styles.diff.color_name }}, line width={{ styles.diff.line_width_pt | tex_number }}pt, line join=bevel, no markers] table[x=x, y=diff, col sep=comma] { {{- csv_filename -}} }; +\addplot+[color={{ styles.diff.color_name }}, line width={{ styles.diff.line_width_pt | tex_number }}pt, line join=bevel, no markers] table[x={ {{- fit_csv.x -}} }, y expr=\thisrow{ {{- fit_csv.meas -}} }-\thisrow{ {{- fit_csv.calc -}} }, col sep=comma] { {{- csv_filename -}} }; \end{groupplot} \end{tikzpicture} \end{document} diff --git a/src/easydiffraction/report/templates/tex/report.tex.j2 b/src/easydiffraction/report/templates/tex/report.tex.j2 index d03191d78..ddd38dece 100644 --- a/src/easydiffraction/report/templates/tex/report.tex.j2 +++ b/src/easydiffraction/report/templates/tex/report.tex.j2 @@ -1,36 +1,67 @@ -\PassOptionsToPackage{table}{xcolor} -\documentclass[11pt,a4paper]{styles/iucrjournals} -\usepackage{graphicx} -\usepackage{siunitx} -\sisetup{detect-all, group-digits=false} -\definecolor{edTableStripe}{RGB}{248,249,250} -\newcommand{\edKeyValueRows}{\rowcolors{1}{edTableStripe}{white}} -\newcommand{\edHeaderRows}{\rowcolors{2}{edTableStripe}{white}} -\makeatletter -\let\title\origtitle -\renewcommand\section{\@startsection {section}{1}{\z@}% - {-3.5ex \@plus -1ex \@minus -.2ex}% - {2.3ex \@plus .2ex}% - {\normalfont\Large}} -\renewcommand\subsection{\@startsection{subsection}{2}{\z@}% - {-3.25ex\@plus -1ex \@minus -.2ex}% - {1.5ex \@plus .2ex}% - {\normalfont\large}} -\renewcommand\subsubsection{\@startsection{subsubsection}{3}{\z@}% - {-3.25ex\@plus -1ex \@minus -.2ex}% - {1.5ex \@plus .2ex}% - {\normalfont\normalsize}} -\renewenvironment{abstract} - {\begin{center} - \abstractname\vspace{-.5em}\vspace{0pt} - \end{center} - \list{}{ - \setlength{\leftmargin}{1cm}% - \setlength{\rightmargin}{\leftmargin}% - }% - \item\relax} - {\endlist\bigskip} -\makeatother +% ==================================================================== +% EasyDiffraction report -- auto-generated LaTeX source +% +% The document layout mirrors tmp/latex/table-style.tex: bundled +% Utopia/Fourier body text, PT Sans headings, numbered sections, +% shaded tables, and fit figures placed exactly where declared. +% ==================================================================== + +\documentclass[11pt]{article} +\usepackage[margin=2.5cm]{geometry} + +% -------------------------------------------------------------------- +% Packages +% -------------------------------------------------------------------- +\usepackage{fourier} % Utopia text + matching Fourier math +\let\utopiarm\rmdefault % save Utopia's roman code +\usepackage{graphicx} % \includegraphics for fit-quality figures +\usepackage{adjustbox} % max-width tables without upscaling +\usepackage{siunitx} % S columns: align numeric data on decimal +\usepackage{float} % [H] float placement +\usepackage[table]{xcolor} % row shading and rule colours +\usepackage{titlesec} % heading numbering, spacing and fonts +\usepackage{paratype} % PT Sans headings; bundled and portable + +% -------------------------------------------------------------------- +% Line and paragraph spacing +% -------------------------------------------------------------------- +\linespread{1.5} +\setlength{\parindent}{0pt} +\setlength{\parskip}{1\baselineskip} +\setlength{\intextsep}{0pt} + +% -------------------------------------------------------------------- +% Tables: number format, colours, row shading +% -------------------------------------------------------------------- +\sisetup{group-digits=false} + +\definecolor{rowshade}{RGB}{ {{- report_style.row_rgb -}} } +\definecolor{tableborder}{RGB}{ {{- report_style.axis_rgb -}} } +\arrayrulecolor{tableborder} + +\newcommand{\rowColorsWithHeader}{\rowcolors{1}{rowshade}{white}} +\newcommand{\rowColorsWithoutHeader}{\rowcolors{1}{white}{rowshade}} +\newlength{\ReportTableColSep} +\setlength{\ReportTableColSep}{0.5em} +\newlength{\ReportWideTableColSep} +\setlength{\ReportWideTableColSep}{0.5em} +\setlength{\tabcolsep}{\ReportTableColSep} + +% -------------------------------------------------------------------- +% Section headings: numbering dot, spacing, font +% -------------------------------------------------------------------- +\titlelabel{\thetitle.\quad} + +\titlespacing*{\section} {0pt}{3.5ex plus 1ex minus .2ex}{0.5ex plus .2ex} +\titlespacing*{\subsection} {0pt}{3.25ex plus 1ex minus .2ex}{0.5ex plus .2ex} +\titlespacing*{\subsubsection}{0pt}{3.25ex plus 1ex minus .2ex}{0.5ex plus .2ex} + +\let\rmdefault\utopiarm +\renewcommand{\familydefault}{\rmdefault} +\newcommand{\HeadFont}{\sffamily} +\titleformat*{\section} {\normalfont\Large\HeadFont} +\titleformat*{\subsection} {\normalfont\large\HeadFont} +\titleformat*{\subsubsection}{\normalfont\normalsize\HeadFont} {% macro tex_field_header(field, fallback) -%} {% set label = (field.label or fallback) | tex_markup -%} {% set units = field.units | tex_unit -%} @@ -44,226 +75,237 @@ {% macro tex_category_header(column) -%} {% set label = (column.latex_label or column.name) | tex_markup -%} {% set units = column.latex_units | tex_unit -%} -{% set header %}{{ label }}{% if column.latex_units %} ({{ units }}){% endif %}{% endset -%} -{% if column.numeric %}\multicolumn{1}{c}{ {{- header -}} }{% else %}\multicolumn{1}{l}{ {{- header -}} }{% endif %} +{{ label }}{% if column.latex_units %} ({{ units }}){% endif %} +{%- endmacro %} +{% macro tex_numeric_header(column, first, last) -%} +\multicolumn{{ "{" }}1{{ "}" }}{{ "{" }}{% if first %}|{% endif %}c{% if last %}|{% endif %}{{ "}" }}{{ "{" }}{{ tex_category_header(column) }}{{ "}" }} {%- endmacro %} {% macro tex_category_cell(cell) -%} {% if cell.numeric %}{{ cell.value | tex_number }}{% else %}{{ cell.value | tex }}{% endif %} {%- endmacro %} {% macro tex_category_value(row, numeric_column) -%} -{% if row.numeric %}{{ row.value | tex_number }}{% elif numeric_column %}\multicolumn{1}{l}{ {{- row.value | tex -}} }{% else %}{{ row.value | tex }}{% endif %} +{% if row.numeric %}{{ row.value | tex_number }}{% elif numeric_column %}\multicolumn{1}{l|}{ {{- row.value | tex -}} }{% else %}{{ row.value | tex }}{% endif %} {%- endmacro %} -%---------------------------------------------------------- -% Title -%---------------------------------------------------------- -\title{ {{- (project.title or project.name or "EasyDiffraction report") | tex -}} } -\author{} - -%---------------------------------------------------------- -% Begin document -%---------------------------------------------------------- \begin{document} -\nolinenumbers -\maketitle + +% ==================================================================== +% Title block -- hand-set, not a \section. +% ==================================================================== +\begin{flushleft} +\HeadFont +{\Large {{ report_style.subtitle | tex }}\newline} +{\LARGE {{ (project.title or project.name or "EasyDiffraction report") | tex }}\par} +\end{flushleft} {% if project.description %} -\begin{abstract} +% -------------------------------------------------------------------- +% Project Description +% -------------------------------------------------------------------- +\section*{Project Description} {{ project.description | tex }} -\end{abstract} {% endif %} -%---------------------------------------------------------- -% Section -%---------------------------------------------------------- +% ==================================================================== +% Project Summary +% ==================================================================== \section{Project Summary} \begin{table}[H] -\edKeyValueRows -\begin{tabular}{ll} -\toprule +\rowColorsWithoutHeader +\begin{tabular}{|ll|} +\hline Short name & {{ project.name | tex }} \\ Title & {{ project.title | tex }} \\ Structures & {{ project.n_phases }} \\ Experiments & {{ project.n_experiments }} \\ Generated & {{ metadata.generated_at | tex }} \\ -\bottomrule +\hline \end{tabular} \end{table} -%---------------------------------------------------------- -% Section -%---------------------------------------------------------- +% ==================================================================== +% Software +% ==================================================================== \section{Software} \begin{table}[H] -\edHeaderRows -\begin{tabular}{lll} -\toprule +\rowColorsWithHeader +\begin{tabular}{|lll|} +\hline Role & Name & Version \\ -\midrule +\hline Framework & {{ software.framework.name | tex }} & {{ software.framework.version | tex }} \\ Calculator & {{ software.calculator.name | tex }} & {{ software.calculator.version | tex }} \\ Minimizer & {{ software.minimizer.name | tex }} & {{ software.minimizer.version | tex }} \\ -\bottomrule +\hline \end{tabular} \end{table} -%---------------------------------------------------------- -% Section -%---------------------------------------------------------- +% ==================================================================== +% Refinement +% ==================================================================== \section{Refinement} \begin{table}[H] -\edHeaderRows -\begin{tabular}{lS} -\toprule -\multicolumn{1}{l}{Quantity} & \multicolumn{1}{c}{Value} \\ -\midrule -Reduced chi-square & {{ refinement.fit_result.reduced_chi_square | tex_number }} \\ -Free parameters & {{ refinement.parameters.free | tex_number }} \\ -Total parameters & {{ refinement.parameters.total | tex_number }} \\ -Constraints & {{ refinement.constraints | tex_number }} \\ -R factor & {{ refinement.fit_result.r_factor_all | tex_number }} \\ -Weighted R factor & {{ refinement.fit_result.wr_factor_all | tex_number }} \\ -\bottomrule +\rowColorsWithHeader +\begin{tabular}{|{{ refinement.colspec }}|} +\hline +Quantity & {Value} \\ +\hline +{% for row in refinement.rows %} +{{ row.label | tex }} & {% if row.numeric %}{{ row.value | tex_number }}{% else %}\multicolumn{1}{l|}{ {{- row.value | tex -}} }{% endif %} \\ +{% endfor %} +\hline \end{tabular} \end{table} -%---------------------------------------------------------- -% Section -%---------------------------------------------------------- +% ==================================================================== +% Structures +% ==================================================================== \section{Structures} {% for structure in structures %} -%---------- -% Structure -%---------- +% -------------------------------------------------------------------- +% Structure: {{ structure.id | tex }} +% -------------------------------------------------------------------- \subsection{ {{- structure.id | tex -}} } {% for category in structure.categories %} -%------------- -\subsubsection{ {{- category.title | tex -}} } +\subsubsection*{ {{- category.title | tex -}} } {% if category.kind == "item" %} \begin{table}[H] -\edKeyValueRows -\begin{tabular}{l{% if category.has_numeric_values %}S{% else %}l{% endif %}} -\toprule +\rowColorsWithoutHeader +\begin{tabular}{|{{ category.colspec }}|} +\hline {% for row in category.rows %} {{ tex_category_label(row) }} & {{ tex_category_value(row, category.has_numeric_values) }} \\ {% endfor %} -\bottomrule +\hline \end{tabular} \end{table} {% else %} - {% if category.scalar_rows %} \begin{table}[H] -\edKeyValueRows -\begin{tabular}{l{% if category.scalar_has_numeric_values %}S{% else %}l{% endif %}} -\toprule +\rowColorsWithoutHeader +\begin{tabular}{|{{ category.scalar_colspec }}|} +\hline {% for row in category.scalar_rows %} {{ tex_category_label(row) }} & {{ tex_category_value(row, category.scalar_has_numeric_values) }} \\ {% endfor %} -\bottomrule +\hline \end{tabular} \end{table} {% endif %} - {% if category.rows %} \begin{table}[H] -\edHeaderRows -\resizebox{\ifdim\width>\linewidth\linewidth\else\width\fi}{!}{ -\begin{tabular}{ {{- category.colspec -}} } -\toprule -{% for column in category.columns %}{{ tex_category_header(column) }}{% if not loop.last %} & {% endif %}{% endfor %} \\ -\midrule +\rowColorsWithHeader +{% if category.table_width == "full" %} +\begingroup +\setlength{\tabcolsep}{\ReportWideTableColSep} +\begin{adjustbox}{max width=\linewidth} +\begin{tabular}{|{{ category.colspec }}|} +{% else %} +\begin{tabular}{|{{ category.colspec }}|} +{% endif %} +\hline +{% for column in category.columns %}{% if column.numeric %}{{ tex_numeric_header(column, loop.first, loop.last) }}{% else %}{{ tex_category_header(column) }}{% endif %}{% if not loop.last %} & {% endif %}{% endfor %} \\ +\hline {% for row in category.rows %} {% for cell in row.cells %}{{ tex_category_cell(cell) }}{% if not loop.last %} & {% endif %}{% endfor %} \\ {% endfor %} -\bottomrule +\hline +{% if category.table_width == "full" %} \end{tabular} -} +\end{adjustbox} +\endgroup +{% else %} +\end{tabular} +{% endif %} \end{table} {% endif %} {% endif %} {% endfor %} {% endfor %} -%---------------------------------------------------------- -% Section -%---------------------------------------------------------- +% ==================================================================== +% Experiments +% ==================================================================== \section{Experiments} {% for experiment in experiments %} -%----------- -% Experiment -%----------- +% -------------------------------------------------------------------- +% Experiment: {{ experiment.id | tex }} +% -------------------------------------------------------------------- \subsection{ {{- experiment.id | tex -}} } {% if experiment.fit_data and tex.fit_figure_paths[experiment.id] %} -%------------- -\subsubsection{Fit quality} +\subsubsection*{Fit quality} \begin{figure}[H] \centering +Measured and calculated fit for {{ experiment.id | tex }}. \includegraphics[width=\linewidth]{ {{- tex.fit_figure_paths[experiment.id] -}} } -\caption{Measured and calculated fit for {{ experiment.id | tex }}.} \end{figure} {% endif %} {% for category in experiment.categories %} -%------------- -\subsubsection{ {{- category.title | tex -}} } +\subsubsection*{ {{- category.title | tex -}} } {% if category.kind == "item" %} \begin{table}[H] -\edKeyValueRows -\begin{tabular}{l{% if category.has_numeric_values %}S{% else %}l{% endif %}} -\toprule +\rowColorsWithoutHeader +\begin{tabular}{|{{ category.colspec }}|} +\hline {% for row in category.rows %} {{ tex_category_label(row) }} & {{ tex_category_value(row, category.has_numeric_values) }} \\ {% endfor %} -\bottomrule +\hline \end{tabular} \end{table} {% else %} - {% if category.scalar_rows %} \begin{table}[H] -\edKeyValueRows -\begin{tabular}{l{% if category.scalar_has_numeric_values %}S{% else %}l{% endif %}} -\toprule +\rowColorsWithoutHeader +\begin{tabular}{|{{ category.scalar_colspec }}|} +\hline {% for row in category.scalar_rows %} {{ tex_category_label(row) }} & {{ tex_category_value(row, category.scalar_has_numeric_values) }} \\ {% endfor %} -\bottomrule +\hline \end{tabular} \end{table} {% endif %} - {% if category.rows %} \begin{table}[H] -\edHeaderRows -\resizebox{\ifdim\width>\linewidth\linewidth\else\width\fi}{!}{ -\begin{tabular}{ {{- category.colspec -}} } -\toprule -{% for column in category.columns %}{{ tex_category_header(column) }}{% if not loop.last %} & {% endif %}{% endfor %} \\ -\midrule +\rowColorsWithHeader +{% if category.table_width == "full" %} +\begingroup +\setlength{\tabcolsep}{\ReportWideTableColSep} +\begin{adjustbox}{max width=\linewidth} +\begin{tabular}{|{{ category.colspec }}|} +{% else %} +\begin{tabular}{|{{ category.colspec }}|} +{% endif %} +\hline +{% for column in category.columns %}{% if column.numeric %}{{ tex_numeric_header(column, loop.first, loop.last) }}{% else %}{{ tex_category_header(column) }}{% endif %}{% if not loop.last %} & {% endif %}{% endfor %} \\ +\hline {% for row in category.rows %} {% for cell in row.cells %}{{ tex_category_cell(cell) }}{% if not loop.last %} & {% endif %}{% endfor %} \\ {% endfor %} -\bottomrule +\hline +{% if category.table_width == "full" %} \end{tabular} -} +\end{adjustbox} +\endgroup +{% else %} +\end{tabular} +{% endif %} \end{table} {% endif %} {% endif %} {% endfor %} {% endfor %} -%---------------------------------------------------------- -% End document -%---------------------------------------------------------- \end{document} diff --git a/src/easydiffraction/report/templates/tex/styles/LICENSES.md b/src/easydiffraction/report/templates/tex/styles/LICENSES.md deleted file mode 100644 index 91848b5de..000000000 --- a/src/easydiffraction/report/templates/tex/styles/LICENSES.md +++ /dev/null @@ -1,142 +0,0 @@ -# Vendored LaTeX Style Licenses - -This file records the third-party licensing for the LaTeX style files -vendored in this directory. The repository-level index is -`THIRD_PARTY_LICENSES.md`. - -## IUCr LaTeX Template Files - -- Upstream URL: https://www.iucr.org/resources/cif/software/latex -- Local source used for vendoring: `tmp/latex/iucrtemplate/` -- License: CC0 1.0 Universal - -| File | Version | Attribution | -| --- | --- | --- | -| `iucrjournals.cls` | No version declared | International Union of Crystallography LaTeX journal class | -| `harvard.sty` | 2.0.5 | Peter Williams, 1994 | - -## CC0 1.0 Universal - -```text -Creative Commons Legal Code - -CC0 1.0 Universal - - CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE - LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN - ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS - INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES - REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS - PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM - THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED - HEREUNDER. - -Statement of Purpose - -The laws of most jurisdictions throughout the world automatically confer -exclusive Copyright and Related Rights (defined below) upon the creator -and subsequent owner(s) (each and all, an "owner") of an original work of -authorship and/or a database (each, a "Work"). - -Certain owners wish to permanently relinquish those rights to a Work for -the purpose of contributing to a commons of creative, cultural and -scientific works ("Commons") that the public can reliably and without fear -of later claims of infringement build upon, modify, incorporate in other -works, reuse and redistribute as freely as possible in any form whatsoever -and for any purposes, including without limitation commercial purposes. -These owners may contribute to the Commons to promote the ideal of a free -culture and the further production of creative, cultural and scientific -works, or to gain reputation or greater distribution for their Work in -part through the use and efforts of others. - -For these and/or other purposes and motivations, and without any -expectation of additional consideration or compensation, the person -associating CC0 with a Work (the "Affirmer"), to the extent that he or she -is an owner of Copyright and Related Rights in the Work, voluntarily -elects to apply CC0 to the Work and publicly distribute the Work under its -terms, with knowledge of his or her Copyright and Related Rights in the -Work and the meaning and intended legal effect of CC0 on those rights. - -1. Copyright and Related Rights. A Work made available under CC0 may be -protected by copyright and related or neighboring rights ("Copyright and -Related Rights"). Copyright and Related Rights include, but are not -limited to, the following: - - i. the right to reproduce, adapt, distribute, perform, display, - communicate, and translate a Work; - ii. moral rights retained by the original author(s) and/or performer(s); -iii. publicity and privacy rights pertaining to a person's image or - likeness depicted in a Work; - iv. rights protecting against unfair competition in regards to a Work, - subject to the limitations in paragraph 4(a), below; - v. rights protecting the extraction, dissemination, use and reuse of data - in a Work; - vi. database rights (such as those arising under Directive 96/9/EC of the - European Parliament and of the Council of 11 March 1996 on the legal - protection of databases, and under any national implementation - thereof, including any amended or successor version of such - directive); and -vii. other similar, equivalent or corresponding rights throughout the - world based on applicable law or treaty, and any national - implementations thereof. - -2. Waiver. To the greatest extent permitted by, but not in contravention -of, applicable law, Affirmer hereby overtly, fully, permanently, -irrevocably and unconditionally waives, abandons, and surrenders all of -Affirmer's Copyright and Related Rights and associated claims and causes -of action, whether now known or unknown (including existing as well as -future claims and causes of action), in the Work (i) in all territories -worldwide, (ii) for the maximum duration provided by applicable law or -treaty (including future time extensions), (iii) in any current or future -medium and for any number of copies, and (iv) for any purpose whatsoever, -including without limitation commercial, advertising or promotional -purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each -member of the public at large and to the detriment of Affirmer's heirs and -successors, fully intending that such Waiver shall not be subject to -revocation, rescission, cancellation, termination, or any other legal or -equitable action to disrupt the quiet enjoyment of the Work by the public -as contemplated by Affirmer's express Statement of Purpose. - -3. Public License Fallback. Should any part of the Waiver for any reason -be judged legally invalid or ineffective under applicable law, then the -Waiver shall be preserved to the maximum extent permitted taking into -account Affirmer's express Statement of Purpose. In addition, to the -extent the Waiver is so judged Affirmer hereby grants to each affected -person a royalty-free, non transferable, non sublicensable, non exclusive, -irrevocable and unconditional license to exercise Affirmer's Copyright and -Related Rights in the Work (i) in all territories worldwide, (ii) for the -maximum duration provided by applicable law or treaty (including future -time extensions), (iii) in any current or future medium and for any number -of copies, and (iv) for any purpose whatsoever, including without -limitation commercial, advertising or promotional purposes (the -"License"). The License shall be deemed effective as of the date CC0 was -applied by Affirmer to the Work. Should any part of the License for any -reason be judged legally invalid or ineffective under applicable law, such -partial invalidity or ineffectiveness shall not invalidate the remainder -of the License, and in such case Affirmer hereby affirms that he or she -will not (i) exercise any of his or her remaining Copyright and Related -Rights in the Work or (ii) assert any associated claims and causes of -action with respect to the Work, in either case contrary to Affirmer's -express Statement of Purpose. - -4. Limitations and Disclaimers. - - a. No trademark or patent rights held by Affirmer are waived, abandoned, - surrendered, licensed or otherwise affected by this document. - b. Affirmer offers the Work as-is and makes no representations or - warranties of any kind concerning the Work, express, implied, - statutory or otherwise, including without limitation warranties of - title, merchantability, fitness for a particular purpose, non - infringement, or the absence of latent or other defects, accuracy, or - the present or absence of errors, whether or not discoverable, all to - the greatest extent permissible under applicable law. - c. Affirmer disclaims responsibility for clearing rights of other persons - that may apply to the Work or any use thereof, including without - limitation any person's Copyright and Related Rights in the Work. - Further, Affirmer disclaims responsibility for obtaining any necessary - consents, permissions or other rights required for any use of the - Work. - d. Affirmer understands and acknowledges that Creative Commons is not a - party to this document and has no duty or obligation with respect to - this CC0 or use of the Work. -``` diff --git a/src/easydiffraction/report/templates/tex/styles/harvard.sty b/src/easydiffraction/report/templates/tex/styles/harvard.sty deleted file mode 100644 index 9a4c64623..000000000 --- a/src/easydiffraction/report/templates/tex/styles/harvard.sty +++ /dev/null @@ -1,270 +0,0 @@ -%% harvard.sty - harvard bibliography style Version 2.0.5 -%% Author: Peter Williams peterw@archsci.arch.su.edu.au -%% Copyright: Peter Williams 1994 -\NeedsTeXFormat{LaTeX2e} -\ProvidesPackage{harvard} -\RequirePackage{ifthen} -\IfFileExists{html.sty}{\RequirePackage{html} -\newcommand{\harvardurl}[1]{\htmladdnormallink*{\textbf{URL:} \textit{##1}}{##1}} -}{ -\newcommand{\harvardurl}[1]{\textbf{URL:} \textit{##1}} -} -\DeclareOption{full}{\citationmode{full}} -\DeclareOption{abbr}{\citationmode{abbr}} -\DeclareOption{default}{\citationmode{default}} -\DeclareOption{agsmcite}{\citationstyle{agsm}} -\DeclareOption{dcucite}{\citationstyle{dcu}} -\DeclareOption{round}{\harvardparenthesis{round}\harvardyearparenthesis{round}} -\DeclareOption{curly}{\harvardparenthesis{curly}\harvardyearparenthesis{curly}} -\DeclareOption{angle}{\harvardparenthesis{angle}\harvardyearparenthesis{angle}} -\DeclareOption{square}{\harvardparenthesis{square}\harvardyearparenthesis{square}} -\DeclareOption{none}{\harvardparenthesis{none}\harvardyearparenthesis{none}} -\DeclareOption*{ - \global\edef\HAR@tmp{\CurrentOption} -%% Berwin A. Turlach - \AtEndDocument{\bibliographystyle{\HAR@tmp}} -} - -%% Kristoffer H. Rose 1995/03/01: -%% do not expand macros in citations: put definitions on .aux file instead. -{\catcode`\:=12 \catcode`\-=12 \catcode`\>=12 \catcode`\<=12 % - \gdef\codeof#1{\expandafter\codeof@\meaning#1<-:}% - \gdef\codeof@#1:->#2<-:{#2}} - -\def\harvardpreambletext{\catcode`\#=12 \harvardpreambletext@} -\def\harvardpreambletext@#1{\def\next{#1}\catcode`\#=6 % - \immediate\write\@auxout{\string\harvardpreambledefs{% - \string\AtBeginDocument{\codeof\next}}}} - -\def\harvardpreambledefs#1{#1\gdef\harvardpreambledefs##1{}} - -\newcommand{\harvarditem}[4][\null]{\item[]% -\if@filesw{ \def\protect##1{\string ##1\space}% -\ifthenelse{\equal{#1}{\null}} - {\def\next{{#4}{#2}{#2}{#3}}} - {\def\next{{#4}{#2}{#1}{#3}}} -\immediate\write\@auxout{\string\harvardcite\codeof\next}% -}\fi% -\protect\hspace*{-\labelwidth}\protect\hspace*{-\labelsep}\ignorespaces% -} - -\newcommand{\harvardcite}[4]{ - \global\@namedef{HAR@fn@#1}{#2} - \global\@namedef{HAR@an@#1}{#3} - \global\@namedef{HAR@yr@#1}{#4} - \global\@namedef{HAR@df@#1}{\csname HAR@fn@#1\endcsname} -} - -\newcommand{\citationmode}[1]{ - \renewcommand{\HAR@citemode}{\csname HAR@cite@#1\endcsname} -} - -\newcommand{\HAR@cite@full}{HAR@fn@} -\newcommand{\HAR@cite@abbr}{HAR@an@} -\newcommand{\HAR@cite@default}{HAR@df@} -\newcommand{\HAR@citemode}{\HAR@cite@default} - -\newcommand{\HAR@citetoaux}[1]{% - \if@filesw\immediate\write\@auxout{\string\citation{#1}}\fi% -} - -\newcommand{\HAR@checkdef}[2]{\@ifundefined{HAR@df@#1}% - {\textbf{?}\@warning{Citation '#1' on page \thepage \space undefined}}% - {#2}% -} - -\newcommand{\HAR@dolist}[2]{\def\@citea{\null}\@for\@citeb:=#1\do% -{\@citea\def\@citea{\HAR@hisep\penalty\@m\ }\HAR@checkdef{\@citeb}% -{#2{\@citeb}\HAR@hysep\penalty\@m\ % -\HAR@year{\@citeb}\HAR@setd{\@citeb}}}% -} - -\def\@enamedef#1{\expandafter\def\csname #1\expandafter\endcsname\expandafter} -\newcommand{\HAR@name}[1]{\csname \HAR@citemode#1\endcsname} -\newcommand{\HAR@fname}[1]{\csname HAR@fn@#1\endcsname} -\newcommand{\HAR@aname}[1]{\csname HAR@an@#1\endcsname} -\newcommand{\HAR@year}[1]{\csname HAR@yr@#1\endcsname} -\newcommand{\HAR@setd}[1]{% -\global\@enamedef{HAR@df@#1}{\csname HAR@an@#1\endcsname}% -} - -%% Berwin A. Turlach -\global\@namedef{HAR@df@*}{\csname HAR@fn@*\endcsname} -\renewcommand{\nocite}[1]{\HAR@citetoaux{#1}% -\@for\@citeb:=#1\do% -{\HAR@checkdef{\@citeb}{}}}% - -\renewcommand{\cite}{\@ifstar{\@ifstar{\HAR@acite}{\HAR@fcite}}{\HAR@dcite}} - -\newcommand{\HAR@dcite}[2][\null]{\HAR@citetoaux{#2}% -{\harvardleft\HAR@dolist{#2}{\HAR@name}\ifthenelse{\equal{#1}{\null}}% - {}{, #1}\harvardright}% -} - -\newcommand{\HAR@acite}[2][\null]{\HAR@citetoaux{#2}% -{\harvardleft\HAR@dolist{#2}{\HAR@aname}\ifthenelse{\equal{#1}{\null}}% - {}{, #1}\harvardright}% -} - -\newcommand{\HAR@fcite}[2][\null]{\HAR@citetoaux{#2}% -{\harvardleft\HAR@dolist{#2}{\HAR@fname}\ifthenelse{\equal{#1}{\null}}% - {}{, #1}\harvardright}% -} - -\newcommand{\citeaffixed}{\@ifstar{\@ifstar{\HAR@aciteaff}{\HAR@fciteaff}}% -{\HAR@dciteaff}% -} - -\newcommand{\HAR@fciteaff}[3][\null]{\HAR@citetoaux{#2}% -{\harvardleft#3\ \HAR@dolist{#2}{\HAR@fname}\ifthenelse{\equal{#1}{\null}}% - {}{, #1}\harvardright}% -} - -\newcommand{\HAR@aciteaff}[3][\null]{\HAR@citetoaux{#2}% -{\harvardleft#3\ \HAR@dolist{#2}{\HAR@aname}\ifthenelse{\equal{#1}{\null}}% - {}{, #1}\harvardright}% -} - -\newcommand{\HAR@dciteaff}[3][\null]{\HAR@citetoaux{#2}% -{\harvardleft#3\ \HAR@dolist{#2}{\HAR@name}\ifthenelse{\equal{#1}{\null}}% - {}{, #1}\harvardright}% -} - -\newcommand{\citeasnoun}{\@ifstar{\@ifstar{\HAR@aciteasn}{\HAR@fciteasn}}% -{\HAR@dciteasn}% -} - -\newcommand{\HAR@fciteasn}[2][\null]{\HAR@citetoaux{#2}\HAR@checkdef{#2}{% -{\HAR@fname{#2}\ \harvardyearleft\HAR@year{#2}\ifthenelse{\equal{#1}{\null}} - {}{, #1}\harvardyearright}\HAR@setd{#2}}% -} - -\newcommand{\HAR@aciteasn}[2][\null]{\HAR@citetoaux{#2}\HAR@checkdef{#2}{% -{\HAR@aname{#2}\ \harvardyearleft\HAR@year{#2}\ifthenelse{\equal{#1}{\null}} - {}{, #1}\harvardyearright}\HAR@setd{#2}}% -} - -\newcommand{\HAR@dciteasn}[2][\null]{\HAR@citetoaux{#2}\HAR@checkdef{#2}{% -{\HAR@name{#2}\ \harvardyearleft\HAR@year{#2}\ifthenelse{\equal{#1}{\null}} - {}{, #1}\harvardyearright}\HAR@setd{#2}}% -} - -\newcommand{\possessivecite}{\@ifstar{\@ifstar{\HAR@acitepos}{\HAR@fcitepos}}% -{\HAR@dcitepos}% -} - -\newcommand{\HAR@fcitepos}[2][\null]{\HAR@citetoaux{#2}\HAR@checkdef{#2}{% -{\HAR@fname{#2}'s \harvardyearleft\HAR@year{#2}\ifthenelse{\equal{#1}{\null}} - {}{, #1}\harvardyearright}\HAR@setd{#2}}% -} - -\newcommand{\HAR@acitepos}[2][\null]{\HAR@citetoaux{#2}\HAR@checkdef{#2}{% -{\HAR@aname{#2}'s \harvardyearleft\HAR@year{#2}\ifthenelse{\equal{#1}{\null}} - {}{, #1}\harvardyearright}\HAR@setd{#2}}% -} - -\newcommand{\HAR@dcitepos}[2][\null]{\HAR@citetoaux{#2}\HAR@checkdef{#2}{% -{\HAR@name{#2}'s \harvardyearleft\HAR@year{#2}\ifthenelse{\equal{#1}{\null}} - {}{, #1}\harvardyearright}\HAR@setd{#2}}% -} - -\newcommand{\citename}{\@ifstar{\@ifstar{\HAR@acitenam}\HAR@fcitenam}% -{\HAR@dcitenam}% -} - -\newcommand{\HAR@fcitenam}[2][\null]{\HAR@citetoaux{#2}\HAR@checkdef{#2}{% -{\HAR@fname{#2}\ifthenelse{\equal{#1}{\null}} - {}{\ \harvardleft#1\harvardright}}}% -} - -\newcommand{\HAR@acitenam}[2][\null]{\HAR@citetoaux{#2}\HAR@checkdef{#2}{% -{\HAR@aname{#2}\ifthenelse{\equal{#1}{\null}} - {}{\ \harvardleft#1\harvardright}}}% -} - -\newcommand{\HAR@dcitenam}[2][\null]{\HAR@citetoaux{#2}\HAR@checkdef{#2}{% -{\HAR@name{#2}\ifthenelse{\equal{#1}{\null}} - {}{\ \harvardleft#1\harvardright}}}% -} - -\newcommand{\citeyear}{\@ifstar{\HAR@citeyrnb}{\HAR@citeyr}} - -\newcommand{\HAR@citeyrnb}[2][\null]{\HAR@citetoaux{#2}% -{\def\@citea{\null}\@for\@citeb:=#2\do% -{\@citea\def\@citea{\HAR@hisep\penalty\@m\ }\HAR@checkdef{\@citeb}% -{\HAR@year{\@citeb}}}\ifthenelse{\equal{#1}{\null}}% -{}{, #1}}% -} - -\newcommand{\HAR@citeyr}[2][\null]{\HAR@citetoaux{#2}% -{\harvardleft\def\@citea{\null}\@for\@citeb:=#2\do% -{\@citea\def\@citea{\HAR@hisep\penalty\@m\ }\HAR@checkdef{\@citeb}% -{\HAR@year{\@citeb}}}\ifthenelse{\equal{#1}{\null}}% -{}{, #1}\harvardright}% -} - -\newcommand{\HAR@hysep@apsr}{\null} -\newcommand{\HAR@hisep@apsr}{;} -\newcommand{\HAR@hysep@agsm}{\null} -\newcommand{\HAR@hisep@agsm}{,} -\newcommand{\HAR@hysep@dcu}{,} -\newcommand{\HAR@hisep@dcu}{;} -\newcommand{\HAR@and@agsm}{\&} -\newcommand{\HAR@and@dcu}{and} -\newcommand{\HAR@and@apsr}{and} -\newcommand{\HAR@hysep}{\HAR@hysep@agsm} -\newcommand{\HAR@hisep}{\HAR@hisep@agsm} -\newcommand{\harvardand}{\HAR@and@agsm} -\newcommand{\citationstyle}[1]{% - \renewcommand{\HAR@hysep}{\csname HAR@hysep@#1\endcsname} - \renewcommand{\HAR@hisep}{\csname HAR@hisep@#1\endcsname} - \renewcommand{\harvardand}{\csname HAR@and@#1\endcsname} -} - -\newcommand{\HAR@bl@round}{(} -\newcommand{\HAR@br@round}{)} -\newcommand{\HAR@bl@square}{[} -\newcommand{\HAR@br@square}{]} -\newcommand{\HAR@bl@curly}{\{} -\newcommand{\HAR@br@curly}{\}} -\newcommand{\HAR@bl@angle}{$<$} -\newcommand{\HAR@br@angle}{$>$} -\newcommand{\HAR@bl@none}{} -\newcommand{\HAR@br@none}{} -\newcommand{\harvardleft}{\HAR@bl@round} -\newcommand{\harvardright}{\HAR@br@round} -\newcommand{\harvardparenthesis}[1]{ - \renewcommand{\harvardleft}{\csname HAR@bl@#1\endcsname} - \renewcommand{\harvardright}{\csname HAR@br@#1\endcsname} - \harvardyearparenthesis{#1} -} - -\newcommand{\harvardyearleft}{\HAR@bl@round} -\newcommand{\harvardyearright}{\HAR@br@round} -\newcommand{\harvardyearparenthesis}[1]{ - \renewcommand{\harvardyearleft}{\csname HAR@bl@#1\endcsname} - \renewcommand{\harvardyearright}{\csname HAR@br@#1\endcsname} -} - -\newcommand{\HAR@checkcitations}[4]{ - \def\HAR@tempa{#2}\expandafter - \ifx \csname HAR@fn@#1\endcsname \HAR@tempa - \def\HAR@tempa{#3}\expandafter - \ifx \csname HAR@an@#1\endcsname \HAR@tempa - \def\HAR@tempa{#4}\expandafter - \ifx \csname HAR@yr@#1\endcsname \HAR@tempa - \else - \@tempswatrue - \fi - \else - \@tempswatrue - \fi - \else - \@tempswatrue - \fi -} - -\AtEndDocument{\renewcommand{\harvardcite}{\HAR@checkcitations}} - -\ExecuteOptions{agsm,agsmcite,default,round} -\ProcessOptions* diff --git a/src/easydiffraction/report/templates/tex/styles/iucrjournals.cls b/src/easydiffraction/report/templates/tex/styles/iucrjournals.cls deleted file mode 100644 index db4138764..000000000 --- a/src/easydiffraction/report/templates/tex/styles/iucrjournals.cls +++ /dev/null @@ -1,385 +0,0 @@ -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% This is the IUCr LaTeX2e class macro file iucrjournals.cls -% This work has been dedicated to the public domain -% License: CC0 1.0 Universal -% https://creativecommons.org/publicdomain/zero/1.0/ -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Usage: -% \documentclass{iucrjournals} -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\NeedsTeXFormat{LaTeX2e} -\ProvidesClass{iucrjournals} -\date{} - -\LoadClass[11pt]{article} - -\RequirePackage{lineno} -\linenumbers - -\RequirePackage[parfill]{parskip} -\RequirePackage{setspace} -\onehalfspacing - -\RequirePackage[margin=1in]{geometry} -\RequirePackage{float} -\RequirePackage{graphicx} - -\RequirePackage{xcolor} -\RequirePackage{hyperref} -\hypersetup{colorlinks = true, allcolors = blue} - -\RequirePackage{authblk} -\RequirePackage{booktabs} - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% bibtex: -\RequirePackage{harvard} -% The following hack addresses a problem with bibtex failing when -% there is whitespace in a comma-separated list of labels passed as -% an argument to \cite - see -% http://tex.stackexchange.com/questions/4517/cite-that-tolerates-whitespace -% -\let\OLDcite\cite -\def\tok@scan#1{% - \ifx#1\relax - \let\tok@next\relax - \else - \edef\my@list{\my@list#1}% - \let\tok@next\tok@scan - \fi - \tok@next -} -\newcommand{\@strip}[2]{% - \def\my@list{}\tok@scan#2\relax\let#1\my@list} -\renewcommand{\cite}[1]{\@strip\@args{#1}\OLDcite\@args} - -% The iucr.bst BibTeX bibliography style requires the harvard.sty package. -% IUCr citations are similar to the "dcu" style within harvard.sty, but -% require the conjunction to be changed to '&'; also abbreviated citations -% ('et al.') are always used. -\citationstyle{dcu} % (Doe, 1990; Soape, 1991) -\renewcommand{\harvardand}{\&} % (Doe & Soape, 1990) -\citationmode{abbr} % (Doe et al., 1990) -\bibliographystyle{iucr} -\renewcommand{\harvardurl}{\relax} % incompatibility with hyperref -% \newblock is output by BibTeX to separate logical sections of a reference -% listing. It serves no useful purpose, and can cause extra spacing to -% intrude -\let\newblock\relax -% A complication of the preferred style of citation of IUCr journals -% is that the volume number for Acta includes the section label, -% which is NOT printed in bold; to accommodate this, \volbf is defined -% and generated by iucr.bst. \volbf needs to test just the first character -% of the volume number -\gdef\@A@{A}% -\gdef\@B@{B}% -\gdef\@C@{C}% -\gdef\@D@{D}% -\gdef\@E@{E}% -\gdef\@F@{F}% -\gdef\@J@{J}% -\gdef\@M@{M}% -\gdef\@S@{S}% -\newif\iffirst\firsttrue -\def\volbf#1{% - {\firsttrue\v@lbf#1\end}% -} -\def\v@lbf#1{% - \ifx#1\end - \let\next=\relax% - \else - \let\next=\v@lbf\iffirst - \def\t@st{#1} - \if\t@st\@A@{\rmfamily{#1}}\else - \if\t@st\@B@{\rmfamily{#1}}\else - \if\t@st\@C@{\rmfamily{#1}}\else - \if\t@st\@D@{\rmfamily{#1}}\else - \if\t@st\@E@{\rmfamily{#1}}\else - \if\t@st\@F@{\rmfamily{#1}}\else - \textbf{#1}% - \fi - \fi - \fi - \fi - \fi - \fi% - \firstfalse% - \else - {\textbf #1}% - \fi% - \fi - \next% -} - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% nice orcid links for use in author block: -\RequirePackage{tikz} -\usetikzlibrary{svg.path} - -\IfFileExists{orcidlink.sty}% -{\RequirePackage{orcidlink}}% -{% orcidlink is post 2020, so include full code if sty file not available -\definecolor{orcidlogocol}{HTML}{A6CE39} -\tikzset{ - orcidlogo/.pic={ - \fill[orcidlogocol] svg{M256,128c0,70.7-57.3,128-128,128C57.3,256,0,198.7,0,128C0,57.3,57.3,0,128,0C198.7,0,256,57.3,256,128z}; - \fill[white] svg{M86.3,186.2H70.9V79.1h15.4v48.4V186.2z} - svg{M108.9,79.1h41.6c39.6,0,57,28.3,57,53.6c0,27.5-21.5,53.6-56.8,53.6h-41.8V79.1z M124.3,172.4h24.5c34.9,0,42.9-26.5,42.9-39.7c0-21.5-13.7-39.7-43.7-39.7h-23.7V172.4z} - svg{M88.7,56.8c0,5.5-4.5,10.1-10.1,10.1c-5.6,0-10.1-4.6-10.1-10.1c0-5.6,4.5-10.1,10.1-10.1C84.2,46.7,88.7,51.3,88.7,56.8z}; - } -} -%% Reciprocal of the height of the svg whose source is above. The -%% original generates a 256pt high graphic; this macro holds 1/256. -\newcommand{\@OrigHeightRecip}{0.00390625} -%% We will compute the current X height to make the logo the right height -\newlength{\@curXheight} - -%% Prevent externalization of the ORCiD logo. -\newcommand{\@preventExternalization}{% -\ifcsname tikz@library@external@loaded\endcsname% -\tikzset{external/export next=false}\else\fi% -} - -\newcommand{\orcidlogo}{% -\texorpdfstring{% -\setlength{\@curXheight}{\fontcharht\font`X}% -\XeTeXLinkBox{% -\@preventExternalization% -\begin{tikzpicture}[yscale=-\@OrigHeightRecip*\@curXheight, -xscale=\@OrigHeightRecip*\@curXheight,transform shape] -\pic{orcidlogo}; -\end{tikzpicture}% -}}{}} -\DeclareRobustCommand\orcidlinkX[1]{\href{https://orcid.org/##1}{% -\orcidlogo}} -\newcommand{\orcidlink}[1]{\orcidlinkX{##1}} -} -\newcommand{\IUCrOrcidlink}[1]{\orcidlink{#1}\,} - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% nice email links for use in author block (based on orcidlink): -\definecolor{IUCr@emaillogocol}{HTML}{AAAAAA} -\tikzset{ - IUCr@emaillogo/.pic={ - \fill[IUCr@emaillogocol] - svg{M 0 11.755 v 66.489 h 90 V 11.755 H 0 z M 45 50.49 L 7.138 15.755 h 75.724 L 45 50.49 z M 33.099 45 L 4 71.695 V 18.304 L 33.099 45 z M 36.058 47.714 L 45 55.918 l 8.943 -8.204 l 28.919 26.53 H 7.138 L 36.058 47.714 z M 56.901 45 L 86 18.304 v 53.392 L 56.901 45 z}; - } -} -\definecolor{IUCr@cemaillogocol}{HTML}{0000FF} -\tikzset{ - IUCr@cemaillogo/.pic={ - \fill[IUCr@cemaillogocol] - svg{M 0 11.755 v 66.489 h 90 V 11.755 H 0 z M 45 50.49 L 7.138 15.755 h 75.724 L 45 50.49 z M 33.099 45 L 4 71.695 V 18.304 L 33.099 45 z M 36.058 47.714 L 45 55.918 l 8.943 -8.204 l 28.919 26.53 H 7.138 L 36.058 47.714 z M 56.901 45 L 86 18.304 v 53.392 L 56.901 45 z}; - } -} -\newcommand{\IUCr@OrigHeightRecipE}{0.01400625} -\newlength{\IUCr@curXheightE} -\newcommand{\IUCr@preventExternalizationE}{% -\ifcsname tikz@library@external@loaded\endcsname% -\tikzset{external/export next=false}\else\fi% -} -\newcommand{\IUCr@emaillogo}{% -\texorpdfstring{% -\setlength{\IUCr@curXheightE}{\fontcharht\font`X}% -\XeTeXLinkBox{% -\IUCr@preventExternalizationE% -\begin{tikzpicture}[yscale=-\IUCr@OrigHeightRecipE*\IUCr@curXheightE, -xscale=\IUCr@OrigHeightRecipE*\IUCr@curXheightE,transform shape] -\pic{IUCr@emaillogo}; -\end{tikzpicture}% -}}{}} -\newcommand{\IUCr@cemaillogo}{% -\texorpdfstring{% -\setlength{\IUCr@curXheightE}{\fontcharht\font`X}% -\XeTeXLinkBox{% -\IUCr@preventExternalizationE% -\begin{tikzpicture}[yscale=-\IUCr@OrigHeightRecipE*\IUCr@curXheightE, -xscale=\IUCr@OrigHeightRecipE*\IUCr@curXheightE,transform shape] -\pic{IUCr@cemaillogo}; -\end{tikzpicture}% -}}{}} -\DeclareRobustCommand\IUCr@emaillinkX[1]{\href{mailto:#1}{% -\IUCr@emaillogo}} -\DeclareRobustCommand\IUCr@cemaillinkX[1]{\href{mailto:#1}{% -\IUCr@cemaillogo}} -\newcommand{\IUCrEmaillink}[1]{\,\IUCr@emaillinkX{#1}\,} -\newcommand{\IUCrCemaillink}[1]{\,\IUCr@cemaillinkX{#1}\,} - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% author footnotes for use in author block -% (alternative to using \thanks or \footnote) - -\newcounter{IUCr@aufnc} -\setcounter{IUCr@aufnc}{0} -\newcommand{\IUCr@storeaufn}[1]{\stepcounter{IUCr@aufnc}\global\expandafter\def\csname aufnX\theIUCr@aufnc\endcsname{#1}} -\newcommand{\IUCr@printaufn}[1]{\footnotesize\IUCr@fnsymbol{#1}\csname aufnX#1\endcsname\\} -\newcommand{\IUCr@printauthornotes}{% -\ifnum\theIUCr@aufnc>0 -\begin{center} -\vskip-22pt -\newcounter{tmpIUCr@aufnc} -\setcounter{tmpIUCr@aufnc}{0} -\loop -\stepcounter{tmpIUCr@aufnc} -%\thetmpIUCr@aufnc -\IUCr@printaufn{\thetmpIUCr@aufnc}% -\addtocounter{IUCr@aufnc}{-1} -\ifnum\theIUCr@aufnc>0 -\repeat -\end{center} -\bigskip -\fi -} -\newcommand{\IUCrAufn}[2][0]{% -% store the note as a new macro for later output using \IUCr@printauthornotes -\if0#1% -\IUCr@storeaufn{#2}% -%$^\theIUCr@aufnc$% -\IUCr@fnsymbol{\theIUCr@aufnc}\,% -\else% allows multiple footnote markers pointing to same text -% if the number is greater than \theIUCr@aufnc count, store as new? -\ifnum#1>\theIUCr@aufnc% -\IUCr@storeaufn{#2}% -\fi% -\IUCr@fnsymbol{\theIUCr@aufnc}\,% -\fi% -} - -\newcommand{\IUCr@fnsymbol}[1]{% -\ifnum#1<6% -$^\IUCr@fnsymbolsingle{#1}$% -\else% -\newcount\@lrepeat -\@lrepeat=#1 -\divide\@lrepeat by 5 -%\the\@lrepeat -%modulo gives symbol number: -\newcount\@modsym -\@modsym=#1 -\divide\@modsym by 5 -\multiply\@modsym by 5 -\multiply\@modsym by -1 -\advance\@modsym by #1\relax -%\the\@modsym -$^{% -\IUCr@fnsymbolsingle{\@modsym}% -\loop -\IUCr@fnsymbolsingle{\@modsym}% -\advance\@lrepeat by -1 -\ifnum\@lrepeat>0 -\repeat -}$% -\fi% -} -\newcommand{\IUCr@fnsymbolsingle}[1]{% - \ensuremath{% - \ifcase#1% 0 - \or % 1 - \dagger - \or % 2 - \ddagger - \or % 3 - \mathsection - \or % 4 - \mathparagraph - \or \| - \else % >= 6 - #1 - \fi - }% -} - -% print author notes after maketitle -\let\IUCr@maketitle=\maketitle -\def\maketitle{% -\IUCr@maketitle -\IUCr@printauthornotes -} - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% commands and formatting tweaks: - -\let\origtitle\title -\renewcommand{\title}[1]{\origtitle{\textbf{#1}}} - -\renewenvironment{abstract} - {%\small - \begin{center} - \bfseries \abstractname\vspace{-.5em}\vspace{0pt} - \end{center} - \list{}{ - \setlength{\leftmargin}{1cm}% - \setlength{\rightmargin}{\leftmargin}% - }% - \item\relax} - {\endlist\bigskip} - -\newenvironment{synopsis}% -{%\small - \begin{center} - \bfseries Synopsis\vspace{-.5em}\vspace{0pt} - \end{center} - \list{}{ - \setlength{\leftmargin}{1cm}% - \setlength{\rightmargin}{\leftmargin}% - }% - \item\relax} - {\endlist\bigskip} - -\newcommand{\keywords}[1]{ - \begin{center} - \small - \list{}{ - \setlength{\leftmargin}{1cm}% - \setlength{\rightmargin}{\leftmargin}% - }% - \item\relax\textbf{Keywords:} #1\endlist\end{center}\bigskip} - -\let\origaffil\affil -\def\affil#1#{\origaffilA{#1}} -\def\origaffilA#1#2{\origaffil#1{\footnotesize #2}} - -\newenvironment{acknowledgements}% -{%\small -\bigskip - %\begin{center} - {\bfseries\Large Acknowledgements}\vspace{-.5em}\vspace{0pt} - %\end{center} - \list{}{ - \setlength{\leftmargin}{0cm}% - \setlength{\rightmargin}{\leftmargin}% - }% - \item\relax} - {\endlist\medskip} - -\newenvironment{funding}% -{%\small -\medskip - %\begin{center} - {\bfseries\Large Funding}\vspace{-.5em}\vspace{0pt} - %\end{center} - \list{}{ - \setlength{\leftmargin}{0cm}% - \setlength{\rightmargin}{\leftmargin}% - }% - \item\relax} - {\endlist\medskip} - -\newcommand{\ConflictsOfInterest}[1]{ - \begin{center} - \small - \list{}{ - \setlength{\leftmargin}{0cm}% - \setlength{\rightmargin}{\leftmargin}% - }% - \item\relax\textbf{Conflicts of interest:} #1\endlist\end{center}\medskip} - -\newcommand{\DataAvailability}[1]{ - \begin{center} - \small - \list{}{ - \setlength{\leftmargin}{0cm}% - \setlength{\rightmargin}{\leftmargin}% - }% - \item\relax\textbf{Data availability:} #1\endlist\end{center}\medskip} - diff --git a/src/easydiffraction/report/tex_renderer.py b/src/easydiffraction/report/tex_renderer.py index e21f89122..3ab5c2654 100644 --- a/src/easydiffraction/report/tex_renderer.py +++ b/src/easydiffraction/report/tex_renderer.py @@ -7,7 +7,6 @@ import csv import pathlib import shutil -from importlib.resources import files from jinja2 import Environment from jinja2 import PackageLoader @@ -18,6 +17,7 @@ from easydiffraction.report.fit_plot import fit_plot_geometry from easydiffraction.report.fit_plot import fit_plot_ranges from easydiffraction.report.fit_plot import fit_plot_styles +from easydiffraction.report.style import report_style_context _TEMPLATE_NAME = 'tex/report.tex.j2' _FIGURE_TEMPLATE_NAME = 'tex/figure.tex.j2' @@ -33,6 +33,35 @@ '~': r'\textasciitilde{}', '^': r'\textasciicircum{}', } +_FIT_X_FIELD_TAGS = { + 'two_theta': '_pd_proc.2theta_scan', + 'time_of_flight': '_pd_meas.time_of_flight', + 'd_spacing': '_pd_proc.d_spacing', + 'x': '_pd_proc.r', + 'r': '_pd_proc.r', +} +_FIT_CSV_FIELD_TAGS = ( + ('point_id', '_pd_data.point_id'), + ('d_spacing', '_pd_proc.d_spacing'), + ('intensity_meas', '_pd_meas.intensity_total'), + ('intensity_meas_su', '_pd_meas.intensity_total_su'), + ('intensity_calc', '_pd_calc.intensity_total'), + ('intensity_bkg', '_pd_calc.intensity_bkg'), + ('calc_status', '_pd_data.refinement_status'), +) +_REFLN_CSV_FIELD_TAGS = ( + ('id', '_refln.id'), + ('phase_id', '_refln.phase_id'), + ('d_spacing', '_refln.d_spacing'), + ('sin_theta_over_lambda', '_refln.sin_theta_over_lambda'), + ('index_h', '_refln.index_h'), + ('index_k', '_refln.index_k'), + ('index_l', '_refln.index_l'), + ('f_calc', '_refln.f_calc'), + ('f_squared_calc', '_refln.f_squared_calc'), + ('two_theta', '_refln.two_theta'), + ('time_of_flight', '_refln.time_of_flight'), +) def tex_report_path( @@ -86,6 +115,7 @@ def render_tex_report(context: dict[str, object]) -> str: Complete LaTeX document. """ template_context = dict(context) + template_context['report_style'] = report_style_context() template_context['tex'] = _tex_context( context, fit_csv_paths=_fit_csv_paths(context), @@ -119,13 +149,12 @@ def save_tex_report( """ output_path = tex_report_path(project, path) tex_dir = output_path.parent - styles_dir = tex_dir / 'styles' _prepare_tex_bundle(tex_dir) - styles_dir.mkdir(parents=True, exist_ok=True) template_context = dict(context) - fit_asset_paths = _write_fit_assets(context, tex_dir) + template_context['report_style'] = report_style_context() + fit_asset_paths = _write_fit_assets(project, context, tex_dir) template_context['tex'] = _tex_context( context, fit_csv_paths=fit_asset_paths['csv'], @@ -135,7 +164,6 @@ def save_tex_report( _render_prepared_context(template_context), encoding='utf-8', ) - _copy_style_files(styles_dir) return output_path @@ -174,21 +202,38 @@ def _prepare_tex_bundle(tex_dir: pathlib.Path) -> None: def _write_fit_assets( + project: object, context: dict[str, object], out_dir: pathlib.Path, ) -> dict[str, dict[str, str]]: """Write fit-data CSV and figure TeX files.""" csv_paths: dict[str, str] = {} figure_paths: dict[str, str] = {} + project_experiments = _project_experiments_by_id(project) for experiment in _experiment_contexts(context): fit_data = experiment.get('fit_data') if fit_data is None: continue experiment_id = str(experiment.get('id') or 'experiment') - csv_path = _write_fit_csv(experiment_id, fit_data, out_dir) + source_experiment = project_experiments.get(experiment_id) + csv_path = _write_fit_csv( + experiment_id, + experiment, + source_experiment, + fit_data, + out_dir, + ) + bragg_csvs = _write_bragg_csvs( + experiment_id, + experiment, + source_experiment, + fit_data, + out_dir, + ) figure_path = _write_fit_figure_tex( experiment=experiment, csv_path=csv_path, + bragg_csvs=bragg_csvs, out_dir=out_dir, ) csv_paths[experiment_id] = f'data/{csv_path.name}' @@ -226,6 +271,8 @@ def _fit_plot_ranges(context: dict[str, object]) -> dict[str, dict[str, float]]: def _write_fit_csv( expt_id: str, + experiment: dict[str, object], + source_experiment: object | None, fit_data: dict[str, object], out_dir: pathlib.Path, ) -> pathlib.Path: @@ -233,12 +280,8 @@ def _write_fit_csv( data_dir = out_dir / 'data' data_dir.mkdir(parents=True, exist_ok=True) csv_path = data_dir / _fit_csv_filename(expt_id) - columns = _fit_csv_columns(expt_id, fit_data) - _validate_fit_csv_columns(expt_id, columns) - with csv_path.open('w', newline='', encoding='utf-8') as handle: - writer = csv.writer(handle) - writer.writerow([name for name, _values in columns]) - writer.writerows(zip(*(values for _name, values in columns), strict=True)) + columns = _fit_csv_columns(expt_id, experiment, source_experiment, fit_data) + _write_csv(csv_path, expt_id, columns) return csv_path @@ -246,6 +289,7 @@ def _write_fit_figure_tex( *, experiment: dict[str, object], csv_path: pathlib.Path, + bragg_csvs: dict[str, dict[str, str]], out_dir: pathlib.Path, ) -> pathlib.Path: """Write one standalone pgfplots TeX figure.""" @@ -253,11 +297,13 @@ def _write_fit_figure_tex( data_dir.mkdir(parents=True, exist_ok=True) experiment_id = str(experiment.get('id') or 'experiment') fit_data = experiment['fit_data'] - figure_path = data_dir / f'{_fit_asset_stem(experiment_id)}.tex' + figure_path = data_dir / f'{_safe_asset_stem(experiment_id)}.tex' template_context = { 'experiment': experiment, 'fit_data': fit_data, 'csv_filename': csv_path.name, + 'fit_csv': _fit_csv_plot_columns(experiment, fit_data), + 'bragg_tick_sources': _bragg_tick_sources(fit_data, bragg_csvs), 'geometry': fit_plot_geometry(fit_data), 'ranges': fit_plot_ranges(fit_data), 'axis_styles': fit_plot_axis_styles(), @@ -291,10 +337,22 @@ def _fit_figure_paths(context: dict[str, object]) -> dict[str, str]: if experiment.get('fit_data') is None: continue experiment_id = str(experiment.get('id') or 'experiment') - paths[experiment_id] = f'data/{_fit_asset_stem(experiment_id)}.pdf' + paths[experiment_id] = f'data/{_safe_asset_stem(experiment_id)}.pdf' return paths +def _project_experiments_by_id(project: object) -> dict[str, object]: + """Return project experiment objects keyed by datablock id.""" + experiments = getattr(project, 'experiments', None) + values = getattr(experiments, 'values', None) + if not callable(values): + return {} + return { + str(getattr(experiment, 'name', '')): experiment + for experiment in values() + } + + def _experiment_contexts(context: dict[str, object]) -> list[dict[str, object]]: """Return experiment contexts from a report context.""" experiments = context.get('experiments') @@ -309,22 +367,24 @@ def _experiment_contexts(context: dict[str, object]) -> list[dict[str, object]]: def _fit_csv_filename(expt_id: str) -> str: """Return a filesystem-safe fit-data CSV filename.""" - return f'{_fit_asset_stem(expt_id)}.csv' + return f'{_safe_asset_stem(expt_id)}.csv' -def _fit_asset_stem(expt_id: str) -> str: - """Return a filesystem-safe fit-data asset stem.""" +def _safe_asset_stem(identifier: str) -> str: + """Return a filesystem-safe report asset stem.""" safe_id = ''.join( char if char.isascii() and (char.isalnum() or char in {'-', '_'}) else '_' - for char in expt_id + for char in identifier ).strip('_') if not safe_id: safe_id = 'experiment' - return f'fit_{safe_id}' + return safe_id def _fit_csv_columns( expt_id: str, + experiment: dict[str, object], + source_experiment: object | None, fit_data: dict[str, object], ) -> list[tuple[str, list[object]]]: """Return ordered CSV columns for one fit-data payload.""" @@ -332,53 +392,362 @@ def _fit_csv_columns( series = fit_data['series'] meas = series['meas'] calc = series['calc'] - diff = series['diff'] + bkg = series.get('bkg') + category_values = _category_values( + source_experiment, + experiment, + code='pd_data', + ) + x_field = _fit_x_field(category_values, fit_data) + row_count = len(list(x_data['values'])) columns = [ - ('x', list(x_data['values'])), - ('meas', list(meas['values'])), + (_FIT_X_FIELD_TAGS[x_field], _fit_csv_values( + category_values, + x_field, + list(x_data['values']), + )), ] - if meas['su'] is not None: - columns.append(('meas_su', list(meas['su']))) + fallback_values = { + 'point_id': [str(index + 1) for index in range(row_count)], + 'd_spacing': _empty_csv_values(row_count), + 'intensity_meas': list(meas['values']), + 'intensity_meas_su': _series_values_or_empty(meas.get('su'), row_count), + 'intensity_calc': list(calc['values']), + 'intensity_bkg': _series_values_or_empty( + None if bkg is None else bkg.get('values'), + row_count, + ), + 'calc_status': _empty_csv_values(row_count), + } columns.extend( - [ - ('calc', list(calc['values'])), - ('diff', list(diff['values'])), - ] + ( + tag, + _fit_csv_values(category_values, field_name, fallback_values[field_name]), + ) + for field_name, tag in _FIT_CSV_FIELD_TAGS ) _validate_fit_csv_columns(expt_id, columns) return columns +def _fit_x_field( + category_values: dict[str, list[object]], + fit_data: dict[str, object], +) -> str: + """Return the pd-data field used as the fit plot x axis.""" + for field_name in _FIT_X_FIELD_TAGS: + if field_name in category_values: + return field_name + x_data = fit_data['x'] + x_name = str(x_data.get('name') or '') + if x_name in _FIT_X_FIELD_TAGS: + return x_name + return 'two_theta' + + +def _fit_csv_plot_columns( + experiment: dict[str, object], + fit_data: dict[str, object], +) -> dict[str, str]: + """Return CSV column tags used by the standalone fit figure.""" + x_field = _fit_x_field(_context_category_values(experiment, 'pd_data'), fit_data) + return { + 'x': _FIT_X_FIELD_TAGS[x_field], + 'meas': '_pd_meas.intensity_total', + 'calc': '_pd_calc.intensity_total', + } + + +def _fit_csv_values( + category_values: dict[str, list[object]], + field_name: str, + fallback: list[object], +) -> list[object]: + """Return category values or fallback values.""" + values = category_values.get(field_name) + if values is None or all(_is_csv_empty(value) for value in values): + return fallback + return values + + +def _series_values_or_empty(values: object, row_count: int) -> list[object]: + """Return series values or an empty CSV column.""" + if values is None: + return _empty_csv_values(row_count) + return list(values) + + +def _empty_csv_values(row_count: int) -> list[object]: + """Return an empty CSV column with ``row_count`` rows.""" + return [''] * row_count + + +def _write_bragg_csvs( + expt_id: str, + experiment: dict[str, object], + source_experiment: object | None, + fit_data: dict[str, object], + out_dir: pathlib.Path, +) -> dict[str, dict[str, str]]: + """Write one Bragg-position CSV per phase.""" + data_dir = out_dir / 'data' + data_dir.mkdir(parents=True, exist_ok=True) + values = _category_values(source_experiment, experiment, code='refln') + if values: + return _write_refln_category_csvs(expt_id, values, data_dir) + return _write_bragg_tick_set_csvs(expt_id, fit_data, data_dir) + + +def _write_refln_category_csvs( + expt_id: str, + values: dict[str, list[object]], + data_dir: pathlib.Path, +) -> dict[str, dict[str, str]]: + """Write reflection-category rows split by phase id.""" + phase_values = values.get('phase_id') + if phase_values is None: + return {} + + row_indexes_by_phase: dict[str, list[int]] = {} + for row_index, phase_value in enumerate(phase_values): + if _is_csv_empty(phase_value): + continue + phase_id = str(phase_value) + row_indexes_by_phase.setdefault(phase_id, []).append(row_index) + + csvs: dict[str, dict[str, str]] = {} + x_column = _refln_x_column(values) + for phase_id, row_indexes in row_indexes_by_phase.items(): + csv_path = data_dir / _bragg_csv_filename(expt_id, phase_id) + columns = _refln_csv_columns(values, row_indexes) + _write_csv(csv_path, expt_id, columns) + csvs[phase_id] = { + 'filename': csv_path.name, + 'x_column': x_column, + } + return csvs + + +def _refln_x_column(values: dict[str, list[object]]) -> str: + """Return the reflection CSV x column for Bragg ticks.""" + if 'two_theta' in values: + return '_refln.two_theta' + if 'time_of_flight' in values: + return '_refln.time_of_flight' + return '_refln.two_theta' + + +def _refln_csv_columns( + values: dict[str, list[object]], + row_indexes: list[int], +) -> list[tuple[str, list[object]]]: + """Return ordered reflection CSV columns.""" + return [ + ( + tag, + [values.get(field_name, [])[index] for index in row_indexes], + ) + for field_name, tag in _REFLN_CSV_FIELD_TAGS + if field_name in values + ] + + +def _write_bragg_tick_set_csvs( + expt_id: str, + fit_data: dict[str, object], + data_dir: pathlib.Path, +) -> dict[str, dict[str, str]]: + """Write fallback Bragg CSVs from plot tick-set data.""" + csvs: dict[str, dict[str, str]] = {} + for tick_set in fit_data.get('bragg_tick_sets') or (): + phase_id = str(tick_set.phase_id) + csv_path = data_dir / _bragg_csv_filename(expt_id, phase_id) + columns = _bragg_tick_set_columns(tick_set) + _write_csv(csv_path, expt_id, columns) + csvs[phase_id] = { + 'filename': csv_path.name, + 'x_column': '_refln.two_theta', + } + return csvs + + +def _bragg_tick_set_columns(tick_set: object) -> list[tuple[str, list[object]]]: + """Return reflection CSV columns from one Bragg tick set.""" + row_count = len(tick_set.x) + return [ + ('_refln.id', [str(index + 1) for index in range(row_count)]), + ('_refln.phase_id', [tick_set.phase_id] * row_count), + ('_refln.index_h', list(tick_set.h)), + ('_refln.index_k', list(tick_set.k)), + ('_refln.index_l', list(tick_set.ell)), + ('_refln.f_calc', list(tick_set.f_calc)), + ('_refln.f_squared_calc', list(tick_set.f_squared_calc)), + ('_refln.two_theta', list(tick_set.x)), + ] + + +def _bragg_csv_filename(expt_id: str, phase_id: str) -> str: + """Return the Bragg-position CSV filename for one phase.""" + return f'{_safe_asset_stem(expt_id)}_{_safe_asset_stem(phase_id)}.csv' + + +def _bragg_tick_sources( + fit_data: dict[str, object], + bragg_csvs: dict[str, dict[str, str]], +) -> list[dict[str, str]]: + """Return template context for Bragg-position CSV sources.""" + sources = [] + for tick_set in fit_data.get('bragg_tick_sets') or (): + phase_id = str(tick_set.phase_id) + bragg_csv = bragg_csvs.get(phase_id) + if bragg_csv is None: + continue + sources.append( + { + 'phase_id': phase_id, + 'csv_filename': bragg_csv['filename'], + 'x_column': bragg_csv['x_column'], + } + ) + return sources + + +def _category_values( + source_experiment: object | None, + experiment: dict[str, object], + *, + code: str, +) -> dict[str, list[object]]: + """Return full category values from the project or context.""" + if source_experiment is not None: + values = _source_category_values(source_experiment, code) + if values: + return values + return _context_category_values(experiment, code) + + +def _source_category_values( + source_experiment: object, + code: str, +) -> dict[str, list[object]]: + """Return category values from a live experiment object.""" + category = _source_category(source_experiment, code) + if category is None: + return {} + category_values = getattr(category, 'values', None) + if not callable(category_values): + return {} + items = list(category_values()) + if not items: + return {} + parameter_rows = [_category_parameters(category, item) for item in items] + names = [parameter.name for parameter in parameter_rows[0]] + values = {name: [] for name in names} + for parameters in parameter_rows: + for name, parameter in zip(names, parameters, strict=True): + values[name].append(_raw_value(parameter)) + return values + + +def _source_category(source_experiment: object, code: str) -> object | None: + """Return a live experiment category matching ``code``.""" + for category in getattr(source_experiment, 'categories', ()): + if _source_category_code(category) == code: + return category + return None + + +def _source_category_code(category: object) -> str | None: + """Return a live category code.""" + identity = getattr(category, '_identity', None) + category_code = getattr(identity, 'category_code', None) + if category_code is not None: + return category_code + item_type = getattr(category, '_item_type', None) + return getattr(item_type, '_category_code', None) + + +def _category_parameters(category: object, item: object) -> list[object]: + """Return loop parameters for one live category row.""" + loop_parameters = getattr(category, '_cif_loop_parameters', None) + if callable(loop_parameters): + return list(loop_parameters(item)) + return list(getattr(item, 'parameters', ())) + + +def _raw_value(value: object) -> object: + """Return a descriptor's raw value for CSV output.""" + return getattr(value, 'value', value) + + +def _context_category_values( + experiment: dict[str, object], + code: str, +) -> dict[str, list[object]]: + """Return category values from a prepared report context.""" + for category in experiment.get('categories') or (): + if not isinstance(category, dict) or category.get('code') != code: + continue + return _context_loop_values(category) + return {} + + +def _context_loop_values(category: dict[str, object]) -> dict[str, list[object]]: + """Return loop values from a prepared category context.""" + columns = category.get('columns') or [] + rows = category.get('rows') or [] + names = [ + str(column.get('name')) + for column in columns + if isinstance(column, dict) + ] + values = {name: [] for name in names} + for row in rows: + if not isinstance(row, dict): + continue + cells = row.get('cells') or [] + for name, cell in zip(names, cells, strict=True): + values[name].append(cell.get('value') if isinstance(cell, dict) else '') + return values + + +def _is_csv_empty(value: object) -> bool: + """Return whether a value should be treated as empty in CSV data.""" + return value is None or (isinstance(value, str) and not value) + + +def _write_csv( + path: pathlib.Path, + expt_id: str, + columns: list[tuple[str, list[object]]], +) -> None: + """Write a CSV file after validating column lengths.""" + _validate_fit_csv_columns(expt_id, columns) + with path.open('w', newline='', encoding='utf-8') as handle: + writer = csv.writer(handle) + writer.writerow([name for name, _values in columns]) + writer.writerows(zip(*(values for _name, values in columns), strict=True)) + + def _validate_fit_csv_columns( expt_id: str, columns: list[tuple[str, list[object]]], ) -> None: """Raise if fit-data CSV columns have inconsistent lengths.""" + reference_name = columns[0][0] expected = len(columns[0][1]) for name, values in columns[1:]: if len(values) == expected: continue msg = ( f"Cannot write report CSV for experiment '{expt_id}': " - f"column 'x' has length {expected}, but column " + f"column '{reference_name}' has length {expected}, but column " f"'{name}' has length {len(values)}." ) raise ValueError(msg) -def _copy_style_files(styles_dir: pathlib.Path) -> None: - """Copy vendored LaTeX style files into a report bundle.""" - source = files('easydiffraction.report').joinpath( - 'templates', - 'tex', - 'styles', - ) - for resource in source.iterdir(): - if resource.is_file(): - (styles_dir / resource.name).write_bytes(resource.read_bytes()) - - def _tex_number(value: object, digits: int = 6) -> str: """Format a number for TeX output.""" if isinstance(value, bool): diff --git a/tests/unit/easydiffraction/display/plotters/test_ascii.py b/tests/unit/easydiffraction/display/plotters/test_ascii.py index 54e533ea0..809cbc772 100644 --- a/tests/unit/easydiffraction/display/plotters/test_ascii.py +++ b/tests/unit/easydiffraction/display/plotters/test_ascii.py @@ -121,7 +121,7 @@ def test_ascii_plotter_plot_powder_meas_vs_calc_announces_plotly_only_bragg_row( f_calc=np.array([10.0]), ), ), - axes_labels=['2θ (degree)', 'Intensity (arb. units)'], + axes_labels=['2θ (deg)', 'Intensity (arb. units)'], title='Powder plot', residual_height_fraction=0.25, bragg_peaks_height_fraction=0.15, diff --git a/tests/unit/easydiffraction/display/plotters/test_plotly.py b/tests/unit/easydiffraction/display/plotters/test_plotly.py index 2d24933b1..35fd5f2b8 100644 --- a/tests/unit/easydiffraction/display/plotters/test_plotly.py +++ b/tests/unit/easydiffraction/display/plotters/test_plotly.py @@ -437,7 +437,7 @@ def fake_show_figure(self, fig): f_calc=np.array([9.0]), ), ), - axes_labels=['2θ (degree)', 'Intensity (arb. units)'], + axes_labels=['2θ (deg)', 'Intensity (arb. units)'], title='Powder', residual_height_fraction=0.25, bragg_peaks_height_fraction=0.10, @@ -497,7 +497,7 @@ def fake_show_figure(self, fig): assert fig.layout.yaxis2.title.text is None assert fig.layout.yaxis3.title.text is None assert fig.layout.yaxis3.zeroline is False - assert fig.layout.xaxis3.title.text == '2θ (degree)' + assert fig.layout.xaxis3.title.text == '2θ (deg)' assert 'Miller indices: (1 0 1)' in bragg_traces[0].text[0] assert 'phase-a' in bragg_traces[0].text[0] @@ -531,7 +531,7 @@ def fake_show_figure(self, fig): f_calc=np.array([10.0]), ), ), - axes_labels=['2θ (degree)', 'Intensity (arb. units)'], + axes_labels=['2θ (deg)', 'Intensity (arb. units)'], title='Powder', residual_height_fraction=0.25, bragg_peaks_height_fraction=0.10, @@ -580,7 +580,7 @@ def test_get_main_intensity_range_uses_unit_padding_for_flat_series(): y_calc=np.array([5.0]), y_resid=None, bragg_tick_sets=(), - axes_labels=['2θ (degree)', 'Intensity (arb. units)'], + axes_labels=['2θ (deg)', 'Intensity (arb. units)'], title='Powder', residual_height_fraction=0.25, bragg_peaks_height_fraction=0.10, @@ -612,7 +612,7 @@ def test_bragg_row_height_pixels_scale_linearly_with_phase_count(): f_calc=np.array([10.0]), ), ), - axes_labels=['2θ (degree)', 'Intensity (arb. units)'], + axes_labels=['2θ (deg)', 'Intensity (arb. units)'], title='Powder', residual_height_fraction=0.25, bragg_peaks_height_fraction=0.10, @@ -681,7 +681,7 @@ def plot_spec(phase_count: int) -> PowderMeasVsCalcSpec: y_calc=np.array([9.0, 11.0, 10.5]), y_resid=np.array([1.0, 1.0, 0.5]), bragg_tick_sets=bragg_tick_sets, - axes_labels=['2θ (degree)', 'Intensity (arb. units)'], + axes_labels=['2θ (deg)', 'Intensity (arb. units)'], title='Powder', residual_height_fraction=0.25, bragg_peaks_height_fraction=0.10, @@ -742,7 +742,7 @@ def fake_show_figure(self, fig): f_calc=np.array([10.0]), ), ), - axes_labels=['2θ (degree)', 'Intensity (arb. units)'], + axes_labels=['2θ (deg)', 'Intensity (arb. units)'], title='Powder', residual_height_fraction=0.25, bragg_peaks_height_fraction=0.10, @@ -773,7 +773,7 @@ def fake_show_figure(self, fig): y_calc=np.array([9.0, 11.0, 10.5]), y_resid=np.array([1.0, 1.0, 0.5]), bragg_tick_sets=(), - axes_labels=['2θ (degree)', 'Intensity (arb. units)'], + axes_labels=['2θ (deg)', 'Intensity (arb. units)'], title='Powder', residual_height_fraction=0.25, bragg_peaks_height_fraction=0.15, @@ -786,7 +786,7 @@ def fake_show_figure(self, fig): assert fig.layout.xaxis.matches == 'x' assert fig.layout.xaxis2.matches == 'x' assert fig.layout.yaxis2.title.text is None - assert fig.layout.xaxis2.title.text == '2θ (degree)' + assert fig.layout.xaxis2.title.text == '2θ (deg)' assert fig.layout.title.font.size == pp.TITLE_FONT_SIZE assert fig.layout.yaxis.title.font.size == pp.AXIS_TITLE_FONT_SIZE assert fig.layout.xaxis2.title.font.size == pp.AXIS_TITLE_FONT_SIZE @@ -817,7 +817,7 @@ def fake_show_figure(self, fig): y_calc=np.array([9.0, 11.0, 10.5]), y_resid=np.array([1.0, 1.0, 0.5]), bragg_tick_sets=(), - axes_labels=['2θ (degree)', 'Intensity (arb. units)'], + axes_labels=['2θ (deg)', 'Intensity (arb. units)'], title='Powder', residual_height_fraction=0.25, bragg_peaks_height_fraction=0.15, @@ -864,7 +864,7 @@ def fake_show_figure(self, fig): y_calc=np.array([180.0, 3400.0, 210.0]), y_resid=np.array([20.0, 200.0, 10.0]), bragg_tick_sets=(), - axes_labels=['2θ (degree)', 'Intensity (arb. units)'], + axes_labels=['2θ (deg)', 'Intensity (arb. units)'], title='Powder', residual_height_fraction=0.25, bragg_peaks_height_fraction=0.15, @@ -919,7 +919,7 @@ def fake_show_figure(self, fig): y_calc=np.array([180.0, 3400.0, 210.0]), y_resid=np.array([20.0, 1200.0, 10.0]), bragg_tick_sets=(), - axes_labels=['2θ (degree)', 'Intensity (arb. units)'], + axes_labels=['2θ (deg)', 'Intensity (arb. units)'], title='Powder', residual_height_fraction=0.25, bragg_peaks_height_fraction=0.15, @@ -958,7 +958,7 @@ def fake_show_figure(self, fig): y_calc=np.array([], dtype=float), y_resid=np.array([], dtype=float), bragg_tick_sets=(), - axes_labels=['2θ (degree)', 'Intensity (arb. units)'], + axes_labels=['2θ (deg)', 'Intensity (arb. units)'], title='Powder', residual_height_fraction=0.25, bragg_peaks_height_fraction=0.15, diff --git a/tests/unit/easydiffraction/display/test_plotting.py b/tests/unit/easydiffraction/display/test_plotting.py index c5e279368..de9b86845 100644 --- a/tests/unit/easydiffraction/display/test_plotting.py +++ b/tests/unit/easydiffraction/display/test_plotting.py @@ -843,7 +843,7 @@ def test_plot_posterior_predictive_summary_uses_consistent_labels_and_styles(mon best_sample_prediction=np.array([9.0, 10.0, 11.0]), ), y_meas=np.array([9.5, 10.5, 11.5]), - axes_labels=['2θ (degree)', 'Intensity (arb. units)'], + axes_labels=['2θ (deg)', 'Intensity (arb. units)'], show_band=True, show_draws=False, ) @@ -1046,7 +1046,7 @@ def test_plot_posterior_predictive_summary_routes_ascii_to_measured_and_map(monk draws=np.array([[8.5, 9.5, 10.5]]), ), y_meas=np.array([9.5, 10.5, 11.5]), - axes_labels=['2θ (degree)', 'Intensity (arb. units)'], + axes_labels=['2θ (deg)', 'Intensity (arb. units)'], show_band=True, show_draws=True, excluded_ranges=((1.2, 1.4),), diff --git a/tests/unit/easydiffraction/io/cif/test_serialize_category_owner_baseline.py b/tests/unit/easydiffraction/io/cif/test_serialize_category_owner_baseline.py index 465fd76ae..808b1a948 100644 --- a/tests/unit/easydiffraction/io/cif/test_serialize_category_owner_baseline.py +++ b/tests/unit/easydiffraction/io/cif/test_serialize_category_owner_baseline.py @@ -5,6 +5,7 @@ from easydiffraction.datablocks.experiment.item.factory import ExperimentFactory from easydiffraction.datablocks.structure.item.base import Structure +from easydiffraction.io.cif.serialize import analysis_from_cif from easydiffraction.project.project import Project @@ -34,11 +35,51 @@ def test_real_analysis_as_cif_is_singleton_section_without_data_header() -> None assert analysis_cif.startswith('_fitting_mode.type single') assert not analysis_cif.startswith('data_') assert '_minimizer.type' in analysis_cif + assert '_software.framework_name' not in analysis_cif assert '_joint_fit.experiment_id' not in analysis_cif assert '_sequential_fit.data_dir' not in analysis_cif assert '_sequential_fit_extract.id' not in analysis_cif +def test_real_analysis_as_cif_includes_stamped_software() -> None: + project = Project(name='proj') + analysis = project.analysis + analysis.software.framework.name = 'EasyDiffraction' + analysis.software.framework.version = '0.17.0' + analysis.software.framework.url = 'https://github.com/easyscience/diffraction-lib' + analysis.software.calculator.name = 'cryspy' + analysis.software.calculator.version = '0.11.0' + analysis.software.minimizer.name = 'lmfit' + analysis.software.minimizer.version = '1.3.4' + analysis.software.timestamp = '2026-05-29T12:00:00+00:00' + + analysis_cif = analysis.as_cif + + assert '_software.framework_name EasyDiffraction' in analysis_cif + assert '_software.framework_version 0.17.0' in analysis_cif + assert '_software.calculator_name cryspy' in analysis_cif + assert '_software.calculator_version 0.11.0' in analysis_cif + assert '_software.minimizer_name lmfit' in analysis_cif + assert '_software.minimizer_version 1.3.4' in analysis_cif + assert '_software.timestamp 2026-05-29T12:00:00+00:00' in analysis_cif + + +def test_real_analysis_from_cif_restores_stamped_software() -> None: + source = Project(name='proj') + source.analysis.software.framework.name = 'EasyDiffraction' + source.analysis.software.framework.version = '0.17.0' + source.analysis.software.calculator.name = 'cryspy' + source.analysis.software.minimizer.name = 'lmfit' + + target = Project(name='restored') + analysis_from_cif(target.analysis, source.analysis.as_cif) + + assert target.analysis.software.framework.name.value == 'EasyDiffraction' + assert target.analysis.software.framework.version.value == '0.17.0' + assert target.analysis.software.calculator.name.value == 'cryspy' + assert target.analysis.software.minimizer.name.value == 'lmfit' + + def test_real_analysis_as_cif_includes_aliases_and_constraints_when_present() -> None: project = Project(name='proj') project.structures.create(name='phase_1') diff --git a/tests/unit/easydiffraction/report/test_data_context.py b/tests/unit/easydiffraction/report/test_data_context.py index b3a9f4aff..26a615af5 100644 --- a/tests/unit/easydiffraction/report/test_data_context.py +++ b/tests/unit/easydiffraction/report/test_data_context.py @@ -41,7 +41,7 @@ def resolve_display_name(context): @staticmethod def resolve_display_units(context): del context - return 'degree' + return 'deg' def _parameter(name, value, uncertainty): @@ -156,6 +156,14 @@ def test_report_data_context_builds_fit_data(): assert list(fit_data['series']['calc']['values']) == [10.0, 20.0] assert list(fit_data['series']['diff']['values']) == [1.0, -1.0] assert fit_data['bragg_tick_sets'] == () + assert context['refinement']['rows'][3]['label'] == 'Constraints' + assert context['refinement']['rows'][3]['number'] == { + 'left': '0', + 'right': '', + 'has_decimal': False, + 'left_ch': 1, + 'right_ch': 1, + } def test_report_data_context_builds_powder_bragg_tick_sets(): @@ -201,3 +209,242 @@ def test_report_data_context_preserves_structure_uncertainties(): assert structure['atom_sites'][0]['fract_x'] == '11.985(31)' assert structure['atom_sites'][0]['adp_iso'] == '0.00658(14)' assert structure['atom_site_aniso'][0]['adp_12'] == '-0.00048(25)' + + +def test_report_category_context_keeps_numeric_string_ids_as_text(): + from easydiffraction.datablocks.experiment.categories.background.line_segment import ( + LineSegmentBackground, + ) + from easydiffraction.report.data_context import _collection_category_context + + category = LineSegmentBackground() + category.create(id='10', x=10.0, y=2.0) + category.create(id='30', x=30.0, y=3.0) + + context = _collection_category_context(category) + + assert context['colspec'] == 'lS[table-format=2.0]S[table-format=1.0]' + assert [ + (column['latex_label'], column['numeric']) + for column in context['columns'] + ] == [ + ('ID', False), + ('$x$', True), + ('Intensity', True), + ] + assert context['rows'][0]['cells'][1]['number'] == { + 'left': '10', + 'right': '', + 'has_decimal': False, + 'left_ch': 2, + 'right_ch': 1, + } + assert context['rows'][0]['cells'][2]['number'] == { + 'left': '2', + 'right': '', + 'has_decimal': True, + 'left_ch': 1, + 'right_ch': 1, + } + + +def test_report_key_value_colspec_uses_numeric_table_format(): + from easydiffraction.report.data_context import _key_value_colspec + + rows = [ + {'value': '11.985(31)', 'numeric': True}, + {'value': '2.', 'numeric': True}, + {'value': 'not refined', 'numeric': False}, + ] + + assert _key_value_colspec(rows) == 'lS[table-format=2.3(2)]' + + +def test_report_loop_rows_skip_identifier_only_rows(): + from easydiffraction.report.data_context import _loop_row_has_report_values + + empty_row = { + 'cells': [ + {'value': '1'}, + {'value': ''}, + {'value': None}, + ], + } + populated_row = { + 'cells': [ + {'value': '1'}, + {'value': '10.5'}, + {'value': None}, + ], + } + + assert not _loop_row_has_report_values(empty_row) + assert _loop_row_has_report_values(populated_row) + + +def test_report_data_loop_rows_are_display_truncated(): + from easydiffraction.report.data_context import _REPORT_LOOP_DISPLAY_LIMIT + from easydiffraction.report.data_context import _truncate_loop_rows + + rows = [ + { + 'cells': [ + {'value': str(index), 'numeric': False, 'number': None}, + {'value': float(index), 'numeric': False, 'number': None}, + ], + } + for index in range(_REPORT_LOOP_DISPLAY_LIMIT + 5) + ] + + truncated = _truncate_loop_rows(rows) + + assert len(truncated) == _REPORT_LOOP_DISPLAY_LIMIT + 1 + assert truncated[0]['cells'][0]['value'] == '0' + assert truncated[9]['cells'][0]['value'] == '9' + assert truncated[10]['cells'][0]['value'] == '...' + assert truncated[10]['cells'][1]['value'] == '' + assert truncated[11]['cells'][0]['value'] == '15' + assert truncated[-1]['cells'][0]['value'] == '24' + + +def test_report_pd_data_columns_use_compact_labels(): + from easydiffraction.datablocks.experiment.categories.data.bragg_pd import ( + PdCwlData, + ) + from easydiffraction.report.data_context import _collection_category_context + + category = PdCwlData() + category._create_items_set_xcoord_and_id(np.array([10.0])) + + context = _collection_category_context(category) + + assert [ + (column['latex_label'], column['html_label']) + for column in context['columns'] + ] == [ + (r'$2\theta$', r'\(2\theta\)'), + ('ID', 'ID'), + (r'$d$', r'\(d\)'), + (r'$I_{\mathrm{meas}}$', r'\(I_{\mathrm{meas}}\)'), + ( + r'$\sigma(I_{\mathrm{meas}})$', + r'\(\sigma(I_{\mathrm{meas}})\)', + ), + (r'$I_{\mathrm{calc}}$', r'\(I_{\mathrm{calc}}\)'), + (r'$I_{\mathrm{bkg}}$', r'\(I_{\mathrm{bkg}}\)'), + ('Status', 'Status'), + ] + + +def test_report_powder_refln_columns_use_compact_labels(): + from easydiffraction.analysis.calculators.base import PowderReflnRecord + from easydiffraction.datablocks.experiment.categories.refln.bragg_pd import ( + PowderCwlReflnData, + ) + from easydiffraction.report.data_context import _collection_category_context + + category = PowderCwlReflnData() + category._replace_from_records( + [ + PowderReflnRecord( + phase_id='phase', + d_spacing=1.0, + sin_theta_over_lambda=0.5, + index_h=1, + index_k=0, + index_l=1, + f_calc=2.0, + f_squared_calc=4.0, + two_theta=20.0, + ) + ] + ) + + context = _collection_category_context(category) + + assert [ + (column['latex_label'], column['html_label']) + for column in context['columns'] + ] == [ + ('ID', 'ID'), + ('Phase', 'Phase'), + (r'$d$', r'\(d\)'), + (r'$\sin\theta/\lambda$', r'\(\sin\theta/\lambda\)'), + (r'$h$', r'\(h\)'), + (r'$k$', r'\(k\)'), + (r'$l$', r'\(l\)'), + (r'$F_{\mathrm{calc}}$', r'\(F_{\mathrm{calc}}\)'), + (r'$F^2_{\mathrm{calc}}$', r'\(F^2_{\mathrm{calc}}\)'), + (r'$2\theta$', r'\(2\theta\)'), + ] + + +def test_report_number_parts_split_decimal_and_uncertainty_text(): + from easydiffraction.report.data_context import _number_parts + + assert _number_parts('0.584(20)') == { + 'left': '0', + 'right': '584(20)', + 'has_decimal': True, + } + assert _number_parts('.5') == { + 'left': '0', + 'right': '5', + 'has_decimal': True, + } + assert _number_parts('1.') == { + 'left': '1', + 'right': '', + 'has_decimal': True, + } + assert _number_parts('1') == { + 'left': '1', + 'right': '', + 'has_decimal': False, + } + + +def test_report_descriptor_rows_normalize_angstrom_for_mathjax(): + from easydiffraction.core.display_handler import DisplayHandler + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.variable import Parameter + from easydiffraction.io.cif.handler import CifHandler + from easydiffraction.report.data_context import _descriptor_rows + + parameter = Parameter( + name='adp_iso', + value_spec=AttributeSpec(default=0.0), + display_handler=DisplayHandler( + latex_name=r'$U_{\mathrm{iso}}$', + latex_units=r'\AA$^2$', + ), + cif_handler=CifHandler(names=['_atom_site.U_iso_or_equiv']), + ) + + rows = _descriptor_rows([parameter]) + + assert rows[0]['html_label'] == r'\(U_{\mathrm{iso}}\)' + assert rows[0]['html_units'] == r'\(\mathring{\mathrm{A}}^2\)' + + +def test_report_descriptor_rows_preserve_mixed_mathjax_label_text(): + from easydiffraction.core.display_handler import DisplayHandler + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.variable import Parameter + from easydiffraction.io.cif.handler import CifHandler + from easydiffraction.report.data_context import _descriptor_rows + + parameter = Parameter( + name='twotheta_offset', + value_spec=AttributeSpec(default=0.0), + display_handler=DisplayHandler( + latex_name=r'$2\theta$ offset', + latex_units=r'$^\circ$', + ), + cif_handler=CifHandler(names=['_instr.2theta_offset']), + ) + + rows = _descriptor_rows([parameter]) + + assert rows[0]['html_label'] == r'\(2\theta\) offset' + assert rows[0]['html_units'] == r'\(\mathrm{deg}\)' diff --git a/tests/unit/easydiffraction/report/test_html_renderer.py b/tests/unit/easydiffraction/report/test_html_renderer.py index a61e0f249..d3650e497 100644 --- a/tests/unit/easydiffraction/report/test_html_renderer.py +++ b/tests/unit/easydiffraction/report/test_html_renderer.py @@ -11,6 +11,70 @@ def _field(label: str, units: str = '') -> dict[str, str]: return {'label': label, 'units': units} +def _category_row( + name: str, + value: object, + *, + label: str | None = None, + html_label: str | None = None, + html_units: str = '', + numeric: bool = False, + number: dict[str, object] | None = None, +) -> dict[str, object]: + return { + 'name': name, + 'label': label or name, + 'html_label': html_label or label or name, + 'html_units': html_units, + 'value': value, + 'numeric': numeric, + 'number': number, + } + + +def _category_column( + name: str, + *, + label: str | None = None, + html_label: str | None = None, + html_units: str = '', + numeric: bool = False, +) -> dict[str, object]: + return { + 'name': name, + 'label': label or name, + 'html_label': html_label or label or name, + 'html_units': html_units, + 'numeric': numeric, + } + + +def _category_cell( + value: object, + *, + numeric: bool = False, + number: dict[str, object] | None = None, +) -> dict[str, object]: + return {'value': value, 'numeric': numeric, 'number': number} + + +def _number( + left: str, + right: str, + *, + has_decimal: bool = True, + left_ch: int = 1, + right_ch: int = 1, +) -> dict[str, object]: + return { + 'left': left, + 'right': right, + 'has_decimal': has_decimal, + 'left_ch': left_ch, + 'right_ch': right_ch, + } + + def _context() -> dict[str, object]: return { 'project': { @@ -38,6 +102,14 @@ def _context() -> dict[str, object]: }, 'parameters': {'free': 0, 'total': 0}, 'constraints': 0, + 'rows': [ + { + 'label': 'Reduced chi-square', + 'value': '1.23', + 'numeric': True, + 'number': _number('1', '23', right_ch=2), + }, + ], }, 'software': { 'framework': {'name': 'EasyDiffraction', 'version': '0.0'}, @@ -61,9 +133,9 @@ def _context() -> dict[str, object]: 'length_a': _field('a', 'A'), 'length_b': _field('b', 'A'), 'length_c': _field('c', 'A'), - 'angle_alpha': _field('alpha', 'degree'), - 'angle_beta': _field('beta', 'degree'), - 'angle_gamma': _field('gamma', 'degree'), + 'angle_alpha': _field('alpha', 'deg'), + 'angle_beta': _field('beta', 'deg'), + 'angle_gamma': _field('gamma', 'deg'), }, 'atom_sites': [ { @@ -101,6 +173,87 @@ def _context() -> dict[str, object]: 'adp_13': _field('U13', 'A^2'), 'adp_23': _field('U23', 'A^2'), }, + 'categories': [ + { + 'kind': 'item', + 'title': 'cell', + 'rows': [ + _category_row( + 'length_a', + '11.985(31)', + html_label='a', + html_units='Å', + numeric=True, + number=_number( + '11', + '985(31)', + left_ch=2, + right_ch=7, + ), + ), + ], + }, + { + 'kind': 'loop', + 'title': 'atom_site', + 'scalar_rows': [], + 'columns': [ + _category_column('label'), + _category_column( + 'adp_iso', + html_label=r'\(U_{\mathrm{iso}}\)', + html_units=r'\(\mathring{\mathrm{A}}^2\)', + numeric=True, + ), + ], + 'rows': [ + { + 'cells': [ + _category_cell('Si1'), + _category_cell( + '0.00658(14)', + numeric=True, + number=_number( + '0', + '00658(14)', + right_ch=9, + ), + ), + ], + }, + ], + }, + { + 'kind': 'loop', + 'title': 'atom_site_aniso', + 'scalar_rows': [], + 'columns': [ + _category_column('label'), + _category_column( + 'adp_12', + html_label=r'\(U_{12}\)', + numeric=True, + ), + ], + 'rows': [ + { + 'cells': [ + _category_cell('Si1'), + _category_cell( + '-0.00048(25)', + numeric=True, + number=_number( + '-0', + '00048(25)', + left_ch=2, + right_ch=9, + ), + ), + ], + }, + ], + }, + ], } ], 'experiments': [], @@ -117,8 +270,16 @@ def test_render_html_report_preserves_structure_uncertainty_text(): assert '0.00658(14)' in html assert '-0.00048(25)' in html assert '

Publication

' not in html - assert '

Abstract

' in html + assert '

Project Description

' in html assert '

Project Summary

' in html + assert '
' in html + assert '
' in html + assert '' not in html + assert '--wide-colsep: 3pt;' in html + assert 'class="numeric"' in html + assert 'class="number"' in html + assert '--number-left: 2ch; --number-right: 7ch' in html + assert 'aria-label="1.23"' in html def test_render_html_report_uses_plotly_fit_style_order(): @@ -144,7 +305,7 @@ def test_render_html_report_uses_plotly_fit_style_order(): }, 'fit_data': { 'x': {'values': [1.0, 2.0], 'display_name': '2theta'}, - 'axes_labels': ['2θ (degree)', 'Intensity (arb. units)'], + 'axes_labels': ['2θ (deg)', 'Intensity (arb. units)'], 'series': { 'meas': {'values': [10.0, 11.0], 'su': [0.1, 0.2]}, 'calc': {'values': [10.0, 12.0]}, diff --git a/tests/unit/easydiffraction/report/test_pdf_compiler.py b/tests/unit/easydiffraction/report/test_pdf_compiler.py index 59f1bbfcd..d694c8893 100644 --- a/tests/unit/easydiffraction/report/test_pdf_compiler.py +++ b/tests/unit/easydiffraction/report/test_pdf_compiler.py @@ -134,3 +134,18 @@ def fail_warning(message): assert pdf_compiler.compile_pdf_report(tex_path) == pdf_path assert pdf_path.is_file() + + +def test_figure_tex_paths_use_experiment_named_assets(tmp_path): + from easydiffraction.report import pdf_compiler + + tex_dir = tmp_path / 'reports' / 'tex' + data_dir = tex_dir / 'data' + data_dir.mkdir(parents=True) + tex_path = tex_dir / 'report.tex' + figure_path = data_dir / 'hrpt.tex' + bragg_csv_path = data_dir / 'hrpt_lbco.csv' + figure_path.write_text(r'\documentclass{standalone}', encoding='utf-8') + bragg_csv_path.write_text('_refln.two_theta\n1.0\n', encoding='utf-8') + + assert pdf_compiler._figure_tex_paths(tex_path) == [figure_path] diff --git a/tests/unit/easydiffraction/report/test_tex_renderer.py b/tests/unit/easydiffraction/report/test_tex_renderer.py index a1971720f..4ac96f194 100644 --- a/tests/unit/easydiffraction/report/test_tex_renderer.py +++ b/tests/unit/easydiffraction/report/test_tex_renderer.py @@ -49,6 +49,12 @@ def _minimal_context() -> dict[str, object]: 'total': 0, }, 'constraints': 0, + 'rows': [ + {'label': 'Free parameters', 'value': 0, 'numeric': True}, + {'label': 'Total parameters', 'value': 0, 'numeric': True}, + {'label': 'Constraints', 'value': 0, 'numeric': True}, + ], + 'colspec': 'lS[table-format=1.0]', }, 'software': { 'framework': {'name': 'EasyDiffraction', 'version': '0.0'}, @@ -66,6 +72,61 @@ def _latex_field(label: str, units: str = '') -> dict[str, str]: return {'label': label, 'units': units} +def _category_row( + name: str, + value: object, + *, + label: str | None = None, + latex_label: str | None = None, + html_label: str | None = None, + units: str = '', + latex_units: str = '', + html_units: str = '', + numeric: bool = False, +) -> dict[str, object]: + """Return one generic category key-value row.""" + return { + 'name': name, + 'label': label or name, + 'latex_label': latex_label or label or name, + 'html_label': html_label or label or name, + 'units': units, + 'latex_units': latex_units or units, + 'html_units': html_units or units, + 'value': value, + 'numeric': numeric, + } + + +def _category_column( + name: str, + *, + label: str | None = None, + latex_label: str | None = None, + html_label: str | None = None, + units: str = '', + latex_units: str = '', + html_units: str = '', + numeric: bool = False, +) -> dict[str, object]: + """Return one generic loop-category column.""" + return { + 'name': name, + 'label': label or name, + 'latex_label': latex_label or label or name, + 'html_label': html_label or label or name, + 'units': units, + 'latex_units': latex_units or units, + 'html_units': html_units or units, + 'numeric': numeric, + } + + +def _category_cell(value: object, *, numeric: bool = False) -> dict[str, object]: + """Return one generic loop-category cell.""" + return {'value': value, 'numeric': numeric} + + def test_render_tex_report_renders_default_document(): from easydiffraction.report.tex_renderer import render_tex_report @@ -73,15 +134,38 @@ def test_render_tex_report_renders_default_document(): context['project']['description'] = 'Project description.' tex = render_tex_report(context) - assert r'\documentclass[11pt,a4paper]{styles/iucrjournals}' in tex - assert r'\title{Report Project}' in tex - assert r'\author{}' in tex - assert r'\begin{abstract}' in tex + assert r'\documentclass[11pt]{article}' in tex + assert r'\usepackage[margin=2.5cm]{geometry}' in tex + assert r'\usepackage{fourier}' in tex + assert r'\usepackage{adjustbox}' in tex + assert r'\usepackage{paratype}' in tex + assert r'\usepackage{titlesec}' in tex + assert r'\sisetup{group-digits=false}' in tex + assert r'\definecolor{rowshade}{RGB}{235,240,248}' in tex + assert r'\definecolor{tableborder}{RGB}{190,199,208}' in tex + assert r'\arrayrulecolor{tableborder}' in tex + assert r'\setlength{\ReportTableColSep}{0.5em}' in tex + assert r'\setlength{\ReportWideTableColSep}{0.5em}' in tex + assert r'\setlength{\tabcolsep}{\ReportTableColSep}' in tex + assert ( + r'\newcommand{\rowColorsWithHeader}' + r'{\rowcolors{1}{rowshade}{white}}' + ) in tex + assert ( + r'\newcommand{\rowColorsWithoutHeader}' + r'{\rowcolors{1}{white}{rowshade}}' + ) in tex + assert r'\titlelabel{\thetitle.\quad}' in tex + assert r'\titleformat*{\section}' in tex + assert r'{\Large EasyDiffraction Report\newline}' in tex + assert r'{\LARGE Report Project\par}' in tex + assert r'\section*{Project Description}' in tex assert 'Project description.' in tex assert r'\section{Project Summary}' in tex assert r'\begin{table}[H]' in tex - assert r'\toprule' in tex - assert r'\bottomrule' in tex + assert r'\rowColorsWithoutHeader' in tex + assert r'\rowColorsWithHeader' in tex + assert r'\hline' in tex assert r'\section{Publication' not in tex @@ -92,57 +176,85 @@ def test_render_tex_report_preserves_structure_uncertainty_text(): context['structures'] = [ { 'id': 'phase', - 'space_group': 'P 1', - 'crystal_system': 'triclinic', - 'cell': { - 'length_a': '11.985(31)', - 'length_b': '2.()', - 'length_c': '3.()', - 'angle_alpha': '90.()', - 'angle_beta': '90.()', - 'angle_gamma': '90.()', - }, - 'cell_latex': { - 'length_a': _latex_field('$a$', r'\AA'), - 'length_b': _latex_field('$b$', r'\AA'), - 'length_c': _latex_field('$c$', r'\AA'), - 'angle_alpha': _latex_field(r'$\alpha$', 'deg'), - 'angle_beta': _latex_field(r'$\beta$', 'deg'), - 'angle_gamma': _latex_field(r'$\gamma$', 'deg'), - }, - 'atom_sites': [ + 'categories': [ { - 'label': 'Si1', - 'type_symbol': 'Si', - 'fract_x': '11.985(31)', - 'fract_y': '0.', - 'fract_z': '0.', - 'occupancy': '1.', - 'adp_iso': '0.00658(14)', - } - ], - 'atom_site_latex': { - 'adp_iso': _latex_field('$U_{iso}$', r'\AA^2'), - }, - 'atom_site_aniso': [ + 'kind': 'item', + 'title': 'cell', + 'rows': [ + _category_row( + 'length_a', + '11.985(31)', + latex_label='$a$', + latex_units=r'\AA', + numeric=True, + ), + _category_row( + 'angle_alpha', + '90', + latex_label=r'$\alpha$', + latex_units=r'$^\circ$', + numeric=True, + ), + _category_row('length_b', '2.', numeric=True), + ], + 'has_numeric_values': True, + 'value_column_numeric': True, + 'colspec': 'lS[table-format=2.3(2)]', + }, { - 'label': 'Si1', - 'adp_11': '0.00658(14)', - 'adp_22': '0.00488(29)', - 'adp_33': '0.00488144', - 'adp_12': '-0.00048(25)', - 'adp_13': '0.', - 'adp_23': '0.00189(13)', - } + 'kind': 'loop', + 'title': 'atom_site', + 'scalar_rows': [], + 'scalar_has_numeric_values': False, + 'columns': [ + _category_column('label'), + _category_column('fract_x', numeric=True), + _category_column( + 'adp_iso', + latex_label='$U_{iso}$', + latex_units=r'\AA$^2$', + numeric=True, + ), + ], + 'rows': [ + { + 'cells': [ + _category_cell('Si1'), + _category_cell('11.985(31)', numeric=True), + _category_cell('0.00658(14)', numeric=True), + ], + }, + ], + 'colspec': ( + 'lS[table-format=2.3(2)]S[table-format=1.5(2)]' + ), + 'table_width': 'full', + }, + { + 'kind': 'loop', + 'title': 'atom_site_aniso', + 'scalar_rows': [], + 'scalar_has_numeric_values': False, + 'columns': [ + _category_column('label'), + _category_column( + 'adp_12', + latex_label='$U_{12}$', + numeric=True, + ), + ], + 'rows': [ + { + 'cells': [ + _category_cell('Si1'), + _category_cell('-0.00048(25)', numeric=True), + ], + }, + ], + 'colspec': 'lS[table-format=+1.5(2)]', + 'table_width': 'half', + }, ], - 'atom_site_aniso_latex': { - 'adp_11': _latex_field('$U_{11}$', r'\AA^2'), - 'adp_22': _latex_field('$U_{22}$', r'\AA^2'), - 'adp_33': _latex_field('$U_{33}$', r'\AA^2'), - 'adp_12': _latex_field('$U_{12}$', r'\AA^2'), - 'adp_13': _latex_field('$U_{13}$', r'\AA^2'), - 'adp_23': _latex_field('$U_{23}$', r'\AA^2'), - }, } ] @@ -151,14 +263,18 @@ def test_render_tex_report_preserves_structure_uncertainty_text(): assert '11.985(31)' in tex assert '0.00658(14)' in tex assert '-0.00048(25)' in tex + assert r'$\alpha$ ($\mathrm{deg}$)' in tex + assert r'$U_{iso}$ ($\mathring{\mathrm{A}}^2$)' in tex + assert r'\subsubsection*{atom\_site}' in tex + assert r'\multicolumn{1}{c|}{$U_{12}$}' in tex + assert r'\begin{adjustbox}{max width=\linewidth}' in tex + assert r'\setlength{\tabcolsep}{\ReportWideTableColSep}' in tex assert ( - r'\subsubsection{Atom site parameters}' '\n\n' - r'\begin{table}[H]' '\n' - r'\begin{tabular}{llccccc}' '\n' - r'\toprule' '\n' - r'Label &' + r'\begin{tabular}{|lS[table-format=2.3(2)]' + r'S[table-format=1.5(2)]|}' ) in tex - assert r'\midrule' in tex + assert r'\resizebox' not in tex + assert r'\hline' in tex def test_render_tex_report_escapes_plain_latex_field_labels(): @@ -183,6 +299,32 @@ def test_render_tex_report_escapes_plain_latex_field_labels(): 'ambient_temperature': _latex_field('ambient_temperature', 'K'), 'ambient_pressure': _latex_field('ambient_pressure', 'kPa'), }, + 'categories': [ + { + 'kind': 'item', + 'title': 'diffrn', + 'rows': [ + _category_row( + 'ambient_temperature', + '', + label='ambient_temperature', + latex_label='ambient_temperature', + units='K', + latex_units='K', + ), + _category_row( + 'ambient_pressure', + '', + label='ambient_pressure', + latex_label='ambient_pressure', + units='kPa', + latex_units='kPa', + ), + ], + 'has_numeric_values': False, + 'value_column_numeric': False, + }, + ], 'fit_data': { 'x': { 'values': [1.0], @@ -204,15 +346,11 @@ def test_render_tex_report_escapes_plain_latex_field_labels(): assert r'ambient\_temperature (K)' in tex assert r'ambient\_pressure (kPa)' in tex - assert r'xlabel={ time\_of\_flight (micro\_seconds) }' in tex - assert r'ylabel={ intensity\_obs }' in tex - assert r'\definecolor{ed_meas}{RGB}{31,119,180}' in tex - assert 'line join=bevel' in tex -def test_render_tex_report_uses_composite_pgfplots_with_error_bars(): +def test_save_tex_report_uses_composite_pgfplots_with_error_bars(tmp_path): from easydiffraction.display.plotters.base import BraggTickSet - from easydiffraction.report.tex_renderer import render_tex_report + from easydiffraction.report.tex_renderer import save_tex_report context = _minimal_context() context['experiments'] = [ @@ -233,9 +371,10 @@ def test_render_tex_report_uses_composite_pgfplots_with_error_bars(): 'ambient_temperature': _latex_field('Temperature', 'K'), 'ambient_pressure': _latex_field('Pressure', 'kPa'), }, + 'categories': [], 'fit_data': { 'x': {'values': [1.0, 2.0]}, - 'axes_labels': ['2θ (degree)', 'Intensity (arb. units)'], + 'axes_labels': ['2θ (deg)', 'Intensity (arb. units)'], 'series': { 'meas': {'values': [10.0, 12.0], 'su': [0.2, 0.3]}, 'calc': {'values': [9.0, 11.0]}, @@ -257,18 +396,44 @@ def test_render_tex_report_uses_composite_pgfplots_with_error_bars(): } ] - tex = render_tex_report(context) + tex_path = tmp_path / 'reports' / 'tex' / 'report.tex' + save_tex_report(object(), context, path=tex_path) + figure_tex = (tex_path.parent / 'data' / 'hrpt.tex').read_text( + encoding='utf-8', + ) + csv_header = (tex_path.parent / 'data' / 'hrpt.csv').read_text( + encoding='utf-8', + ).splitlines()[0] + bragg_csv_header = ( + tex_path.parent / 'data' / 'hrpt_phase-a.csv' + ).read_text(encoding='utf-8').splitlines()[0] - assert r'\usepgfplotslibrary{groupplots}' in tex - assert r'\begin{groupplot}' in tex - assert 'group size=1 by 3' in tex - assert 'mark layer=like plot' in tex - assert 'y error=meas_su' in tex - assert 'mark=|' in tex - assert 'ytick={1}' in tex - assert 'yticklabels={{phase-a}}' in tex - assert 'ylabel={Residual}' in tex - assert tex.index('color=ed_meas') < tex.index('color=ed_calc') + assert r'\usepackage{fourier}' in figure_tex + assert r'\usepackage{paratype}' in figure_tex + assert r'\usepgfplotslibrary{groupplots}' in figure_tex + assert r'\begin{groupplot}' in figure_tex + assert 'group size=1 by 3' in figure_tex + assert 'mark layer=like plot' in figure_tex + assert 'mark options={fill=ed_meas, draw=ed_meas, fill opacity=1' in figure_tex + assert 'table[x={_pd_proc.2theta_scan}, y={_pd_meas.intensity_total}' in figure_tex + assert 'table[x={_pd_proc.2theta_scan}, y={_pd_calc.intensity_total}' in figure_tex + assert r'\thisrow{_pd_meas.intensity_total}' in figure_tex + assert 'hrpt_phase-a.csv' in figure_tex + assert 'coordinates {' not in figure_tex + assert 'mark=|' in figure_tex + assert 'ytick={1}' in figure_tex + assert 'yticklabels={{phase-a}}' in figure_tex + assert figure_tex.index('color=ed_meas') < figure_tex.index('color=ed_calc') + assert csv_header == ( + '_pd_proc.2theta_scan,_pd_data.point_id,_pd_proc.d_spacing,' + '_pd_meas.intensity_total,_pd_meas.intensity_total_su,' + '_pd_calc.intensity_total,_pd_calc.intensity_bkg,' + '_pd_data.refinement_status' + ) + assert bragg_csv_header == ( + '_refln.id,_refln.phase_id,_refln.index_h,_refln.index_k,' + '_refln.index_l,_refln.f_calc,_refln.f_squared_calc,_refln.two_theta' + ) def test_save_tex_report_removes_stale_managed_bundle_dirs(tmp_path): @@ -285,5 +450,4 @@ def test_save_tex_report_removes_stale_managed_bundle_dirs(tmp_path): assert not (tex_dir / 'data').exists() assert not (tex_dir / 'figures').exists() - assert not (tex_dir / 'styles' / 'stale.txt').exists() - assert (tex_dir / 'styles' / 'iucrjournals.cls').is_file() + assert not (tex_dir / 'styles').exists() From f0a854d277f0fb5b39d8bb0b746f831b953ca3ee Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Fri, 29 May 2026 17:04:45 +0200 Subject: [PATCH 071/129] Tighten section number spacing --- src/easydiffraction/report/templates/html/style.css | 4 ++-- src/easydiffraction/report/templates/tex/report.tex.j2 | 2 +- tests/unit/easydiffraction/report/test_html_renderer.py | 1 + tests/unit/easydiffraction/report/test_tex_renderer.py | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/easydiffraction/report/templates/html/style.css b/src/easydiffraction/report/templates/html/style.css index e5f6951b8..5d7e7150f 100644 --- a/src/easydiffraction/report/templates/html/style.css +++ b/src/easydiffraction/report/templates/html/style.css @@ -105,7 +105,7 @@ p { } .numbered-section > h2::before { - margin-right: 1em; + margin-right: 0.5em; content: counter(report-section) "."; } @@ -114,7 +114,7 @@ p { } .numbered-section .record > h3::before { - margin-right: 1em; + margin-right: 0.5em; content: counter(report-section) "." counter(report-subsection) "."; } diff --git a/src/easydiffraction/report/templates/tex/report.tex.j2 b/src/easydiffraction/report/templates/tex/report.tex.j2 index ddd38dece..1432963e2 100644 --- a/src/easydiffraction/report/templates/tex/report.tex.j2 +++ b/src/easydiffraction/report/templates/tex/report.tex.j2 @@ -50,7 +50,7 @@ % -------------------------------------------------------------------- % Section headings: numbering dot, spacing, font % -------------------------------------------------------------------- -\titlelabel{\thetitle.\quad} +\titlelabel{\thetitle.\enspace} \titlespacing*{\section} {0pt}{3.5ex plus 1ex minus .2ex}{0.5ex plus .2ex} \titlespacing*{\subsection} {0pt}{3.25ex plus 1ex minus .2ex}{0.5ex plus .2ex} diff --git a/tests/unit/easydiffraction/report/test_html_renderer.py b/tests/unit/easydiffraction/report/test_html_renderer.py index d3650e497..0d17f29d0 100644 --- a/tests/unit/easydiffraction/report/test_html_renderer.py +++ b/tests/unit/easydiffraction/report/test_html_renderer.py @@ -275,6 +275,7 @@ def test_render_html_report_preserves_structure_uncertainty_text(): assert '
' in html assert '
' in html assert '' not in html + assert 'margin-right: 0.5em;' in html assert '--wide-colsep: 3pt;' in html assert 'class="numeric"' in html assert 'class="number"' in html diff --git a/tests/unit/easydiffraction/report/test_tex_renderer.py b/tests/unit/easydiffraction/report/test_tex_renderer.py index 4ac96f194..4a8c7bdbe 100644 --- a/tests/unit/easydiffraction/report/test_tex_renderer.py +++ b/tests/unit/easydiffraction/report/test_tex_renderer.py @@ -155,7 +155,7 @@ def test_render_tex_report_renders_default_document(): r'\newcommand{\rowColorsWithoutHeader}' r'{\rowcolors{1}{white}{rowshade}}' ) in tex - assert r'\titlelabel{\thetitle.\quad}' in tex + assert r'\titlelabel{\thetitle.\enspace}' in tex assert r'\titleformat*{\section}' in tex assert r'{\Large EasyDiffraction Report\newline}' in tex assert r'{\LARGE Report Project\par}' in tex From 2a26701951bcf845fcadd7f93b4c62af9ec32d1d Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Fri, 29 May 2026 17:08:08 +0200 Subject: [PATCH 072/129] Replace report format list with boolean flags --- .../adrs/accepted/iucr-cif-tag-alignment.md | 20 +- .../accepted/project-summary-rendering.md | 204 ++++++++---------- docs/docs/api-reference/report.md | 5 - docs/docs/tutorials/ed-14.ipynb | 13 +- docs/docs/tutorials/ed-14.py | 11 +- docs/docs/tutorials/ed-3.ipynb | 13 +- docs/docs/tutorials/ed-3.py | 11 +- .../user-guide/analysis-workflow/project.md | 5 +- .../user-guide/analysis-workflow/report.md | 5 +- src/easydiffraction/__main__.py | 2 +- .../project/categories/report/default.py | 26 +-- src/easydiffraction/report/__init__.py | 3 - src/easydiffraction/report/enums.py | 5 - src/easydiffraction/report/pdf_compiler.py | 2 +- .../project/categories/report/test_default.py | 30 ++- 15 files changed, 166 insertions(+), 189 deletions(-) diff --git a/docs/dev/adrs/accepted/iucr-cif-tag-alignment.md b/docs/dev/adrs/accepted/iucr-cif-tag-alignment.md index 6a8c6f956..5962bc97f 100644 --- a/docs/dev/adrs/accepted/iucr-cif-tag-alignment.md +++ b/docs/dev/adrs/accepted/iucr-cif-tag-alignment.md @@ -145,8 +145,8 @@ observation drives the policy: - **Reports** — a separate `project.report` facade that pulls live Python state and emits journal report artifacts under `reports/`. The IUCr CIF one-off method is `project.report.save_cif()`; the - regular `project.save()` call emits configured reports from - `project.report.formats`. This path applies all IUCr renames, + regular `project.save()` call emits configured reports from the + `project.report.{cif,html,tex,pdf}` booleans. This path applies all IUCr renames, structural reshapings, multi-datablock layout, and project-extension namespacing (`_easydiffraction_*`). It replaces the unimplemented `project.summary` placeholder. **Export only — no round-trip.** @@ -307,12 +307,12 @@ project.report.save() # write configured reports only `project.summary` (currently an unimplemented placeholder) is removed and replaced by `project.report` — a facade slot that owns journal -report generation. `project.report.formats` controls which reports -`project.save()` emits. Per-format methods (`save_cif()`, -`save_html()`, `save_tex()`, `save_pdf()`) write one-off artifacts -without changing that configuration. The no-arg `project.report.save()` -uses `project.report.formats` and raises `ValueError` when no formats -are enabled. +report generation. The `project.report.{cif,html,tex,pdf}` booleans +control which reports `project.save()` emits. Per-format methods +(`save_cif()`, `save_html()`, `save_tex()`, `save_pdf()`) write +one-off artifacts without changing that configuration. The no-arg +`project.report.save()` uses those booleans and raises `ValueError` +when no formats are enabled. #### 2.2 Output location @@ -987,7 +987,7 @@ Policy: manual editing required: `project.report.save_cif()` produces an upload-ready file at `reports/.cif` matching the multi-datablock publication convention. Users who want CIF reports on - every project save can set `project.report.formats = ['cif']`. + every project save can set `project.report.cif = True`. - Publication-metadata placeholders are emitted as `?` in `data_global` so users know where to fill in journal-required info before submission. @@ -1035,7 +1035,7 @@ Policy: - [`project-facade-and-persistence.md`](project-facade-and-persistence.md) — `project.summary` facade slot is removed and replaced by `project.report`. The accepted `project.save(report=True)` flag is - superseded by `project.report.formats` for configured reports and + superseded by report booleans for configured reports and `project.report.save_cif()` for the IUCr CIF one-off path. `summary.cif` is no longer written by default `Project.save()`; the slot is repurposed for IUCr / journal report generation in diff --git a/docs/dev/adrs/accepted/project-summary-rendering.md b/docs/dev/adrs/accepted/project-summary-rendering.md index d95222089..c0aec695c 100644 --- a/docs/dev/adrs/accepted/project-summary-rendering.md +++ b/docs/dev/adrs/accepted/project-summary-rendering.md @@ -25,8 +25,8 @@ multi-datablock IUCr submission CIF written to persisted fields (`cif`, `html`, `tex`, `pdf`, `html_offline`) on `project.cif`, plus ad-hoc per-format methods (`save_html()`, `save_cif()`, `save_tex()`, `save_pdf()`). The -Python-side `project.report.formats` is a convenience property -view over the four format booleans. The LaTeX writer hardcodes +Python-side API uses those same boolean descriptors directly, matching +the persisted CIF shape. The LaTeX writer hardcodes `iucrjournals` as its document class — there is no style selector, no `_report.style` field, no `style=` arg on `save_tex()` / `save_pdf()`. The @@ -148,8 +148,7 @@ In scope: terminal/Jupyter, HTML, and LaTeX rendering surfaces, a configuration category (five scalar fields — `project.report.{cif, html, tex, pdf, html_offline}` — - persisted in `project.cif`; `project.report.formats` is a - property view over the four format booleans), and ad-hoc + persisted in `project.cif`), and ad-hoc per-format save methods. **All report formats are opt-in via the configuration; every format defaults to `False` so `project.save()` writes nothing under `reports/` until a @@ -181,8 +180,7 @@ Out of scope: cross-references. - The IUCr CIF submission export tag policy and multi-datablock layout. Covered by the alignment ADR; the output file lives at - `reports/.cif` and is opt-in via - `project.report.formats = ['cif', ...]`. + `reports/.cif` and is opt-in via `project.report.cif = True`. - Pre-existing project-level singleton categories (`_info.*`, `_chart.*`, `_table.*`, `_verbosity.*`). Covered by the in-flight @@ -267,23 +265,10 @@ class (`iucrjournals`); adding another style is deferred work, not a v1 selector. See §3 for the reasoning behind the single-style choice. -For convenience, `project.report.formats` is exposed as a -**property view** — reading it returns a list of the -currently-`True` formats; assigning a list flips the four -booleans to match: - -```python -project.report.formats # → [] -project.report.formats = ['cif', 'html'] -project.report.cif # → True -project.report.html # → True -project.report.tex # → False -project.report.formats # → ['cif', 'html'] -``` - -The list view is the more idiomatic surface for "set of enabled -formats"; the booleans are what CIF persists. Both spellings are -equivalent and round-trip cleanly. +There is no separate list-style `project.report.formats` property. +The Python API intentionally mirrors CIF and the other project-level +configuration categories: each persisted scalar descriptor is set +directly. ```python import easydiffraction as ed @@ -292,11 +277,8 @@ project = ed.Project() # … set up structures, experiments, run fit … # Configure once — persisted in project.cif (see §1.3 below). -# Either spelling works: -project.report.formats = ['cif', 'html'] -# or, equivalently: -# project.report.cif = True -# project.report.html = True +project.report.cif = True +project.report.html = True project.report.html_offline = False # Every subsequent save now emits the configured reports too. @@ -323,13 +305,9 @@ class ReportFormatEnum(str, Enum): ``` The four per-format booleans (`project.report.cif`, `.html`, -`.tex`, `.pdf`) carry one `ReportFormatEnum` member each as a -class-level constant identifying which format they enable. The -`formats` property view returns a list of `ReportFormatEnum` -members (`[ReportFormatEnum.CIF, ReportFormatEnum.HTML]`), -which compare equal to the bare string values for ergonomic -user code (`'cif' in project.report.formats` still works -because `(str, Enum)` inherits string equality). +`.tex`, `.pdf`) are the public configuration API. Internal save +dispatch may use `ReportFormatEnum` members to keep the finite +format set explicit, but the enum is not a user-facing selector. There is no `ReportStyleEnum`. The LaTeX writer hardcodes `iucrjournals` as its document class (see §3); when a future @@ -339,7 +317,7 @@ together with a new `_report.style` config field. #### 1.2 Ad-hoc per-format methods Each format has its own explicit write method on the facade, -independent of `project.report.formats`. Use when a user wants +independent of the persisted report booleans. Use when a user wants to produce a one-off artifact without changing the persistent configuration. @@ -349,7 +327,7 @@ project.report.save_html(offline: bool = False) # writes reports/.html project.report.save_tex() # writes reports/tex/{.tex, ...} project.report.save_pdf() # writes reports/.pdf (compiles TeX too) -# Convenience: write everything currently in project.report.formats. +# Convenience: write every report enabled by project.report booleans. # Raises ValueError if no formats are configured (see below). project.report.save() # reads config, no flags @@ -382,7 +360,7 @@ accepted IUCr `project.save(report=True)` flag is removed (see ADRs amended); reports are configured on `project.report.*`. ```python -project.save() # writes project files + whatever is in project.report.formats +project.save() # writes project files + enabled report booleans ``` `Summary.as_cif()` and `summary_to_cif()` were already deleted @@ -399,7 +377,7 @@ about reports, so calling it with nothing configured is a user error. ```python -# project.report.formats == [] (default — unconfigured) +# project.report.{cif,html,tex,pdf} == False (default — unconfigured) project.save() # → writes project.cif + structures/ + experiments/ + analysis/ @@ -410,8 +388,7 @@ project.report.save() # → raises: # ValueError( # "project.report.save() called with no formats enabled. " -# "Set project.report.{cif,html,tex,pdf} = True (or assign a " -# "list via project.report.formats), or call a per-format " +# "Set project.report.{cif,html,tex,pdf} = True, or call a per-format " # "method directly (project.report.save_html(), etc.)." # ) ``` @@ -423,7 +400,7 @@ silently no-op when the user explicitly asked for a report. asked to save the project, not the reports. The per-format methods (`save_cif()`, `save_html()`, etc.) -never inspect `project.report.formats` — they always write +never inspect the persisted report booleans — they always write their format unconditionally. They are explicit one-offs. #### 1.3 CIF persistence of the configuration @@ -469,7 +446,7 @@ always a concrete CIF value, never an empty loop or missing block: ```text -# Default (project.report.formats = []): +# Default (project.report.{cif,html,tex,pdf} = False): _report.cif no _report.html no _report.tex no @@ -483,14 +460,13 @@ on its descriptor; the `formats` property view returns `[]`. The four per-format booleans give the IUCr-aware tooling (`gemmi`, `publCIF`) a typed, validatable view of the configuration — each format is a known enum item with type -`Boolean`, not a parsed string. The Python list view -(`project.report.formats`) is a convenience computed from these -four booleans at access time; it has no separate CIF storage. +`Boolean`, not a parsed string. There is no additional Python list +view with separate storage; the booleans are the source of truth. Adding a new format in the future (e.g. `markdown`) is a one-line schema extension: add `_report.markdown` to the dictionary and a `project.report.markdown` boolean to the -descriptor. The list property picks it up automatically. +descriptor, then include it in the internal save dispatch. Loading a `project.cif` populates `project.report.*` per the project-facade-and-persistence contract; on the next @@ -804,17 +780,17 @@ that: The sweep is a Phase 1 step in the implementation plan, not an ADR-level decision. -### 2. HTML report — config-driven via `project.report.formats` +### 2. HTML report — config-driven via `project.report.html` -`'html' in project.report.formats` causes `project.save()` to -write `reports/.html`. The empty default -(`formats = []`) keeps `reports/` from being touched at all on -plain `project.save()`. For one-off HTML without changing the -persistent config, call `project.report.save_html()` directly. +`project.report.html = True` causes `project.save()` to write +`reports/.html`. The all-`False` default keeps +`reports/` from being touched at all on plain `project.save()`. +For one-off HTML without changing the persistent config, call +`project.report.save_html()` directly. ```python # Persistent — every subsequent save writes the HTML report. -project.report.formats = ['html'] +project.report.html = True project.report.html_offline = False # CDN-Plotly (default) project.save() # → reports/.html @@ -962,7 +938,8 @@ tables: `reports/` is created lazily — only when at least one format is configured (or an ad-hoc method is called). A user iterating on -a fit with the default `formats = []` produces no extra files. +a fit with the default all-`False` report booleans produces no +extra files. Rationale for the config category (replacing the earlier flag-based and "auto on every save" positions): @@ -971,10 +948,10 @@ flag-based and "auto on every save" positions): `project.chart.type`, `project.table.type`, `project.verbosity.fit` follow the same pattern — set once, persisted in `project.cif`, applied on every save. -- `project.save()` has one job: save the project. With - `formats = []` the report behaviour is unchanged from before - this ADR; with `formats = ['html']` HTML appears on every - save without needing a flag on each call. +- `project.save()` has one job: save the project. With all report + booleans `False`, the report behaviour is unchanged from before + this ADR; with `project.report.html = True`, HTML appears on + every save without needing a flag on each call. - The GUI's Summary tab consumes `project.report.data_context()` in-memory, not the HTML file — so the GUI-consistency story does not depend on the HTML file existing at any particular @@ -1049,10 +1026,10 @@ the IUCr "one CIF per article" convention): Plotly display constants for style — one source of truth for both data and visual conventions. -### 3. LaTeX + PDF — config-driven via `project.report.formats` +### 3. LaTeX + PDF — config-driven via booleans -LaTeX is a **publish-time** artifact. `'tex'` and `'pdf'` are -added to `project.report.formats` when the user wants them. +LaTeX is a **publish-time** artifact. `project.report.tex` and +`project.report.pdf` are enabled when the user wants them. There is no style selector: the LaTeX writer ships exactly one document class (`iucrjournals`); the **content layout deliberately does not replicate IUCr's published journal @@ -1063,11 +1040,12 @@ rather than "ready-to-submit manuscript". ```python # Persistent — every save writes TeX + assets. -project.report.formats = ['tex'] +project.report.tex = True project.save() # → reports/tex/{...} # Persistent — every save writes the compiled PDF too. -project.report.formats = ['tex', 'pdf'] +project.report.tex = True +project.report.pdf = True project.save() # → reports/tex/{...} + reports/.pdf # One-off, ignoring config. @@ -1078,11 +1056,11 @@ project.report.save_pdf() # TeX + PDF (PDF implies TeX) project.report.as_tex() -> str ``` -**`'pdf' in formats` implies the TeX source is also written** — -a PDF without the editable `.tex` source is useless if the user -wants to tweak before re-compiling. Asking for the PDF always -writes the TeX next to it. Equivalently, `save_pdf()` writes -the TeX assets as a side-effect. +**`project.report.pdf = True` implies the TeX source is also +written** — a PDF without the editable `.tex` source is useless +if the user wants to tweak before re-compiling. Asking for the +PDF always writes the TeX next to it. Equivalently, +`save_pdf()` writes the TeX assets as a side-effect. Future `project.report.html_style` (dark mode, journal-mimicking HTML layout) can land separately without collision because it @@ -1113,15 +1091,15 @@ Per-project filenames (`.{cif,html,pdf}`) share a root in Single style (`iucrjournals`) — no multi-style infrastructure. **Full reports/ tree when all formats are configured.** -`project.report.formats = ['cif', 'html', 'tex', 'pdf']`: +`project.report.cif/html/tex/pdf = True`: ``` / reports/ # populated by project.save() per config - .cif # ← 'cif' in project.report.formats (alignment ADR §2) - .html # ← 'html' in project.report.formats (this ADR §2) - .pdf # ← 'pdf' in project.report.formats (this ADR §3.4) - tex/ # ← 'tex' or 'pdf' in project.report.formats (this ADR §3) + .cif # ← project.report.cif + .html # ← project.report.html + .pdf # ← project.report.pdf + tex/ # ← project.report.tex or project.report.pdf .tex # main document; tables + \includegraphics of figure PDFs data/ fit_.csv # profile data: x, meas, calc, diff (+ meas_su) @@ -1135,7 +1113,7 @@ Single style (`iucrjournals`) — no multi-style infrastructure. **Examples by configuration.** -`project.report.formats = []` (default — nothing written): +`project.report.{cif,html,tex,pdf} = False` (default — nothing written): ``` / @@ -1146,7 +1124,7 @@ Single style (`iucrjournals`) — no multi-style infrastructure. # reports/ directory does not exist ``` -`project.report.formats = ['cif']` (journal-submission CIF only): +`project.report.cif = True` (journal-submission CIF only): ``` / @@ -1158,7 +1136,7 @@ Single style (`iucrjournals`) — no multi-style infrastructure. .cif # IUCr-aligned, multi-datablock (alignment ADR §2.3) ``` -`project.report.formats = ['html']` + `html_offline = True` +`project.report.html = True` + `html_offline = True` (self-contained inspection page): ``` @@ -1171,7 +1149,7 @@ Single style (`iucrjournals`) — no multi-style infrastructure. .html # ~3 MB, Plotly inlined ``` -`project.report.formats = ['cif', 'html', 'pdf']` (typical +`project.report.cif/html/pdf = True` (typical pre-submission bundle): ``` @@ -1191,10 +1169,10 @@ pre-submission bundle): styles/harvard.sty ``` -`reports/` is created lazily — only when at least one format -sits in `project.report.formats` (or an ad-hoc method is called). +`reports/` is created lazily — only when at least one report +boolean is enabled (or an ad-hoc method is called). The `tex/`, `tex/data/`, and `tex/styles/` subfolders appear -only when `'tex'` or `'pdf'` is in `project.report.formats` (or +only when `project.report.tex` or `project.report.pdf` is `True` (or `save_tex()` / `save_pdf()` is invoked). The `` portion of every filename comes from @@ -1507,8 +1485,8 @@ Behaviour: pixi add tectonic # recommended (conda-forge) conda install -c conda-forge tectonic # or any TeX Live distribution (latexmk / pdflatex) - Then re-run project.save() (with 'pdf' in project.report.formats) - or project.report.save_pdf(). + Then set project.report.pdf = True and re-run project.save(), + or call project.report.save_pdf(). ``` The library does not bundle a TeX distribution — TeX Live is @@ -1680,7 +1658,7 @@ uniformly: populate." No warning, no exception; the report still renders end-to-end so users iterating on a configuration before fitting see the rest of the page. -- **IUCr CIF export (`'cif' in project.report.formats`).** The +- **IUCr CIF export (`project.report.cif = True`).** The `_easydiffraction_software.{framework, calculator, minimizer}` triple emits `?` placeholders consistent with the IUCr ADR's unset-field convention. The derived @@ -1955,21 +1933,20 @@ Two subcommands match the Python `project.save()` vs `project.report.save_*()` split: ```bash -ed save # project files + whatever is in project.report.formats +ed save # project files + enabled report booleans ed save-report --html # one-off — write reports/.html only ed save-report --cif --tex --pdf # one-off — full LaTeX bundle + CIF ``` `ed save-report` with no `--cif`/`--html`/`--tex`/`--pdf` exits -with a clear error pointing the user at the configuration -category (`ed config project.report.formats html cif`). +with a clear error pointing the user at the report booleans. `--pdf` implies `--tex` so the user always gets the editable source next to the PDF. For users who want to **persist** the choice across runs, the -configuration category is set the usual way — interactively -through `ed config project.report.formats html cif`, by editing -`project.cif` directly, or programmatically — and `ed save` +configuration category is set the usual way — by setting +`project.report. = True`, by editing `project.cif` +directly, or programmatically — and `ed save` picks it up on every subsequent save. CLI flags are short (no `_report` suffix; the subcommand name @@ -1978,10 +1955,10 @@ stay symmetric: | Python (config — persisted) | Python (ad-hoc — one-off) | CLI (one-off subcommand) | | ----------------------------------------- | ------------------------------------ | ------------------------------------- | -| `project.report.formats = ['html']` | `project.report.save_html()` | `ed save-report --html` | -| `project.report.formats = ['cif']` | `project.report.save_cif()` | `ed save-report --cif` | -| `project.report.formats = ['tex']` | `project.report.save_tex()` | `ed save-report --tex` | -| `project.report.formats = ['pdf']` | `project.report.save_pdf()` | `ed save-report --pdf` | +| `project.report.html = True` | `project.report.save_html()` | `ed save-report --html` | +| `project.report.cif = True` | `project.report.save_cif()` | `ed save-report --cif` | +| `project.report.tex = True` | `project.report.save_tex()` | `ed save-report --tex` | +| `project.report.pdf = True` | `project.report.save_pdf()` | `ed save-report --pdf` | | `project.report.html_offline = True` | `save_html(offline=True)` | `--html --offline` | ### 8. Fields the library currently lacks @@ -2019,8 +1996,8 @@ benefits every renderer (HTML, PDF, terminal, GUI) simultaneously. - `summary.cif` placeholder goes away (alignment ADR + this ADR jointly retire it); no more "To be added..." on disk. -- HTML can be auto-regenerated on every save by adding `'html'` - to `project.report.formats` once (or via the GUI's "Export" +- HTML can be auto-regenerated on every save by setting + `project.report.html = True` once (or via the GUI's "Export" panel). Zero-friction inspection for users who want it, no surprise file writes for users who don't. - LaTeX export covers the "send the refinement table to my @@ -2040,17 +2017,16 @@ benefits every renderer (HTML, PDF, terminal, GUI) simultaneously. ### Trade-offs -- All report outputs are opt-in. Default - `project.report.formats = []` means daily `project.save()` - calls write only project files — `reports/` isn't created - until a format is configured (or an ad-hoc method is called). +- All report outputs are opt-in. The default all-`False` report + booleans mean daily `project.save()` calls write only project + files — `reports/` isn't created until a format is configured + (or an ad-hoc method is called). - HTML is small (~50–300 KB CDN-mode, ~few MB offline); users - who want it on every save add `'html'` to - `project.report.formats` once. + who want it on every save set `project.report.html = True` once. - LaTeX bundle (`reports/tex/` + `reports/.pdf`) is a handful of files (`.tex`, CSV data per experiment, two vendored class/style files, compiled PDF) — only written - when `'tex'` or `'pdf'` is in `project.report.formats` (or + when `project.report.tex` or `project.report.pdf` is `True` (or an ad-hoc `save_tex()` / `save_pdf()` call is made). Total per-save footprint is dominated by the CSVs; the `tex/styles/` directory holds ~20 KB across 2 files. @@ -2092,10 +2068,9 @@ benefits every renderer (HTML, PDF, terminal, GUI) simultaneously. come from the new `project.report` configuration category (§1.1, §1.3) — five scalar items persisted to `project.cif` (`_report.cif`, `_report.html`, `_report.tex`, `_report.pdf`, - `_report.html_offline`). The Python-side - `project.report.formats` is a property view over the four - format booleans. Set the configuration once; `project.save()` - applies it on every save thereafter. Replaces the flag with + `_report.html_offline`). Set the configuration once through + those booleans; `project.save()` applies it on every save + thereafter. Replaces the flag with persisted configuration, matching the existing `project.chart`, `project.table`, `project.verbosity` pattern. @@ -2213,9 +2188,9 @@ benefits every renderer (HTML, PDF, terminal, GUI) simultaneously. ## Open Questions - **GUI Export panel.** Two reasonable shapes: (a) checkboxes - edit `project.report.formats` directly + a "Save now" button - that calls `project.save()` (config-driven, matches the - Python surface) or (b) checkboxes drive ad-hoc per-format + edit `project.report.{cif,html,tex,pdf}` directly + a "Save + now" button that calls `project.save()` (config-driven, matches + the Python surface) or (b) checkboxes drive ad-hoc per-format calls (`save_html()`, `save_pdf()`) without changing the persisted config (one-off ergonomics). Either fits the configuration / ad-hoc split in §1; the exact GUI layout @@ -2252,9 +2227,9 @@ An earlier revision of this ADR had HTML always-on, no opt-in flag, the philosophy being "every artefact stays fresh". Rejected in favour of the configuration approach: `project.save()` does one job (save the project) and reads -`project.report.formats` to decide which reports come along. -Empty config = no reports. Users who want auto-fresh HTML add -`'html'` to `project.report.formats` once; the GUI consumes +the report booleans to decide which reports come along. Empty +config = no reports. Users who want auto-fresh HTML set +`project.report.html = True` once; the GUI consumes `project.report.data_context()` in-memory, not the HTML file, so freshness of the file does not affect GUI consistency. @@ -2344,7 +2319,8 @@ configuration category on the project (persisted in `project.cif`) and applied automatically on every save: ```python -project.report.formats = ['cif', 'html'] # which formats project.save() emits +project.report.cif = True # emit IUCr CIF on save +project.report.html = True # emit HTML on save project.report.html_offline = False # Plotly via CDN (default) or inlined project.save() diff --git a/docs/docs/api-reference/report.md b/docs/docs/api-reference/report.md index a86db090b..6224ecdef 100644 --- a/docs/docs/api-reference/report.md +++ b/docs/docs/api-reference/report.md @@ -8,7 +8,6 @@ - tex - pdf - html_offline - - formats - data_context - save - save_cif @@ -18,7 +17,3 @@ - as_tex - save_pdf - show_report - -## Enums - -::: easydiffraction.report.enums.ReportFormatEnum diff --git a/docs/docs/tutorials/ed-14.ipynb b/docs/docs/tutorials/ed-14.ipynb index 43b600762..76b1ca781 100644 --- a/docs/docs/tutorials/ed-14.ipynb +++ b/docs/docs/tutorials/ed-14.ipynb @@ -447,9 +447,10 @@ "## Step 6: Generate Report\n", "\n", "By default, no report files are generated. Here we enable HTML and\n", - "TeX reports for regular project saves, then request a one-off PDF.\n", - "The generated report files will be saved in the `reports` folder of\n", - "the project directory." + "TeX reports, so that both the HTML and PDF report files are generated\n", + "when saving the project.\n", + "\n", + "We also support saving the report in CIF and TEX formats." ] }, { @@ -459,9 +460,9 @@ "metadata": {}, "outputs": [], "source": [ - "project.report.formats = ['html', 'tex']\n", - "project.save()\n", - "project.report.save_pdf()" + "project.report.html = True\n", + "project.report.pdf = True\n", + "project.save()" ] } ], diff --git a/docs/docs/tutorials/ed-14.py b/docs/docs/tutorials/ed-14.py index e1991d02e..654db0a3b 100644 --- a/docs/docs/tutorials/ed-14.py +++ b/docs/docs/tutorials/ed-14.py @@ -163,11 +163,12 @@ # ## Step 6: Generate Report # # By default, no report files are generated. Here we enable HTML and -# TeX reports for regular project saves, then request a one-off PDF. -# The generated report files will be saved in the `reports` folder of -# the project directory. +# TeX reports, so that both the HTML and PDF report files are generated +# when saving the project. +# +# We also support saving the report in CIF and TEX formats. # %% -project.report.formats = ['html', 'tex'] +project.report.html = True +project.report.pdf = True project.save() -project.report.save_pdf() diff --git a/docs/docs/tutorials/ed-3.ipynb b/docs/docs/tutorials/ed-3.ipynb index 985f02bc3..1c143ca2a 100644 --- a/docs/docs/tutorials/ed-3.ipynb +++ b/docs/docs/tutorials/ed-3.ipynb @@ -138,8 +138,7 @@ "#### Save Project\n", "\n", "When saving the project for the first time, you need to specify the\n", - "directory path. In the example below, the project is saved to a\n", - "temporary location defined by the system." + "directory path." ] }, { @@ -149,7 +148,7 @@ "metadata": {}, "outputs": [], "source": [ - "project.save_as(dir_path='lbco_hrpt', temporary=True)" + "project.save_as(dir_path='projects/lbco_hrpt')" ] }, { @@ -1563,9 +1562,11 @@ "metadata": {}, "outputs": [], "source": [ - "project.report.formats = ['html', 'tex']\n", - "project.save()\n", - "project.report.save_pdf()" + "project.report.cif = True\n", + "project.report.html = True\n", + "project.report.tex = True\n", + "project.report.pdf = True\n", + "project.save()" ] }, { diff --git a/docs/docs/tutorials/ed-3.py b/docs/docs/tutorials/ed-3.py index bfd02e483..2dd91def9 100644 --- a/docs/docs/tutorials/ed-3.py +++ b/docs/docs/tutorials/ed-3.py @@ -55,11 +55,10 @@ # #### Save Project # # When saving the project for the first time, you need to specify the -# directory path. In the example below, the project is saved to a -# temporary location defined by the system. +# directory path. # %% -project.save_as(dir_path='lbco_hrpt', temporary=True) +project.save_as(dir_path='projects/lbco_hrpt') # %% [markdown] # #### Set Up Data Plotter @@ -630,9 +629,11 @@ # the project directory. # %% -project.report.formats = ['html', 'tex'] +project.report.cif = True +project.report.html = True +project.report.tex = True +project.report.pdf = True project.save() -project.report.save_pdf() # %% # project.report.show_report() # Need this? diff --git a/docs/docs/user-guide/analysis-workflow/project.md b/docs/docs/user-guide/analysis-workflow/project.md index 5dd8e6b9e..45f227d41 100644 --- a/docs/docs/user-guide/analysis-workflow/project.md +++ b/docs/docs/user-guide/analysis-workflow/project.md @@ -104,8 +104,9 @@ directory, showing the contents of all files in the project. If you save the project right after creating it, the project directory will only contain the `project.cif` file. The other folders and files will be created as you add structures, experiments, and set up the analysis. The - reports folder is created only when `project.report.formats` selects - at least one report format before `project.save()`. + reports folder is created only when at least one of + `project.report.cif`, `project.report.html`, `project.report.tex`, + or `project.report.pdf` is set to `True` before `project.save()`. ### 1. project.cif diff --git a/docs/docs/user-guide/analysis-workflow/report.md b/docs/docs/user-guide/analysis-workflow/report.md index 7626dd2f0..c3e1b47a8 100644 --- a/docs/docs/user-guide/analysis-workflow/report.md +++ b/docs/docs/user-guide/analysis-workflow/report.md @@ -49,10 +49,11 @@ report formats. | `project.report.pdf` | `bool` | Write a PDF report when a TeX engine is available. | | `project.report.html_offline` | `bool` | Embed HTML assets instead of using CDN links. | -The `formats` property is a compact way to set the four format flags: +Enable each saved report format through its boolean flag: ```python -project.report.formats = ['html', 'cif'] +project.report.html = True +project.report.cif = True project.save() ``` diff --git a/src/easydiffraction/__main__.py b/src/easydiffraction/__main__.py index 115ea4fc5..e151a218a 100644 --- a/src/easydiffraction/__main__.py +++ b/src/easydiffraction/__main__.py @@ -376,7 +376,7 @@ def save_report( if not _selected_report_flags(cif=cif, html=html, tex=tex, pdf=pdf): typer.echo( 'No report format selected. Use --cif, --html, --tex, or --pdf; ' - 'or configure project.report.formats and save the project.', + 'or enable project.report.{cif,html,tex,pdf} and save the project.', err=True, ) raise typer.Exit(code=1) diff --git a/src/easydiffraction/project/categories/report/default.py b/src/easydiffraction/project/categories/report/default.py index a90f47c4f..80cffe8ca 100644 --- a/src/easydiffraction/project/categories/report/default.py +++ b/src/easydiffraction/project/categories/report/default.py @@ -4,7 +4,6 @@ from __future__ import annotations -from collections.abc import Iterable from textwrap import wrap from typing import TYPE_CHECKING @@ -29,8 +28,7 @@ _NO_REPORT_FORMATS_MESSAGE = ( 'project.report.save() called with no formats enabled. ' - 'Set project.report.{cif,html,tex,pdf} = True (or assign a ' - 'list via project.report.formats), or call a per-format ' + 'Set project.report.{cif,html,tex,pdf} = True, or call a per-format ' 'method directly (project.report.save_html(), etc.).' ) @@ -131,9 +129,8 @@ def html_offline(self) -> BoolDescriptor: def html_offline(self, value: bool) -> None: self._html_offline.value = value - @property - def formats(self) -> list[ReportFormatEnum]: - """Enabled report-output formats.""" + def _enabled_formats(self) -> list[ReportFormatEnum]: + """Return report-output formats enabled by boolean flags.""" formats = [] if self._cif.value: formats.append(ReportFormatEnum.CIF) @@ -145,21 +142,6 @@ def formats(self) -> list[ReportFormatEnum]: formats.append(ReportFormatEnum.PDF) return formats - @formats.setter - def formats( - self, - formats: Iterable[ReportFormatEnum | str] | ReportFormatEnum | str, - ) -> None: - if isinstance(formats, (ReportFormatEnum, str)): - values = [formats] - else: - values = list(formats) - enabled = {ReportFormatEnum(value) for value in values} - self.cif = ReportFormatEnum.CIF in enabled - self.html = ReportFormatEnum.HTML in enabled - self.tex = ReportFormatEnum.TEX in enabled - self.pdf = ReportFormatEnum.PDF in enabled - @property def project(self) -> object: """Project owning this report category.""" @@ -494,7 +476,7 @@ def _save_configured(self) -> list[pathlib.Path]: """Write enabled formats, returning quietly when none are set.""" report_paths = [] tex_path = None - for report_format in self.formats: + for report_format in self._enabled_formats(): if report_format is ReportFormatEnum.CIF: report_paths.append(self.save_cif()) elif report_format is ReportFormatEnum.HTML: diff --git a/src/easydiffraction/report/__init__.py b/src/easydiffraction/report/__init__.py index 9d9149898..a91c621a9 100644 --- a/src/easydiffraction/report/__init__.py +++ b/src/easydiffraction/report/__init__.py @@ -4,9 +4,6 @@ from __future__ import annotations -from easydiffraction.report.enums import ReportFormatEnum - - def __getattr__(name: str) -> object: """Load report objects that would otherwise form import cycles.""" if name == 'Report': diff --git a/src/easydiffraction/report/enums.py b/src/easydiffraction/report/enums.py index eceea0818..95cabde86 100644 --- a/src/easydiffraction/report/enums.py +++ b/src/easydiffraction/report/enums.py @@ -14,8 +14,3 @@ class ReportFormatEnum(StrEnum): HTML = 'html' TEX = 'tex' PDF = 'pdf' - - @classmethod - def default(cls) -> ReportFormatEnum: - """Return the default report format.""" - return cls.CIF diff --git a/src/easydiffraction/report/pdf_compiler.py b/src/easydiffraction/report/pdf_compiler.py index ee13b9120..fb9d0581f 100644 --- a/src/easydiffraction/report/pdf_compiler.py +++ b/src/easydiffraction/report/pdf_compiler.py @@ -23,7 +23,7 @@ pixi add tectonic conda install -c conda-forge tectonic # or any TeX Live distribution (latexmk / pdflatex) -Then re-run project.save() with 'pdf' in project.report.formats or +Then set project.report.pdf = True and re-run project.save(), or call project.report.save_pdf(). The .tex and data/ bundle remains under reports/tex/.""" diff --git a/tests/unit/easydiffraction/project/categories/report/test_default.py b/tests/unit/easydiffraction/project/categories/report/test_default.py index 1b6cf231d..87f0c12df 100644 --- a/tests/unit/easydiffraction/project/categories/report/test_default.py +++ b/tests/unit/easydiffraction/project/categories/report/test_default.py @@ -4,6 +4,30 @@ from __future__ import annotations +import pytest + + +def test_report_has_no_formats_property(): + from easydiffraction.project.categories.report.default import Report + + report = Report() + + with pytest.raises(AttributeError, match="Unknown attribute 'formats'"): + report.formats # noqa: B018 + + +def test_report_save_without_outputs_points_to_boolean_flags(): + from easydiffraction.project.categories.report.default import Report + + report = Report() + + with pytest.raises(ValueError) as exc_info: + report.save() + + message = str(exc_info.value) + assert 'project.report.{cif,html,tex,pdf}' in message + assert 'project.report.formats' not in message + def test_save_configured_reuses_tex_bundle_for_pdf(tmp_path, monkeypatch): from easydiffraction.project.categories.report.default import Report @@ -13,7 +37,8 @@ def test_save_configured_reuses_tex_bundle_for_pdf(tmp_path, monkeypatch): pdf_path = tmp_path / 'reports' / 'demo.pdf' calls = [] report = Report() - report.formats = ['tex', 'pdf'] + report.tex = True + report.pdf = True def fake_save_tex(self): del self @@ -46,7 +71,8 @@ def test_save_configured_omits_missing_compiled_pdf(tmp_path, monkeypatch): tex_path = tmp_path / 'reports' / 'tex' / 'demo.tex' pdf_path = tmp_path / 'reports' / 'demo.pdf' report = Report() - report.formats = ['tex', 'pdf'] + report.tex = True + report.pdf = True def fake_save_tex(self): del self From 3d7ce4e2205f02246f1d4e26757d0c8b587efe9b Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Fri, 29 May 2026 18:57:17 +0200 Subject: [PATCH 073/129] Fix console report lattice rows --- src/easydiffraction/project/categories/report/default.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/easydiffraction/project/categories/report/default.py b/src/easydiffraction/project/categories/report/default.py index 80cffe8ca..49f689e40 100644 --- a/src/easydiffraction/project/categories/report/default.py +++ b/src/easydiffraction/project/categories/report/default.py @@ -211,8 +211,8 @@ def show_crystallographic_data(self) -> None: columns_alignment = ['left', 'right', 'right', 'left'] columns_data = [ Report._fmt_row('a', structure.cell.length_a), - Report._fmt_row('b', structure.cell.length_a), - Report._fmt_row('c', structure.cell.length_a), + Report._fmt_row('b', structure.cell.length_b), + Report._fmt_row('c', structure.cell.length_c), Report._fmt_row('α', structure.cell.angle_alpha), # noqa: RUF001 Report._fmt_row('β', structure.cell.angle_beta), Report._fmt_row('γ', structure.cell.angle_gamma), # noqa: RUF001 @@ -272,7 +272,7 @@ def show_experimental_data(self) -> None: console.print( f'{expt.type.sample_form.value}, ' f'{expt.type.radiation_probe.value}, ' - f'{expt.type.beam_mode.value}', + f'{expt.type.beam_mode.value}, ' f'{expt.type.scattering_type.value}', ) From 7168a306cf66a3c66a65b4947715ab974b7aae08 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Fri, 29 May 2026 18:57:35 +0200 Subject: [PATCH 074/129] Remove orphaned report module tests --- .../easydiffraction/report/test_report.py | 107 ----------- .../report/test_report_details.py | 168 ------------------ 2 files changed, 275 deletions(-) delete mode 100644 tests/unit/easydiffraction/report/test_report.py delete mode 100644 tests/unit/easydiffraction/report/test_report_details.py diff --git a/tests/unit/easydiffraction/report/test_report.py b/tests/unit/easydiffraction/report/test_report.py deleted file mode 100644 index 8d64e785f..000000000 --- a/tests/unit/easydiffraction/report/test_report.py +++ /dev/null @@ -1,107 +0,0 @@ -# SPDX-FileCopyrightText: 2026 EasyScience contributors -# SPDX-License-Identifier: BSD-3-Clause - -from __future__ import annotations - -from types import SimpleNamespace - - -def _project(tmp_path): - return SimpleNamespace( - name='report_project', - info=SimpleNamespace(path=tmp_path), - structures={}, - experiments={}, - analysis=SimpleNamespace( - minimizer=SimpleNamespace(type='lmfit'), - fit_result=SimpleNamespace(), - ), - ) - - -def test_report_save_writes_submission_cif(tmp_path): - from easydiffraction.report.report import Report - - report = Report(_project(tmp_path)) - - report_path = report.save() - - assert report_path == tmp_path / 'reports' / 'report_project.cif' - assert report_path.is_file() - assert report_path.read_text(encoding='utf-8').startswith('data_global\n') - - -def test_report_save_check_runs_validation(tmp_path, monkeypatch): - from easydiffraction.report.report import Report - - report = Report(_project(tmp_path)) - checked_paths = [] - - def fake_check(*, path=None): - checked_paths.append(path) - - monkeypatch.setattr(report, 'check', fake_check) - - report_path = report.save(check=True) - - assert checked_paths == [report_path] - - -def test_report_show_report_prints_sections(capsys): - from easydiffraction.report.report import Report - - class Info: - title = 'T' - description = '' - - class Project: - def __init__(self): - self.info = Info() - self.structures = {} # empty mapping to exercise loops safely - self.experiments = {} # empty mapping to exercise loops safely - - class A: - class Minimizer: - type = 'lmfit' - - minimizer = Minimizer() - - class R: - reduced_chi_square = 0.0 - - fit_results = R() - - self.analysis = A() - - report = Report(Project()) - report.show_report() - out = capsys.readouterr().out - # Verify that all top-level sections appear (titles are uppercased by formatter) - assert 'PROJECT INFO' in out - assert 'CRYSTALLOGRAPHIC DATA' in out - assert 'EXPERIMENTS' in out - assert 'FITTING' in out - - -def test_report_help(capsys): - from easydiffraction.report.report import Report - - class P: - pass - - report = Report(P()) - report.help() - out = capsys.readouterr().out - assert 'save()' in out - assert 'check()' in out - assert 'show_report()' in out - assert 'show_project_info()' in out - assert 'show_fitting_details()' in out - - -def test_module_import(): - import easydiffraction.report.report as MUT - - expected_module_name = 'easydiffraction.report.report' - actual_module_name = MUT.__name__ - assert expected_module_name == actual_module_name diff --git a/tests/unit/easydiffraction/report/test_report_details.py b/tests/unit/easydiffraction/report/test_report_details.py deleted file mode 100644 index dd11946eb..000000000 --- a/tests/unit/easydiffraction/report/test_report_details.py +++ /dev/null @@ -1,168 +0,0 @@ -# SPDX-FileCopyrightText: 2026 EasyScience contributors -# SPDX-License-Identifier: BSD-3-Clause - -# -- Stub classes for test_report_crystallographic_and_experimental --- - - -class _Val: - def __init__(self, v, uncertainty=None): - self.value = v - self.uncertainty = uncertainty - - -class _CellParam: - def __init__(self, name, value, uncertainty=None): - self.name = name - self.value = value - self.uncertainty = uncertainty - - -class _Cell: - def __init__(self): - self.length_a = _CellParam('length_a', 5.4321) - self.length_b = _CellParam('length_b', 5.4321) - self.length_c = _CellParam('length_c', 5.4321) - self.angle_alpha = _CellParam('angle_alpha', 90.0) - self.angle_beta = _CellParam('angle_beta', 90.0) - self.angle_gamma = _CellParam('angle_gamma', 90.0) - - -class _Site: - def __init__(self, label, typ, x, y, z, occ, biso): - self.label = _Val(label) - self.type_symbol = _Val(typ) - self.fract_x = _Val(x) - self.fract_y = _Val(y) - self.fract_z = _Val(z) - self.occupancy = _Val(occ) - self.adp_iso = _Val(biso) - - -class _Model: - def __init__(self): - self.name = 'phaseA' - self.space_group = type('SG', (), {'name_h_m': _Val('P 1')})() - self.cell = _Cell() - self.atom_sites = [_Site('Na1', 'Na', 0.1, 0.2, 0.3, 1.0, 0.5)] - - -class _Instr: - def __init__(self): - self.setup_wavelength = _Val(1.23456) - self.calib_twotheta_offset = _Val(0.12345) - - def _public_attrs(self): - return ['setup_wavelength', 'calib_twotheta_offset'] - - -class _Peak: - def __init__(self): - self.type = 'pseudo-Voigt' - self.broad_gauss_u = _Val(0.1) - self.broad_gauss_v = _Val(0.2) - self.broad_gauss_w = _Val(0.3) - self.broad_lorentz_x = _Val(0.4) - self.broad_lorentz_y = _Val(0.5) - - def _public_attrs(self): - return [ - 'broad_gauss_u', - 'broad_gauss_v', - 'broad_gauss_w', - 'broad_lorentz_x', - 'broad_lorentz_y', - ] - - -class _Expt: - def __init__(self): - self.name = 'exp1' - typ = type( - 'T', - (), - { - 'sample_form': _Val('powder'), - 'radiation_probe': _Val('neutron'), - 'beam_mode': _Val('constant wavelength'), - 'scattering_type': _Val('total'), - }, - ) - self.type = typ() - self.calculator = type( - 'Calculator', - (), - {'type': 'cryspy'}, - )() - self.instrument = _Instr() - self.peak = _Peak() - - def _public_attrs(self): - return ['instrument', 'peak'] - - -class _Info: - title = 'T' - description = '' - - -class _StubProject: - def __init__(self): - self.info = _Info() - self.structures = {'phaseA': _Model()} - self.experiments = {'exp1': _Expt()} - - class A: - class Minimizer: - type = 'lmfit' - - minimizer = Minimizer() - - class R: - reduced_chi_square = 1.23 - - fit_results = R() - - self.analysis = A() - - -# ---------------------------------------------------------------------- - - -def test_report_crystallographic_and_experimental_sections(capsys): - from easydiffraction.report.report import Report - - report = Report(_StubProject()) - # Run both sections separately for targeted assertions - report.show_crystallographic_data() - report.show_experimental_data() - out = capsys.readouterr().out - - # Crystallographic section - assert 'CRYSTALLOGRAPHIC DATA' in out - assert '🧩 phaseA' in out - assert 'Space group' in out - assert 'P 1' in out - assert 'Parameter' in out - assert ' a ' in out - assert ' α ' in out # noqa: RUF001 - assert 'Atom sites' in out - assert 'Na1' in out - assert 'Na' in out - - # Experimental section - assert 'EXPERIMENTS' in out - assert '🔬 exp1' in out - assert 'powder' in out - assert 'neutron' in out - assert 'constant wavelength' in out - assert 'total' in out - assert 'Calculation engine' in out - assert 'cryspy' in out - assert 'Wavelength' in out - assert '1.23456'[:6] in out - assert '2θ offset' in out - assert '0.12345'[:6] in out - assert 'Profile type' in out - assert 'pseudo-Voigt' in out - assert 'Peak broadening (Gaussian)' in out - assert 'Peak broadening (Lorentzian)' in out From 300145e986d63b12d59dfda9d26c8bb2fb1fcd2e Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Fri, 29 May 2026 18:58:04 +0200 Subject: [PATCH 075/129] Return intended PDF report path --- src/easydiffraction/project/categories/report/default.py | 3 +-- .../easydiffraction/project/categories/report/test_default.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/easydiffraction/project/categories/report/default.py b/src/easydiffraction/project/categories/report/default.py index 49f689e40..19b771f7e 100644 --- a/src/easydiffraction/project/categories/report/default.py +++ b/src/easydiffraction/project/categories/report/default.py @@ -495,6 +495,5 @@ def _save_configured(self) -> list[pathlib.Path]: ) pdf_path = compile_pdf_report(tex_path) - if pdf_path.is_file(): - report_paths.append(pdf_path) + report_paths.append(pdf_path) return report_paths diff --git a/tests/unit/easydiffraction/project/categories/report/test_default.py b/tests/unit/easydiffraction/project/categories/report/test_default.py index 87f0c12df..679d9c637 100644 --- a/tests/unit/easydiffraction/project/categories/report/test_default.py +++ b/tests/unit/easydiffraction/project/categories/report/test_default.py @@ -64,7 +64,7 @@ def fake_compile_pdf_report(path): assert calls == ['tex', ('pdf', tex_path)] -def test_save_configured_omits_missing_compiled_pdf(tmp_path, monkeypatch): +def test_save_configured_returns_intended_missing_pdf(tmp_path, monkeypatch): from easydiffraction.project.categories.report.default import Report from easydiffraction.report import pdf_compiler @@ -85,4 +85,4 @@ def fake_compile_pdf_report(path): monkeypatch.setattr(Report, 'save_tex', fake_save_tex) monkeypatch.setattr(pdf_compiler, 'compile_pdf_report', fake_compile_pdf_report) - assert report._save_configured() == [tex_path] + assert report._save_configured() == [tex_path, pdf_path] From 3cbeb6c1c70b48b3fa305f183b15a34aaeb9d0a0 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Fri, 29 May 2026 18:59:43 +0200 Subject: [PATCH 076/129] Load IUCr dictionaries lazily --- src/easydiffraction/report/check.py | 37 ++++++++++++++----- .../unit/easydiffraction/report/test_check.py | 13 +++++-- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/easydiffraction/report/check.py b/src/easydiffraction/report/check.py index cfd074519..ce0f68034 100644 --- a/src/easydiffraction/report/check.py +++ b/src/easydiffraction/report/check.py @@ -7,6 +7,7 @@ import pathlib import re from dataclasses import dataclass +from functools import cache from typing import TYPE_CHECKING from typing import NoReturn @@ -36,6 +37,16 @@ def ok(self) -> bool: return not self.errors +@dataclass(frozen=True) +class _DictionaryCache: + """Parsed dictionary state for generated-CIF validation.""" + + paths: tuple[pathlib.Path, ...] + documents: tuple[gemmi.cif.Document, ...] + load_errors: tuple[str, ...] + tags: frozenset[str] + + def check_report( path: str | pathlib.Path, *, @@ -156,7 +167,7 @@ def _unknown_tag_warnings( return [f'Unknown IUCr tag: {tag}' for tag in unknown_tags] -def _unknown_tag_errors(content: str, known_tags: set[str]) -> list[str]: +def _unknown_tag_errors(content: str, known_tags: frozenset[str]) -> list[str]: """Return unknown-tag diagnostics from CIF content.""" if not known_tags: return [] @@ -179,11 +190,18 @@ def _known_dictionary_tags(dictionary_paths: tuple[pathlib.Path, ...]) -> set[st return tags -_CACHED_DICTIONARY_PATHS = _dictionary_paths(None) -_CACHED_DICTIONARY_DOCUMENTS, _CACHED_DICTIONARY_LOAD_ERRORS = ( - _read_dictionary_documents(_CACHED_DICTIONARY_PATHS) -) -_CACHED_DICTIONARY_TAGS = _known_dictionary_tags(_CACHED_DICTIONARY_PATHS) +@cache +def _cached_dictionaries() -> _DictionaryCache: + """Return lazily loaded dictionary state for writer validation.""" + dictionary_paths = _dictionary_paths(None) + dictionary_documents, load_errors = _read_dictionary_documents(dictionary_paths) + dictionary_tags = frozenset(_known_dictionary_tags(dictionary_paths)) + return _DictionaryCache( + paths=dictionary_paths, + documents=dictionary_documents, + load_errors=load_errors, + tags=dictionary_tags, + ) def _validate_iucr_cif(content: str) -> None: @@ -194,11 +212,12 @@ def _validate_iucr_cif(content: str) -> None: _raise_writer_error(f'Failed to parse generated IUCr CIF: {exc}') diagnostics: list[str] = [] - if _CACHED_DICTIONARY_DOCUMENTS: + dictionary_cache = _cached_dictionaries() + if dictionary_cache.documents: diagnostics.extend( - _gemmi_dictionary_errors(document, _CACHED_DICTIONARY_DOCUMENTS) + _gemmi_dictionary_errors(document, dictionary_cache.documents) ) - diagnostics.extend(_unknown_tag_errors(content, _CACHED_DICTIONARY_TAGS)) + diagnostics.extend(_unknown_tag_errors(content, dictionary_cache.tags)) if diagnostics: _raise_writer_error('\n'.join(diagnostics)) diff --git a/tests/unit/easydiffraction/report/test_check.py b/tests/unit/easydiffraction/report/test_check.py index d2883cc4a..71a1c8e50 100644 --- a/tests/unit/easydiffraction/report/test_check.py +++ b/tests/unit/easydiffraction/report/test_check.py @@ -43,10 +43,15 @@ def test_validate_iucr_cif_skips_unloadable_optional_dictionaries(monkeypatch): monkeypatch.setattr( check_mod, - '_CACHED_DICTIONARY_LOAD_ERRORS', - ('Failed to load CIF dictionary tmp/iucr-dicts/cif_core.dic',), + '_cached_dictionaries', + lambda: check_mod._DictionaryCache( + paths=(), + documents=(), + load_errors=( + 'Failed to load CIF dictionary tmp/iucr-dicts/cif_core.dic', + ), + tags=frozenset(), + ), ) - monkeypatch.setattr(check_mod, '_CACHED_DICTIONARY_DOCUMENTS', ()) - monkeypatch.setattr(check_mod, '_CACHED_DICTIONARY_TAGS', set()) check_mod._validate_iucr_cif('data_test\n_audit.creation_method EasyDiffraction\n') From b6211de85ea33fd8a7ca4dc4d0c6d16bfb9ce0e7 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Fri, 29 May 2026 19:00:55 +0200 Subject: [PATCH 077/129] Warn on skipped IUCr dictionary loads --- src/easydiffraction/report/check.py | 7 +++++++ tests/unit/easydiffraction/report/test_check.py | 15 ++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/easydiffraction/report/check.py b/src/easydiffraction/report/check.py index ce0f68034..fb7bff6d0 100644 --- a/src/easydiffraction/report/check.py +++ b/src/easydiffraction/report/check.py @@ -14,6 +14,7 @@ import gemmi from easydiffraction.core.errors import EasyDiffractionWriterError +from easydiffraction.utils.logging import log if TYPE_CHECKING: from collections.abc import Iterable @@ -213,6 +214,12 @@ def _validate_iucr_cif(content: str) -> None: diagnostics: list[str] = [] dictionary_cache = _cached_dictionaries() + if dictionary_cache.load_errors: + load_error_text = '\n'.join(dictionary_cache.load_errors) + log.warning( + 'Generated IUCr CIF validation skipped one or more dictionaries:\n' + + load_error_text + ) if dictionary_cache.documents: diagnostics.extend( _gemmi_dictionary_errors(document, dictionary_cache.documents) diff --git a/tests/unit/easydiffraction/report/test_check.py b/tests/unit/easydiffraction/report/test_check.py index 71a1c8e50..97e9f5549 100644 --- a/tests/unit/easydiffraction/report/test_check.py +++ b/tests/unit/easydiffraction/report/test_check.py @@ -41,6 +41,7 @@ def test_check_report_warns_for_unknown_non_extension_tags(tmp_path): def test_validate_iucr_cif_skips_unloadable_optional_dictionaries(monkeypatch): from easydiffraction.report import check as check_mod + warnings = [] monkeypatch.setattr( check_mod, '_cached_dictionaries', @@ -53,5 +54,17 @@ def test_validate_iucr_cif_skips_unloadable_optional_dictionaries(monkeypatch): tags=frozenset(), ), ) + monkeypatch.setattr( + check_mod.log, + 'warning', + staticmethod(lambda *parts: warnings.append(' '.join(parts))), + ) - check_mod._validate_iucr_cif('data_test\n_audit.creation_method EasyDiffraction\n') + check_mod._validate_iucr_cif( + 'data_test\n_audit.creation_method EasyDiffraction\n' + ) + + assert ( + 'Failed to load CIF dictionary tmp/iucr-dicts/cif_core.dic' + in warnings[0] + ) From cd39f39608ece111c0af311e3804c1fd93c9fb18 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Fri, 29 May 2026 19:01:38 +0200 Subject: [PATCH 078/129] Use powder phase id tag in IUCr refln loop --- docs/dev/adrs/accepted/iucr-cif-tag-alignment.md | 6 ++++-- docs/dev/plans/iucr-cif-tag-alignment.md | 3 ++- src/easydiffraction/io/cif/iucr_writer.py | 2 +- tests/unit/easydiffraction/io/cif/test_iucr_writer.py | 2 ++ 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/dev/adrs/accepted/iucr-cif-tag-alignment.md b/docs/dev/adrs/accepted/iucr-cif-tag-alignment.md index 5962bc97f..f191f85ab 100644 --- a/docs/dev/adrs/accepted/iucr-cif-tag-alignment.md +++ b/docs/dev/adrs/accepted/iucr-cif-tag-alignment.md @@ -640,12 +640,14 @@ _refln.index_k _refln.index_l _refln.F_squared_meas _refln.F_squared_calc -_refln.phase_calc +_pd_refln.phase_id _refln.d_spacing ``` Column set adapted from the corpus content (`bal5001.cif`, `hb8206.cif`) -with tag form taken from `cif_core.dic`. +with tag form taken from `cif_core.dic` and `cif_pow.dic`. The phase +identifier uses the powder dictionary's `_pd_refln.phase_id`; it is not +the calculated structure-factor phase angle `_refln.phase_calc`. #### 2.3e Powder profile-data loop diff --git a/docs/dev/plans/iucr-cif-tag-alignment.md b/docs/dev/plans/iucr-cif-tag-alignment.md index 4658b5728..d1169e3c3 100644 --- a/docs/dev/plans/iucr-cif-tag-alignment.md +++ b/docs/dev/plans/iucr-cif-tag-alignment.md @@ -389,7 +389,8 @@ generated-artifact exceptions. `_pd_meas.intensity_total`, `_pd_calc.intensity_total`, `_pd_proc.intensity_bkg_calc`, `_pd_proc_ls.weight`. - Powder reflections loop per §2.3d: - `_refln.{index_h/k/l, F_squared_meas, F_squared_calc, phase_calc, d_spacing}`. + `_refln.{index_h/k/l, F_squared_meas, F_squared_calc, d_spacing}` + plus `_pd_refln.phase_id` for the powder phase identifier. - Cross-block reference markers (`_pd_block_id`, `_pd_block_diffractogram_id`) emitted with pipe-delimited identifiers matching the §2.3 examples. diff --git a/src/easydiffraction/io/cif/iucr_writer.py b/src/easydiffraction/io/cif/iucr_writer.py index 8e5b61ce2..a248b6668 100644 --- a/src/easydiffraction/io/cif/iucr_writer.py +++ b/src/easydiffraction/io/cif/iucr_writer.py @@ -675,7 +675,7 @@ def _write_powder_refln_loop(lines: list[str], experiment: object) -> None: '_refln.index_l', '_refln.F_squared_meas', '_refln.F_squared_calc', - '_refln.phase_calc', + '_pd_refln.phase_id', '_refln.d_spacing', ), rows, diff --git a/tests/unit/easydiffraction/io/cif/test_iucr_writer.py b/tests/unit/easydiffraction/io/cif/test_iucr_writer.py index 6a891d123..afa40fa57 100644 --- a/tests/unit/easydiffraction/io/cif/test_iucr_writer.py +++ b/tests/unit/easydiffraction/io/cif/test_iucr_writer.py @@ -317,6 +317,8 @@ def test_write_iucr_cif_emits_powder_cwl_blocks(tmp_path): assert 'data_powder_pwd_1' in text assert '_pd_meas.2theta_scan' in text assert '_pd_meas.time_of_flight' not in text + assert '_pd_refln.phase_id' in text + assert '_refln.phase_calc' not in text assert '_pd_proc.info_excluded_regions' in text assert '_easydiffraction_background.type' in text From a9691a06591ce7be62977e380c2f51f0e8f7b4ea Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Fri, 29 May 2026 19:02:13 +0200 Subject: [PATCH 079/129] Remove dead PDF compiler helpers --- src/easydiffraction/report/pdf_compiler.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/easydiffraction/report/pdf_compiler.py b/src/easydiffraction/report/pdf_compiler.py index fb9d0581f..bddaa4a34 100644 --- a/src/easydiffraction/report/pdf_compiler.py +++ b/src/easydiffraction/report/pdf_compiler.py @@ -105,14 +105,6 @@ def _find_engines() -> list[tuple[str, str]]: return engines -def _find_engine() -> tuple[str, str] | None: - """Return the first available TeX engine.""" - engines = _find_engines() - if not engines: - return None - return engines[0] - - def _compile_report_bundle( engine: tuple[str, str], tex_path: pathlib.Path, @@ -168,7 +160,7 @@ def _compile_pdf( result = subprocess.run( command, cwd=compile_tex_path.parent, - env=_compile_environment(compile_tex_path), + env=_compile_environment(), text=True, capture_output=True, check=False, @@ -228,9 +220,8 @@ def _compile_command( ] -def _compile_environment(tex_path: pathlib.Path) -> dict[str, str]: +def _compile_environment() -> dict[str, str]: """Return a TeX subprocess environment.""" - del tex_path return os.environ.copy() From a928f11c4f0deb9122b0152a089c723a82616ac5 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Fri, 29 May 2026 19:10:27 +0200 Subject: [PATCH 080/129] Apply pixi run fix auto-fixes --- src/easydiffraction/__init__.py | 8 ++++++-- src/easydiffraction/core/datablock.py | 2 +- .../datablocks/experiment/item/base.py | 4 +++- src/easydiffraction/io/cif/iucr_writer.py | 4 +++- .../project/categories/publication/default.py | 3 ++- .../project/categories/report/default.py | 13 +++++++------ src/easydiffraction/project/publication_loader.py | 4 +++- 7 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/easydiffraction/__init__.py b/src/easydiffraction/__init__.py index d647a12a8..0f055d442 100644 --- a/src/easydiffraction/__init__.py +++ b/src/easydiffraction/__init__.py @@ -9,7 +9,9 @@ def _path_is_writable(path: pathlib.Path) -> bool: - """Return whether a directory can be used for runtime cache files.""" + """ + Return whether a directory can be used for runtime cache files. + """ try: path.mkdir(parents=True, exist_ok=True) probe = path / '.easydiffraction-write-test' @@ -21,7 +23,9 @@ def _path_is_writable(path: pathlib.Path) -> bool: def _ensure_matplotlib_config_dir() -> None: - """Set a stable Matplotlib cache dir when the default is unusable.""" + """ + Set a stable Matplotlib cache dir when the default is unusable. + """ if os.environ.get('MPLCONFIGDIR'): return default_dir = pathlib.Path.home() / '.matplotlib' diff --git a/src/easydiffraction/core/datablock.py b/src/easydiffraction/core/datablock.py index 7dca0d4d1..623ab4d90 100644 --- a/src/easydiffraction/core/datablock.py +++ b/src/easydiffraction/core/datablock.py @@ -49,7 +49,7 @@ def _cif_for_display( Parameters ---------- - max_loop_display : int, default=20 + max_loop_display : int, default=DEFAULT_LOOP_DISPLAY_LIMIT Maximum number of rows to show per loop category. Returns diff --git a/src/easydiffraction/datablocks/experiment/item/base.py b/src/easydiffraction/datablocks/experiment/item/base.py index 1e8b49ac2..c6a1e2f9a 100644 --- a/src/easydiffraction/datablocks/experiment/item/base.py +++ b/src/easydiffraction/datablocks/experiment/item/base.py @@ -632,7 +632,9 @@ def data(self) -> object: @property def x_descriptor(self) -> NumericDescriptor: - """Descriptor that owns the powder experiment's x-axis metadata.""" + """ + Descriptor that owns the powder experiment's x-axis metadata. + """ return self.data.x_descriptor def fit_data_arrays(self) -> dict[str, np.ndarray | None]: diff --git a/src/easydiffraction/io/cif/iucr_writer.py b/src/easydiffraction/io/cif/iucr_writer.py index a248b6668..e5a7b8e78 100644 --- a/src/easydiffraction/io/cif/iucr_writer.py +++ b/src/easydiffraction/io/cif/iucr_writer.py @@ -179,7 +179,9 @@ def _write_computing_section(lines: list[str], project: object) -> None: def _write_publication_sections(lines: list[str], project: object) -> None: - """Append publication metadata from the project publication owner.""" + """ + Append publication metadata from the project publication owner. + """ publication = getattr(project, 'publication', None) _write_publication_item_section( lines, diff --git a/src/easydiffraction/project/categories/publication/default.py b/src/easydiffraction/project/categories/publication/default.py index e2ca8001b..7e140a578 100644 --- a/src/easydiffraction/project/categories/publication/default.py +++ b/src/easydiffraction/project/categories/publication/default.py @@ -654,6 +654,7 @@ def load(self, path: str | pathlib.Path) -> None: Raises ------ ValueError - If the file extension, top-level shape, or any key is invalid. + If the file extension, top-level shape, or any key is + invalid. """ load_publication(self, path) diff --git a/src/easydiffraction/project/categories/report/default.py b/src/easydiffraction/project/categories/report/default.py index 19b771f7e..693762f9e 100644 --- a/src/easydiffraction/project/categories/report/default.py +++ b/src/easydiffraction/project/categories/report/default.py @@ -388,7 +388,6 @@ def save_html(self, offline: bool = False) -> pathlib.Path: ------- pathlib.Path Path of the written HTML report. - """ from easydiffraction.report.html_renderer import save_html_report # noqa: PLC0415 @@ -445,8 +444,8 @@ def save_pdf(self) -> pathlib.Path: Returns ------- pathlib.Path - Path of the PDF report, or the intended PDF path when no - TeX engine is available. + Path of the PDF report, or the intended PDF path when no TeX + engine is available. """ from easydiffraction.report.pdf_compiler import save_pdf_report # noqa: PLC0415 @@ -464,8 +463,8 @@ def save(self) -> list[pathlib.Path]: Raises ------ ValueError - If no report formats are configured. ``project.save()`` - is the no-op-on-empty entry point. + If no report formats are configured. ``project.save()`` is + the no-op-on-empty entry point. """ report_paths = self._save_configured() if not report_paths: @@ -473,7 +472,9 @@ def save(self) -> list[pathlib.Path]: return report_paths def _save_configured(self) -> list[pathlib.Path]: - """Write enabled formats, returning quietly when none are set.""" + """ + Write enabled formats, returning quietly when none are set. + """ report_paths = [] tex_path = None for report_format in self._enabled_formats(): diff --git a/src/easydiffraction/project/publication_loader.py b/src/easydiffraction/project/publication_loader.py index 1ce4c8aff..9bbf30613 100644 --- a/src/easydiffraction/project/publication_loader.py +++ b/src/easydiffraction/project/publication_loader.py @@ -163,7 +163,9 @@ def _apply_author_rows( publication: Publication, authors: list[dict[str, str | None]], ) -> None: - """Replace the publication author collection with normalized rows.""" + """ + Replace the publication author collection with normalized rows. + """ publication.authors._adopt_items([]) publication.authors._mark_parent_dirty() for author in authors: From 963d8943bd4b0d9507fffba7e8e4aa4c9ad3b609 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Fri, 29 May 2026 19:11:00 +0200 Subject: [PATCH 081/129] Apply ruff format auto-fixes --- src/easydiffraction/analysis/analysis.py | 3 +- src/easydiffraction/core/units_vocabulary.py | 44 +++++----- .../experiment/categories/data/total_pd.py | 2 +- .../categories/extinction/becker_coppens.py | 2 +- .../categories/atom_site_aniso/default.py | 2 +- .../project/categories/report/default.py | 6 +- .../project/publication_loader.py | 16 ++-- src/easydiffraction/report/__init__.py | 3 +- src/easydiffraction/report/check.py | 7 +- src/easydiffraction/report/data_context.py | 88 ++++++------------- src/easydiffraction/report/fit_plot.py | 21 ++--- src/easydiffraction/report/html_renderer.py | 14 +-- src/easydiffraction/report/pdf_compiler.py | 10 +-- src/easydiffraction/report/tex_renderer.py | 46 ++++------ .../io/cif/test_iucr_writer.py | 5 +- .../project/categories/report/test_default.py | 2 +- .../unit/easydiffraction/report/test_check.py | 13 +-- .../report/test_data_context.py | 43 ++++----- .../report/test_tex_renderer.py | 18 ++-- 19 files changed, 126 insertions(+), 219 deletions(-) diff --git a/src/easydiffraction/analysis/analysis.py b/src/easydiffraction/analysis/analysis.py index 62c474380..a749a7d27 100644 --- a/src/easydiffraction/analysis/analysis.py +++ b/src/easydiffraction/analysis/analysis.py @@ -611,8 +611,7 @@ def _combine_software_values( if not unique_values: return None, None, None return tuple( - ', '.join(str(value[index]) for value in unique_values if value[index]) - or None + ', '.join(str(value[index]) for value in unique_values if value[index]) or None for index in range(3) ) diff --git a/src/easydiffraction/core/units_vocabulary.py b/src/easydiffraction/core/units_vocabulary.py index e9fa510cc..bd538d106 100644 --- a/src/easydiffraction/core/units_vocabulary.py +++ b/src/easydiffraction/core/units_vocabulary.py @@ -4,29 +4,27 @@ from __future__ import annotations -VALID_UNITS_CODES: frozenset[str] = frozenset( - { - 'angstrom_squared', - 'angstroms', - 'arcminutes', - 'degrees', - 'degrees_squared', - 'kelvins', - 'kilopascals', - 'microsecond_angstroms', - 'microseconds', - 'microseconds_per_angstrom', - 'microseconds_per_angstrom_squared', - 'microseconds_squared', - 'microseconds_squared_per_angstrom_squared', - 'micrometres', - 'none', - 'reciprocal_angstrom_squared', - 'reciprocal_angstroms', - 'teslas', - 'volts_per_metre', - } -) +VALID_UNITS_CODES: frozenset[str] = frozenset({ + 'angstrom_squared', + 'angstroms', + 'arcminutes', + 'degrees', + 'degrees_squared', + 'kelvins', + 'kilopascals', + 'microsecond_angstroms', + 'microseconds', + 'microseconds_per_angstrom', + 'microseconds_per_angstrom_squared', + 'microseconds_squared', + 'microseconds_squared_per_angstrom_squared', + 'micrometres', + 'none', + 'reciprocal_angstrom_squared', + 'reciprocal_angstroms', + 'teslas', + 'volts_per_metre', +}) _LEGACY_UNITS_ALIASES: dict[str, str] = { '': 'none', diff --git a/src/easydiffraction/datablocks/experiment/categories/data/total_pd.py b/src/easydiffraction/datablocks/experiment/categories/data/total_pd.py index b729009a4..d1fd4a31d 100644 --- a/src/easydiffraction/datablocks/experiment/categories/data/total_pd.py +++ b/src/easydiffraction/datablocks/experiment/categories/data/total_pd.py @@ -8,10 +8,10 @@ from easydiffraction.core.category import CategoryCollection from easydiffraction.core.category import CategoryItem +from easydiffraction.core.display_handler import DisplayHandler from easydiffraction.core.metadata import CalculatorSupport from easydiffraction.core.metadata import Compatibility from easydiffraction.core.metadata import TypeInfo -from easydiffraction.core.display_handler import DisplayHandler from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.validation import MembershipValidator from easydiffraction.core.validation import RangeValidator diff --git a/src/easydiffraction/datablocks/experiment/categories/extinction/becker_coppens.py b/src/easydiffraction/datablocks/experiment/categories/extinction/becker_coppens.py index 82bb88b01..4ddd12c8b 100644 --- a/src/easydiffraction/datablocks/experiment/categories/extinction/becker_coppens.py +++ b/src/easydiffraction/datablocks/experiment/categories/extinction/becker_coppens.py @@ -6,10 +6,10 @@ from __future__ import annotations +from easydiffraction.core.display_handler import DisplayHandler from easydiffraction.core.metadata import CalculatorSupport from easydiffraction.core.metadata import Compatibility from easydiffraction.core.metadata import TypeInfo -from easydiffraction.core.display_handler import DisplayHandler from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.validation import MembershipValidator from easydiffraction.core.validation import RangeValidator diff --git a/src/easydiffraction/datablocks/structure/categories/atom_site_aniso/default.py b/src/easydiffraction/datablocks/structure/categories/atom_site_aniso/default.py index afa623195..03a3c9473 100644 --- a/src/easydiffraction/datablocks/structure/categories/atom_site_aniso/default.py +++ b/src/easydiffraction/datablocks/structure/categories/atom_site_aniso/default.py @@ -12,8 +12,8 @@ from easydiffraction.core.category import CategoryCollection from easydiffraction.core.category import CategoryItem -from easydiffraction.core.metadata import TypeInfo from easydiffraction.core.display_handler import DisplayHandler +from easydiffraction.core.metadata import TypeInfo from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.validation import RangeValidator from easydiffraction.core.variable import Parameter diff --git a/src/easydiffraction/project/categories/report/default.py b/src/easydiffraction/project/categories/report/default.py index 693762f9e..e09b30b36 100644 --- a/src/easydiffraction/project/categories/report/default.py +++ b/src/easydiffraction/project/categories/report/default.py @@ -11,8 +11,8 @@ from easydiffraction.core.metadata import TypeInfo from easydiffraction.core.validation import AttributeSpec from easydiffraction.core.variable import BoolDescriptor -from easydiffraction.io.cif.iucr_writer import write_iucr_cif from easydiffraction.io.cif.handler import CifHandler +from easydiffraction.io.cif.iucr_writer import write_iucr_cif from easydiffraction.project.categories.report.factory import ReportFactory from easydiffraction.report.data_context import build_report_data_context from easydiffraction.report.enums import ReportFormatEnum @@ -481,9 +481,7 @@ def _save_configured(self) -> list[pathlib.Path]: if report_format is ReportFormatEnum.CIF: report_paths.append(self.save_cif()) elif report_format is ReportFormatEnum.HTML: - report_paths.append( - self.save_html(offline=bool(self.html_offline.value)) - ) + report_paths.append(self.save_html(offline=bool(self.html_offline.value))) elif report_format is ReportFormatEnum.TEX: tex_path = self.save_tex() report_paths.append(tex_path) diff --git a/src/easydiffraction/project/publication_loader.py b/src/easydiffraction/project/publication_loader.py index 9bbf30613..ab4104b02 100644 --- a/src/easydiffraction/project/publication_loader.py +++ b/src/easydiffraction/project/publication_loader.py @@ -121,15 +121,13 @@ def _author_rows(value: object) -> list[dict[str, str | None]]: if key not in _AUTHOR_FIELDS: raise ValueError(f'authors.{key}') - rows.append( - { - 'name': _required_text(f'authors[{idx}].name', row.get('name')), - 'address': _optional_text(f'authors[{idx}].address', row.get('address')), - 'footnote': _optional_text(f'authors[{idx}].footnote', row.get('footnote')), - 'id_orcid': _optional_text(f'authors[{idx}].id_orcid', row.get('id_orcid')), - 'id_iucr': _optional_text(f'authors[{idx}].id_iucr', row.get('id_iucr')), - } - ) + rows.append({ + 'name': _required_text(f'authors[{idx}].name', row.get('name')), + 'address': _optional_text(f'authors[{idx}].address', row.get('address')), + 'footnote': _optional_text(f'authors[{idx}].footnote', row.get('footnote')), + 'id_orcid': _optional_text(f'authors[{idx}].id_orcid', row.get('id_orcid')), + 'id_iucr': _optional_text(f'authors[{idx}].id_iucr', row.get('id_iucr')), + }) return rows diff --git a/src/easydiffraction/report/__init__.py b/src/easydiffraction/report/__init__.py index a91c621a9..fbf353cc5 100644 --- a/src/easydiffraction/report/__init__.py +++ b/src/easydiffraction/report/__init__.py @@ -4,11 +4,12 @@ from __future__ import annotations + def __getattr__(name: str) -> object: """Load report objects that would otherwise form import cycles.""" if name == 'Report': from easydiffraction.project.categories.report.default import Report return Report - msg = f"module {__name__!r} has no attribute {name!r}" + msg = f'module {__name__!r} has no attribute {name!r}' raise AttributeError(msg) diff --git a/src/easydiffraction/report/check.py b/src/easydiffraction/report/check.py index fb7bff6d0..4d221a378 100644 --- a/src/easydiffraction/report/check.py +++ b/src/easydiffraction/report/check.py @@ -217,13 +217,10 @@ def _validate_iucr_cif(content: str) -> None: if dictionary_cache.load_errors: load_error_text = '\n'.join(dictionary_cache.load_errors) log.warning( - 'Generated IUCr CIF validation skipped one or more dictionaries:\n' - + load_error_text + 'Generated IUCr CIF validation skipped one or more dictionaries:\n' + load_error_text ) if dictionary_cache.documents: - diagnostics.extend( - _gemmi_dictionary_errors(document, dictionary_cache.documents) - ) + diagnostics.extend(_gemmi_dictionary_errors(document, dictionary_cache.documents)) diagnostics.extend(_unknown_tag_errors(content, dictionary_cache.tags)) if diagnostics: diff --git a/src/easydiffraction/report/data_context.py b/src/easydiffraction/report/data_context.py index 79417fa8c..2b9a7b2a3 100644 --- a/src/easydiffraction/report/data_context.py +++ b/src/easydiffraction/report/data_context.py @@ -123,9 +123,7 @@ _REPORT_LOOP_DISPLAY_LIMIT = DEFAULT_LOOP_DISPLAY_LIMIT _FULL_WIDTH_TABLE_CHAR_LIMIT = 40 _TRUNCATED_DATA_CATEGORY_CODES = frozenset({'pd_data', 'total_data'}) -_NUMERIC_TEXT_RE = re.compile( - r'^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:\(\d+\))?(?:[eE][+-]?\d+)?$' -) +_NUMERIC_TEXT_RE = re.compile(r'^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:\(\d+\))?(?:[eE][+-]?\d+)?$') _NUMBER_PARTS_RE = re.compile( r'^(?P[+-]?)' r'(?:(?P\d+)(?:\.(?P\d*))?' @@ -166,9 +164,7 @@ def build(self) -> dict[str, object]: return { 'project': self._project_context(structures, experiments), 'structures': [self._structure_context(structure) for structure in structures], - 'experiments': [ - self._experiment_context(experiment) for experiment in experiments - ], + 'experiments': [self._experiment_context(experiment) for experiment in experiments], 'refinement': self._refinement_context(), 'software': self._software_context(), 'publication': self._publication_context(), @@ -217,10 +213,7 @@ def _structure_context(self, structure: object) -> dict[str, object]: _STRUCTURE_CELL_FIELDS, context='latex', ), - 'atom_sites': [ - self._atom_site_context(atom_site) - for atom_site in atom_sites - ], + 'atom_sites': [self._atom_site_context(atom_site) for atom_site in atom_sites], 'atom_site_display': _display_field_metadata( atom_sites[0] if atom_sites else None, _ATOM_SITE_FIELDS, @@ -232,8 +225,7 @@ def _structure_context(self, structure: object) -> dict[str, object]: context='latex', ), 'atom_site_aniso': [ - self._atom_site_aniso_context(aniso_site) - for aniso_site in aniso_sites + self._atom_site_aniso_context(aniso_site) for aniso_site in aniso_sites ], 'atom_site_aniso_display': _display_field_metadata( aniso_sites[0] if aniso_sites else None, @@ -457,8 +449,7 @@ def _display_field_metadata( ) -> dict[str, dict[str, str]]: """Return display labels and units for a fixed field list.""" return { - field: _display_metadata(_safe_attr(owner, field), context=context) - for field in fields + field: _display_metadata(_safe_attr(owner, field), context=context) for field in fields } @@ -569,9 +560,7 @@ def _collection_category_context( rows = [row for row in rows if _loop_row_has_report_values(row)] if truncate: rows = _truncate_loop_rows(rows) - scalar_rows = _defined_descriptor_rows( - _descriptor_rows(category.scalar_descriptors) - ) + scalar_rows = _defined_descriptor_rows(_descriptor_rows(category.scalar_descriptors)) _mark_numeric_columns(columns, rows) _apply_cell_number_alignment(columns, rows) return { @@ -609,11 +598,7 @@ def _loop_row_has_report_values(row: dict[str, object]) -> bool: def _loop_row_value_count(row: dict[str, object]) -> int: """Return the number of populated cells in a loop row.""" - return sum( - 1 - for cell in row['cells'] - if not _is_empty_value(cell['value']) - ) + return sum(1 for cell in row['cells'] if not _is_empty_value(cell['value'])) def _truncate_loop_rows( @@ -635,8 +620,7 @@ def _ellipsis_loop_row(reference_row: dict[str, object]) -> dict[str, object]: """Return an ellipsis row matching a loop row shape.""" cells = [{'value': '...', 'numeric': False, 'number': None}] cells.extend( - {'value': '', 'numeric': False, 'number': None} - for _ in reference_row['cells'][1:] + {'value': '', 'numeric': False, 'number': None} for _ in reference_row['cells'][1:] ) return {'cells': cells} @@ -660,10 +644,7 @@ def _estimated_loop_table_chars( for index, column in enumerate(columns): label_width = len(_plain_table_text(_column_display_label(column))) value_width = max( - ( - len(_plain_table_text(row['cells'][index]['value'])) - for row in rows - ), + (len(_plain_table_text(row['cells'][index]['value'])) for row in rows), default=0, ) widths.append(max(label_width, value_width) + 4) @@ -694,8 +675,7 @@ def _collection_columns( if not items: return [] return [ - _column_context(parameter) - for parameter in _collection_loop_parameters(category, items[0]) + _column_context(parameter) for parameter in _collection_loop_parameters(category, items[0]) ] @@ -738,23 +718,18 @@ def _descriptor_rows( rows = [] for parameter in parameters: value = _display_value(parameter) - rows.append( - { - 'name': parameter.name, - 'label': _display_label(parameter, context='html'), - 'latex_label': _display_label(parameter, context='latex'), - 'html_label': _html_label(parameter), - 'units': _display_units(_descriptor_units(parameter, context='html')), - 'latex_units': _display_units( - _descriptor_units(parameter, context='latex') - ), - 'html_units': _html_units(parameter), - 'value': value, - 'numeric': _descriptor_is_numeric(parameter) - and _is_numeric_value(value), - 'number': None, - } - ) + rows.append({ + 'name': parameter.name, + 'label': _display_label(parameter, context='html'), + 'latex_label': _display_label(parameter, context='latex'), + 'html_label': _html_label(parameter), + 'units': _display_units(_descriptor_units(parameter, context='html')), + 'latex_units': _display_units(_descriptor_units(parameter, context='latex')), + 'html_units': _html_units(parameter), + 'value': value, + 'numeric': _descriptor_is_numeric(parameter) and _is_numeric_value(value), + 'number': None, + }) _apply_row_number_alignment(rows) return rows @@ -781,9 +756,7 @@ def _mark_numeric_columns( """Mark each loop column that can use numeric alignment.""" for index, column in enumerate(columns): column_values = [row['cells'][index]['value'] for row in rows] - is_numeric = bool(column['numeric_candidate']) and _values_are_numeric( - column_values - ) + is_numeric = bool(column['numeric_candidate']) and _values_are_numeric(column_values) column['numeric'] = is_numeric if is_numeric: column['table_format'] = _siunitx_table_format(column_values) @@ -867,9 +840,7 @@ def _siunitx_number_parts(value: object) -> dict[str, object] | None: def _apply_row_number_alignment(rows: list[dict[str, object]]) -> None: """Add HTML decimal-alignment metadata to key-value rows.""" - number_parts = [ - _number_parts(row['value']) if row['numeric'] else None for row in rows - ] + number_parts = [_number_parts(row['value']) if row['numeric'] else None for row in rows] left_ch, right_ch = _number_widths(number_parts) for row, parts in zip(rows, number_parts, strict=True): row['number'] = _number_context(parts, left_ch, right_ch) @@ -883,9 +854,7 @@ def _apply_cell_number_alignment( for index, column in enumerate(columns): if not column['numeric']: continue - number_parts = [ - _number_parts(row['cells'][index]['value']) for row in rows - ] + number_parts = [_number_parts(row['cells'][index]['value']) for row in rows] left_ch, right_ch = _number_widths(number_parts) column['number_left_ch'] = left_ch column['number_right_ch'] = right_ch @@ -963,9 +932,7 @@ def _number_text(value: object) -> str: def _values_are_numeric(values: Iterable[object]) -> bool: """Return whether all populated values are numeric.""" populated_values = [value for value in values if not _is_empty_value(value)] - return bool(populated_values) and all( - _is_numeric_value(value) for value in populated_values - ) + return bool(populated_values) and all(_is_numeric_value(value) for value in populated_values) def _rows_have_numeric_values(rows: Iterable[dict[str, object]]) -> bool: @@ -1083,7 +1050,8 @@ def _degree_unit_math(value: str) -> str: def _plain_unit_text(value: str) -> str: """Return plain unit text normalized for report display.""" return ( - value.replace('degrees_squared', 'deg^2') + value + .replace('degrees_squared', 'deg^2') .replace('degree_squared', 'deg^2') .replace('degrees squared', 'deg^2') .replace('degree squared', 'deg^2') diff --git a/src/easydiffraction/report/fit_plot.py b/src/easydiffraction/report/fit_plot.py index a9d821af3..de06f2217 100644 --- a/src/easydiffraction/report/fit_plot.py +++ b/src/easydiffraction/report/fit_plot.py @@ -64,8 +64,7 @@ def fit_plot_styles() -> dict[str, dict[str, Any]]: """Return Plotly-derived series styles for report figures.""" return { - key: _fit_plot_style(key, source_key) - for key, source_key in _STYLE_SOURCE_KEYS.items() + key: _fit_plot_style(key, source_key) for key, source_key in _STYLE_SOURCE_KEYS.items() } @@ -118,10 +117,7 @@ def fit_plot_geometry(fit_data: dict[str, Any]) -> dict[str, float]: height_sum = sum(row_heights) stack_height = _FIGURE_AXIS_WIDTH_CM * _FIGURE_AXIS_HEIGHT_TO_WIDTH row_area_height = stack_height * _subplot_available_height_fraction(row_count) - scaled_heights = [ - row_area_height * row_height / height_sum - for row_height in row_heights - ] + scaled_heights = [row_area_height * row_height / height_sum for row_height in row_heights] return { 'axis_width_cm': _FIGURE_AXIS_WIDTH_CM, 'main_height_cm': scaled_heights[0], @@ -190,12 +186,8 @@ def _non_bragg_row_heights( has_residual: bool, ) -> tuple[float, float | None]: plot_area_height = _composite_plot_area_height() - available_row_pixels = ( - plot_area_height * _subplot_available_height_fraction(row_count) - ) - baseline_bragg_pixels = ( - _bragg_tick_symbol_height_pixels() if has_bragg_ticks else 0.0 - ) + available_row_pixels = plot_area_height * _subplot_available_height_fraction(row_count) + baseline_bragg_pixels = _bragg_tick_symbol_height_pixels() if has_bragg_ticks else 0.0 non_bragg_pixels = max(available_row_pixels - baseline_bragg_pixels, 1.0) if not has_residual: @@ -216,10 +208,7 @@ def _subplot_available_height_fraction(row_count: int) -> float: def _bragg_tick_symbol_height_pixels() -> float: - return ( - BRAGG_TICK_MARKER_SIZE * BRAGG_TICK_SYMBOL_HEIGHT_SCALE - + BRAGG_TICK_MARKER_LINE_WIDTH - ) + return BRAGG_TICK_MARKER_SIZE * BRAGG_TICK_SYMBOL_HEIGHT_SCALE + BRAGG_TICK_MARKER_LINE_WIDTH def _bragg_row_height_pixels(tick_set_count: int) -> float: diff --git a/src/easydiffraction/report/html_renderer.py b/src/easydiffraction/report/html_renderer.py index 9e20bbdb4..dc557a60e 100644 --- a/src/easydiffraction/report/html_renderer.py +++ b/src/easydiffraction/report/html_renderer.py @@ -234,11 +234,7 @@ def _fit_data_figure( residual_height_fraction=DEFAULT_RESID_HEIGHT, bragg_peaks_height_fraction=DEFAULT_BRAGG_ROW, y_bkg=np.asarray(y_bkg, dtype=float) if y_bkg is not None else None, - y_meas_su=( - np.asarray(y_meas_su, dtype=float) - if y_meas_su is not None - else None - ), + y_meas_su=(np.asarray(y_meas_su, dtype=float) if y_meas_su is not None else None), ) ) @@ -307,7 +303,7 @@ def _axis_title(x_data: dict[str, object]) -> str: """Return a display axis title for fit figures.""" units = x_data.get('display_units') if units: - return f"{x_data.get('display_name')} ({units})" + return f'{x_data.get("display_name")} ({units})' return str(x_data.get('display_name') or '') @@ -338,8 +334,4 @@ def _experiment_contexts(context: dict[str, object]) -> list[dict[str, object]]: experiments = context.get('experiments') if not isinstance(experiments, list): return [] - return [ - experiment - for experiment in experiments - if isinstance(experiment, dict) - ] + return [experiment for experiment in experiments if isinstance(experiment, dict)] diff --git a/src/easydiffraction/report/pdf_compiler.py b/src/easydiffraction/report/pdf_compiler.py index bddaa4a34..e208ac266 100644 --- a/src/easydiffraction/report/pdf_compiler.py +++ b/src/easydiffraction/report/pdf_compiler.py @@ -171,10 +171,7 @@ def _compile_pdf( msg = _compiler_error_message(engine_name, tex_path, result) raise RuntimeError(msg) if not compile_pdf_path.is_file(): - msg = ( - f"TeX engine '{engine_name}' completed but did not write " - f"'{pdf_path}'." - ) + msg = f"TeX engine '{engine_name}' completed but did not write '{pdf_path}'." raise RuntimeError(msg) return None @@ -234,7 +231,4 @@ def _compiler_error_message( details = (result.stderr or result.stdout).strip() if len(details) > 4000: details = details[-4000:] - return ( - f"TeX engine '{engine_name}' failed while compiling '{tex_path}'.\n" - f'{details}' - ) + return f"TeX engine '{engine_name}' failed while compiling '{tex_path}'.\n{details}" diff --git a/src/easydiffraction/report/tex_renderer.py b/src/easydiffraction/report/tex_renderer.py index 3ab5c2654..316478586 100644 --- a/src/easydiffraction/report/tex_renderer.py +++ b/src/easydiffraction/report/tex_renderer.py @@ -311,7 +311,9 @@ def _write_fit_figure_tex( 'bragg_styles': fit_bragg_tick_styles(), } figure_path.write_text( - _environment().get_template(_FIGURE_TEMPLATE_NAME).render( + _environment() + .get_template(_FIGURE_TEMPLATE_NAME) + .render( **template_context, ), encoding='utf-8', @@ -347,10 +349,7 @@ def _project_experiments_by_id(project: object) -> dict[str, object]: values = getattr(experiments, 'values', None) if not callable(values): return {} - return { - str(getattr(experiment, 'name', '')): experiment - for experiment in values() - } + return {str(getattr(experiment, 'name', '')): experiment for experiment in values()} def _experiment_contexts(context: dict[str, object]) -> list[dict[str, object]]: @@ -358,11 +357,7 @@ def _experiment_contexts(context: dict[str, object]) -> list[dict[str, object]]: experiments = context.get('experiments') if not isinstance(experiments, list): return [] - return [ - experiment - for experiment in experiments - if isinstance(experiment, dict) - ] + return [experiment for experiment in experiments if isinstance(experiment, dict)] def _fit_csv_filename(expt_id: str) -> str: @@ -402,11 +397,14 @@ def _fit_csv_columns( row_count = len(list(x_data['values'])) columns = [ - (_FIT_X_FIELD_TAGS[x_field], _fit_csv_values( - category_values, - x_field, - list(x_data['values']), - )), + ( + _FIT_X_FIELD_TAGS[x_field], + _fit_csv_values( + category_values, + x_field, + list(x_data['values']), + ), + ), ] fallback_values = { 'point_id': [str(index + 1) for index in range(row_count)], @@ -603,13 +601,11 @@ def _bragg_tick_sources( bragg_csv = bragg_csvs.get(phase_id) if bragg_csv is None: continue - sources.append( - { - 'phase_id': phase_id, - 'csv_filename': bragg_csv['filename'], - 'x_column': bragg_csv['x_column'], - } - ) + sources.append({ + 'phase_id': phase_id, + 'csv_filename': bragg_csv['filename'], + 'x_column': bragg_csv['x_column'], + }) return sources @@ -697,11 +693,7 @@ def _context_loop_values(category: dict[str, object]) -> dict[str, list[object]] """Return loop values from a prepared category context.""" columns = category.get('columns') or [] rows = category.get('rows') or [] - names = [ - str(column.get('name')) - for column in columns - if isinstance(column, dict) - ] + names = [str(column.get('name')) for column in columns if isinstance(column, dict)] values = {name: [] for name in names} for row in rows: if not isinstance(row, dict): diff --git a/tests/unit/easydiffraction/io/cif/test_iucr_writer.py b/tests/unit/easydiffraction/io/cif/test_iucr_writer.py index afa40fa57..12217863d 100644 --- a/tests/unit/easydiffraction/io/cif/test_iucr_writer.py +++ b/tests/unit/easydiffraction/io/cif/test_iucr_writer.py @@ -449,10 +449,7 @@ def test_iucr_extinction_extensions_preserve_parameter_uncertainties(): extinction.radius.free = True extinction.radius.uncertainty = 0.4931 experiment = SimpleNamespace(extinction=extinction) - items = { - item.tag: item.value - for item in _extinction_items(experiment, extension=True) - } + items = {item.tag: item.value for item in _extinction_items(experiment, extension=True)} lines = [] _write_item( diff --git a/tests/unit/easydiffraction/project/categories/report/test_default.py b/tests/unit/easydiffraction/project/categories/report/test_default.py index 679d9c637..fdc96da8a 100644 --- a/tests/unit/easydiffraction/project/categories/report/test_default.py +++ b/tests/unit/easydiffraction/project/categories/report/test_default.py @@ -13,7 +13,7 @@ def test_report_has_no_formats_property(): report = Report() with pytest.raises(AttributeError, match="Unknown attribute 'formats'"): - report.formats # noqa: B018 + report.formats def test_report_save_without_outputs_points_to_boolean_flags(): diff --git a/tests/unit/easydiffraction/report/test_check.py b/tests/unit/easydiffraction/report/test_check.py index 97e9f5549..56b3a3611 100644 --- a/tests/unit/easydiffraction/report/test_check.py +++ b/tests/unit/easydiffraction/report/test_check.py @@ -48,9 +48,7 @@ def test_validate_iucr_cif_skips_unloadable_optional_dictionaries(monkeypatch): lambda: check_mod._DictionaryCache( paths=(), documents=(), - load_errors=( - 'Failed to load CIF dictionary tmp/iucr-dicts/cif_core.dic', - ), + load_errors=('Failed to load CIF dictionary tmp/iucr-dicts/cif_core.dic',), tags=frozenset(), ), ) @@ -60,11 +58,6 @@ def test_validate_iucr_cif_skips_unloadable_optional_dictionaries(monkeypatch): staticmethod(lambda *parts: warnings.append(' '.join(parts))), ) - check_mod._validate_iucr_cif( - 'data_test\n_audit.creation_method EasyDiffraction\n' - ) + check_mod._validate_iucr_cif('data_test\n_audit.creation_method EasyDiffraction\n') - assert ( - 'Failed to load CIF dictionary tmp/iucr-dicts/cif_core.dic' - in warnings[0] - ) + assert 'Failed to load CIF dictionary tmp/iucr-dicts/cif_core.dic' in warnings[0] diff --git a/tests/unit/easydiffraction/report/test_data_context.py b/tests/unit/easydiffraction/report/test_data_context.py index 26a615af5..39c0d22bd 100644 --- a/tests/unit/easydiffraction/report/test_data_context.py +++ b/tests/unit/easydiffraction/report/test_data_context.py @@ -224,10 +224,7 @@ def test_report_category_context_keeps_numeric_string_ids_as_text(): context = _collection_category_context(category) assert context['colspec'] == 'lS[table-format=2.0]S[table-format=1.0]' - assert [ - (column['latex_label'], column['numeric']) - for column in context['columns'] - ] == [ + assert [(column['latex_label'], column['numeric']) for column in context['columns']] == [ ('ID', False), ('$x$', True), ('Intensity', True), @@ -318,10 +315,7 @@ def test_report_pd_data_columns_use_compact_labels(): context = _collection_category_context(category) - assert [ - (column['latex_label'], column['html_label']) - for column in context['columns'] - ] == [ + assert [(column['latex_label'], column['html_label']) for column in context['columns']] == [ (r'$2\theta$', r'\(2\theta\)'), ('ID', 'ID'), (r'$d$', r'\(d\)'), @@ -344,28 +338,23 @@ def test_report_powder_refln_columns_use_compact_labels(): from easydiffraction.report.data_context import _collection_category_context category = PowderCwlReflnData() - category._replace_from_records( - [ - PowderReflnRecord( - phase_id='phase', - d_spacing=1.0, - sin_theta_over_lambda=0.5, - index_h=1, - index_k=0, - index_l=1, - f_calc=2.0, - f_squared_calc=4.0, - two_theta=20.0, - ) - ] - ) + category._replace_from_records([ + PowderReflnRecord( + phase_id='phase', + d_spacing=1.0, + sin_theta_over_lambda=0.5, + index_h=1, + index_k=0, + index_l=1, + f_calc=2.0, + f_squared_calc=4.0, + two_theta=20.0, + ) + ]) context = _collection_category_context(category) - assert [ - (column['latex_label'], column['html_label']) - for column in context['columns'] - ] == [ + assert [(column['latex_label'], column['html_label']) for column in context['columns']] == [ ('ID', 'ID'), ('Phase', 'Phase'), (r'$d$', r'\(d\)'), diff --git a/tests/unit/easydiffraction/report/test_tex_renderer.py b/tests/unit/easydiffraction/report/test_tex_renderer.py index 4a8c7bdbe..55bd25c3f 100644 --- a/tests/unit/easydiffraction/report/test_tex_renderer.py +++ b/tests/unit/easydiffraction/report/test_tex_renderer.py @@ -225,9 +225,7 @@ def test_render_tex_report_preserves_structure_uncertainty_text(): ], }, ], - 'colspec': ( - 'lS[table-format=2.3(2)]S[table-format=1.5(2)]' - ), + 'colspec': ('lS[table-format=2.3(2)]S[table-format=1.5(2)]'), 'table_width': 'full', }, { @@ -401,12 +399,16 @@ def test_save_tex_report_uses_composite_pgfplots_with_error_bars(tmp_path): figure_tex = (tex_path.parent / 'data' / 'hrpt.tex').read_text( encoding='utf-8', ) - csv_header = (tex_path.parent / 'data' / 'hrpt.csv').read_text( - encoding='utf-8', - ).splitlines()[0] + csv_header = ( + (tex_path.parent / 'data' / 'hrpt.csv') + .read_text( + encoding='utf-8', + ) + .splitlines()[0] + ) bragg_csv_header = ( - tex_path.parent / 'data' / 'hrpt_phase-a.csv' - ).read_text(encoding='utf-8').splitlines()[0] + (tex_path.parent / 'data' / 'hrpt_phase-a.csv').read_text(encoding='utf-8').splitlines()[0] + ) assert r'\usepackage{fourier}' in figure_tex assert r'\usepackage{paratype}' in figure_tex From 89712f8c9d543e729b96805400cc53513f08b5ad Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Fri, 29 May 2026 19:21:27 +0200 Subject: [PATCH 082/129] Fix Phase 2 lint failures --- src/easydiffraction/__init__.py | 36 +- src/easydiffraction/__main__.py | 5 +- .../analysis/calculators/crysfml.py | 11 +- .../analysis/calculators/pdffit.py | 7 +- src/easydiffraction/core/variable.py | 2 +- .../datablocks/experiment/item/base.py | 3 +- src/easydiffraction/display/plotting.py | 85 +++-- src/easydiffraction/io/cif/iucr_writer.py | 29 +- .../project/categories/publication/default.py | 7 +- .../project/categories/report/default.py | 308 +++++++++--------- src/easydiffraction/project/project.py | 2 +- .../project/publication_loader.py | 21 +- src/easydiffraction/report/__init__.py | 10 +- src/easydiffraction/report/pdf_compiler.py | 14 +- .../utils/matplotlib_config.py | 36 ++ .../project/categories/report/test_default.py | 2 +- .../report/test_pdf_compiler.py | 2 +- 17 files changed, 306 insertions(+), 274 deletions(-) create mode 100644 src/easydiffraction/utils/matplotlib_config.py diff --git a/src/easydiffraction/__init__.py b/src/easydiffraction/__init__.py index 0f055d442..8b73614c1 100644 --- a/src/easydiffraction/__init__.py +++ b/src/easydiffraction/__init__.py @@ -3,41 +3,6 @@ from __future__ import annotations -import os -import pathlib -import tempfile - - -def _path_is_writable(path: pathlib.Path) -> bool: - """ - Return whether a directory can be used for runtime cache files. - """ - try: - path.mkdir(parents=True, exist_ok=True) - probe = path / '.easydiffraction-write-test' - probe.write_text('', encoding='utf-8') - probe.unlink() - except OSError: - return False - return True - - -def _ensure_matplotlib_config_dir() -> None: - """ - Set a stable Matplotlib cache dir when the default is unusable. - """ - if os.environ.get('MPLCONFIGDIR'): - return - default_dir = pathlib.Path.home() / '.matplotlib' - if _path_is_writable(default_dir): - return - fallback_dir = pathlib.Path(tempfile.gettempdir()) / 'easydiffraction-matplotlib' - if _path_is_writable(fallback_dir): - os.environ['MPLCONFIGDIR'] = str(fallback_dir) - - -_ensure_matplotlib_config_dir() - from easydiffraction.datablocks.experiment.item.factory import ExperimentFactory from easydiffraction.datablocks.structure.item.factory import StructureFactory from easydiffraction.io.ascii import extract_data_paths_from_dir @@ -45,6 +10,7 @@ def _ensure_matplotlib_config_dir() -> None: from easydiffraction.io.ascii import extract_metadata from easydiffraction.io.ascii import extract_project_from_zip from easydiffraction.project.project import Project +from easydiffraction.utils import matplotlib_config as _matplotlib_config from easydiffraction.utils.logging import Logger from easydiffraction.utils.logging import console from easydiffraction.utils.logging import log diff --git a/src/easydiffraction/__main__.py b/src/easydiffraction/__main__.py index e151a218a..443e2fa94 100644 --- a/src/easydiffraction/__main__.py +++ b/src/easydiffraction/__main__.py @@ -3,8 +3,11 @@ from __future__ import annotations -import pathlib import sys +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + import pathlib # Ensure UTF-8 output on all platforms (e.g. Windows with cp1252) if hasattr(sys.stdout, 'reconfigure'): diff --git a/src/easydiffraction/analysis/calculators/crysfml.py b/src/easydiffraction/analysis/calculators/crysfml.py index 4774366f3..72e645f17 100644 --- a/src/easydiffraction/analysis/calculators/crysfml.py +++ b/src/easydiffraction/analysis/calculators/crysfml.py @@ -3,6 +3,7 @@ from __future__ import annotations +from typing import TYPE_CHECKING from typing import Any import numpy as np @@ -10,11 +11,13 @@ from easydiffraction.analysis.calculators.base import CalculatorBase from easydiffraction.analysis.calculators.factory import CalculatorFactory from easydiffraction.core.metadata import TypeInfo -from easydiffraction.datablocks.experiment.collection import Experiments -from easydiffraction.datablocks.experiment.item.base import ExperimentBase from easydiffraction.datablocks.experiment.item.enums import BeamModeEnum -from easydiffraction.datablocks.structure.collection import Structures -from easydiffraction.datablocks.structure.item.base import Structure + +if TYPE_CHECKING: + from easydiffraction.datablocks.experiment.collection import Experiments + from easydiffraction.datablocks.experiment.item.base import ExperimentBase + from easydiffraction.datablocks.structure.collection import Structures + from easydiffraction.datablocks.structure.item.base import Structure try: from crysfml import cfml_py_utilities diff --git a/src/easydiffraction/analysis/calculators/pdffit.py b/src/easydiffraction/analysis/calculators/pdffit.py index 19f5021b7..0f0193190 100644 --- a/src/easydiffraction/analysis/calculators/pdffit.py +++ b/src/easydiffraction/analysis/calculators/pdffit.py @@ -12,14 +12,17 @@ import os import re from pathlib import Path +from typing import TYPE_CHECKING import numpy as np from easydiffraction.analysis.calculators.base import CalculatorBase from easydiffraction.analysis.calculators.factory import CalculatorFactory from easydiffraction.core.metadata import TypeInfo -from easydiffraction.datablocks.experiment.item.base import ExperimentBase -from easydiffraction.datablocks.structure.item.base import Structure + +if TYPE_CHECKING: + from easydiffraction.datablocks.experiment.item.base import ExperimentBase + from easydiffraction.datablocks.structure.item.base import Structure def _open_pdffit_devnull() -> object: diff --git a/src/easydiffraction/core/variable.py b/src/easydiffraction/core/variable.py index f2ad76714..16152b3fd 100644 --- a/src/easydiffraction/core/variable.py +++ b/src/easydiffraction/core/variable.py @@ -8,7 +8,6 @@ import numpy as np from easydiffraction.core.diagnostic import Diagnostics -from easydiffraction.core.display_handler import DisplayHandler from easydiffraction.core.guard import GuardedBase from easydiffraction.core.units_vocabulary import normalize_units_code from easydiffraction.core.validation import AttributeSpec @@ -20,6 +19,7 @@ from easydiffraction.utils.logging import log if TYPE_CHECKING: + from easydiffraction.core.display_handler import DisplayHandler from easydiffraction.core.posterior import PosteriorParameterSummary from easydiffraction.io.cif.handler import CifHandler diff --git a/src/easydiffraction/datablocks/experiment/item/base.py b/src/easydiffraction/datablocks/experiment/item/base.py index c6a1e2f9a..e0093b7be 100644 --- a/src/easydiffraction/datablocks/experiment/item/base.py +++ b/src/easydiffraction/datablocks/experiment/item/base.py @@ -511,7 +511,8 @@ def x_descriptor(self) -> NumericDescriptor | None: """Return None because single-crystal data has no 1-D x axis.""" return None - def fit_data_arrays(self) -> dict[str, np.ndarray | None]: + @staticmethod + def fit_data_arrays() -> dict[str, np.ndarray | None]: """Return no 1-D fit arrays for single-crystal experiments.""" return {} diff --git a/src/easydiffraction/display/plotting.py b/src/easydiffraction/display/plotting.py index 448f6cb73..7a5079172 100644 --- a/src/easydiffraction/display/plotting.py +++ b/src/easydiffraction/display/plotting.py @@ -5325,6 +5325,54 @@ def _plot_calc_data( excluded_ranges=excluded_ranges, ) + def _powder_meas_vs_calc_series( + self, + pattern: object, + ctx: dict[str, object], + plot_options: _MeasVsCalcPlotOptions, + ) -> _PowderMeasVsCalcSeries: + """Return filtered measured/calculated powder series.""" + y_meas = self._filtered_y_array( + pattern.intensity_meas, ctx['x_array'], ctx['x_min'], ctx['x_max'] + ) + y_calc = self._filtered_y_array( + pattern.intensity_calc, ctx['x_array'], ctx['x_min'], ctx['x_max'] + ) + y_meas_su = self._optional_filtered_y_array( + getattr(pattern, 'intensity_meas_su', None), + ctx, + ) + y_bkg = self._optional_filtered_y_array( + getattr(pattern, 'intensity_bkg', None), + ctx, + ) + if not self._show_background_enabled( + plot_options, + background_available=y_bkg is not None, + ): + y_bkg = None + return _PowderMeasVsCalcSeries( + y_meas=y_meas, + y_calc=y_calc, + y_meas_su=y_meas_su, + y_bkg=y_bkg, + ) + + def _optional_filtered_y_array( + self, + values: object | None, + ctx: dict[str, object], + ) -> np.ndarray | None: + """Return filtered optional y values.""" + if values is None: + return None + return self._filtered_y_array( + values, + ctx['x_array'], + ctx['x_min'], + ctx['x_max'], + ) + def _plot_meas_vs_calc_data( self, experiment: object, @@ -5396,38 +5444,7 @@ def _plot_meas_vs_calc_data( if ctx is None: return - y_meas = self._filtered_y_array( - pattern.intensity_meas, ctx['x_array'], ctx['x_min'], ctx['x_max'] - ) - y_calc = self._filtered_y_array( - pattern.intensity_calc, ctx['x_array'], ctx['x_min'], ctx['x_max'] - ) - y_meas_su_raw = getattr(pattern, 'intensity_meas_su', None) - y_meas_su = ( - self._filtered_y_array( - y_meas_su_raw, - ctx['x_array'], - ctx['x_min'], - ctx['x_max'], - ) - if y_meas_su_raw is not None - else None - ) - y_bkg_raw = getattr(pattern, 'intensity_bkg', None) - y_bkg = ( - self._filtered_y_array(y_bkg_raw, ctx['x_array'], ctx['x_min'], ctx['x_max']) - if y_bkg_raw is not None - else None - ) - if not self._show_background_enabled(plot_options, background_available=y_bkg is not None): - y_bkg = None - - powder_series = _PowderMeasVsCalcSeries( - y_meas=y_meas, - y_calc=y_calc, - y_meas_su=y_meas_su, - y_bkg=y_bkg, - ) + powder_series = self._powder_meas_vs_calc_series(pattern, ctx, plot_options) excluded_ranges = ( self._excluded_ranges( experiment=experiment, @@ -5452,8 +5469,8 @@ def _plot_meas_vs_calc_data( self._plot_line_meas_vs_calc( ctx=ctx, - y_meas=y_meas, - y_calc=y_calc, + y_meas=powder_series.y_meas, + y_calc=powder_series.y_calc, show_residual=False if plot_options.show_residual is None else plot_options.show_residual, diff --git a/src/easydiffraction/io/cif/iucr_writer.py b/src/easydiffraction/io/cif/iucr_writer.py index e5a7b8e78..8719e56a2 100644 --- a/src/easydiffraction/io/cif/iucr_writer.py +++ b/src/easydiffraction/io/cif/iucr_writer.py @@ -776,8 +776,7 @@ def _write_loop( lines.append('loop_') lines.extend(tag_list) - for row in formatted_rows: - lines.append(f' {" ".join(row)}') + lines.extend(f' {" ".join(row)}' for row in formatted_rows) def _write_item(lines: list[str], tag: str, value: object) -> None: @@ -800,18 +799,20 @@ def _section(lines: list[str], title: str) -> None: def _format_item_value(value: object) -> str: """Format a CIF item value for report output.""" if _is_cif_descriptor(value): - return _format_descriptor_value(value) - if not isinstance(value, str): - return format_value(value) - if value in {'?', '.'}: - return value - if '\n' in value or len(value) > _TEXT_WRAP_WIDTH: - return _format_text_field(value) - if not value.strip(): - return '?' - if _needs_quotes(value): - return _quote_string(value) - return value + formatted = _format_descriptor_value(value) + elif not isinstance(value, str): + formatted = format_value(value) + elif value in {'?', '.'}: + formatted = value + elif '\n' in value or len(value) > _TEXT_WRAP_WIDTH: + formatted = _format_text_field(value) + elif not value.strip(): + formatted = '?' + elif _needs_quotes(value): + formatted = _quote_string(value) + else: + formatted = value + return formatted def _format_descriptor_value(value: object) -> str: diff --git a/src/easydiffraction/project/categories/publication/default.py b/src/easydiffraction/project/categories/publication/default.py index 7e140a578..0e94a64fc 100644 --- a/src/easydiffraction/project/categories/publication/default.py +++ b/src/easydiffraction/project/categories/publication/default.py @@ -4,7 +4,7 @@ from __future__ import annotations -import pathlib +from typing import TYPE_CHECKING from easydiffraction.core.category import CategoryCollection from easydiffraction.core.category import CategoryItem @@ -16,12 +16,15 @@ from easydiffraction.project.categories.publication.factory import PublicationFactory from easydiffraction.project.publication_loader import load_publication +if TYPE_CHECKING: + import pathlib + class PublicationItemBase(CategoryItem): """Base for optional publication metadata scalar categories.""" + @staticmethod def _optional_string( - self, *, name: str, cif_name: str, diff --git a/src/easydiffraction/project/categories/report/default.py b/src/easydiffraction/project/categories/report/default.py index e09b30b36..fb4af2d79 100644 --- a/src/easydiffraction/project/categories/report/default.py +++ b/src/easydiffraction/project/categories/report/default.py @@ -33,143 +33,8 @@ ) -@ReportFactory.register -class Report(CategoryItem): - """ - Generates reports and exports results from the project. - - This class collects and presents all relevant information about the - fitted model, experiments, and analysis results. - """ - - _category_code = 'report' - - type_info = TypeInfo( - tag='default', - description='Project report category', - ) - - def __init__(self) -> None: - """Initialize report-output configuration descriptors.""" - super().__init__() - - self._cif = BoolDescriptor( - name='cif', - description='Whether to write CIF reports when saving.', - value_spec=AttributeSpec(default=False), - cif_handler=CifHandler(names=['_report.cif']), - ) - self._html = BoolDescriptor( - name='html', - description='Whether to write HTML reports when saving.', - value_spec=AttributeSpec(default=False), - cif_handler=CifHandler(names=['_report.html']), - ) - self._tex = BoolDescriptor( - name='tex', - description='Whether to write TeX reports when saving.', - value_spec=AttributeSpec(default=False), - cif_handler=CifHandler(names=['_report.tex']), - ) - self._pdf = BoolDescriptor( - name='pdf', - description='Whether to write PDF reports when saving.', - value_spec=AttributeSpec(default=False), - cif_handler=CifHandler(names=['_report.pdf']), - ) - self._html_offline = BoolDescriptor( - name='html_offline', - description='Whether HTML reports should embed assets.', - value_spec=AttributeSpec(default=False), - cif_handler=CifHandler(names=['_report.html_offline']), - ) - - @property - def cif(self) -> BoolDescriptor: - """Whether to write CIF reports when saving.""" - return self._cif - - @cif.setter - def cif(self, value: bool) -> None: - self._cif.value = value - - @property - def html(self) -> BoolDescriptor: - """Whether to write HTML reports when saving.""" - return self._html - - @html.setter - def html(self, value: bool) -> None: - self._html.value = value - - @property - def tex(self) -> BoolDescriptor: - """Whether to write TeX reports when saving.""" - return self._tex - - @tex.setter - def tex(self, value: bool) -> None: - self._tex.value = value - - @property - def pdf(self) -> BoolDescriptor: - """Whether to write PDF reports when saving.""" - return self._pdf - - @pdf.setter - def pdf(self, value: bool) -> None: - self._pdf.value = value - - @property - def html_offline(self) -> BoolDescriptor: - """Whether HTML reports should embed assets.""" - return self._html_offline - - @html_offline.setter - def html_offline(self, value: bool) -> None: - self._html_offline.value = value - - def _enabled_formats(self) -> list[ReportFormatEnum]: - """Return report-output formats enabled by boolean flags.""" - formats = [] - if self._cif.value: - formats.append(ReportFormatEnum.CIF) - if self._html.value: - formats.append(ReportFormatEnum.HTML) - if self._tex.value: - formats.append(ReportFormatEnum.TEX) - if self._pdf.value: - formats.append(ReportFormatEnum.PDF) - return formats - - @property - def project(self) -> object: - """Project owning this report category.""" - return self._parent - - def data_context(self) -> dict[str, object]: - """Return shared report-rendering data.""" - return build_report_data_context(self.project) - - def help(self) -> None: - """Print available report methods.""" - render_object_help(self) - - @staticmethod - def _fmt_row( - pretty_name: str, - parameter: Parameter, - ) -> list[str]: - digits = 8 - value = f'{parameter.value:.{digits}f}' - uncertainty = parameter.uncertainty - uncertainty = f'{uncertainty:.{digits}f}' if uncertainty is not None else '' - units = parameter.resolve_display_units('gui') - return [pretty_name, value, uncertainty, units] - - # ------------------------------------------ - # Report Generation - # ------------------------------------------ +class _ReportDisplayMixin: + """Console display methods for report categories.""" def show_report(self) -> None: """Print a full project report covering all sections.""" @@ -210,12 +75,12 @@ def show_crystallographic_data(self) -> None: columns_headers = ['Parameter', 'Value', 'Uncertainty', 'Unit'] columns_alignment = ['left', 'right', 'right', 'left'] columns_data = [ - Report._fmt_row('a', structure.cell.length_a), - Report._fmt_row('b', structure.cell.length_b), - Report._fmt_row('c', structure.cell.length_c), - Report._fmt_row('α', structure.cell.angle_alpha), # noqa: RUF001 - Report._fmt_row('β', structure.cell.angle_beta), - Report._fmt_row('γ', structure.cell.angle_gamma), # noqa: RUF001 + self._fmt_row('a', structure.cell.length_a), + self._fmt_row('b', structure.cell.length_b), + self._fmt_row('c', structure.cell.length_c), + self._fmt_row('α', structure.cell.angle_alpha), # noqa: RUF001 + self._fmt_row('β', structure.cell.angle_beta), + self._fmt_row('γ', structure.cell.angle_gamma), # noqa: RUF001 ] render_table( columns_headers=columns_headers, @@ -297,9 +162,9 @@ def show_experimental_data(self) -> None: columns_headers = ['Parameter', 'Value', 'Uncertainty', 'Unit'] columns_alignment = ['left', 'right', 'right', 'left'] columns_data = [ - Report._fmt_row('U', expt.peak.broad_gauss_u), - Report._fmt_row('V', expt.peak.broad_gauss_v), - Report._fmt_row('W', expt.peak.broad_gauss_w), + self._fmt_row('U', expt.peak.broad_gauss_u), + self._fmt_row('V', expt.peak.broad_gauss_v), + self._fmt_row('W', expt.peak.broad_gauss_w), ] render_table( columns_headers=columns_headers, @@ -313,8 +178,8 @@ def show_experimental_data(self) -> None: columns_headers = ['Parameter', 'Value', 'Uncertainty', 'Unit'] columns_alignment = ['left', 'right', 'right', 'left'] columns_data = [ - Report._fmt_row('X', expt.peak.broad_lorentz_x), - Report._fmt_row('Y', expt.peak.broad_lorentz_y), + self._fmt_row('X', expt.peak.broad_lorentz_x), + self._fmt_row('Y', expt.peak.broad_lorentz_y), ] render_table( columns_headers=columns_headers, @@ -326,10 +191,10 @@ def show_experimental_data(self) -> None: columns_headers = ['Parameter', 'Value', 'Uncertainty', 'Unit'] columns_alignment = ['left', 'right', 'right', 'left'] columns_data = [ - Report._fmt_row('p1', expt.peak.asym_empir_1), - Report._fmt_row('p2', expt.peak.asym_empir_2), - Report._fmt_row('p3', expt.peak.asym_empir_3), - Report._fmt_row('p4', expt.peak.asym_empir_4), + self._fmt_row('p1', expt.peak.asym_empir_1), + self._fmt_row('p2', expt.peak.asym_empir_2), + self._fmt_row('p3', expt.peak.asym_empir_3), + self._fmt_row('p4', expt.peak.asym_empir_4), ] render_table( columns_headers=columns_headers, @@ -359,6 +224,141 @@ def show_fitting_details(self) -> None: columns_data=fit_metrics, ) + +@ReportFactory.register +class Report(_ReportDisplayMixin, CategoryItem): + """ + Generates reports and exports results from the project. + + This class collects and presents all relevant information about the + fitted model, experiments, and analysis results. + """ + + _category_code = 'report' + + type_info = TypeInfo( + tag='default', + description='Project report category', + ) + + def __init__(self) -> None: + """Initialize report-output configuration descriptors.""" + super().__init__() + + self._cif = BoolDescriptor( + name='cif', + description='Whether to write CIF reports when saving.', + value_spec=AttributeSpec(default=False), + cif_handler=CifHandler(names=['_report.cif']), + ) + self._html = BoolDescriptor( + name='html', + description='Whether to write HTML reports when saving.', + value_spec=AttributeSpec(default=False), + cif_handler=CifHandler(names=['_report.html']), + ) + self._tex = BoolDescriptor( + name='tex', + description='Whether to write TeX reports when saving.', + value_spec=AttributeSpec(default=False), + cif_handler=CifHandler(names=['_report.tex']), + ) + self._pdf = BoolDescriptor( + name='pdf', + description='Whether to write PDF reports when saving.', + value_spec=AttributeSpec(default=False), + cif_handler=CifHandler(names=['_report.pdf']), + ) + self._html_offline = BoolDescriptor( + name='html_offline', + description='Whether HTML reports should embed assets.', + value_spec=AttributeSpec(default=False), + cif_handler=CifHandler(names=['_report.html_offline']), + ) + + @property + def cif(self) -> BoolDescriptor: + """Whether to write CIF reports when saving.""" + return self._cif + + @cif.setter + def cif(self, value: bool) -> None: + self._cif.value = value + + @property + def html(self) -> BoolDescriptor: + """Whether to write HTML reports when saving.""" + return self._html + + @html.setter + def html(self, value: bool) -> None: + self._html.value = value + + @property + def tex(self) -> BoolDescriptor: + """Whether to write TeX reports when saving.""" + return self._tex + + @tex.setter + def tex(self, value: bool) -> None: + self._tex.value = value + + @property + def pdf(self) -> BoolDescriptor: + """Whether to write PDF reports when saving.""" + return self._pdf + + @pdf.setter + def pdf(self, value: bool) -> None: + self._pdf.value = value + + @property + def html_offline(self) -> BoolDescriptor: + """Whether HTML reports should embed assets.""" + return self._html_offline + + @html_offline.setter + def html_offline(self, value: bool) -> None: + self._html_offline.value = value + + def _enabled_formats(self) -> list[ReportFormatEnum]: + """Return report-output formats enabled by boolean flags.""" + formats = [] + if self._cif.value: + formats.append(ReportFormatEnum.CIF) + if self._html.value: + formats.append(ReportFormatEnum.HTML) + if self._tex.value: + formats.append(ReportFormatEnum.TEX) + if self._pdf.value: + formats.append(ReportFormatEnum.PDF) + return formats + + @property + def project(self) -> object: + """Project owning this report category.""" + return self._parent + + def data_context(self) -> dict[str, object]: + """Return shared report-rendering data.""" + return build_report_data_context(self.project) + + def help(self) -> None: + """Print available report methods.""" + render_object_help(self) + + @staticmethod + def _fmt_row( + pretty_name: str, + parameter: Parameter, + ) -> list[str]: + digits = 8 + value = f'{parameter.value:.{digits}f}' + uncertainty = parameter.uncertainty + uncertainty = f'{uncertainty:.{digits}f}' if uncertainty is not None else '' + units = parameter.resolve_display_units('gui') + return [pretty_name, value, uncertainty, units] + def save_cif(self) -> pathlib.Path: """ Write the IUCr submission report. @@ -375,7 +375,7 @@ def save_cif(self) -> pathlib.Path: """ return write_iucr_cif(self.project) - def save_html(self, offline: bool = False) -> pathlib.Path: + def save_html(self, *, offline: bool = False) -> pathlib.Path: """ Write the HTML report. @@ -393,7 +393,7 @@ def save_html(self, offline: bool = False) -> pathlib.Path: return save_html_report(self.project, self.data_context(), offline=offline) - def as_html(self, offline: bool = False) -> str: + def as_html(self, *, offline: bool = False) -> str: """ Render the HTML report. diff --git a/src/easydiffraction/project/project.py b/src/easydiffraction/project/project.py index 910640563..c1c42eadb 100644 --- a/src/easydiffraction/project/project.py +++ b/src/easydiffraction/project/project.py @@ -27,7 +27,6 @@ from easydiffraction.project.categories.publication import PublicationFactory from easydiffraction.project.display import ProjectDisplay from easydiffraction.project.project_config import ProjectConfig -from easydiffraction.report import Report from easydiffraction.utils.enums import VerbosityEnum from easydiffraction.utils.environment import resolve_artifact_path from easydiffraction.utils.logging import console @@ -41,6 +40,7 @@ from easydiffraction.project.categories.table import Table from easydiffraction.project.categories.verbosity import Verbosity from easydiffraction.project.project_info import ProjectInfo + from easydiffraction.report import Report def _apply_csv_row_to_params( diff --git a/src/easydiffraction/project/publication_loader.py b/src/easydiffraction/project/publication_loader.py index ab4104b02..43e8a0c73 100644 --- a/src/easydiffraction/project/publication_loader.py +++ b/src/easydiffraction/project/publication_loader.py @@ -54,16 +54,16 @@ def _read_publication_data(path: pathlib.Path) -> Mapping[str, object]: """Read a publication metadata file by extension.""" ext = path.suffix.lower() if ext == '.toml': - data = tomllib.loads(path.read_text()) + data = tomllib.loads(path.read_text(encoding='utf-8')) elif ext == '.json': - data = json.loads(path.read_text()) + data = json.loads(path.read_text(encoding='utf-8')) else: msg = f'Unsupported publication-info format: {ext}. Use .toml or .json.' raise ValueError(msg) if not isinstance(data, Mapping): msg = 'Publication-info file must contain a top-level object.' - raise ValueError(msg) + raise TypeError(msg) return data @@ -94,13 +94,13 @@ def _keywords(value: object) -> list[str]: return [] if not isinstance(value, list): msg = "Publication-info field 'body_keywords' must be a list of strings." - raise ValueError(msg) + raise TypeError(msg) keywords: list[str] = [] for idx, keyword in enumerate(value): if not isinstance(keyword, str): msg = f"Publication-info field 'body_keywords[{idx}]' must be a string." - raise ValueError(msg) + raise TypeError(msg) keywords.append(keyword) return keywords @@ -109,17 +109,18 @@ def _author_rows(value: object) -> list[dict[str, str | None]]: """Validate publication author rows from the publication file.""" if not isinstance(value, list): msg = "Publication-info field 'authors' must be a list of objects." - raise ValueError(msg) + raise TypeError(msg) rows: list[dict[str, str | None]] = [] for idx, row in enumerate(value): if not isinstance(row, Mapping): msg = f"Publication-info field 'authors[{idx}]' must be an object." - raise ValueError(msg) + raise TypeError(msg) for key in row: if key not in _AUTHOR_FIELDS: - raise ValueError(f'authors.{key}') + msg = f'authors.{key}' + raise ValueError(msg) rows.append({ 'name': _required_text(f'authors[{idx}].name', row.get('name')), @@ -190,7 +191,9 @@ def load_publication(publication: Publication, path: str | pathlib.Path) -> None Raises ------ ValueError - If the file extension, top-level shape, or any key is invalid. + If the file extension or any key is invalid. + TypeError + If the top-level shape or nested field shape is invalid. """ data = _read_publication_data(pathlib.Path(path)) updates, keywords, authors = _validate_publication_data(data) diff --git a/src/easydiffraction/report/__init__.py b/src/easydiffraction/report/__init__.py index fbf353cc5..970118e85 100644 --- a/src/easydiffraction/report/__init__.py +++ b/src/easydiffraction/report/__init__.py @@ -4,12 +4,4 @@ from __future__ import annotations - -def __getattr__(name: str) -> object: - """Load report objects that would otherwise form import cycles.""" - if name == 'Report': - from easydiffraction.project.categories.report.default import Report - - return Report - msg = f'module {__name__!r} has no attribute {name!r}' - raise AttributeError(msg) +from easydiffraction.project.categories.report.default import Report diff --git a/src/easydiffraction/report/pdf_compiler.py b/src/easydiffraction/report/pdf_compiler.py index e208ac266..9d36b8cdc 100644 --- a/src/easydiffraction/report/pdf_compiler.py +++ b/src/easydiffraction/report/pdf_compiler.py @@ -5,19 +5,23 @@ from __future__ import annotations import os -import pathlib import shutil -import subprocess +import subprocess # noqa: S404 +from typing import TYPE_CHECKING from easydiffraction.report.tex_renderer import save_tex_report from easydiffraction.utils.logging import log +if TYPE_CHECKING: + import pathlib + _ENGINE_ORDER = ('tectonic', 'latexmk', 'pdflatex') _ENGINE_RUNTIME_FAILURE_MARKERS = ( 'panicked at', 'event loop thread panicked', 'Attempted to create a NULL object', ) +_MAX_COMPILER_DETAILS_LENGTH = 4000 _INSTALL_HINT = """PDF skipped: no TeX engine on PATH. Install one with: pixi add tectonic @@ -157,7 +161,7 @@ def _compile_pdf( compile_tex_path, compile_pdf_path.parent, ) - result = subprocess.run( + result = subprocess.run( # noqa: S603 command, cwd=compile_tex_path.parent, env=_compile_environment(), @@ -229,6 +233,6 @@ def _compiler_error_message( ) -> str: """Return a concise compiler failure message.""" details = (result.stderr or result.stdout).strip() - if len(details) > 4000: - details = details[-4000:] + if len(details) > _MAX_COMPILER_DETAILS_LENGTH: + details = details[-_MAX_COMPILER_DETAILS_LENGTH:] return f"TeX engine '{engine_name}' failed while compiling '{tex_path}'.\n{details}" diff --git a/src/easydiffraction/utils/matplotlib_config.py b/src/easydiffraction/utils/matplotlib_config.py new file mode 100644 index 000000000..33230d82b --- /dev/null +++ b/src/easydiffraction/utils/matplotlib_config.py @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Configure Matplotlib cache paths for restricted environments.""" + +from __future__ import annotations + +import os +import pathlib +import tempfile + + +def _path_is_writable(path: pathlib.Path) -> bool: + """Return whether runtime caches can use a directory.""" + try: + path.mkdir(parents=True, exist_ok=True) + probe = path / '.easydiffraction-write-test' + probe.write_text('', encoding='utf-8') + probe.unlink() + except OSError: + return False + return True + + +def ensure_matplotlib_config_dir() -> None: + """Set a stable Matplotlib cache dir.""" + if os.environ.get('MPLCONFIGDIR'): + return + default_dir = pathlib.Path.home() / '.matplotlib' + if _path_is_writable(default_dir): + return + fallback_dir = pathlib.Path(tempfile.gettempdir()) / 'easydiffraction-matplotlib' + if _path_is_writable(fallback_dir): + os.environ['MPLCONFIGDIR'] = str(fallback_dir) + + +ensure_matplotlib_config_dir() diff --git a/tests/unit/easydiffraction/project/categories/report/test_default.py b/tests/unit/easydiffraction/project/categories/report/test_default.py index fdc96da8a..7108da722 100644 --- a/tests/unit/easydiffraction/project/categories/report/test_default.py +++ b/tests/unit/easydiffraction/project/categories/report/test_default.py @@ -21,7 +21,7 @@ def test_report_save_without_outputs_points_to_boolean_flags(): report = Report() - with pytest.raises(ValueError) as exc_info: + with pytest.raises(ValueError, match='no formats enabled') as exc_info: report.save() message = str(exc_info.value) diff --git a/tests/unit/easydiffraction/report/test_pdf_compiler.py b/tests/unit/easydiffraction/report/test_pdf_compiler.py index d694c8893..d5159673c 100644 --- a/tests/unit/easydiffraction/report/test_pdf_compiler.py +++ b/tests/unit/easydiffraction/report/test_pdf_compiler.py @@ -3,7 +3,7 @@ from __future__ import annotations -import subprocess +import subprocess # noqa: S404 def test_compile_pdf_runs_from_tex_dir_with_absolute_output(tmp_path, monkeypatch): From 45f5aa435ddc0106dfafc803c64f5889b4c2e1d5 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Fri, 29 May 2026 19:23:18 +0200 Subject: [PATCH 083/129] Restore lazy report import --- src/easydiffraction/report/__init__.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/easydiffraction/report/__init__.py b/src/easydiffraction/report/__init__.py index 970118e85..f8fd9e8b8 100644 --- a/src/easydiffraction/report/__init__.py +++ b/src/easydiffraction/report/__init__.py @@ -4,4 +4,12 @@ from __future__ import annotations -from easydiffraction.project.categories.report.default import Report + +def __getattr__(name: str) -> object: + """Load report objects that would otherwise form import cycles.""" + if name == 'Report': + from easydiffraction.project.categories.report.default import Report # noqa: PLC0415 + + return Report + msg = f'module {__name__!r} has no attribute {name!r}' + raise AttributeError(msg) From 99d470ba3f93058452e25cf1a1a03573029faf61 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Fri, 29 May 2026 19:41:46 +0200 Subject: [PATCH 084/129] Align Phase 2 tests with report configuration --- src/easydiffraction/core/variable.py | 2 ++ src/easydiffraction/display/plotting.py | 6 +++++- src/easydiffraction/report/fit_plot.py | 3 +++ .../easydiffraction/analysis/test_analysis.py | 1 + .../easydiffraction/core/test_parameters.py | 8 +++---- .../project/categories/report/test_default.py | 4 +++- .../easydiffraction/project/test_project.py | 21 +++++++------------ .../project/test_project_config.py | 19 ++++++++++++++++- .../test_project_load_and_summary_wrap.py | 5 +++-- 9 files changed, 46 insertions(+), 23 deletions(-) diff --git a/src/easydiffraction/core/variable.py b/src/easydiffraction/core/variable.py index 16152b3fd..3264fa4f1 100644 --- a/src/easydiffraction/core/variable.py +++ b/src/easydiffraction/core/variable.py @@ -242,6 +242,8 @@ def resolve_display_units(self, context: str) -> str: """ self._validate_display_context(context) fallback = str(getattr(self, 'units', '')) + if fallback == 'none': + fallback = '' if self._display_handler is None: return fallback if context == 'latex': diff --git a/src/easydiffraction/display/plotting.py b/src/easydiffraction/display/plotting.py index 7a5079172..65c4d7bb7 100644 --- a/src/easydiffraction/display/plotting.py +++ b/src/easydiffraction/display/plotting.py @@ -5853,7 +5853,11 @@ def _plot_param_series_from_csv( x_label = 'Experiment No.' # Y-axis label from descriptor - param_units = getattr(param_descriptor, 'units', '') + param_units = ( + param_descriptor.resolve_display_units('gui') + if hasattr(param_descriptor, 'resolve_display_units') + else getattr(param_descriptor, 'units', '') + ) y_label = f'Parameter value ({param_units})' if param_units else 'Parameter value' title = f"Parameter '{column_name}' across fit results" diff --git a/src/easydiffraction/report/fit_plot.py b/src/easydiffraction/report/fit_plot.py index de06f2217..14fdece9b 100644 --- a/src/easydiffraction/report/fit_plot.py +++ b/src/easydiffraction/report/fit_plot.py @@ -75,6 +75,9 @@ def fit_plot_ranges(fit_data: dict[str, Any]) -> dict[str, float]: _numeric_values(fit_data['series']['meas']['values']), _numeric_values(fit_data['series']['calc']['values']), ] + background = fit_data['series'].get('bkg') + if background is not None: + y_series.append(_numeric_values(background['values'])) x_min, x_max = _data_range([x_values]) y_min, y_max = _data_range(y_series) diff --git a/tests/unit/easydiffraction/analysis/test_analysis.py b/tests/unit/easydiffraction/analysis/test_analysis.py index 6786d839f..5cda12a5d 100644 --- a/tests/unit/easydiffraction/analysis/test_analysis.py +++ b/tests/unit/easydiffraction/analysis/test_analysis.py @@ -784,6 +784,7 @@ def test_run_sequential_sets_mode_and_saves_project(monkeypatch, tmp_path): project = SimpleNamespace( info=SimpleNamespace(path=tmp_path), + experiments=SimpleNamespace(values=list), save_calls=0, _varname='proj', ) diff --git a/tests/unit/easydiffraction/core/test_parameters.py b/tests/unit/easydiffraction/core/test_parameters.py index f81e35e87..a4a4979a3 100644 --- a/tests/unit/easydiffraction/core/test_parameters.py +++ b/tests/unit/easydiffraction/core/test_parameters.py @@ -35,13 +35,13 @@ def test_numeric_descriptor_str_includes_units(): d = NumericDescriptor( name='w', value_spec=AttributeSpec(default=1.23), - units='deg', + units='degrees', cif_handler=CifHandler(names=['_x.w']), ) s = str(d) assert s.startswith('<') assert s.endswith('>') - assert 'deg' in s + assert 'degrees' in s assert 'w' in s @@ -53,7 +53,7 @@ def test_parameter_string_repr_and_as_cif_and_flags(): p = Parameter( name='a', value_spec=AttributeSpec(default=0.0), - units='A', + units='angstroms', cif_handler=CifHandler(names=['_param.a']), ) p.value = 2.5 @@ -63,7 +63,7 @@ def test_parameter_string_repr_and_as_cif_and_flags(): s = str(p) assert '± 0.1' in s - assert 'A' in s + assert 'angstroms' in s assert '(free=True)' in s # CIF line: free param with uncertainty uses 2-sig-digit esd brackets diff --git a/tests/unit/easydiffraction/project/categories/report/test_default.py b/tests/unit/easydiffraction/project/categories/report/test_default.py index 7108da722..f0887a02c 100644 --- a/tests/unit/easydiffraction/project/categories/report/test_default.py +++ b/tests/unit/easydiffraction/project/categories/report/test_default.py @@ -7,9 +7,11 @@ import pytest -def test_report_has_no_formats_property(): +def test_report_has_no_formats_property(monkeypatch): from easydiffraction.project.categories.report.default import Report + from easydiffraction.utils.logging import Logger + monkeypatch.setattr(Logger, '_reaction', Logger.Reaction.RAISE, raising=True) report = Report() with pytest.raises(AttributeError, match="Unknown attribute 'formats'"): diff --git a/tests/unit/easydiffraction/project/test_project.py b/tests/unit/easydiffraction/project/test_project.py index 522ab774e..b243df68b 100644 --- a/tests/unit/easydiffraction/project/test_project.py +++ b/tests/unit/easydiffraction/project/test_project.py @@ -80,7 +80,6 @@ def test_project_exposes_chart_table_and_display_facades(): assert isinstance(project.display, ProjectDisplay) assert isinstance(project.report, Report) assert hasattr(project.report, 'save') - assert hasattr(project.report, 'check') assert hasattr(project.report, 'show_report') @@ -190,26 +189,20 @@ def test_project_save_report_writes_submission_cif(tmp_path): project = Project(name='report_project') project.save_as(str(tmp_path / 'proj')) - project.save(report=True) + project.report.cif = True + project.save() assert not (tmp_path / 'proj' / 'summary.cif').exists() assert (tmp_path / 'proj' / 'reports' / 'report_project.cif').is_file() -def test_project_save_report_check_runs_validation(tmp_path, monkeypatch): +def test_project_save_rejects_removed_report_keyword(tmp_path): + import pytest + from easydiffraction.project.project import Project project = Project(name='checked_report') - checked_paths = [] - - def fake_check(*, path=None): - checked_paths.append(path) - project.save_as(str(tmp_path / 'proj')) - monkeypatch.setattr(project.report, 'check', fake_check) - - project.save(report=True, check=True) - assert checked_paths == [ - tmp_path / 'proj' / 'reports' / 'checked_report.cif', - ] + with pytest.raises(TypeError, match="unexpected keyword argument 'report'"): + project.save(report=True) diff --git a/tests/unit/easydiffraction/project/test_project_config.py b/tests/unit/easydiffraction/project/test_project_config.py index f35d814a8..c921b3199 100644 --- a/tests/unit/easydiffraction/project/test_project_config.py +++ b/tests/unit/easydiffraction/project/test_project_config.py @@ -9,6 +9,7 @@ def test_project_config_exposes_project_info_chart_and_table_categories(): from easydiffraction.core.category_owner import CategoryOwner from easydiffraction.project.categories.chart import Chart + from easydiffraction.project.categories.report import Report from easydiffraction.project.categories.table import Table from easydiffraction.project.project_config import ProjectConfig from easydiffraction.project.project_info import ProjectInfo @@ -18,9 +19,11 @@ def test_project_config_exposes_project_info_chart_and_table_categories(): assert isinstance(config, CategoryOwner) assert isinstance(config.info, ProjectInfo) assert isinstance(config.chart, Chart) + assert isinstance(config.report, Report) assert isinstance(config.table, Table) assert config.info._parent is config assert config.chart._parent is config + assert config.report._parent is config assert config.table._parent is config assert config.info.name == 'beer' assert config.info.title == 'Beer title' @@ -30,10 +33,17 @@ def test_project_config_exposes_project_info_chart_and_table_categories(): assert isinstance(config.info.last_modified, datetime.datetime) assert config.verbosity._parent is config assert config.verbosity.fit.value == 'full' - assert config.categories == [config.info, config.chart, config.table, config.verbosity] + assert config.categories == [ + config.info, + config.chart, + config.report, + config.table, + config.verbosity, + ] assert config.parameters == ( config.info.parameters + config.chart.parameters + + config.report.parameters + config.table.parameters + config.verbosity.parameters ) @@ -53,6 +63,11 @@ def test_project_config_as_cif_has_project_chart_and_table_sections_without_data assert '_project.created' in cif_text assert '_project.last_modified' in cif_text assert '_chart.type' in cif_text + assert '_report.cif' in cif_text + assert '_report.html' in cif_text + assert '_report.tex' in cif_text + assert '_report.pdf' in cif_text + assert '_report.html_offline' in cif_text assert '_table.type' in cif_text assert '_chart.type auto' in cif_text assert '_table.type auto' in cif_text @@ -69,6 +84,7 @@ def test_project_save_and_load_use_auto_display_defaults_when_unset(tmp_path): assert not project_cif.startswith('data_') assert '_chart.type auto' in project_cif + assert '_report.cif false' in project_cif assert '_table.type auto' in project_cif assert '_verbosity.fit full' in project_cif @@ -91,6 +107,7 @@ def test_project_save_and_load_keep_project_config_section_format(tmp_path): assert not project_cif.startswith('data_') assert '_project.id beer' in project_cif assert '_chart.type asciichartpy' in project_cif + assert '_report.cif false' in project_cif assert '_table.type rich' in project_cif assert '_verbosity.fit full' in project_cif diff --git a/tests/unit/easydiffraction/project/test_project_load_and_summary_wrap.py b/tests/unit/easydiffraction/project/test_project_load_and_summary_wrap.py index 1f9de101f..78f8b43f4 100644 --- a/tests/unit/easydiffraction/project/test_project_load_and_summary_wrap.py +++ b/tests/unit/easydiffraction/project/test_project_load_and_summary_wrap.py @@ -26,7 +26,7 @@ def test_project_load_reads_project_info(tmp_path): def test_report_show_project_info_wraps_description(capsys): - from easydiffraction.report.report import Report + from easydiffraction.project.categories.report.default import Report long_desc = ' '.join(['desc'] * 50) # long text to trigger wrapping @@ -38,7 +38,8 @@ class Project: def __init__(self): self.info = Info() - report = Report(Project()) + report = Report() + report._parent = Project() report.show_project_info() out = capsys.readouterr().out # Title and Description paragraph headers present From 696f2a312bbd89b74bdf5c9acb38c0c05cdf1d80 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Fri, 29 May 2026 19:44:18 +0200 Subject: [PATCH 085/129] Handle nonnumeric descriptors without units --- src/easydiffraction/core/variable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/easydiffraction/core/variable.py b/src/easydiffraction/core/variable.py index 3264fa4f1..c00c16f6b 100644 --- a/src/easydiffraction/core/variable.py +++ b/src/easydiffraction/core/variable.py @@ -241,7 +241,7 @@ def resolve_display_units(self, context: str) -> str: If ``context`` is not a supported display context. """ self._validate_display_context(context) - fallback = str(getattr(self, 'units', '')) + fallback = str(getattr(self, '_units', '')) if fallback == 'none': fallback = '' if self._display_handler is None: From 3a8a546051d762b1b21a6a53aa86986cdabefd2d Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Fri, 29 May 2026 19:45:57 +0200 Subject: [PATCH 086/129] Apply Phase 2 formatting fixes --- .../adrs/accepted/iucr-cif-tag-alignment.md | 120 +- .../project-facade-and-persistence.md | 13 +- .../accepted/project-summary-rendering.md | 2610 +- .../python-cif-category-correspondence.md | 22 +- docs/dev/package-structure/full.md | 56 +- docs/dev/package-structure/short.md | 33 +- docs/dev/plans/project-summary-rendering.md | 2167 +- docs/docs/api-reference/report.md | 21 +- .../user-guide/analysis-workflow/report.md | 14 +- .../report/templates/html/style.css | 8 +- .../report/templates/html/vendor/LICENSES.md | 6 +- .../html/vendor/mathjax-tex-mml-chtml.js | 67828 +++++++++++++++- 12 files changed, 70242 insertions(+), 2656 deletions(-) diff --git a/docs/dev/adrs/accepted/iucr-cif-tag-alignment.md b/docs/dev/adrs/accepted/iucr-cif-tag-alignment.md index f191f85ab..ec91ca631 100644 --- a/docs/dev/adrs/accepted/iucr-cif-tag-alignment.md +++ b/docs/dev/adrs/accepted/iucr-cif-tag-alignment.md @@ -143,13 +143,14 @@ observation drives the policy: parametric peak-shape items at all. File path scopes them; no prefix needed. - **Reports** — a separate `project.report` facade that pulls live - Python state and emits journal report artifacts under `reports/`. - The IUCr CIF one-off method is `project.report.save_cif()`; the - regular `project.save()` call emits configured reports from the - `project.report.{cif,html,tex,pdf}` booleans. This path applies all IUCr renames, - structural reshapings, multi-datablock layout, and project-extension - namespacing (`_easydiffraction_*`). It replaces the unimplemented - `project.summary` placeholder. **Export only — no round-trip.** + Python state and emits journal report artifacts under `reports/`. The + IUCr CIF one-off method is `project.report.save_cif()`; the regular + `project.save()` call emits configured reports from the + `project.report.{cif,html,tex,pdf}` booleans. This path applies all + IUCr renames, structural reshapings, multi-datablock layout, and + project-extension namespacing (`_easydiffraction_*`). It replaces the + unimplemented `project.summary` placeholder. **Export only — no + round-trip.** ## Current State @@ -158,44 +159,44 @@ Project CIF categories audited against `cif_core.dic` v3.4.0 and category changes in the default save; the "IUCr export" column shows the dotted DDLm tag emitted by the IUCr CIF report writer. -| Category (current) | IUCr dictionary | Default-save tier | IUCr export (dotted DDLm) | -| ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `_cell.*` | core | Structure — unchanged | `_cell.length_a`, `_cell.angle_alpha`, etc. | -| `_atom_site.*` (most fields) | core | Structure — unchanged | `_atom_site.label`, `_atom_site.fract_x`, … | -| `_atom_site.adp_type` | core (`_atom_site.ADP_type`) | Structure — casing fix | `_atom_site.ADP_type` (uppercase ADP per dictionary). | -| `_atom_site.wyckoff_letter` | core (`_atom_site.Wyckoff_symbol`) | Structure — rename | `_atom_site.Wyckoff_symbol` (uppercase W, "symbol" not "letter"). | -| `_atom_site.B_iso_or_equiv` / `U_iso_or_equiv` | core | Structure — single-tag emit | `_atom_site.B_iso_or_equiv` xor `_atom_site.U_iso_or_equiv` per row, based on `_atom_site.ADP_type`. | -| `_atom_site_aniso.B_*` / `U_*` | core | Structure — single-tag emit | `_atom_site_aniso.B_*` xor `_atom_site_aniso.U_*` per row. | -| `_space_group.name_h_m` | core (`_space_group.name_H-M_alt`) | Structure — casing fix | `_space_group.name_H-M_alt`. | -| `_space_group.it_coordinate_system_code` | core (`_space_group.IT_coordinate_system_code`) | Structure — casing fix | `_space_group.IT_coordinate_system_code`. | -| symmetry operations | core (`_space_group_symop.*`) | (not emitted today) | `_space_group_symop.id` + `_space_group_symop.operation_xyz` loop alongside the H-M name. | -| `_diffrn.ambient_temperature`, `ambient_pressure` | core | Experiment — unchanged | `_diffrn.ambient_temperature`, `_diffrn.ambient_pressure`. | -| `_diffrn.ambient_magnetic_field`, `ambient_electric_field` | none | Experiment — unchanged | `_easydiffraction_diffrn.ambient_magnetic_field`, `…electric_field` (project extension). | -| `_refln.*` | core | (no default save under refln) | `_refln.*` reflections loop (column set differs by domain — see §2.3). | -| `_pd_meas.*`, `_pd_proc.*`, `_pd_calc.*`, `_pd_data.*` | pdCIF | Experiment — unchanged | `_pd_meas.*`, `_pd_proc.*`, `_pd_calc.*` profile-data loop (see §2.3). | -| `_pd_background.*` | pdCIF | Experiment — unchanged | `_pd_background.*`. | -| `_pd_phase_block.*` | pdCIF | Experiment — unchanged | `_pd_phase_block.*`. | -| `_sc_crystal_block.*` | community (no IUCr counterpart) | Experiment — unchanged | `_easydiffraction_sc_crystal_block.*` in IUCr export. | -| `_instr.wavelength` | core (`_diffrn_radiation_wavelength.value`) | Experiment — unchanged | `_diffrn_radiation_wavelength.{id, value, wt}` — single-row category for monochromatic; loop only for multi-λ. | -| `_instr.2theta_offset` | pdCIF (`_pd_calib.2theta_offset`) | Experiment — unchanged | `_pd_calib.2theta_offset`. | -| `_instr.2theta_bank`, `d_to_tof_*` | pdCIF (`_pd_calib_d_to_tof.*` loop) | Experiment — unchanged | Four-row loop `_pd_calib_d_to_tof.{id, coeff, power, coeff_su, diffractogram_id}`. | -| `_peak.*` (parametric profile shape) | none (pdCIF has no shape parameters) | Experiment — unchanged | `_easydiffraction_peak.*` + `_pd_proc_ls.profile_function` free-text descriptor. | -| `_extinction.*` | core (`_refine_ls.extinction_*` items) | Experiment — unchanged | `_easydiffraction_extinction.*` + dual emit `_refine_ls.extinction_{method,coef,expression}`. | -| `_excluded_region.*` | pdCIF (`_pd_proc.info_excluded_regions` free-text) | Experiment — unchanged | `_easydiffraction_excluded_region.*` + `_pd_proc.info_excluded_regions` free-text rendering. | -| `_expt_type.*` | none | Experiment — unchanged | `_easydiffraction_experiment_type.*`. | +| Category (current) | IUCr dictionary | Default-save tier | IUCr export (dotted DDLm) | +| ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `_cell.*` | core | Structure — unchanged | `_cell.length_a`, `_cell.angle_alpha`, etc. | +| `_atom_site.*` (most fields) | core | Structure — unchanged | `_atom_site.label`, `_atom_site.fract_x`, … | +| `_atom_site.adp_type` | core (`_atom_site.ADP_type`) | Structure — casing fix | `_atom_site.ADP_type` (uppercase ADP per dictionary). | +| `_atom_site.wyckoff_letter` | core (`_atom_site.Wyckoff_symbol`) | Structure — rename | `_atom_site.Wyckoff_symbol` (uppercase W, "symbol" not "letter"). | +| `_atom_site.B_iso_or_equiv` / `U_iso_or_equiv` | core | Structure — single-tag emit | `_atom_site.B_iso_or_equiv` xor `_atom_site.U_iso_or_equiv` per row, based on `_atom_site.ADP_type`. | +| `_atom_site_aniso.B_*` / `U_*` | core | Structure — single-tag emit | `_atom_site_aniso.B_*` xor `_atom_site_aniso.U_*` per row. | +| `_space_group.name_h_m` | core (`_space_group.name_H-M_alt`) | Structure — casing fix | `_space_group.name_H-M_alt`. | +| `_space_group.it_coordinate_system_code` | core (`_space_group.IT_coordinate_system_code`) | Structure — casing fix | `_space_group.IT_coordinate_system_code`. | +| symmetry operations | core (`_space_group_symop.*`) | (not emitted today) | `_space_group_symop.id` + `_space_group_symop.operation_xyz` loop alongside the H-M name. | +| `_diffrn.ambient_temperature`, `ambient_pressure` | core | Experiment — unchanged | `_diffrn.ambient_temperature`, `_diffrn.ambient_pressure`. | +| `_diffrn.ambient_magnetic_field`, `ambient_electric_field` | none | Experiment — unchanged | `_easydiffraction_diffrn.ambient_magnetic_field`, `…electric_field` (project extension). | +| `_refln.*` | core | (no default save under refln) | `_refln.*` reflections loop (column set differs by domain — see §2.3). | +| `_pd_meas.*`, `_pd_proc.*`, `_pd_calc.*`, `_pd_data.*` | pdCIF | Experiment — unchanged | `_pd_meas.*`, `_pd_proc.*`, `_pd_calc.*` profile-data loop (see §2.3). | +| `_pd_background.*` | pdCIF | Experiment — unchanged | `_pd_background.*`. | +| `_pd_phase_block.*` | pdCIF | Experiment — unchanged | `_pd_phase_block.*`. | +| `_sc_crystal_block.*` | community (no IUCr counterpart) | Experiment — unchanged | `_easydiffraction_sc_crystal_block.*` in IUCr export. | +| `_instr.wavelength` | core (`_diffrn_radiation_wavelength.value`) | Experiment — unchanged | `_diffrn_radiation_wavelength.{id, value, wt}` — single-row category for monochromatic; loop only for multi-λ. | +| `_instr.2theta_offset` | pdCIF (`_pd_calib.2theta_offset`) | Experiment — unchanged | `_pd_calib.2theta_offset`. | +| `_instr.2theta_bank`, `d_to_tof_*` | pdCIF (`_pd_calib_d_to_tof.*` loop) | Experiment — unchanged | Four-row loop `_pd_calib_d_to_tof.{id, coeff, power, coeff_su, diffractogram_id}`. | +| `_peak.*` (parametric profile shape) | none (pdCIF has no shape parameters) | Experiment — unchanged | `_easydiffraction_peak.*` + `_pd_proc_ls.profile_function` free-text descriptor. | +| `_extinction.*` | core (`_refine_ls.extinction_*` items) | Experiment — unchanged | `_easydiffraction_extinction.*` + dual emit `_refine_ls.extinction_{method,coef,expression}`. | +| `_excluded_region.*` | pdCIF (`_pd_proc.info_excluded_regions` free-text) | Experiment — unchanged | `_easydiffraction_excluded_region.*` + `_pd_proc.info_excluded_regions` free-text rendering. | +| `_expt_type.*` | none | Experiment — unchanged | `_easydiffraction_experiment_type.*`. | | `_calculator.type`, `_minimizer.type` | none | Analysis — unchanged | Selection fields remain settings only; identity is read from `analysis.software` for `_easydiffraction_software.{framework, calculator, minimizer}` and `_computing.structure_refinement`. | -| `_software.*` | none | Analysis — new provenance category | Source for `_easydiffraction_software.{framework, calculator, minimizer}`, `_easydiffraction_software.fit_datetime`, and `_computing.structure_refinement` in `data_global`. | -| `_minimizer.*` settings (tolerances, max_iter, …) | none | Analysis — unchanged | `_easydiffraction_minimizer.*` (settings only, separate from the identification triple). | -| `_fitting_mode.type`, `_background.type` | none | Analysis / Experiment — unchanged | `_easydiffraction_fitting_mode.type`, `_easydiffraction_background.type` selectors. | -| `_fit_result.reduced_chi_square`, `n_data_points`, `n_parameters` | core (`_refine_ls.*`) and pdCIF (`_pd_proc_ls.*`) | Analysis — unchanged (topology-neutral) | Shape-shifting per topology: see §1.2 and §3 transformers. | -| `_fit_result.*` (R-factors, counts, profile/background function) | core / pdCIF | Analysis — new fields under `_fit_result.*` | IUCr export remaps to per-topology `_refine_ls.*` / `_pd_proc_ls.*`; item names already match dictionary casing (§1.2). | -| `_fit_result.*` (Bayesian diagnostics, success, message, fitting_time, iterations, result_kind) | none | Analysis — unchanged | `_easydiffraction_fit_result.*`. | -| `_fit_parameter`, `_fit_parameter_correlation` | none / partial | Analysis — unchanged | `_easydiffraction_fit_parameter*` (no IUCr counterpart for per-parameter posterior). | -| `_alias`, `_constraint` | none | Analysis — unchanged | `_easydiffraction_alias*`, `_easydiffraction_constraint*`. | -| `_joint_fit`, `_sequential_fit*` | none | Analysis — unchanged | `_easydiffraction_joint_fit*`, `_easydiffraction_sequential_fit*`. | -| reflection-set aggregates | core (`_reflns.*`) | Analysis — new fields | `_reflns.number_total`, `_reflns.number_gt`, `_reflns.threshold_expression` (e.g. `'I>3\s(I)'`). | -| publication metadata | core (`_journal.*`, `_publ_author.*`, `_publ_contact_author.*`, `_audit.*`) | (not emitted today) | Emitted in `data_global` block per §2.3a with `?` placeholders. | -| analysis-stack identification | core (`_computing.structure_refinement`) | Analysis — `_software.*` persisted | `_easydiffraction_software.{framework, calculator, minimizer}` triple + `_easydiffraction_software.fit_datetime` + `_computing.structure_refinement` derived from `analysis.software`. | +| `_software.*` | none | Analysis — new provenance category | Source for `_easydiffraction_software.{framework, calculator, minimizer}`, `_easydiffraction_software.fit_datetime`, and `_computing.structure_refinement` in `data_global`. | +| `_minimizer.*` settings (tolerances, max_iter, …) | none | Analysis — unchanged | `_easydiffraction_minimizer.*` (settings only, separate from the identification triple). | +| `_fitting_mode.type`, `_background.type` | none | Analysis / Experiment — unchanged | `_easydiffraction_fitting_mode.type`, `_easydiffraction_background.type` selectors. | +| `_fit_result.reduced_chi_square`, `n_data_points`, `n_parameters` | core (`_refine_ls.*`) and pdCIF (`_pd_proc_ls.*`) | Analysis — unchanged (topology-neutral) | Shape-shifting per topology: see §1.2 and §3 transformers. | +| `_fit_result.*` (R-factors, counts, profile/background function) | core / pdCIF | Analysis — new fields under `_fit_result.*` | IUCr export remaps to per-topology `_refine_ls.*` / `_pd_proc_ls.*`; item names already match dictionary casing (§1.2). | +| `_fit_result.*` (Bayesian diagnostics, success, message, fitting_time, iterations, result_kind) | none | Analysis — unchanged | `_easydiffraction_fit_result.*`. | +| `_fit_parameter`, `_fit_parameter_correlation` | none / partial | Analysis — unchanged | `_easydiffraction_fit_parameter*` (no IUCr counterpart for per-parameter posterior). | +| `_alias`, `_constraint` | none | Analysis — unchanged | `_easydiffraction_alias*`, `_easydiffraction_constraint*`. | +| `_joint_fit`, `_sequential_fit*` | none | Analysis — unchanged | `_easydiffraction_joint_fit*`, `_easydiffraction_sequential_fit*`. | +| reflection-set aggregates | core (`_reflns.*`) | Analysis — new fields | `_reflns.number_total`, `_reflns.number_gt`, `_reflns.threshold_expression` (e.g. `'I>3\s(I)'`). | +| publication metadata | core (`_journal.*`, `_publ_author.*`, `_publ_contact_author.*`, `_audit.*`) | (not emitted today) | Emitted in `data_global` block per §2.3a with `?` placeholders. | +| analysis-stack identification | core (`_computing.structure_refinement`) | Analysis — `_software.*` persisted | `_easydiffraction_software.{framework, calculator, minimizer}` triple + `_easydiffraction_software.fit_datetime` + `_computing.structure_refinement` derived from `analysis.software`. | ## Decision @@ -309,10 +310,10 @@ project.report.save() # write configured reports only and replaced by `project.report` — a facade slot that owns journal report generation. The `project.report.{cif,html,tex,pdf}` booleans control which reports `project.save()` emits. Per-format methods -(`save_cif()`, `save_html()`, `save_tex()`, `save_pdf()`) write -one-off artifacts without changing that configuration. The no-arg -`project.report.save()` uses those booleans and raises `ValueError` -when no formats are enabled. +(`save_cif()`, `save_html()`, `save_tex()`, `save_pdf()`) write one-off +artifacts without changing that configuration. The no-arg +`project.report.save()` uses those booleans and raises `ValueError` when +no formats are enabled. #### 2.2 Output location @@ -545,9 +546,8 @@ similar) is deferred — see Deferred Work. #### 2.3a-i `_easydiffraction_software` framework The IUCr submission needs to identify the analysis stack. The project -emits one structured category in `data_global` from -`analysis.software`, carrying three role-keyed strings and an optional -fit timestamp: +emits one structured category in `data_global` from `analysis.software`, +carrying three role-keyed strings and an optional fit timestamp: ``` _easydiffraction_software.framework 'EasyDiffraction 0.17.0' @@ -1041,8 +1041,8 @@ Policy: `project.report.save_cif()` for the IUCr CIF one-off path. `summary.cif` is no longer written by default `Project.save()`; the slot is repurposed for IUCr / journal report generation in - `reports/.cif` (see §2). The unimplemented - `summary_to_cif()` placeholder code path + `reports/.cif` (see §2). The unimplemented `summary_to_cif()` + placeholder code path ([`project.py:464`](../../../../src/easydiffraction/project/project.py)) is removed as part of the implementation plan; no summary content survives the transition because nothing was being written there in the @@ -1052,8 +1052,8 @@ Policy: and replaced by `project.report.help()` (same responsibilities, new slot name). All other entries in the help-surface table are unaffected. -- [`project-summary-rendering.md`](project-summary-rendering.md) - — amends this ADR's report API: public `check()` / `check=True` are +- [`project-summary-rendering.md`](project-summary-rendering.md) — + amends this ADR's report API: public `check()` / `check=True` are removed, gemmi validation moves inside CIF write paths, the `_easydiffraction_software.*` triple is read from `analysis.software`, and `_easydiffraction_software.fit_datetime` is added when fit @@ -1123,11 +1123,11 @@ it. ## Deferred Work - **Publication-metadata override hook.** A user-supplied - `reports/publ_info.json` (or `publ_info.toml`) read by - the IUCr report writer to replace the `?` placeholders in - `data_global` (`_journal.*`, `_publ_*`, `_publ_author.*` loop - entries). Out of scope for the first pass; revisit once the IUCr - export is shipping and users have feedback on workflow friction. + `reports/publ_info.json` (or `publ_info.toml`) read by the IUCr report + writer to replace the `?` placeholders in `data_global` (`_journal.*`, + `_publ_*`, `_publ_author.*` loop entries). Out of scope for the first + pass; revisit once the IUCr export is shipping and users have feedback + on workflow friction. - **Crystallographic sanity validation.** The §2.5 validator covers spec compliance only. A future pass could integrate IUCr's web checkCIF (HTTP POST to the checkCIF endpoint) or bundle a local subset of its diff --git a/docs/dev/adrs/accepted/project-facade-and-persistence.md b/docs/dev/adrs/accepted/project-facade-and-persistence.md index 0f0389ff8..6d16f7118 100644 --- a/docs/dev/adrs/accepted/project-facade-and-persistence.md +++ b/docs/dev/adrs/accepted/project-facade-and-persistence.md @@ -59,10 +59,10 @@ under `reports/`. The previous `project.summary` placeholder and its Expose journal-submission metadata as `project.publication`. It is a top-level owner with CIF-aligned sibling categories for `_journal.*`, -`_journal_date.*`, `_journal_coeditor.*`, -`_publ_contact_author.*`, `_publ_body.*`, and the `_publ_author.*` -loop. These singleton publication categories persist in `project.cif` -and feed report exports; `reports/.cif` remains export-only. +`_journal_date.*`, `_journal_coeditor.*`, `_publ_contact_author.*`, +`_publ_body.*`, and the `_publ_author.*` loop. These singleton +publication categories persist in `project.cif` and feed report exports; +`reports/.cif` remains export-only. Keep project information available as `project.info`. The Python name avoids a confusing `project.project` access path, while the persisted @@ -91,9 +91,8 @@ it must not emit a `_project.path` CIF item. The project-level singleton categories currently persisted in `project.cif` are `_project.*`, `_chart.*`, `_report.*`, `_table.*`, -`_verbosity.*`, `_journal.*`, `_journal_date.*`, -`_journal_coeditor.*`, `_publ_contact_author.*`, `_publ_body.*`, and -the `_publ_author.*` loop. +`_verbosity.*`, `_journal.*`, `_journal_date.*`, `_journal_coeditor.*`, +`_publ_contact_author.*`, `_publ_body.*`, and the `_publ_author.*` loop. ## Consequences diff --git a/docs/dev/adrs/accepted/project-summary-rendering.md b/docs/dev/adrs/accepted/project-summary-rendering.md index c0aec695c..74d7d6ecd 100644 --- a/docs/dev/adrs/accepted/project-summary-rendering.md +++ b/docs/dev/adrs/accepted/project-summary-rendering.md @@ -9,87 +9,80 @@ on-demand journal-style LaTeX export, and (eventually) the GUI Summary tab all consume and emit. Runs alongside, and **extends**, the accepted -[`iucr-cif-tag-alignment.md`](../accepted/iucr-cif-tag-alignment.md) -ADR (landed as PR #184). The alignment ADR established: +[`iucr-cif-tag-alignment.md`](../accepted/iucr-cif-tag-alignment.md) ADR +(landed as PR #184). The alignment ADR established: - A new `project.report` facade slot (replaces the unimplemented - `project.summary` placeholder), with `save()` and `check()` - methods. + `project.summary` placeholder), with `save()` and `check()` methods. - A single `reports/` directory at project root. - A `project.save(report=True)` opt-in flag for the IUCr CIF. That ADR currently scopes `project.report` to **CIF only** — the -multi-datablock IUCr submission CIF written to -`reports/.cif`. This ADR keeps the facade and adds a -**`project.report` configuration category** with five scalar -persisted fields (`cif`, `html`, `tex`, `pdf`, `html_offline`) -on `project.cif`, plus ad-hoc per-format methods +multi-datablock IUCr submission CIF written to `reports/.cif`. +This ADR keeps the facade and adds a **`project.report` configuration +category** with five scalar persisted fields (`cif`, `html`, `tex`, +`pdf`, `html_offline`) on `project.cif`, plus ad-hoc per-format methods (`save_html()`, `save_cif()`, `save_tex()`, `save_pdf()`). The Python-side API uses those same boolean descriptors directly, matching -the persisted CIF shape. The LaTeX writer hardcodes -`iucrjournals` as its document class — there is no style -selector, no `_report.style` field, no `style=` arg on -`save_tex()` / `save_pdf()`. The -accepted IUCr `project.save(report=True)` flag is **removed**; -reports come from the config category, not from boolean flags. -All four format booleans default to `False` so `project.save()` -writes nothing under `reports/` until the user configures -otherwise, preserving the "no surprise files" property. - -Coordination points with the alignment ADR (no blocking conflicts; -its Open Questions section is empty): +the persisted CIF shape. The LaTeX writer hardcodes `iucrjournals` as +its document class — there is no style selector, no `_report.style` +field, no `style=` arg on `save_tex()` / `save_pdf()`. The accepted IUCr +`project.save(report=True)` flag is **removed**; reports come from the +config category, not from boolean flags. All four format booleans +default to `False` so `project.save()` writes nothing under `reports/` +until the user configures otherwise, preserving the "no surprise files" +property. + +Coordination points with the alignment ADR (no blocking conflicts; its +Open Questions section is empty): - **Software-stack identification** — the alignment ADR's §2.3a-i defines `_easydiffraction_software.{framework, calculator, minimizer}` as the structured CIF emission, plus a concatenated `_computing.structure_refinement` free-text string. This ADR's §4 - adopts the same three-role triple as the Python-side attribute - layout so the same data flows into both write paths. + adopts the same three-role triple as the Python-side attribute layout + so the same data flows into both write paths. - **Spec-compliance validation** — the alignment ADR's §2.5 added - `project.report.check()` (gemmi-based) and a `check=True` - flag on `project.report.save()`. This ADR **removes the public - surface** and moves the gemmi pass to an internal pre-write - step **inside the CIF emission paths only** — `save_cif()` and - the `cif` branch under `project.save()` (§1.4). HTML, TeX, and - PDF outputs are not gemmi-validatable and get no pre-write - validation; LaTeX errors surface at PDF-compile time via the - TeX engine. A writer that emits non-compliant CIF raises - `EasyDiffractionWriterError` instead. This ADR's deferred - `check_completeness()` (publication-side completeness) is a - separate concern that stays in Deferred Work. -- **Publication metadata source** — the alignment ADR's Deferred - Work proposes a user-supplied `reports/publ_info.{toml,json}` - to replace `?` placeholders. Both write paths read the same - Python attribute, **`project.publication`** — a new top-level - on `Project`, sibling to `project.info` and `project.analysis`. - The schema is defined in §5 of this ADR: six CIF-aligned - sibling categories (`journal`, `journal_date`, `journal_coeditor`, - `contact_author`, `body`, `authors`) with full IUCr-tag fidelity. - The loader accepts TOML (primary) and JSON (fallback); selection - is by file extension. + `project.report.check()` (gemmi-based) and a `check=True` flag on + `project.report.save()`. This ADR **removes the public surface** and + moves the gemmi pass to an internal pre-write step **inside the CIF + emission paths only** — `save_cif()` and the `cif` branch under + `project.save()` (§1.4). HTML, TeX, and PDF outputs are not + gemmi-validatable and get no pre-write validation; LaTeX errors + surface at PDF-compile time via the TeX engine. A writer that emits + non-compliant CIF raises `EasyDiffractionWriterError` instead. This + ADR's deferred `check_completeness()` (publication-side completeness) + is a separate concern that stays in Deferred Work. +- **Publication metadata source** — the alignment ADR's Deferred Work + proposes a user-supplied `reports/publ_info.{toml,json}` to replace + `?` placeholders. Both write paths read the same Python attribute, + **`project.publication`** — a new top-level on `Project`, sibling to + `project.info` and `project.analysis`. The schema is defined in §5 of + this ADR: six CIF-aligned sibling categories (`journal`, + `journal_date`, `journal_coeditor`, `contact_author`, `body`, + `authors`) with full IUCr-tag fidelity. The loader accepts TOML + (primary) and JSON (fallback); selection is by file extension. Also touches: -- [`analysis-cif-fit-state.md`](../accepted/analysis-cif-fit-state.md) - — adds an `analysis.software` provenance category that serialises +- [`analysis-cif-fit-state.md`](../accepted/analysis-cif-fit-state.md) — + adds an `analysis.software` provenance category that serialises through the analysis tier. - [`minimizer-input-output-split.md`](../accepted/minimizer-input-output-split.md) — the new provenance category lives alongside the existing minimizer/fit-result pairing, not inside it. - [`project-facade-and-persistence.md`](../accepted/project-facade-and-persistence.md) - — two changes: `project.report` gains a persisted - configuration category (`_report.*` in `project.cif`, see §1.3), - turning the facade into a hybrid of helper methods plus - persisted config; and a new top-level `project.publication` - owner is added alongside the existing `project.info`, - `project.structures`, `project.experiments`, + — two changes: `project.report` gains a persisted configuration + category (`_report.*` in `project.cif`, see §1.3), turning the facade + into a hybrid of helper methods plus persisted config; and a new + top-level `project.publication` owner is added alongside the existing + `project.info`, `project.structures`, `project.experiments`, `project.analysis`, `project.report` facade slots (see §5). - [`python-cif-category-correspondence.md`](python-cif-category-correspondence.md) — owns the Python↔CIF correspondence rule for **two** new - project-level singleton surfaces: - `project.report.* ↔ _report.*` (five scalar items, §1.3) and - `project.publication.*` sibling categories ↔ `_journal.*`, - `_publ_author.*`, `_publ_contact_author.*`, etc. (§5). + project-level singleton surfaces: `project.report.* ↔ _report.*` (five + scalar items, §1.3) and `project.publication.*` sibling categories ↔ + `_journal.*`, `_publ_author.*`, `_publ_contact_author.*`, etc. (§5). ## Context @@ -99,26 +92,25 @@ The library today has four shapes of summary output: project metadata, crystallographic data per phase, experimental configuration, and fit metrics ([report.py](../../../../src/easydiffraction/report/report.py)). - (Pre-PR #184 this was `Summary.show_report()` on - `project.summary`; the IUCr alignment ADR replaced the unimplemented - placeholder.) + (Pre-PR #184 this was `Summary.show_report()` on `project.summary`; + the IUCr alignment ADR replaced the unimplemented placeholder.) - `summary.cif` — was written into the project root on every - `project.save()` as the literal string `"To be added..."` until - PR #184 removed both the writer call and the placeholder method. - Not a valid CIF block in any version that shipped. + `project.save()` as the literal string `"To be added..."` until PR + #184 removed both the writer call and the placeholder method. Not a + valid CIF block in any version that shipped. - The old GUI's "Summary" tab — a single page listing project info, crystal data, data collection, refinement engine + goodness-of-fit, with an "Export summary" panel (Name, Format = HTML, Location). - An eventual journal manuscript — currently produced by hand from the scientist's notes and the values shown in the GUI Summary tab. -These four are renderings of the **same** logical view. Every field -the GUI shows is already reachable from the live Python objects; the -summary is not a source of truth and has no field of its own that -isn't computable from `project`, its `structures`, its `experiments`, -and `analysis.fit_results`. The exception is software provenance — -which calculation engine and minimizer (with versions) produced the -fit — which the library does not currently capture anywhere. +These four are renderings of the **same** logical view. Every field the +GUI shows is already reachable from the live Python objects; the summary +is not a source of truth and has no field of its own that isn't +computable from `project`, its `structures`, its `experiments`, and +`analysis.fit_results`. The exception is software provenance — which +calculation engine and minimizer (with versions) produced the fit — +which the library does not currently capture anywhere. Two pressures act on the design: @@ -127,77 +119,73 @@ Two pressures act on the design: programmatic API, not a CIF or an HTML file to re-parse. - **Submission-grade output.** Scientists publish in IUCr journals, Phys. Rev. B, J. Appl. Cryst., and others. The CIF side of that is - covered by the alignment ADR's IUCr export. The **manuscript** - side (refinement tables formatted to journal style) is not. - -The default-save `summary.cif` placeholder was the visible artefact -of the unresolved design question. The alignment ADR has since -replaced the unimplemented `project.summary` slot with a -`project.report` facade scoped to IUCr CIF generation -(`reports/.cif`). That resolves the CIF half of the -question but leaves the GUI Summary tab, the terminal -`show_report()`, the human-readable HTML, and the manuscript-bound -LaTeX/PDF without a definition. This ADR fills the gap by extending -the same `project.report` facade with non-CIF rendering surfaces. + covered by the alignment ADR's IUCr export. The **manuscript** side + (refinement tables formatted to journal style) is not. + +The default-save `summary.cif` placeholder was the visible artefact of +the unresolved design question. The alignment ADR has since replaced the +unimplemented `project.summary` slot with a `project.report` facade +scoped to IUCr CIF generation (`reports/.cif`). That resolves +the CIF half of the question but leaves the GUI Summary tab, the +terminal `show_report()`, the human-readable HTML, and the +manuscript-bound LaTeX/PDF without a definition. This ADR fills the gap +by extending the same `project.report` facade with non-CIF rendering +surfaces. ## Scope In scope: - Extend the alignment ADR's `project.report` facade with - terminal/Jupyter, HTML, and LaTeX rendering surfaces, a - configuration category (five scalar fields — - `project.report.{cif, html, tex, pdf, html_offline}` — - persisted in `project.cif`), and ad-hoc - per-format save methods. **All report formats are opt-in via the - configuration; every format defaults to `False` so - `project.save()` writes nothing under `reports/` until a + terminal/Jupyter, HTML, and LaTeX rendering surfaces, a configuration + category (five scalar fields — + `project.report.{cif, html, tex, pdf, html_offline}` — persisted in + `project.cif`), and ad-hoc per-format save methods. **All report + formats are opt-in via the configuration; every format defaults to + `False` so `project.save()` writes nothing under `reports/` until a format is enabled** — see §1 and §2 for the rationale. - Define the shared "summary data context" (one dictionary) that terminal, HTML, LaTeX, and GUI renderers all consume. - Add a Python-side software-provenance category on `analysis` (`analysis.software`) recording calculation-engine and minimization-engine name + version + URL stamped at fit time. - Persisted in `analysis/analysis.cif` (amends the IUCr ADR's - "Analysis — unchanged" stance for these fields; see §4 and the - ADRs-amended list). -- Add a new top-level `project.publication` owner on `Project` - (sibling to `project.info`, `project.structures`, - `project.experiments`, `project.analysis`, `project.report`) - carrying the `_publ_*` / `_journal_*` publication metadata - the IUCr writer otherwise emits as `?` placeholders. See §5; - amends `project-facade-and-persistence.md` and complements - `python-cif-category-correspondence.md`. -- Ship exactly one LaTeX style (`iucrjournals`) — no style - selector, no `ReportStyleEnum`, no `_report.style` field. - Multi-style support (REVTeX, Elsevier, etc.) is deferred - to a follow-up ADR; see "Deferred Work". + Persisted in `analysis/analysis.cif` (amends the IUCr ADR's "Analysis + — unchanged" stance for these fields; see §4 and the ADRs-amended + list). +- Add a new top-level `project.publication` owner on `Project` (sibling + to `project.info`, `project.structures`, `project.experiments`, + `project.analysis`, `project.report`) carrying the `_publ_*` / + `_journal_*` publication metadata the IUCr writer otherwise emits as + `?` placeholders. See §5; amends `project-facade-and-persistence.md` + and complements `python-cif-category-correspondence.md`. +- Ship exactly one LaTeX style (`iucrjournals`) — no style selector, no + `ReportStyleEnum`, no `_report.style` field. Multi-style support + (REVTeX, Elsevier, etc.) is deferred to a follow-up ADR; see "Deferred + Work". Out of scope: - CIF tag-name decisions for any serialised field. Those are the alignment ADR's job; this ADR notes recommended mappings and cross-references. -- The IUCr CIF submission export tag policy and multi-datablock - layout. Covered by the alignment ADR; the output file lives at +- The IUCr CIF submission export tag policy and multi-datablock layout. + Covered by the alignment ADR; the output file lives at `reports/.cif` and is opt-in via `project.report.cif = True`. - Pre-existing project-level singleton categories (`_info.*`, - `_chart.*`, `_table.*`, `_verbosity.*`). Covered by the - in-flight + `_chart.*`, `_table.*`, `_verbosity.*`). Covered by the in-flight [`python-cif-category-correspondence.md`](python-cif-category-correspondence.md). - This ADR **does** add one new project-level singleton - category, `_report.*`, alongside them (see §1.3 and the - ADRs-amended list); that surface is not delegated to the - correspondence ADR. + This ADR **does** add one new project-level singleton category, + `_report.*`, alongside them (see §1.3 and the ADRs-amended list); that + surface is not delegated to the correspondence ADR. - Static-image PDF generation. HTML prints from any browser; LaTeX compiles to PDF locally. No bundled PDF writer. -- Markdown export. Trivial follow-on if the Jinja base templates are - in place, but no current user requirement. +- Markdown export. Trivial follow-on if the Jinja base templates are in + place, but no current user requirement. ## Design Philosophy: Summary as a View -Summary is a **derived view**, not a persisted artifact. The same -data dictionary feeds every renderer: +Summary is a **derived view**, not a persisted artifact. The same data +dictionary feeds every renderer: ``` project, structures[], experiments[], analysis.fit_results, @@ -212,61 +200,58 @@ analysis.software (new — see §4) └──► GUI Summary tab (programmatic; eventual) ``` -Render targets never duplicate the data — they consume the same -context. New summary fields are added in one place (the context -builder); every renderer picks them up. +Render targets never duplicate the data — they consume the same context. +New summary fields are added in one place (the context builder); every +renderer picks them up. ## Decision ### 1. Extend `project.report` with rendering methods and a config category The alignment ADR has already created the `project.report` facade -(replacing the unimplemented `project.summary` placeholder). This -ADR extends it along two axes: - -- A new **configuration category** on `project.report` — - persisted in `project.cif`, matching the existing - `project.chart`, `project.table`, `project.verbosity` config - pattern — that records *which* report formats `project.save()` - emits and *how*. -- A new set of **ad-hoc per-format methods** for explicit - one-off writes that bypass the configuration. - -The accepted IUCr `project.save(report=True)` flag and the -public `project.report.check()` method are both **removed** (see -the ADRs-amended list). Reports come from configuration, not -boolean flags; dictionary-spec validation runs internally -before every CIF write (and only before CIF writes — HTML, TeX, -and PDF have no spec to validate against; see §1.4) and -surfaces as an error on writer bugs, not as a user opt-in. +(replacing the unimplemented `project.summary` placeholder). This ADR +extends it along two axes: + +- A new **configuration category** on `project.report` — persisted in + `project.cif`, matching the existing `project.chart`, `project.table`, + `project.verbosity` config pattern — that records _which_ report + formats `project.save()` emits and _how_. +- A new set of **ad-hoc per-format methods** for explicit one-off writes + that bypass the configuration. + +The accepted IUCr `project.save(report=True)` flag and the public +`project.report.check()` method are both **removed** (see the +ADRs-amended list). Reports come from configuration, not boolean flags; +dictionary-spec validation runs internally before every CIF write (and +only before CIF writes — HTML, TeX, and PDF have no spec to validate +against; see §1.4) and surfaces as an error on writer bugs, not as a +user opt-in. #### 1.1 Configuration category — `project.report.*` -Persisted fields on `project.report`, populated by the user -once and read by `project.save()` thereafter: - -| Field | Type | Default | Effect | -| --------------------------- | ------------ | --------- | ------------------------------------------------------------------------------------------------------ | -| `project.report.cif` | `bool` | `False` | When `True`, `project.save()` writes `reports/.cif`. | -| `project.report.html` | `bool` | `False` | When `True`, `project.save()` writes `reports/.html`. | -| `project.report.tex` | `bool` | `False` | When `True`, `project.save()` writes `reports/tex/{.tex, data/, styles/}`. | -| `project.report.pdf` | `bool` | `False` | When `True`, `project.save()` writes `reports/.pdf` (and `tex/` as a side-effect). | -| `project.report.html_offline` | `bool` | `False` | When `True`, the HTML report is **fully self-contained** — inline-bundles both Plotly and MathJax (~3 MB + ~1.5 MB on top of the otherwise-empty document). Otherwise both load from CDN. | - -Four per-format scalar booleans (`cif`, `html`, `tex`, `pdf`) -plus `html_offline` — **five fields total**, all single-row in -CIF. Matches the existing `project.chart`, `project.table`, -`project.verbosity` scalar-config shape verbatim. All booleans -default to `False`, so an unconfigured project produces no -`reports/` directory at all. - -There is no `style` field. The LaTeX output ships exactly one -class (`iucrjournals`); adding another style is deferred -work, not a v1 selector. See §3 for the reasoning behind the -single-style choice. - -There is no separate list-style `project.report.formats` property. -The Python API intentionally mirrors CIF and the other project-level +Persisted fields on `project.report`, populated by the user once and +read by `project.save()` thereafter: + +| Field | Type | Default | Effect | +| ----------------------------- | ------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `project.report.cif` | `bool` | `False` | When `True`, `project.save()` writes `reports/.cif`. | +| `project.report.html` | `bool` | `False` | When `True`, `project.save()` writes `reports/.html`. | +| `project.report.tex` | `bool` | `False` | When `True`, `project.save()` writes `reports/tex/{.tex, data/, styles/}`. | +| `project.report.pdf` | `bool` | `False` | When `True`, `project.save()` writes `reports/.pdf` (and `tex/` as a side-effect). | +| `project.report.html_offline` | `bool` | `False` | When `True`, the HTML report is **fully self-contained** — inline-bundles both Plotly and MathJax (~3 MB + ~1.5 MB on top of the otherwise-empty document). Otherwise both load from CDN. | + +Four per-format scalar booleans (`cif`, `html`, `tex`, `pdf`) plus +`html_offline` — **five fields total**, all single-row in CIF. Matches +the existing `project.chart`, `project.table`, `project.verbosity` +scalar-config shape verbatim. All booleans default to `False`, so an +unconfigured project produces no `reports/` directory at all. + +There is no `style` field. The LaTeX output ships exactly one class +(`iucrjournals`); adding another style is deferred work, not a v1 +selector. See §3 for the reasoning behind the single-style choice. + +There is no separate list-style `project.report.formats` property. The +Python API intentionally mirrors CIF and the other project-level configuration categories: each persisted scalar descriptor is set directly. @@ -291,8 +276,7 @@ project.save() ##### Enum backing per the closed-values ADR -The set of report formats is a finite closed set, so per the -accepted +The set of report formats is a finite closed set, so per the accepted [`enum-backed-closed-values.md`](../accepted/enum-backed-closed-values.md) contract it is represented internally as `(str, Enum)`: @@ -304,22 +288,21 @@ class ReportFormatEnum(str, Enum): PDF = 'pdf' ``` -The four per-format booleans (`project.report.cif`, `.html`, -`.tex`, `.pdf`) are the public configuration API. Internal save -dispatch may use `ReportFormatEnum` members to keep the finite -format set explicit, but the enum is not a user-facing selector. +The four per-format booleans (`project.report.cif`, `.html`, `.tex`, +`.pdf`) are the public configuration API. Internal save dispatch may use +`ReportFormatEnum` members to keep the finite format set explicit, but +the enum is not a user-facing selector. -There is no `ReportStyleEnum`. The LaTeX writer hardcodes -`iucrjournals` as its document class (see §3); when a future -ADR adds a second style, the `ReportStyleEnum` is reintroduced -together with a new `_report.style` config field. +There is no `ReportStyleEnum`. The LaTeX writer hardcodes `iucrjournals` +as its document class (see §3); when a future ADR adds a second style, +the `ReportStyleEnum` is reintroduced together with a new +`_report.style` config field. #### 1.2 Ad-hoc per-format methods -Each format has its own explicit write method on the facade, -independent of the persisted report booleans. Use when a user wants -to produce a one-off artifact without changing the persistent -configuration. +Each format has its own explicit write method on the facade, independent +of the persisted report booleans. Use when a user wants to produce a +one-off artifact without changing the persistent configuration. ```python project.report.save_cif() # writes reports/.cif @@ -347,34 +330,33 @@ project.report.show_experimental_data() project.report.show_fitting_details() ``` -Per-format method signatures only carry the args that apply to -that format — `save_html(offline=True)` is unambiguous; there -is no `save_tex(style=...)` because the LaTeX writer ships -exactly one style (`iucrjournals`), so a style selector would -be dead weight. The cross-format mixing that the earlier -flag-based draft had (`html_offline` ignored when -`html=False`, `style=` ignored without `tex=True`) is gone. +Per-format method signatures only carry the args that apply to that +format — `save_html(offline=True)` is unambiguous; there is no +`save_tex(style=...)` because the LaTeX writer ships exactly one style +(`iucrjournals`), so a style selector would be dead weight. The +cross-format mixing that the earlier flag-based draft had +(`html_offline` ignored when `html=False`, `style=` ignored without +`tex=True`) is gone. -`project.save()` itself takes no report-related arguments. The -accepted IUCr `project.save(report=True)` flag is removed (see -ADRs amended); reports are configured on `project.report.*`. +`project.save()` itself takes no report-related arguments. The accepted +IUCr `project.save(report=True)` flag is removed (see ADRs amended); +reports are configured on `project.report.*`. ```python project.save() # writes project files + enabled report booleans ``` -`Summary.as_cif()` and `summary_to_cif()` were already deleted -by the alignment ADR; this ADR's removal of `project.save(report=True)` +`Summary.as_cif()` and `summary_to_cif()` were already deleted by the +alignment ADR; this ADR's removal of `project.save(report=True)` finishes the flag-cleanup. **Empty-configuration behaviour split.** -The two entry points behave differently when no formats are -enabled — a deliberate split: `project.save()` writes the -project regardless (reports are a side-effect of configuration, -not the point of the call); `project.report.save()` is *only* -about reports, so calling it with nothing configured is a user -error. +The two entry points behave differently when no formats are enabled — a +deliberate split: `project.save()` writes the project regardless +(reports are a side-effect of configuration, not the point of the call); +`project.report.save()` is _only_ about reports, so calling it with +nothing configured is a user error. ```python # project.report.{cif,html,tex,pdf} == False (default — unconfigured) @@ -394,21 +376,21 @@ project.report.save() ``` The Python error matches the CLI's existing behaviour for -`ed save-report` with no flags (§7) — both surfaces refuse to -silently no-op when the user explicitly asked for a report. -`project.save()` keeps the no-report default because the user -asked to save the project, not the reports. +`ed save-report` with no flags (§7) — both surfaces refuse to silently +no-op when the user explicitly asked for a report. `project.save()` +keeps the no-report default because the user asked to save the project, +not the reports. -The per-format methods (`save_cif()`, `save_html()`, etc.) -never inspect the persisted report booleans — they always write -their format unconditionally. They are explicit one-offs. +The per-format methods (`save_cif()`, `save_html()`, etc.) never inspect +the persisted report booleans — they always write their format +unconditionally. They are explicit one-offs. #### 1.3 CIF persistence of the configuration -The configuration category serialises to `project.cif` next to -the other project-level singleton categories (`_info.*`, -`_chart.*`, `_table.*`, `_verbosity.*`). The CIF tag prefix is -`_report.*` — a Set category with five scalar items, no loops: +The configuration category serialises to `project.cif` next to the other +project-level singleton categories (`_info.*`, `_chart.*`, `_table.*`, +`_verbosity.*`). The CIF tag prefix is `_report.*` — a Set category with +five scalar items, no loops: ```text data_ @@ -432,18 +414,16 @@ _report.pdf no _report.html_offline no ``` -All five items are scalar DDLm dotted entries — the category -is declared `_definition.class Set` so a single value per -item, no loops permitted. Matches the existing `_chart.*`, -`_table.*`, `_verbosity.*` category shape exactly. The -`yes`/`no` boolean encoding follows the project's existing CIF -boolean convention. +All five items are scalar DDLm dotted entries — the category is declared +`_definition.class Set` so a single value per item, no loops permitted. +Matches the existing `_chart.*`, `_table.*`, `_verbosity.*` category +shape exactly. The `yes`/`no` boolean encoding follows the project's +existing CIF boolean convention. -The default unconfigured state writes four explicit `no` -values for the format booleans (not an absent or empty -representation), so the "no formats enabled" condition is -always a concrete CIF value, never an empty loop or missing -block: +The default unconfigured state writes four explicit `no` values for the +format booleans (not an absent or empty representation), so the "no +formats enabled" condition is always a concrete CIF value, never an +empty loop or missing block: ```text # Default (project.report.{cif,html,tex,pdf} = False): @@ -454,74 +434,70 @@ _report.pdf no _report.html_offline no ``` -Load semantics are symmetric: every `no` reads back as `False` -on its descriptor; the `formats` property view returns `[]`. +Load semantics are symmetric: every `no` reads back as `False` on its +descriptor; the `formats` property view returns `[]`. -The four per-format booleans give the IUCr-aware tooling -(`gemmi`, `publCIF`) a typed, validatable view of the -configuration — each format is a known enum item with type -`Boolean`, not a parsed string. There is no additional Python list -view with separate storage; the booleans are the source of truth. +The four per-format booleans give the IUCr-aware tooling (`gemmi`, +`publCIF`) a typed, validatable view of the configuration — each format +is a known enum item with type `Boolean`, not a parsed string. There is +no additional Python list view with separate storage; the booleans are +the source of truth. -Adding a new format in the future (e.g. `markdown`) is a -one-line schema extension: add `_report.markdown` to the -dictionary and a `project.report.markdown` boolean to the -descriptor, then include it in the internal save dispatch. +Adding a new format in the future (e.g. `markdown`) is a one-line schema +extension: add `_report.markdown` to the dictionary and a +`project.report.markdown` boolean to the descriptor, then include it in +the internal save dispatch. Loading a `project.cif` populates `project.report.*` per the -project-facade-and-persistence contract; on the next -`project.save()`, the configured formats emit automatically with -no further user action. +project-facade-and-persistence contract; on the next `project.save()`, +the configured formats emit automatically with no further user action. ##### Why not its own CIF file? -The project already has two distinct facade patterns for -top-level `project.*` slots, used deliberately for different -purposes. `project.report` is **Pattern A** — lightweight -project-level singleton config — not Pattern B — heavy datablock -owner with its own CIF file. The split is summarised below. - -| Slot | Pattern | CIF location | Python shape | -| -------------------------------------- | ------- | ------------------------------------------- | ----------------------------------------------------------- | -| `project.info` | A | `project.cif` (`_info.*`) | small `CategoryItem` | -| `project.chart` | A | `project.cif` (`_chart.*`) | `CategoryItem` (one field) | -| `project.table` | A | `project.cif` (`_table.*`) | `CategoryItem` (one field) | -| `project.verbosity` | A | `project.cif` (`_verbosity.*`) | `CategoryItem` (one field) | -| **`project.report`** (this ADR) | **A** | **`project.cif` (`_report.*`)** | **`CategoryItem` (five fields) plus action methods** | -| `project.publication` (this ADR, §5) | A | `project.cif` (`_publ_*` / `_journal_*`) | `CategoryOwner` of six sibling categories | -| `project.analysis` | B | `analysis/analysis.cif` | `CategoryOwner` (heavy datablock) | -| `project.structures[name]` | B | `structures/.cif` | `CategoryOwner` (heavy datablock) | -| `project.experiments[name]` | B | `experiments/.cif` | `CategoryOwner` (heavy datablock) | +The project already has two distinct facade patterns for top-level +`project.*` slots, used deliberately for different purposes. +`project.report` is **Pattern A** — lightweight project-level singleton +config — not Pattern B — heavy datablock owner with its own CIF file. +The split is summarised below. + +| Slot | Pattern | CIF location | Python shape | +| ------------------------------------ | ------- | ---------------------------------------- | ---------------------------------------------------- | +| `project.info` | A | `project.cif` (`_info.*`) | small `CategoryItem` | +| `project.chart` | A | `project.cif` (`_chart.*`) | `CategoryItem` (one field) | +| `project.table` | A | `project.cif` (`_table.*`) | `CategoryItem` (one field) | +| `project.verbosity` | A | `project.cif` (`_verbosity.*`) | `CategoryItem` (one field) | +| **`project.report`** (this ADR) | **A** | **`project.cif` (`_report.*`)** | **`CategoryItem` (five fields) plus action methods** | +| `project.publication` (this ADR, §5) | A | `project.cif` (`_publ_*` / `_journal_*`) | `CategoryOwner` of six sibling categories | +| `project.analysis` | B | `analysis/analysis.cif` | `CategoryOwner` (heavy datablock) | +| `project.structures[name]` | B | `structures/.cif` | `CategoryOwner` (heavy datablock) | +| `project.experiments[name]` | B | `experiments/.cif` | `CategoryOwner` (heavy datablock) | Reasons `project.report` is Pattern A, not Pattern B: - Five scalar config items do not justify a separate file (`reports/report.cif` would be a tiny file holding five lines). -- A `reports/report.cif` would force the `reports/` directory to - exist even when every format boolean is `False` and no reports - are written — breaks the "no surprise files" property the - design is built around. +- A `reports/report.cif` would force the `reports/` directory to exist + even when every format boolean is `False` and no reports are written — + breaks the "no surprise files" property the design is built around. - Splits report configuration from chart / table / verbosity - configuration, which already share `project.cif` for the same - reason — they are all project-level preferences, not domain - data. + configuration, which already share `project.cif` for the same reason — + they are all project-level preferences, not domain data. What makes `project.report` look heavier than `project.chart` / -`project.table` / `project.verbosity` is the action methods on -the facade (`save_cif()`, `save_html()`, `show_report()`, -`data_context()`, etc.). Those live on the Python class -alongside the configuration fields, which is the facade-hybrid -amendment to `project-facade-and-persistence.md` already -recorded in the ADRs-amended list. The action methods do not -change where the configuration persists — that stays in -`project.cif`. +`project.table` / `project.verbosity` is the action methods on the +facade (`save_cif()`, `save_html()`, `show_report()`, `data_context()`, +etc.). Those live on the Python class alongside the configuration +fields, which is the facade-hybrid amendment to +`project-facade-and-persistence.md` already recorded in the ADRs-amended +list. The action methods do not change where the configuration persists +— that stays in `project.cif`. #### 1.4 Validation moves internal — CIF only, writer-correctness only -The accepted IUCr ADR §2.5 exposed `project.report.check()` and -a `check=True` flag for gemmi-based dictionary-spec validation. -Both are **removed** in favour of a pre-write self-check inside -the CIF emission paths only: +The accepted IUCr ADR §2.5 exposed `project.report.check()` and a +`check=True` flag for gemmi-based dictionary-spec validation. Both are +**removed** in favour of a pre-write self-check inside the CIF emission +paths only: ```text project.save() @@ -538,57 +514,52 @@ project.save() **Scope split — what is validated and how:** -| Output | Validation | Failure mode | -| ----------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------- | -| `reports/.cif` | gemmi parse always; dictionary checks when local dictionaries load | `EasyDiffractionWriterError` for malformed generated CIF or dictionary diagnostics | -| `reports/.html` | none at write time | n/a — HTML is a render of the data context, not a typed format | -| `reports/tex/` | none at write time | n/a — LaTeX errors surface at PDF-compile time, with the engine's message | -| `reports/.pdf` | TeX engine's own compilation (returns non-zero on error) | engine-specific message; the `.tex` and `data/` CSVs are still written | +| Output | Validation | Failure mode | +| ------------------------ | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------- | +| `reports/.cif` | gemmi parse always; dictionary checks when local dictionaries load | `EasyDiffractionWriterError` for malformed generated CIF or dictionary diagnostics | +| `reports/.html` | none at write time | n/a — HTML is a render of the data context, not a typed format | +| `reports/tex/` | none at write time | n/a — LaTeX errors surface at PDF-compile time, with the engine's message | +| `reports/.pdf` | TeX engine's own compilation (returns non-zero on error) | engine-specific message; the `.tex` and `data/` CSVs are still written | -The dictionaries under `tmp/iucr-dicts/` are optional local -validation aids, not report inputs. If Gemmi cannot load those -local dictionary files, the writer skips dictionary-specific -checks after confirming the generated CIF itself parses; this -avoids blocking report generation on a stale or incompatible -dictionary cache. +The dictionaries under `tmp/iucr-dicts/` are optional local validation +aids, not report inputs. If Gemmi cannot load those local dictionary +files, the writer skips dictionary-specific checks after confirming the +generated CIF itself parses; this avoids blocking report generation on a +stale or incompatible dictionary cache. User-input validation (e.g., "is the email address syntactically -valid?", "is the ORCID well-formed?") happens **upstream** at -the descriptor's `value_spec` validator — the same boundary -where every other user input is checked. That's a separate -concern from the writer self-check above: descriptor validators -raise `typeguard.TypeCheckError` or the project's -`ValidationError` at *assignment time*, before any save is -attempted. By the time the writer runs, the values it receives -are already shape-correct; the gemmi pass on the CIF output -catches *writer* bugs (wrong tag, wrong type, malformed loop), -not user bugs. - -Rationale: dictionary compliance is a *writer-correctness* -property, not a user choice. A user can't fix a non-compliant -emission without modifying project state — and even then, the -writer should refuse to emit a malformed file in the first -place. Making validation a user-visible API surface invites -users to skip it; making it internal makes it impossible to -skip. Cached dictionary parsing keeps the overhead to a one-time -~200 ms session cost. The `EasyDiffractionWriterError` includes -the full gemmi diagnostic so bug reports are actionable. - -A separate, *completeness*-oriented check -(`project.report.check_completeness()`) — flagging unfilled -`_publ_*` / `_journal_*` placeholders for journal submission, -which is a publication-readiness question rather than a -writer-correctness one — is a different concern and stays in -Deferred Work. +valid?", "is the ORCID well-formed?") happens **upstream** at the +descriptor's `value_spec` validator — the same boundary where every +other user input is checked. That's a separate concern from the writer +self-check above: descriptor validators raise `typeguard.TypeCheckError` +or the project's `ValidationError` at _assignment time_, before any save +is attempted. By the time the writer runs, the values it receives are +already shape-correct; the gemmi pass on the CIF output catches _writer_ +bugs (wrong tag, wrong type, malformed loop), not user bugs. + +Rationale: dictionary compliance is a _writer-correctness_ property, not +a user choice. A user can't fix a non-compliant emission without +modifying project state — and even then, the writer should refuse to +emit a malformed file in the first place. Making validation a +user-visible API surface invites users to skip it; making it internal +makes it impossible to skip. Cached dictionary parsing keeps the +overhead to a one-time ~200 ms session cost. The +`EasyDiffractionWriterError` includes the full gemmi diagnostic so bug +reports are actionable. + +A separate, _completeness_-oriented check +(`project.report.check_completeness()`) — flagging unfilled `_publ_*` / +`_journal_*` placeholders for journal submission, which is a +publication-readiness question rather than a writer-correctness one — is +a different concern and stays in Deferred Work. #### 1.5 Descriptor display metadata — `DisplayHandler` -Parameter names like `u_iso` and unit strings like `Ų` need -prettier representations for the HTML and PDF renderers. The -ADR introduces a new optional handler on the descriptor base -classes (`Parameter`, `NumericDescriptor`, `StringDescriptor`) -that carries the typeset variants in a single place, sibling -to the existing `cif_handler`: +Parameter names like `u_iso` and unit strings like `Ų` need prettier +representations for the HTML and PDF renderers. The ADR introduces a new +optional handler on the descriptor base classes (`Parameter`, +`NumericDescriptor`, `StringDescriptor`) that carries the typeset +variants in a single place, sibling to the existing `cif_handler`: ```python from dataclasses import dataclass @@ -607,56 +578,53 @@ class DisplayHandler: latex_units: str | None = None # LaTeX text/math unit string ``` -`DisplayHandler` lives at -`src/easydiffraction/core/display_handler.py` alongside the -existing `CifHandler` in `src/easydiffraction/io/cif/handler.py` -— a frozen dataclass per the project's value-object convention -(matches `TypeInfo`, `Compatibility`, `CalculatorSupport` per -AGENTS.md). `slots=True` keeps memory overhead constant per -attached descriptor. +`DisplayHandler` lives at `src/easydiffraction/core/display_handler.py` +alongside the existing `CifHandler` in +`src/easydiffraction/io/cif/handler.py` — a frozen dataclass per the +project's value-object convention (matches `TypeInfo`, `Compatibility`, +`CalculatorSupport` per AGENTS.md). `slots=True` keeps memory overhead +constant per attached descriptor. -The plain `name` and `units` fields keep their existing role -on the descriptor, but their **content convention changes**: +The plain `name` and `units` fields keep their existing role on the +descriptor, but their **content convention changes**: - `name` — Python identifier; ASCII snake_case; unchanged. -- `units` — **ASCII only**, following the CIF DDLm - `_units.code` vocabulary from - [`cif_core.dic`](../../../../tmp/iucr-dicts/cif_core.dic) - **verbatim** when the dictionary defines a value for the - unit. The dictionary's vocabulary is a single source of - truth, but it is **not** uniformly plural — singular and - plural forms appear mixed across units (each unit is whatever - the dictionary actually says). Verified codes from +- `units` — **ASCII only**, following the CIF DDLm `_units.code` + vocabulary from + [`cif_core.dic`](../../../../tmp/iucr-dicts/cif_core.dic) **verbatim** + when the dictionary defines a value for the unit. The dictionary's + vocabulary is a single source of truth, but it is **not** uniformly + plural — singular and plural forms appear mixed across units (each + unit is whatever the dictionary actually says). Verified codes from `cif_core.dic`: - | What we need | `_units.code` value | Source line in cif_core.dic | - | ---------------- | ----------------------- | ------------------------------- | - | Å (length) | `angstroms` (plural) | line 1213 | - | Ų (area) | `angstrom_squared` (singular `angstrom`) | line 1178 | - | ° (angle) | `degrees` (plural) | line 500, 519, 1247, … | - | K (temperature) | `kelvins` (plural) | line 210, 232, 287, 316 | - | Pa (pressure) | `kilopascals` (plural) | line 115, 136, 161, 184 | - | µs (time) | `microseconds` (plural) | (from `cif_pow.dic` TOF text) | - | Da (mass) | `dalton` (singular) | line 753 | - | MGy (dose) | `megagray` (singular) | line 592, 607 | - | Å⁻¹ (reciprocal) | `reciprocal_angstroms` | line 795, 825 | - | Å⁻² (reciprocal area) | `reciprocal_angstrom_squared` | line 1552, 1587 | - | dimensionless | `none` | line 459, 480, … | + | What we need | `_units.code` value | Source line in cif_core.dic | + | --------------------- | ---------------------------------------- | ----------------------------- | + | Å (length) | `angstroms` (plural) | line 1213 | + | Ų (area) | `angstrom_squared` (singular `angstrom`) | line 1178 | + | ° (angle) | `degrees` (plural) | line 500, 519, 1247, … | + | K (temperature) | `kelvins` (plural) | line 210, 232, 287, 316 | + | Pa (pressure) | `kilopascals` (plural) | line 115, 136, 161, 184 | + | µs (time) | `microseconds` (plural) | (from `cif_pow.dic` TOF text) | + | Da (mass) | `dalton` (singular) | line 753 | + | MGy (dose) | `megagray` (singular) | line 592, 607 | + | Å⁻¹ (reciprocal) | `reciprocal_angstroms` | line 795, 825 | + | Å⁻² (reciprocal area) | `reciprocal_angstrom_squared` | line 1552, 1587 | + | dimensionless | `none` | line 459, 480, … | - **Units the dictionary does not define.** The crystallographic - vocabulary includes a handful of compound units that - `cif_core.dic` does not assign a `_units.code` to — the one - example currently in scope is `deg²` (squared degrees, used - for some angular variance metrics). Convention for these: - extend the same naming pattern (`degrees_squared`) as a - **project-internal code** with no `_units.code` round-trip. - The implementation plan keeps a small `units_vocabulary.py` - module listing every code (dictionary and project-internal) - so a sweep can validate every `units=` string at + vocabulary includes a handful of compound units that `cif_core.dic` + does not assign a `_units.code` to — the one example currently in + scope is `deg²` (squared degrees, used for some angular variance + metrics). Convention for these: extend the same naming pattern + (`degrees_squared`) as a **project-internal code** with no + `_units.code` round-trip. The implementation plan keeps a small + `units_vocabulary.py` module listing every code (dictionary and + project-internal) so a sweep can validate every `units=` string at descriptor-declaration time. -The Unicode-symbol form (`Ų`) moves into `display_units`; -the LaTeX form (`\AA$^2$`) into `latex_units`. +The Unicode-symbol form (`Ų`) moves into `display_units`; the LaTeX +form (`\AA$^2$`) into `latex_units`. ##### Worked example — `u_iso` @@ -676,117 +644,108 @@ self._u_iso = Parameter( ) ``` -| Renderer / context | Name uses | Units uses | -| ------------------------- | ---------------------------------- | -------------------- | -| LaTeX (`save_tex`) | `$U_{\mathrm{iso}}$` | `\AA$^2$` | -| HTML (`save_html`, MathJax-rendered) | `$U_{\mathrm{iso}}$` | `\AA$^2$` | -| HTML pre-MathJax / GUI / `show_report()` | `Uiso` | `Ų` | -| `project.report.data_context()` raw dict | both available | both available | -| CIF emission | `_atom_site.U_iso_or_equiv` | (no `_units.code` row today) | -| Python code / repr | `u_iso` | `angstrom_squared` | +| Renderer / context | Name uses | Units uses | +| ---------------------------------------- | --------------------------- | ---------------------------- | +| LaTeX (`save_tex`) | `$U_{\mathrm{iso}}$` | `\AA$^2$` | +| HTML (`save_html`, MathJax-rendered) | `$U_{\mathrm{iso}}$` | `\AA$^2$` | +| HTML pre-MathJax / GUI / `show_report()` | `Uiso` | `Ų` | +| `project.report.data_context()` raw dict | both available | both available | +| CIF emission | `_atom_site.U_iso_or_equiv` | (no `_units.code` row today) | +| Python code / repr | `u_iso` | `angstrom_squared` | ##### Resolution rules -The renderers consult the `DisplayHandler` (if attached) using -a per-context fallback chain: +The renderers consult the `DisplayHandler` (if attached) using a +per-context fallback chain: - **LaTeX context** (`save_tex`, `save_pdf`, `as_tex`): `handler.latex_name or descriptor.name`, `handler.latex_units or descriptor.units`. - **HTML context** (`save_html`, `as_html`): `handler.display_name or descriptor.name`, - `handler.display_units or descriptor.units`. - The HTML template additionally surrounds `handler.latex_name` - / `handler.latex_units` with `\(...\)` math delimiters so - MathJax picks them up where the descriptor has typeset - variants — i.e., HTML can show the same `$U_{\mathrm{iso}}$` - the PDF shows, while a GUI tooltip or `show_report()` - printout falls back to `display_*`. + `handler.display_units or descriptor.units`. The HTML template + additionally surrounds `handler.latex_name` / `handler.latex_units` + with `\(...\)` math delimiters so MathJax picks them up where the + descriptor has typeset variants — i.e., HTML can show the same + `$U_{\mathrm{iso}}$` the PDF shows, while a GUI tooltip or + `show_report()` printout falls back to `display_*`. - **GUI / terminal / `show_*()` context**: `handler.display_name or descriptor.name`, `handler.display_units or descriptor.units`. Each chain falls through to the descriptor's plain fields, so -**descriptors without a `display_handler` continue to work -unchanged** — they simply render as `u_iso` / `angstrom_squared` -in all contexts. Adding a `display_handler` is opt-in per -descriptor. - -**Table-rendering paths MUST read through the resolution chain -above, not the plain `descriptor.units` field directly.** This -is a strict requirement because `units=` now holds ASCII CIF -DDLm codes (`'angstrom_squared'`) that would look ridiculous -as a column header. Concretely the following call sites -migrate in the implementation sweep: - -- Every `show_*()` method on `Report` (terminal / Jupyter - table builders) — the unit column or row header is built - from `display_units or units`, not `units` alone. -- Every Jinja macro in `templates/base.j2` that formats a - parameter row — same resolution rule. +**descriptors without a `display_handler` continue to work unchanged** — +they simply render as `u_iso` / `angstrom_squared` in all contexts. +Adding a `display_handler` is opt-in per descriptor. + +**Table-rendering paths MUST read through the resolution chain above, +not the plain `descriptor.units` field directly.** This is a strict +requirement because `units=` now holds ASCII CIF DDLm codes +(`'angstrom_squared'`) that would look ridiculous as a column header. +Concretely the following call sites migrate in the implementation sweep: + +- Every `show_*()` method on `Report` (terminal / Jupyter table + builders) — the unit column or row header is built from + `display_units or units`, not `units` alone. +- Every Jinja macro in `templates/base.j2` that formats a parameter row + — same resolution rule. - The HTML template (`templates/html/report.html.j2`) uses - `display_units` for non-math contexts and the latex_units - variant inside `\(...\)` math delimiters where the - descriptor declares both. -- The LaTeX template (`templates/tex/report.tex.j2`) uses - `latex_units` (falling through `display_units` then `units` - if not declared). -- The shared `data_context()` (§6) builder exposes both - rendered strings per parameter so neither template has to - re-derive the fallback chain — the resolution happens once - in the builder. - -External / third-party readers that hard-coded -`parameter.units` to compare against `'Ų'` (the prior Unicode -form) are flagged in the Open Questions section for a -project-wide audit before the sweep lands. + `display_units` for non-math contexts and the latex_units variant + inside `\(...\)` math delimiters where the descriptor declares both. +- The LaTeX template (`templates/tex/report.tex.j2`) uses `latex_units` + (falling through `display_units` then `units` if not declared). +- The shared `data_context()` (§6) builder exposes both rendered strings + per parameter so neither template has to re-derive the fallback chain + — the resolution happens once in the builder. + +External / third-party readers that hard-coded `parameter.units` to +compare against `'Ų'` (the prior Unicode form) are flagged in the Open +Questions section for a project-wide audit before the sweep lands. ##### Why a handler class instead of four kwargs Two design pressures: -- The fields cluster — they are all "how to display this - parameter" — so a single handler keeps the descriptor - constructor flat. `display_handler=DisplayHandler(latex_name=..., - display_name=...)` reads cleaner than four sibling kwargs. +- The fields cluster — they are all "how to display this parameter" — so + a single handler keeps the descriptor constructor flat. + `display_handler=DisplayHandler(latex_name=..., display_name=...)` + reads cleaner than four sibling kwargs. - Future display targets (Markdown export, GUI tooltips, an ASCII-fallback for terminal narrow-mode) can add fields to - `DisplayHandler` without growing the descriptor constructor - signature. + `DisplayHandler` without growing the descriptor constructor signature. The mechanism mirrors the existing `cif_handler=CifHandler(...)` -pattern, so anyone reading the descriptor declarations sees the -same shape for CIF metadata and display metadata. +pattern, so anyone reading the descriptor declarations sees the same +shape for CIF metadata and display metadata. ##### Migration sweep -Existing descriptors use `units='Å'` / `'Ų'` / `'°'` etc. -(Unicode short forms). The implementation plan owns the sweep -that: - -- Converts every existing `units=` Unicode string to the - ASCII CIF DDLm form (`'Ų'` → `'angstrom_squared'`). -- Adds `display_handler=DisplayHandler(...)` to descriptors - the renderers benefit from prettifying (atom-site - positions / ADPs, cell parameters, fit-result R-factors, - refinement statistics, peak parameters, …). Descriptors - the renderers don't show (CIF-only internal state) get no - handler — the fallback to `name`/`units` is fine. -- Verifies the `_chart`, `_table`, `_verbosity` enum values - and other singleton-config CIF strings don't accidentally - collide with the new units vocabulary (they shouldn't — - those are tag values, not unit codes). - -The sweep is a Phase 1 step in the implementation plan, not -an ADR-level decision. +Existing descriptors use `units='Å'` / `'Ų'` / `'°'` etc. (Unicode +short forms). The implementation plan owns the sweep that: + +- Converts every existing `units=` Unicode string to the ASCII CIF DDLm + form (`'Ų'` → `'angstrom_squared'`). +- Adds `display_handler=DisplayHandler(...)` to descriptors the + renderers benefit from prettifying (atom-site positions / ADPs, cell + parameters, fit-result R-factors, refinement statistics, peak + parameters, …). Descriptors the renderers don't show (CIF-only + internal state) get no handler — the fallback to `name`/`units` is + fine. +- Verifies the `_chart`, `_table`, `_verbosity` enum values and other + singleton-config CIF strings don't accidentally collide with the new + units vocabulary (they shouldn't — those are tag values, not unit + codes). + +The sweep is a Phase 1 step in the implementation plan, not an ADR-level +decision. ### 2. HTML report — config-driven via `project.report.html` `project.report.html = True` causes `project.save()` to write -`reports/.html`. The all-`False` default keeps -`reports/` from being touched at all on plain `project.save()`. -For one-off HTML without changing the persistent config, call -`project.report.save_html()` directly. +`reports/.html`. The all-`False` default keeps `reports/` from +being touched at all on plain `project.save()`. For one-off HTML without +changing the persistent config, call `project.report.save_html()` +directly. ```python # Persistent — every subsequent save writes the HTML report. @@ -804,239 +763,217 @@ project.report.save_html() # CDN: Plotly + MathJax both from CDN project.report.save_html(offline=True) # inline: Plotly + MathJax both inlined ``` -Asset-bundling modes — `html_offline` controls **both** assets -together (single switch, single contract): +Asset-bundling modes — `html_offline` controls **both** assets together +(single switch, single contract): -- **CDN mode (default)** — Plotly via - `include_plotlyjs='cdn'` (~50-300 KB on top of the - otherwise-empty document, depending on chart count); - MathJax from `https://cdn.jsdelivr.net/npm/mathjax@3/...` - via ``. + - Template emits one of two ``. - `False`: ``. - - When `html_offline=True`, the renderer copies the - vendored file next to the emitted `.html` so - the relative `
Short nameShort nameShort nameShort name