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.
- forktex_documents/__init__.py +291 -0
- forktex_documents/assets/styles/paged.css +314 -0
- forktex_documents/domain/__init__.py +36 -0
- forktex_documents/domain/base.py +81 -0
- forktex_documents/domain/blocks.py +1253 -0
- forktex_documents/domain/canonical.py +139 -0
- forktex_documents/domain/catalog.py +280 -0
- forktex_documents/domain/document.py +118 -0
- forktex_documents/domain/entities.py +563 -0
- forktex_documents/domain/errors.py +163 -0
- forktex_documents/domain/fiscal.py +193 -0
- forktex_documents/domain/formatting.py +167 -0
- forktex_documents/domain/instance.py +159 -0
- forktex_documents/domain/localization.py +250 -0
- forktex_documents/domain/mandates.py +137 -0
- forktex_documents/domain/numbering.py +108 -0
- forktex_documents/domain/registry.py +268 -0
- forktex_documents/domain/series.py +119 -0
- forktex_documents/domain/theme.py +317 -0
- forktex_documents/ports.py +290 -0
- forktex_documents/py.typed +0 -0
- forktex_documents/render/__init__.py +36 -0
- forktex_documents/render/assets.py +113 -0
- forktex_documents/render/charts.py +318 -0
- forktex_documents/render/projector.py +271 -0
- forktex_documents-0.1.0.dist-info/METADATA +179 -0
- forktex_documents-0.1.0.dist-info/RECORD +30 -0
- forktex_documents-0.1.0.dist-info/WHEEL +4 -0
- forktex_documents-0.1.0.dist-info/licenses/LICENSE +45 -0
- forktex_documents-0.1.0.dist-info/licenses/NOTICE +41 -0
|
@@ -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
|
+
"""
|