forktex-documents 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,291 @@
1
+ # Copyright (C) 2026 FORKTEX S.R.L.
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-ForkTex-Commercial
4
+ #
5
+ # This file is part of ForkTex Documents.
6
+ #
7
+ # For commercial licensing -- including use in proprietary products, SaaS
8
+ # deployments, or any context where AGPL obligations cannot be met -- you
9
+ # MUST obtain a commercial license from FORKTEX S.R.L. (info@forktex.com).
10
+ #
11
+ # This program is free software: you can redistribute it and/or modify
12
+ # it under the terms of the GNU Affero General Public License as published by
13
+ # the Free Software Foundation, either version 3 of the License, or
14
+ # (at your option) any later version.
15
+ #
16
+ # This program is distributed in the hope that it will be useful,
17
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
+ # GNU Affero General Public License for more details.
20
+ #
21
+ # You should have received a copy of the GNU Affero General Public License
22
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
23
+
24
+ """forktex-documents — every document the company issues, from one framework.
25
+
26
+ A contract, an invoice, an offer, a report, a deck: all the same thing. Structured data,
27
+ composed into a canonical, projected through a theme. Particular kinds are instantiations
28
+ over the common denominator, never parallel frameworks.
29
+
30
+ The shape, in one pass:
31
+
32
+ ```python
33
+ DocumentRequest what you author: entity refs + params (JSON, the truth)
34
+ ↓ resolve + localize + compose ← all arithmetic lives here
35
+ FamilyCanonical what the document means; mandates checked
36
+ ↓ .blocks() ↓ read directly
37
+ Document (block tree) UBL / e-Factura XML
38
+ ↓ project(theme)
39
+ Artifact html · pdf (a UI over the JSON)
40
+ ```
41
+
42
+ `__all__` is the contract. Anything not in it is internal and may move.
43
+ """
44
+
45
+ from __future__ import annotations
46
+
47
+ from forktex_documents.domain.base import DocumentModel
48
+ from forktex_documents.domain.blocks import (
49
+ Block,
50
+ CalloutBlock,
51
+ CardBlock,
52
+ ChartBlock,
53
+ ChecklistBlock,
54
+ ChecklistOption,
55
+ ClauseBlock,
56
+ Column,
57
+ ComparisonBlock,
58
+ ComparisonRow,
59
+ DataPoint,
60
+ FeatureItem,
61
+ FeatureListBlock,
62
+ FlowBlock,
63
+ FlowStep,
64
+ FootRow,
65
+ GroupedTableBlock,
66
+ HeadingBlock,
67
+ HeatmapBlock,
68
+ ImageBlock,
69
+ ImageGridBlock,
70
+ KeyValue,
71
+ KeyValuesBlock,
72
+ ListBlock,
73
+ ListItem,
74
+ MatrixBlock,
75
+ MoneyLine,
76
+ PageBreakBlock,
77
+ ParagraphBlock,
78
+ PartyCardBlock,
79
+ Series,
80
+ Signatory,
81
+ SignaturesBlock,
82
+ SpacerBlock,
83
+ StatTile,
84
+ StatTilesBlock,
85
+ TableBlock,
86
+ TableGroup,
87
+ TimelineBlock,
88
+ TimelineEntry,
89
+ TotalsBlock,
90
+ )
91
+ from forktex_documents.domain.canonical import FamilyCanonical, Mandate, check_mandates
92
+ from forktex_documents.domain.catalog import (
93
+ Catalog,
94
+ DocTypeDefinition,
95
+ FamilyDefinition,
96
+ InputSpec,
97
+ check_fragment_paths,
98
+ resolve_inheritance,
99
+ )
100
+ from forktex_documents.domain.document import Band, Document, Section
101
+ from forktex_documents.domain.entities import (
102
+ BankAccount,
103
+ EntityKindSpec,
104
+ EntityRef,
105
+ FieldSpec,
106
+ Party,
107
+ PostalAddress,
108
+ Product,
109
+ ResolvedEntity,
110
+ compile_entity_model,
111
+ )
112
+ from forktex_documents.domain.errors import (
113
+ AssetMissingError,
114
+ CatalogError,
115
+ ComposeError,
116
+ EntityResolutionError,
117
+ InheritanceError,
118
+ LocalizationError,
119
+ NumberingError,
120
+ PlaceholderError,
121
+ ProjectionError,
122
+ SchemaViolationError,
123
+ UnknownDocTypeError,
124
+ )
125
+ from forktex_documents.domain.fiscal import (
126
+ RetentionPolicy,
127
+ VatRate,
128
+ VatSchedule,
129
+ markup_from_price,
130
+ price_from_markup,
131
+ )
132
+ from forktex_documents.domain.formatting import RenderContext
133
+ from forktex_documents.domain.instance import DocumentInstance, DocumentRequest, SourceVersions
134
+ from forktex_documents.domain.localization import (
135
+ LegislationProfile,
136
+ LocaleBundle,
137
+ Localization,
138
+ TextFragment,
139
+ )
140
+ from forktex_documents.domain.numbering import DocumentNumber, SeriesKey
141
+ from forktex_documents.domain.registry import (
142
+ build,
143
+ canonicals_by_family,
144
+ dump,
145
+ known,
146
+ registered,
147
+ verify_registry,
148
+ )
149
+ from forktex_documents.domain.series import BURNED, InMemoryRegister
150
+ from forktex_documents.domain.theme import (
151
+ ChartPalette,
152
+ InkTokens,
153
+ PageGeometry,
154
+ Theme,
155
+ TypeScale,
156
+ )
157
+ from forktex_documents.ports import (
158
+ Artifact,
159
+ ArtifactStore,
160
+ AssetSource,
161
+ ComposeContext,
162
+ Composer,
163
+ DocTypeSource,
164
+ EntityResolver,
165
+ LocaleSource,
166
+ Numberer,
167
+ Projector,
168
+ )
169
+ from forktex_documents.render.assets import LayeredAssets, PackagedAssets
170
+ from forktex_documents.render.charts import render_chart, render_heatmap
171
+ from forktex_documents.render.projector import HtmlProjector
172
+
173
+ __all__ = [
174
+ "BURNED",
175
+ "Artifact",
176
+ "ArtifactStore",
177
+ "AssetMissingError",
178
+ "AssetSource",
179
+ "Band",
180
+ "BankAccount",
181
+ "Block",
182
+ "CalloutBlock",
183
+ "CardBlock",
184
+ "Catalog",
185
+ "CatalogError",
186
+ "ChartBlock",
187
+ "ChartPalette",
188
+ "ChecklistBlock",
189
+ "ChecklistOption",
190
+ "ClauseBlock",
191
+ "Column",
192
+ "ComparisonBlock",
193
+ "ComparisonRow",
194
+ "ComposeContext",
195
+ "ComposeError",
196
+ "Composer",
197
+ "DataPoint",
198
+ "DocTypeDefinition",
199
+ "DocTypeSource",
200
+ "Document",
201
+ "DocumentInstance",
202
+ "DocumentModel",
203
+ "DocumentNumber",
204
+ "DocumentRequest",
205
+ "EntityKindSpec",
206
+ "EntityRef",
207
+ "EntityResolutionError",
208
+ "EntityResolver",
209
+ "FamilyCanonical",
210
+ "FamilyDefinition",
211
+ "FeatureItem",
212
+ "FeatureListBlock",
213
+ "FieldSpec",
214
+ "FlowBlock",
215
+ "FlowStep",
216
+ "FootRow",
217
+ "GroupedTableBlock",
218
+ "HeadingBlock",
219
+ "HeatmapBlock",
220
+ "HtmlProjector",
221
+ "ImageBlock",
222
+ "ImageGridBlock",
223
+ "InMemoryRegister",
224
+ "InheritanceError",
225
+ "InkTokens",
226
+ "InputSpec",
227
+ "KeyValue",
228
+ "KeyValuesBlock",
229
+ "LayeredAssets",
230
+ "LegislationProfile",
231
+ "ListBlock",
232
+ "ListItem",
233
+ "LocaleBundle",
234
+ "LocaleSource",
235
+ "Localization",
236
+ "LocalizationError",
237
+ "Mandate",
238
+ "MatrixBlock",
239
+ "MoneyLine",
240
+ "Numberer",
241
+ "NumberingError",
242
+ "PackagedAssets",
243
+ "PageBreakBlock",
244
+ "PageGeometry",
245
+ "ParagraphBlock",
246
+ "Party",
247
+ "PartyCardBlock",
248
+ "PlaceholderError",
249
+ "PostalAddress",
250
+ "Product",
251
+ "ProjectionError",
252
+ "Projector",
253
+ "RenderContext",
254
+ "ResolvedEntity",
255
+ "RetentionPolicy",
256
+ "SchemaViolationError",
257
+ "Section",
258
+ "Series",
259
+ "SeriesKey",
260
+ "Signatory",
261
+ "SignaturesBlock",
262
+ "SourceVersions",
263
+ "SpacerBlock",
264
+ "StatTile",
265
+ "StatTilesBlock",
266
+ "TableBlock",
267
+ "TableGroup",
268
+ "TextFragment",
269
+ "Theme",
270
+ "TimelineBlock",
271
+ "TimelineEntry",
272
+ "TotalsBlock",
273
+ "TypeScale",
274
+ "UnknownDocTypeError",
275
+ "VatRate",
276
+ "VatSchedule",
277
+ "build",
278
+ "canonicals_by_family",
279
+ "check_fragment_paths",
280
+ "check_mandates",
281
+ "compile_entity_model",
282
+ "dump",
283
+ "known",
284
+ "markup_from_price",
285
+ "price_from_markup",
286
+ "registered",
287
+ "render_chart",
288
+ "render_heatmap",
289
+ "resolve_inheritance",
290
+ "verify_registry",
291
+ ]
@@ -0,0 +1,314 @@
1
+ /*
2
+ * Copyright (C) 2026 FORKTEX S.R.L.
3
+ *
4
+ * SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-ForkTex-Commercial
5
+ *
6
+ * This file is part of ForkTex Documents.
7
+ *
8
+ * For commercial licensing -- including use in proprietary products, SaaS
9
+ * deployments, or any context where AGPL obligations cannot be met -- you
10
+ * MUST obtain a commercial license from FORKTEX S.R.L. (info@forktex.com).
11
+ *
12
+ * This program is free software: you can redistribute it and/or modify
13
+ * it under the terms of the GNU Affero General Public License as published by
14
+ * the Free Software Foundation, either version 3 of the License, or
15
+ * (at your option) any later version.
16
+ *
17
+ * This program is distributed in the hope that it will be useful,
18
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
+ * GNU Affero General Public License for more details.
21
+ *
22
+ * You should have received a copy of the GNU Affero General Public License
23
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
24
+ */
25
+
26
+ /* ==========================================================================
27
+ paged.css — page structure and the block vocabulary.
28
+
29
+ Every colour, size and measurement is a CSS custom property emitted from the
30
+ Theme. Nothing here carries a literal brand colour, which is what makes
31
+ re-theming a document a change to one object rather than to this file.
32
+
33
+ The pagination half is carried from the one renderer that got it right; the
34
+ comments say why each rule exists, because each of them is load-bearing and
35
+ none is obvious.
36
+ ========================================================================== */
37
+
38
+ @page {
39
+ size: var(--page-width) var(--page-height);
40
+ margin: var(--margin-top) var(--margin-right) var(--margin-bottom) var(--margin-left);
41
+
42
+ /* The current section's name, lifted by `string-set` below. This is how a
43
+ reader of page 7 knows which section they are in. */
44
+ @top-left { content: string(band-left); font-size: var(--size-micro); color: var(--ink-muted);
45
+ vertical-align: bottom; padding-bottom: 4mm; }
46
+ @top-right { content: string(band-right); font-size: var(--size-micro); color: var(--ink-muted);
47
+ vertical-align: bottom; padding-bottom: 4mm; }
48
+ @bottom-left { content: string(foot-left); font-size: var(--size-micro); color: var(--ink-muted);
49
+ vertical-align: top; padding-top: 4mm; }
50
+ /* counter(pages) is why a total page count is possible at all — it is resolved
51
+ after layout, so nothing upstream can know it. */
52
+ @bottom-right { content: counter(page) " / " counter(pages);
53
+ font-size: var(--size-micro); color: var(--ink-muted);
54
+ vertical-align: top; padding-top: 4mm; }
55
+ }
56
+
57
+ /* A cover page takes the full sheet: no margins, no bands, no number. */
58
+ @page cover { margin: 0; @top-left{content:none} @top-right{content:none}
59
+ @bottom-left{content:none} @bottom-right{content:none} }
60
+ .cover { page: cover; break-after: page; }
61
+
62
+ /* ---- page-break control -------------------------------------------------
63
+ `keep` is emitted by the projector for every block whose kind declares
64
+ itself atomic. A signature block split across a page boundary produces a
65
+ document where one party signs a page that does not name the other. */
66
+ .keep { break-inside: avoid; }
67
+ .break-before { break-before: page; }
68
+ h1, h2, h3, h4 { break-after: avoid; } /* never orphan a heading */
69
+ h2 + *, h3 + * { break-before: avoid; }
70
+ p { orphans: 3; widows: 3; }
71
+ tr { break-inside: avoid; }
72
+ thead { display: table-header-group; } /* repeat on every page */
73
+ tfoot { display: table-footer-group; } /* total stays with its table */
74
+
75
+ /* ---- base --------------------------------------------------------------- */
76
+ html { font-size: var(--size-body); }
77
+ body {
78
+ font-family: var(--font-sans);
79
+ font-size: var(--size-body);
80
+ line-height: var(--line-height);
81
+ color: var(--ink-primary);
82
+ background: var(--surface);
83
+ margin: 0;
84
+ }
85
+ p { margin: 0 0 2.2mm; }
86
+ strong { font-weight: 600; }
87
+ code { font-family: var(--font-mono); font-size: 0.92em; }
88
+ a { color: inherit; text-decoration: none; }
89
+
90
+ /* A filled-in value, as opposed to boilerplate. The internship contract marks
91
+ every supplied date and amount this way; it is how a reader checks a draft. */
92
+ .value { color: var(--accent-ink); font-weight: 600; }
93
+
94
+ /* ---- document furniture ------------------------------------------------- */
95
+ .doc-head { display: flex; justify-content: space-between; align-items: flex-start;
96
+ border-bottom: 0.6mm solid var(--accent-ink); padding-bottom: 3mm; margin-bottom: 8mm; }
97
+ .doc-head .issuer { font-weight: 700; letter-spacing: 0.01em; }
98
+ .doc-head .idline { font-size: var(--size-micro); color: var(--ink-secondary); margin-top: 0.8mm; }
99
+ .doc-head .ref { text-align: right; font-weight: 700; font-size: var(--size-small); }
100
+ .doc-head .ref .date{ font-weight: 400; color: var(--ink-secondary); margin-top: 0.8mm; }
101
+
102
+ .doc-title { font-size: var(--size-h1); font-weight: 700; line-height: 1.15; margin: 0 0 1.5mm; }
103
+ .doc-title.centered { text-align: center; color: var(--accent-ink); }
104
+ .doc-sub { color: var(--ink-secondary); margin: 0 0 2mm; }
105
+ .doc-meta { font-size: var(--size-small); color: var(--ink-muted); margin: 0 0 7mm; }
106
+ .title-rule { width: 14mm; height: 0.9mm; background: var(--accent-ink); margin: 2.5mm auto 6mm; }
107
+
108
+ /* ---- headings ----------------------------------------------------------- */
109
+ h2, h3, h4 { margin: 7mm 0 2.5mm; font-weight: 700; }
110
+ h2 { font-size: var(--size-h2); }
111
+ h3 { font-size: var(--size-h3); }
112
+ h4 { font-size: var(--size-body); }
113
+ .h-rule { border-bottom: 0.35mm solid var(--accent-ink); padding-bottom: 1.6mm; }
114
+ .h-band { background: var(--accent-ink); color: #fff; padding: 1.8mm 3mm; margin: 7mm 0 0;
115
+ font-size: var(--size-h3); }
116
+ .h-eyebrow{ display: block; background: var(--surface-soft); color: var(--accent-ink);
117
+ border-radius: 4mm; padding: 1.4mm 4mm; font-size: var(--size-micro);
118
+ font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; margin: 0 0 3mm; }
119
+ .h-num { color: var(--ink-muted); font-variant-numeric: tabular-nums; }
120
+
121
+ /* ---- lists -------------------------------------------------------------- */
122
+ ul, ol { margin: 0 0 2.5mm; padding-left: 5mm; }
123
+ li { margin-bottom: 0.9mm; }
124
+ li > ul, li > ol { margin-top: 0.9mm; }
125
+
126
+ /* ---- clauses ------------------------------------------------------------ */
127
+ .clause { margin: 5mm 0; }
128
+ .clause-head { display: flex; align-items: baseline; gap: 2.5mm; margin-bottom: 1.8mm; }
129
+ .clause-badge { display: inline-block; min-width: 5.5mm; text-align: center;
130
+ background: var(--accent-ink); color: #fff; font-weight: 700;
131
+ font-size: var(--size-small); border-radius: 1.2mm; padding: 0.6mm 1.2mm; }
132
+ .clause-title { font-weight: 700; }
133
+ .clause-body { margin-left: 0; }
134
+ .clause .clause { margin-left: 6mm; } /* nesting reads as indentation */
135
+
136
+ /* ---- party card --------------------------------------------------------- */
137
+ .party-card { border: 0.25mm solid var(--border); background: var(--surface-soft);
138
+ border-radius: 2mm; padding: 4mm 5mm; margin: 3mm 0; }
139
+ .party-role { font-size: var(--size-micro); text-transform: uppercase; letter-spacing: 0.08em;
140
+ color: var(--ink-muted); margin-bottom: 1.2mm; }
141
+ .designation { display: inline-block; margin-top: 2.5mm; background: var(--surface-soft);
142
+ color: var(--accent-ink); border: 0.25mm solid var(--accent-ink);
143
+ border-radius: 3mm; padding: 1mm 3mm; font-size: var(--size-micro);
144
+ font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; }
145
+ .conjunction { text-align: center; color: var(--ink-muted); font-weight: 700;
146
+ letter-spacing: 0.15em; margin: 2mm 0; font-size: var(--size-small); }
147
+
148
+ /* ---- tables ------------------------------------------------------------- */
149
+ table { width: 100%; border-collapse: collapse; margin: 3mm 0 4mm; font-size: var(--size-small); }
150
+ th, td { padding: 1.7mm 2.4mm; text-align: left; vertical-align: top;
151
+ border-bottom: 0.2mm solid var(--border); }
152
+ thead th { background: var(--surface-soft); font-weight: 700; font-size: var(--size-micro);
153
+ text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-secondary); }
154
+ table.filled thead th { background: var(--accent-ink); color: #fff; text-transform: none;
155
+ letter-spacing: 0; font-size: var(--size-small); }
156
+ td.numeric, th.numeric { text-align: right; font-variant-numeric: tabular-nums; }
157
+ tfoot td { font-weight: 700; background: var(--surface-soft); border-top: 0.4mm solid var(--border); }
158
+ caption { caption-side: top; text-align: left; font-size: var(--size-small);
159
+ color: var(--ink-secondary); margin-bottom: 1.5mm; }
160
+ .empty { color: var(--ink-muted); }
161
+ ul.cell-list { margin: 0; padding-left: 3.5mm; }
162
+ ul.cell-list li { margin-bottom: 0.5mm; }
163
+
164
+ /* A grouped table's band header. The columns are shared across groups, so the
165
+ band is a full-width row inside the same table — separate tables would each
166
+ size their own columns and the document would visibly wobble. */
167
+ tr.group-band td { background: var(--accent-ink); color: #fff; font-weight: 700;
168
+ font-size: var(--size-small); padding: 1.8mm 2.4mm; }
169
+ tr.subtotal td { font-weight: 700; background: var(--surface-soft); }
170
+
171
+ /* ---- key/value ---------------------------------------------------------- */
172
+ .kv { margin: 3mm 0 4mm; }
173
+ .kv-title { font-weight: 700; margin-bottom: 1.8mm; }
174
+ .kv-grid { display: grid; gap: 1.4mm 5mm; }
175
+ .kv-row { display: flex; gap: 2.5mm; }
176
+ .kv-label { color: var(--ink-secondary); min-width: 32mm; }
177
+ .kv-value { font-weight: 600; }
178
+
179
+ /* ---- totals ------------------------------------------------------------- */
180
+ .totals { margin: 4mm 0; border: 0.25mm solid var(--border); border-radius: 2mm; overflow: hidden; }
181
+ .totals .line { display: flex; justify-content: space-between; padding: 1.8mm 4mm;
182
+ border-bottom: 0.2mm solid var(--border); }
183
+ .totals .grand { display: flex; justify-content: space-between; padding: 2.4mm 4mm;
184
+ background: var(--surface-soft); font-weight: 700; font-size: var(--size-h3); }
185
+ .totals .amount { font-variant-numeric: tabular-nums; }
186
+ .totals .note { color: var(--ink-muted); font-size: var(--size-micro); }
187
+
188
+ /* ---- stat tiles --------------------------------------------------------- */
189
+ .tiles { display: flex; flex-wrap: wrap; gap: 3mm; margin: 3mm 0 5mm; }
190
+ .tile { flex: 1 1 34mm; border: 0.25mm solid var(--border); border-radius: 2mm;
191
+ padding: 3.5mm 4mm; background: var(--surface-soft); }
192
+ .tile .figure { font-size: var(--size-h2); font-weight: 700; color: var(--accent-ink);
193
+ line-height: 1.1; font-variant-numeric: tabular-nums; }
194
+ .tile .label { font-size: var(--size-micro); text-transform: uppercase; letter-spacing: 0.06em;
195
+ color: var(--ink-secondary); margin-top: 1mm; }
196
+ .tile .note { font-size: var(--size-micro); color: var(--ink-muted); margin-top: 0.8mm; }
197
+ .tile.good .figure { color: var(--status-good); }
198
+ .tile.warning .figure { color: var(--status-warning); }
199
+ .tile.serious .figure { color: var(--status-serious); }
200
+ .tile.critical .figure { color: var(--status-critical); }
201
+
202
+ /* ---- cards, callouts ---------------------------------------------------- */
203
+ .card { border: 0.25mm solid var(--border); border-radius: 2.5mm; padding: 4.5mm 5mm;
204
+ margin: 4mm 0; }
205
+ .card > .card-title { font-weight: 700; font-size: var(--size-h3); margin: 0 0 0.8mm; }
206
+ .card > .card-sub { color: var(--ink-muted); font-size: var(--size-small); margin: 0 0 3mm; }
207
+ /* Form checkboxes. A real bordered box, because the printed form is what gets signed
208
+ and the signer has to see there is a choice to make. Sized in mm and not em: it is
209
+ drawn to be written in with a pen. */
210
+ .checklist { margin: 3.5mm 0 4mm; }
211
+ .checklist .option { display: flex; align-items: baseline; gap: 2.5mm; margin-bottom: 1.8mm; }
212
+ .checklist .box { flex: 0 0 auto; width: 4mm; height: 4mm; border: 0.35mm solid var(--ink-primary);
213
+ text-align: center; line-height: 3.6mm; font-weight: 700; font-size: 3mm; }
214
+ .checklist .box.unchecked { color: transparent; }
215
+ .checklist .fill { display: inline-block; min-width: 45mm;
216
+ border-bottom: 0.25mm dotted var(--ink-secondary); }
217
+ /* The footnote is not an option. Separated from the group so it cannot be read as one. */
218
+ .checklist .form-note { margin-top: 2.5mm; font-size: 8pt; color: var(--ink-secondary); }
219
+
220
+ .callout { border-left: 0.8mm solid var(--accent-ink); background: var(--surface-soft);
221
+ padding: 3mm 4mm; margin: 3.5mm 0; border-radius: 0 1.5mm 1.5mm 0; }
222
+ .callout.warning { border-left-color: var(--status-warning); }
223
+ .callout.legal { border-left-color: var(--status-serious); }
224
+ .callout .callout-title { font-weight: 700; margin-bottom: 1mm; }
225
+
226
+ /* ---- images ------------------------------------------------------------- */
227
+ figure { margin: 0 0 3mm; }
228
+ figure img { width: 100%; height: auto; display: block; border-radius: 1.5mm;
229
+ border: 0.2mm solid var(--border); }
230
+ figcaption { font-size: var(--size-micro); color: var(--ink-muted); text-align: center;
231
+ margin-top: 1.2mm; }
232
+ .image-grid { display: grid; gap: 3mm; margin: 3mm 0 4mm; }
233
+
234
+ /* ---- feature list, comparison ------------------------------------------- */
235
+ .features { display: flex; flex-direction: column; gap: 2mm; margin: 3mm 0; }
236
+ .feature { display: flex; gap: 3mm; align-items: flex-start; border: 0.25mm solid var(--border);
237
+ border-radius: 2mm; padding: 2.8mm 3.5mm; }
238
+ .feature .icon { font-size: var(--size-h3); line-height: 1; }
239
+ .feature .f-title { font-weight: 700; }
240
+ .feature .f-detail { color: var(--ink-secondary); font-size: var(--size-small); }
241
+
242
+ .comparison { display: flex; flex-direction: column; gap: 2mm; margin: 3mm 0; }
243
+ .compare-row { display: grid; grid-template-columns: 1fr 6mm 1fr; align-items: center; gap: 2mm;
244
+ border: 0.25mm solid var(--border); border-radius: 2mm; padding: 2.4mm 3mm; }
245
+ .compare-row .before { border-left: 0.7mm solid var(--status-critical); padding-left: 2.5mm; }
246
+ .compare-row .after { border-left: 0.7mm solid var(--status-good); padding-left: 2.5mm; }
247
+ .compare-row .arrow { text-align: center; color: var(--ink-muted); }
248
+ .compare-labels { display: grid; grid-template-columns: 1fr 6mm 1fr; gap: 2mm;
249
+ font-size: var(--size-micro); text-transform: uppercase;
250
+ letter-spacing: 0.08em; color: var(--ink-muted); padding: 0 3mm; }
251
+
252
+ /* ---- timeline, flow, matrix --------------------------------------------- */
253
+ .timeline { margin: 3mm 0; border-left: 0.35mm solid var(--border); padding-left: 4mm; }
254
+ .timeline .entry { margin-bottom: 2.5mm; }
255
+ .timeline .when { font-weight: 700; font-size: var(--size-small); color: var(--accent-ink); }
256
+ .timeline .detail { color: var(--ink-secondary); font-size: var(--size-small); }
257
+ .flow { display: flex; align-items: stretch; gap: 0; margin: 4mm 0; }
258
+ .flow .step { flex: 1; border: 0.25mm solid var(--border); border-radius: 2mm;
259
+ padding: 2.8mm 3mm; text-align: center; }
260
+ .flow .step .s-label { font-weight: 700; font-size: var(--size-small); }
261
+ .flow .step .s-detail { color: var(--ink-secondary); font-size: var(--size-micro); }
262
+ .flow .sep { display: flex; align-items: center; color: var(--ink-muted); padding: 0 1.5mm; }
263
+
264
+ /* ---- charts ------------------------------------------------------------- */
265
+ .chart { margin: 3mm 0 4mm; }
266
+ .chart .chart-title { font-weight: 700; margin-bottom: 1.5mm; }
267
+ .chart svg { display: block; width: 100%; height: auto; }
268
+ .legend { display: flex; flex-wrap: wrap; gap: 4mm; margin-bottom: 2mm;
269
+ font-size: var(--size-micro); color: var(--ink-secondary); }
270
+ .legend .item { display: flex; align-items: center; gap: 1.5mm; }
271
+ .legend .swatch { width: 2.6mm; height: 2.6mm; border-radius: 0.5mm; }
272
+ .chart-table { font-size: var(--size-micro); }
273
+
274
+ /* ---- heatmap ------------------------------------------------------------ */
275
+ .heatmap { margin: 3mm 0 4mm; }
276
+ .heatmap svg { display: block; }
277
+ .heatmap .hm-legend { font-size: var(--size-micro); color: var(--ink-muted); margin-top: 1.5mm; }
278
+
279
+ /* ---- signatures --------------------------------------------------------- */
280
+ .signatures { margin-top: 9mm; }
281
+ .signatures h3 { margin-bottom: 3mm; }
282
+ .sig-row { display: flex; gap: 8mm; }
283
+ .sig { flex: 1; }
284
+ .sig .role { font-size: var(--size-micro); text-transform: uppercase; letter-spacing: 0.07em;
285
+ color: var(--ink-muted); }
286
+ .sig .entity { font-weight: 700; margin-top: 0.8mm; }
287
+ .sig .person { color: var(--ink-secondary); font-size: var(--size-small); }
288
+ .sig .line { border-bottom: 0.25mm solid var(--ink-muted); height: 11mm; margin-top: 1.5mm; }
289
+ .sig .hint { font-size: var(--size-micro); color: var(--ink-muted); margin-top: 1mm; }
290
+
291
+ /* ---- slide geometry ----------------------------------------------------- */
292
+ /* A deck is this stylesheet plus a landscape page and one section per slide.
293
+ Nothing else about a presentation is special. */
294
+ .slide { display: flex; flex-direction: column; min-height: 100%; }
295
+ .slide .slide-title { font-size: var(--size-h1); font-weight: 700; margin: 0 0 2mm; }
296
+ .slide .slide-lede { font-size: var(--size-h3); color: var(--ink-secondary); margin: 0 0 5mm; }
297
+
298
+ /* ---- browser preview ---------------------------------------------------- */
299
+ /* The browser shows neither page breaks nor bands, so the PDF is the artefact
300
+ to check before sending. The sheet outline exists to make the margins real. */
301
+ @media screen {
302
+ body { background: #E2E8F0; padding: 10mm 0; }
303
+ .sheet { width: var(--page-width); box-sizing: border-box;
304
+ padding: var(--margin-top) var(--margin-right) var(--margin-bottom) var(--margin-left);
305
+ margin: 0 auto 8mm; background: var(--surface);
306
+ box-shadow: 0 2px 14px rgba(2,8,23,0.18); }
307
+ }
308
+ @media print { .sheet { width: auto; padding: 0; margin: 0; } }
309
+
310
+ /* The running section name. `string-set` only fires for elements in the box tree,
311
+ so this is rendered-but-invisible rather than `display:none` — which would set
312
+ nothing. Zero height with hidden overflow keeps it out of the visible flow. */
313
+ .running-src { string-set: band-right content(); display: block; height: 0;
314
+ overflow: hidden; font-size: 0; }
@@ -0,0 +1,36 @@
1
+ # Copyright (C) 2026 FORKTEX S.R.L.
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-ForkTex-Commercial
4
+ #
5
+ # This file is part of ForkTex Documents.
6
+ #
7
+ # For commercial licensing -- including use in proprietary products, SaaS
8
+ # deployments, or any context where AGPL obligations cannot be met -- you
9
+ # MUST obtain a commercial license from FORKTEX S.R.L. (info@forktex.com).
10
+ #
11
+ # This program is free software: you can redistribute it and/or modify
12
+ # it under the terms of the GNU Affero General Public License as published by
13
+ # the Free Software Foundation, either version 3 of the License, or
14
+ # (at your option) any later version.
15
+ #
16
+ # This program is distributed in the hope that it will be useful,
17
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
+ # GNU Affero General Public License for more details.
20
+ #
21
+ # You should have received a copy of the GNU Affero General Public License
22
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
23
+
24
+ """The domain layer — the document model, and nothing that performs I/O.
25
+
26
+ Grepping this directory for ``Path``, ``open(`` or ``read_text`` returns nothing but
27
+ docstrings. That is the whole point of the boundary: it makes "the core is pure" a check
28
+ rather than a claim, so a document can be composed and asserted with no filesystem, no
29
+ database and no PDF engine.
30
+
31
+ Assets, styles and templates reach the renderer through the ``AssetSource`` port instead.
32
+
33
+ Nothing here is re-exported: consumers import from the package facade, and the layers above
34
+ import the module they need. A layer that re-exports its own contents becomes a second place
35
+ to look for one name.
36
+ """