oapi-profile-builder 3.2.1__tar.gz → 3.3.0__tar.gz
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.
- {oapi_profile_builder-3.2.1/src/oapi_profile_builder.egg-info → oapi_profile_builder-3.3.0}/PKG-INFO +46 -6
- {oapi_profile_builder-3.2.1 → oapi_profile_builder-3.3.0}/README.md +45 -5
- {oapi_profile_builder-3.2.1 → oapi_profile_builder-3.3.0}/pyproject.toml +1 -1
- {oapi_profile_builder-3.2.1 → oapi_profile_builder-3.3.0}/src/oapi_profile_builder/cli.py +1 -1
- oapi_profile_builder-3.3.0/src/oapi_profile_builder/compile.py +126 -0
- {oapi_profile_builder-3.2.1 → oapi_profile_builder-3.3.0}/src/oapi_profile_builder/cover.py +106 -19
- {oapi_profile_builder-3.2.1 → oapi_profile_builder-3.3.0}/src/oapi_profile_builder/generate.py +293 -14
- {oapi_profile_builder-3.2.1 → oapi_profile_builder-3.3.0}/src/oapi_profile_builder/models.py +123 -0
- {oapi_profile_builder-3.2.1 → oapi_profile_builder-3.3.0/src/oapi_profile_builder.egg-info}/PKG-INFO +46 -6
- oapi_profile_builder-3.2.1/src/oapi_profile_builder/compile.py +0 -75
- {oapi_profile_builder-3.2.1 → oapi_profile_builder-3.3.0}/LICENSE +0 -0
- {oapi_profile_builder-3.2.1 → oapi_profile_builder-3.3.0}/setup.cfg +0 -0
- {oapi_profile_builder-3.2.1 → oapi_profile_builder-3.3.0}/src/oapi_profile_builder/__init__.py +0 -0
- {oapi_profile_builder-3.2.1 → oapi_profile_builder-3.3.0}/src/oapi_profile_builder/cite.py +0 -0
- {oapi_profile_builder-3.2.1 → oapi_profile_builder-3.3.0}/src/oapi_profile_builder/cite_features.py +0 -0
- {oapi_profile_builder-3.2.1 → oapi_profile_builder-3.3.0}/src/oapi_profile_builder/server_validation.py +0 -0
- {oapi_profile_builder-3.2.1 → oapi_profile_builder-3.3.0}/src/oapi_profile_builder.egg-info/SOURCES.txt +0 -0
- {oapi_profile_builder-3.2.1 → oapi_profile_builder-3.3.0}/src/oapi_profile_builder.egg-info/dependency_links.txt +0 -0
- {oapi_profile_builder-3.2.1 → oapi_profile_builder-3.3.0}/src/oapi_profile_builder.egg-info/entry_points.txt +0 -0
- {oapi_profile_builder-3.2.1 → oapi_profile_builder-3.3.0}/src/oapi_profile_builder.egg-info/requires.txt +0 -0
- {oapi_profile_builder-3.2.1 → oapi_profile_builder-3.3.0}/src/oapi_profile_builder.egg-info/top_level.txt +0 -0
{oapi_profile_builder-3.2.1/src/oapi_profile_builder.egg-info → oapi_profile_builder-3.3.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oapi-profile-builder
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Summary: Authoritative tooling for creating OGC API Service Profiles (EDR, Features)
|
|
5
5
|
Author-email: Shane Mill <shane.mill@noaa.gov>
|
|
6
6
|
License: Apache License
|
|
@@ -234,18 +234,20 @@ Everything about the document — cover, title-page metadata, legal page, footer
|
|
|
234
234
|
|
|
235
235
|
| What you want to change | Field | Mechanism |
|
|
236
236
|
|---|---|---|
|
|
237
|
-
| Cover logo
|
|
237
|
+
| Cover logo, tagline, fonts, watermark (replaces the OGC cover) | `document_metadata.cover` | Generated cover image via `:coverpage-image:` full replacement |
|
|
238
238
|
| Title, doc number, edition, dates, sub-title/status | `title`, `version`, `document_metadata.{doc_number, doc_type, doc_subtype, status, *_date}` | Metanorma document attributes |
|
|
239
239
|
| PDF colours (titles, tables, cover, backgrounds) | `document_metadata.colors` | `:presentation-metadata-color-*:` |
|
|
240
240
|
| Page-ii legal text (copyright / license / notice) | `document_metadata.boilerplate` | `:boilerplate-authority:` |
|
|
241
241
|
| Footer organisation name | `document_metadata.copyright_holder` | `:copyright-holder:` |
|
|
242
242
|
| Remove the OGC logo from the legal page | `document_metadata.suppress_flavor_logo` | targeted `:pdf-stylesheet-override:` |
|
|
243
|
+
| Remove OGC design (crossing lines, circled numbers, rules, navy divider pages) | `document_metadata.suppress_design_elements` (or the individual `suppress_crossing_lines` / `plain_section_numbers` / `suppress_title_underlines`) | `:pdf-stylesheet-override:` + colour attributes |
|
|
244
|
+
| Every-page DRAFT-style watermark (over content) | `document_metadata.page_watermark` | `:pdf-stylesheet-override:` (footer overlay) |
|
|
243
245
|
| Requirement / conformance identifier namespace | `spec_uri_base` | derives `req_uri` / `conf_uri` |
|
|
244
|
-
| Submitter table, notice paragraph, normative refs | `document_metadata.{submitters, notice, normative_references}` | Metanorma attributes / sections |
|
|
246
|
+
| Submitter table, notice paragraph, normative refs, extra terms | `document_metadata.{submitters, notice, normative_references, terms}` | Metanorma attributes / sections |
|
|
245
247
|
|
|
246
|
-
Custom branding requires Pillow for the cover image: `pip install 'oapi-profile-builder[pdf]'`. Field-by-field detail is in the [`document_metadata`](#document_metadata) reference below; [`examples/nwp_radar.yaml`](examples/nwp_radar.yaml) is a complete DGIWG-branded example.
|
|
248
|
+
Custom branding requires Pillow for the cover image: `pip install 'oapi-profile-builder[pdf]'`. Field-by-field detail is in the [`document_metadata`](#document_metadata) reference below; [`examples/nwp_radar.yaml`](examples/nwp_radar.yaml) is a complete DGIWG-branded example. Omit all of these fields and the output keeps the standard OGC house style.
|
|
247
249
|
|
|
248
|
-
> The `suppress_flavor_logo`
|
|
250
|
+
> The `suppress_flavor_logo`, `suppress_design_elements`/`suppress_*`, and `page_watermark` overrides reach into the OGC Metanorma flavor's XSL templates (e.g. `insertLogoPreface`, `insertCrossingLines`, `insertSectionNumInCircle`, `insertFooter`) and are coupled to that flavor. Everything else uses documented Metanorma attributes. mn2pdf merges our same-named templates over the flavor's at equal precedence (via `merge_override.xsl`), which is what makes these work; a future metanorma-ogc release could require refreshing the template names. For long-term SDO adoption, a dedicated Metanorma "taste"/flavor is the most durable path.
|
|
249
251
|
|
|
250
252
|
---
|
|
251
253
|
|
|
@@ -701,6 +703,10 @@ document_metadata:
|
|
|
701
703
|
tagline: Delivering Military Advantage through multi-national geospatial interoperability
|
|
702
704
|
background: "#FFFFFF" # optional cover background (default white)
|
|
703
705
|
text_color: "#1F3864" # optional cover text colour (default black)
|
|
706
|
+
font_regular: "Source Sans Pro" # optional: .ttf/.otf path OR installed family name (fontconfig)
|
|
707
|
+
font_bold: "Source Sans Pro" # optional bold face; derived from font_regular when unset
|
|
708
|
+
font_italic: "Source Sans Pro" # optional italic face for the tagline
|
|
709
|
+
watermark: "DRAFT" # optional diagonal watermark stamped on the cover
|
|
704
710
|
colors:
|
|
705
711
|
text: "#000000" # body text
|
|
706
712
|
cover_text: "#1F3864" # cover text / section numbers / ToC
|
|
@@ -733,7 +739,41 @@ document_metadata:
|
|
|
733
739
|
feedback: "Comments on this document should be directed to DGIWG."
|
|
734
740
|
```
|
|
735
741
|
|
|
736
|
-
|
|
742
|
+
#### Removing the OGC page design (crossing lines, circled numbers, divider pages)
|
|
743
|
+
|
|
744
|
+
The OGC flavor also draws decorative design elements: the blue "crossing lines with a dot" motif, circled section-divider numbers, a short rule under section titles, and full-navy section-divider pages. One switch removes them all:
|
|
745
|
+
|
|
746
|
+
- **`suppress_design_elements: true`** — a consolidated switch that:
|
|
747
|
+
- removes the blue crossing-lines + dot motif (on divider/cover pages via a template override; on the Contents/preface pages, which draw the lines inline in the flavor's own colour, by whiting that colour out);
|
|
748
|
+
- renders section numbers as plain text (e.g. `1 Scope`, `i Abstract`) instead of coloured circles;
|
|
749
|
+
- removes the short rule beneath section titles;
|
|
750
|
+
- rebrands the full-navy section-divider pages to a clean white page with a DGIWG-navy (from `colors.title`) plain number and title.
|
|
751
|
+
|
|
752
|
+
The individual toggles `suppress_crossing_lines`, `plain_section_numbers`, and `suppress_title_underlines` are still available if you want finer control; `suppress_design_elements` simply turns all three on plus the divider rebrand.
|
|
753
|
+
|
|
754
|
+
- **`page_watermark`** — stamps a light diagonal watermark (e.g. `DRAFT`) on **every page**, rendered *over* the content including tables. This is distinct from `cover.watermark`, which marks only the generated cover image. The cover page is left unmarked.
|
|
755
|
+
|
|
756
|
+
```yaml
|
|
757
|
+
document_metadata:
|
|
758
|
+
suppress_design_elements: true # plain numbers, no crossing lines/rules, white divider pages
|
|
759
|
+
page_watermark: DRAFT # every-page diagonal watermark (over content)
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
With `cover` + `colors` + `copyright_holder` + `boilerplate` + `suppress_flavor_logo` + `suppress_design_elements` (and `spec_uri_base` for the requirement/conformance identifiers), the generated PDF carries no OGC branding — only legitimate citations to the OGC standards the profile conforms to. Omit these fields and you get the standard OGC house style. See [`examples/nwp_radar.yaml`](examples/nwp_radar.yaml).
|
|
763
|
+
|
|
764
|
+
> These design-element overrides (and the `page_watermark` footer overlay) reach into the OGC Metanorma flavor's XSL template names and are coupled to that flavor; a future metanorma-ogc release could require refreshing them.
|
|
765
|
+
|
|
766
|
+
#### Extending Terms and Definitions
|
|
767
|
+
|
|
768
|
+
Add profile-specific terms (e.g. EDR Part 3 or Pub/Sub terms) appended after the base terms in the Terms and Definitions section:
|
|
769
|
+
|
|
770
|
+
```yaml
|
|
771
|
+
document_metadata:
|
|
772
|
+
terms:
|
|
773
|
+
- term: service profile
|
|
774
|
+
definition: A named, referenceable set of constraints and additional requirements applied to an OGC API - EDR implementation.
|
|
775
|
+
source: "SOURCE: OGC API - EDR Part 3: Service Profiles (draft)"
|
|
776
|
+
```
|
|
737
777
|
|
|
738
778
|
---
|
|
739
779
|
|
|
@@ -187,18 +187,20 @@ Everything about the document — cover, title-page metadata, legal page, footer
|
|
|
187
187
|
|
|
188
188
|
| What you want to change | Field | Mechanism |
|
|
189
189
|
|---|---|---|
|
|
190
|
-
| Cover logo
|
|
190
|
+
| Cover logo, tagline, fonts, watermark (replaces the OGC cover) | `document_metadata.cover` | Generated cover image via `:coverpage-image:` full replacement |
|
|
191
191
|
| Title, doc number, edition, dates, sub-title/status | `title`, `version`, `document_metadata.{doc_number, doc_type, doc_subtype, status, *_date}` | Metanorma document attributes |
|
|
192
192
|
| PDF colours (titles, tables, cover, backgrounds) | `document_metadata.colors` | `:presentation-metadata-color-*:` |
|
|
193
193
|
| Page-ii legal text (copyright / license / notice) | `document_metadata.boilerplate` | `:boilerplate-authority:` |
|
|
194
194
|
| Footer organisation name | `document_metadata.copyright_holder` | `:copyright-holder:` |
|
|
195
195
|
| Remove the OGC logo from the legal page | `document_metadata.suppress_flavor_logo` | targeted `:pdf-stylesheet-override:` |
|
|
196
|
+
| Remove OGC design (crossing lines, circled numbers, rules, navy divider pages) | `document_metadata.suppress_design_elements` (or the individual `suppress_crossing_lines` / `plain_section_numbers` / `suppress_title_underlines`) | `:pdf-stylesheet-override:` + colour attributes |
|
|
197
|
+
| Every-page DRAFT-style watermark (over content) | `document_metadata.page_watermark` | `:pdf-stylesheet-override:` (footer overlay) |
|
|
196
198
|
| Requirement / conformance identifier namespace | `spec_uri_base` | derives `req_uri` / `conf_uri` |
|
|
197
|
-
| Submitter table, notice paragraph, normative refs | `document_metadata.{submitters, notice, normative_references}` | Metanorma attributes / sections |
|
|
199
|
+
| Submitter table, notice paragraph, normative refs, extra terms | `document_metadata.{submitters, notice, normative_references, terms}` | Metanorma attributes / sections |
|
|
198
200
|
|
|
199
|
-
Custom branding requires Pillow for the cover image: `pip install 'oapi-profile-builder[pdf]'`. Field-by-field detail is in the [`document_metadata`](#document_metadata) reference below; [`examples/nwp_radar.yaml`](examples/nwp_radar.yaml) is a complete DGIWG-branded example.
|
|
201
|
+
Custom branding requires Pillow for the cover image: `pip install 'oapi-profile-builder[pdf]'`. Field-by-field detail is in the [`document_metadata`](#document_metadata) reference below; [`examples/nwp_radar.yaml`](examples/nwp_radar.yaml) is a complete DGIWG-branded example. Omit all of these fields and the output keeps the standard OGC house style.
|
|
200
202
|
|
|
201
|
-
> The `suppress_flavor_logo`
|
|
203
|
+
> The `suppress_flavor_logo`, `suppress_design_elements`/`suppress_*`, and `page_watermark` overrides reach into the OGC Metanorma flavor's XSL templates (e.g. `insertLogoPreface`, `insertCrossingLines`, `insertSectionNumInCircle`, `insertFooter`) and are coupled to that flavor. Everything else uses documented Metanorma attributes. mn2pdf merges our same-named templates over the flavor's at equal precedence (via `merge_override.xsl`), which is what makes these work; a future metanorma-ogc release could require refreshing the template names. For long-term SDO adoption, a dedicated Metanorma "taste"/flavor is the most durable path.
|
|
202
204
|
|
|
203
205
|
---
|
|
204
206
|
|
|
@@ -654,6 +656,10 @@ document_metadata:
|
|
|
654
656
|
tagline: Delivering Military Advantage through multi-national geospatial interoperability
|
|
655
657
|
background: "#FFFFFF" # optional cover background (default white)
|
|
656
658
|
text_color: "#1F3864" # optional cover text colour (default black)
|
|
659
|
+
font_regular: "Source Sans Pro" # optional: .ttf/.otf path OR installed family name (fontconfig)
|
|
660
|
+
font_bold: "Source Sans Pro" # optional bold face; derived from font_regular when unset
|
|
661
|
+
font_italic: "Source Sans Pro" # optional italic face for the tagline
|
|
662
|
+
watermark: "DRAFT" # optional diagonal watermark stamped on the cover
|
|
657
663
|
colors:
|
|
658
664
|
text: "#000000" # body text
|
|
659
665
|
cover_text: "#1F3864" # cover text / section numbers / ToC
|
|
@@ -686,7 +692,41 @@ document_metadata:
|
|
|
686
692
|
feedback: "Comments on this document should be directed to DGIWG."
|
|
687
693
|
```
|
|
688
694
|
|
|
689
|
-
|
|
695
|
+
#### Removing the OGC page design (crossing lines, circled numbers, divider pages)
|
|
696
|
+
|
|
697
|
+
The OGC flavor also draws decorative design elements: the blue "crossing lines with a dot" motif, circled section-divider numbers, a short rule under section titles, and full-navy section-divider pages. One switch removes them all:
|
|
698
|
+
|
|
699
|
+
- **`suppress_design_elements: true`** — a consolidated switch that:
|
|
700
|
+
- removes the blue crossing-lines + dot motif (on divider/cover pages via a template override; on the Contents/preface pages, which draw the lines inline in the flavor's own colour, by whiting that colour out);
|
|
701
|
+
- renders section numbers as plain text (e.g. `1 Scope`, `i Abstract`) instead of coloured circles;
|
|
702
|
+
- removes the short rule beneath section titles;
|
|
703
|
+
- rebrands the full-navy section-divider pages to a clean white page with a DGIWG-navy (from `colors.title`) plain number and title.
|
|
704
|
+
|
|
705
|
+
The individual toggles `suppress_crossing_lines`, `plain_section_numbers`, and `suppress_title_underlines` are still available if you want finer control; `suppress_design_elements` simply turns all three on plus the divider rebrand.
|
|
706
|
+
|
|
707
|
+
- **`page_watermark`** — stamps a light diagonal watermark (e.g. `DRAFT`) on **every page**, rendered *over* the content including tables. This is distinct from `cover.watermark`, which marks only the generated cover image. The cover page is left unmarked.
|
|
708
|
+
|
|
709
|
+
```yaml
|
|
710
|
+
document_metadata:
|
|
711
|
+
suppress_design_elements: true # plain numbers, no crossing lines/rules, white divider pages
|
|
712
|
+
page_watermark: DRAFT # every-page diagonal watermark (over content)
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
With `cover` + `colors` + `copyright_holder` + `boilerplate` + `suppress_flavor_logo` + `suppress_design_elements` (and `spec_uri_base` for the requirement/conformance identifiers), the generated PDF carries no OGC branding — only legitimate citations to the OGC standards the profile conforms to. Omit these fields and you get the standard OGC house style. See [`examples/nwp_radar.yaml`](examples/nwp_radar.yaml).
|
|
716
|
+
|
|
717
|
+
> These design-element overrides (and the `page_watermark` footer overlay) reach into the OGC Metanorma flavor's XSL template names and are coupled to that flavor; a future metanorma-ogc release could require refreshing them.
|
|
718
|
+
|
|
719
|
+
#### Extending Terms and Definitions
|
|
720
|
+
|
|
721
|
+
Add profile-specific terms (e.g. EDR Part 3 or Pub/Sub terms) appended after the base terms in the Terms and Definitions section:
|
|
722
|
+
|
|
723
|
+
```yaml
|
|
724
|
+
document_metadata:
|
|
725
|
+
terms:
|
|
726
|
+
- term: service profile
|
|
727
|
+
definition: A named, referenceable set of constraints and additional requirements applied to an OGC API - EDR implementation.
|
|
728
|
+
source: "SOURCE: OGC API - EDR Part 3: Service Profiles (draft)"
|
|
729
|
+
```
|
|
690
730
|
|
|
691
731
|
---
|
|
692
732
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "oapi-profile-builder"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.3.0"
|
|
8
8
|
description = "Authoritative tooling for creating OGC API Service Profiles (EDR, Features)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# =================================================================
|
|
2
|
+
#
|
|
3
|
+
# Authors: Shane Mill <shane.mill@noaa.gov>
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2026 Shane Mill
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person
|
|
8
|
+
# obtaining a copy of this software and associated documentation
|
|
9
|
+
# files (the "Software"), to deal in the Software without
|
|
10
|
+
# restriction, including without limitation the rights to use,
|
|
11
|
+
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
# copies of the Software, and to permit persons to whom the
|
|
13
|
+
# Software is furnished to do so, subject to the following
|
|
14
|
+
# conditions:
|
|
15
|
+
#
|
|
16
|
+
# The above copyright notice and this permission notice shall be
|
|
17
|
+
# included in all copies or substantial portions of the Software.
|
|
18
|
+
#
|
|
19
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
20
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
21
|
+
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
22
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
23
|
+
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
24
|
+
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
25
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
26
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|
|
27
|
+
#
|
|
28
|
+
# =================================================================
|
|
29
|
+
"""
|
|
30
|
+
PDF compilation: shell out to the Metanorma Docker image.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import shutil
|
|
36
|
+
import subprocess
|
|
37
|
+
import sys
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def compile_pdf(output_dir: Path, profile=None) -> bool:
|
|
42
|
+
"""
|
|
43
|
+
Run `docker run metanorma/metanorma compile` against document.adoc
|
|
44
|
+
in output_dir. Returns True on success.
|
|
45
|
+
|
|
46
|
+
If *profile* is provided, custom fonts declared in document_metadata will be
|
|
47
|
+
pre-installed into the fontist cache so Metanorma can embed them.
|
|
48
|
+
"""
|
|
49
|
+
if shutil.which("docker") is None:
|
|
50
|
+
print("docker not found. Install Docker to use --pdf.", file=sys.stderr)
|
|
51
|
+
sys.exit(1)
|
|
52
|
+
|
|
53
|
+
doc = output_dir / "document.adoc"
|
|
54
|
+
if not doc.exists():
|
|
55
|
+
print(f"document.adoc not found in {output_dir}", file=sys.stderr)
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
|
|
58
|
+
fonts_dir = Path.home() / ".fontist" / "fonts"
|
|
59
|
+
fonts_dir.mkdir(parents=True, exist_ok=True)
|
|
60
|
+
|
|
61
|
+
# Pre-install any custom fonts requested by the profile so fontist inside
|
|
62
|
+
# the container recognizes them during PDF generation.
|
|
63
|
+
extra_fonts: list[str] = []
|
|
64
|
+
if profile is not None:
|
|
65
|
+
m = getattr(profile, "document_metadata", None)
|
|
66
|
+
if m:
|
|
67
|
+
for attr in ("body_font", "header_font", "monospace_font"):
|
|
68
|
+
val = getattr(m, attr, None)
|
|
69
|
+
if val:
|
|
70
|
+
extra_fonts.append(val)
|
|
71
|
+
|
|
72
|
+
# De-duplicate while preserving order.
|
|
73
|
+
if extra_fonts:
|
|
74
|
+
seen: set[str] = set()
|
|
75
|
+
extra_fonts = [f for f in extra_fonts if not (f in seen or seen.add(f))] # type: ignore[func-returns-value]
|
|
76
|
+
|
|
77
|
+
# Build the Docker command. When custom fonts are needed, we run fontist
|
|
78
|
+
# install + metanorma compile in the *same* container so the fontist
|
|
79
|
+
# database is available at compile time. We also monkey-patch the OGC
|
|
80
|
+
# processor's fonts_manifest so mn2pdf receives the correct font paths.
|
|
81
|
+
if extra_fonts:
|
|
82
|
+
install_cmds = " && ".join(f"fontist install '{f}'" for f in extra_fonts)
|
|
83
|
+
# Ruby snippet to inject extra fonts into the OGC processor's manifest
|
|
84
|
+
# so mn2pdf's font-manifest YAML includes them.
|
|
85
|
+
font_hash_entries = ", ".join(f'\\"{f}\\" => nil' for f in extra_fonts)
|
|
86
|
+
# Write the Ruby patch via heredoc to avoid shell quoting issues.
|
|
87
|
+
patch_rb_escaped = (
|
|
88
|
+
'require \\"metanorma-ogc\\"; '
|
|
89
|
+
"module Metanorma; module Ogc; class Processor; "
|
|
90
|
+
"alias_method :_orig_fonts_manifest, :fonts_manifest; "
|
|
91
|
+
f"def fonts_manifest; _orig_fonts_manifest.merge({{{font_hash_entries}}}); end; "
|
|
92
|
+
"end; end; end"
|
|
93
|
+
)
|
|
94
|
+
shell_script = (
|
|
95
|
+
f"{install_cmds} && "
|
|
96
|
+
f'printf "%s" "{patch_rb_escaped}" > /tmp/_font_patch.rb && '
|
|
97
|
+
"RUBYOPT='-r/tmp/_font_patch.rb' "
|
|
98
|
+
"metanorma compile --agree-to-terms -t ogc -x pdf document.adoc"
|
|
99
|
+
)
|
|
100
|
+
cmd = [
|
|
101
|
+
"docker", "run", "--rm",
|
|
102
|
+
"-v", f"{output_dir.resolve()}:/metanorma",
|
|
103
|
+
"-v", f"{fonts_dir}:/config/fonts",
|
|
104
|
+
"-w", "/metanorma",
|
|
105
|
+
"metanorma/metanorma",
|
|
106
|
+
"bash", "-c", shell_script,
|
|
107
|
+
]
|
|
108
|
+
print(f"Installing custom fonts ({', '.join(extra_fonts)}) and compiling PDF...")
|
|
109
|
+
else:
|
|
110
|
+
cmd = [
|
|
111
|
+
"docker", "run", "--rm",
|
|
112
|
+
"-v", f"{output_dir.resolve()}:/metanorma",
|
|
113
|
+
"-v", f"{fonts_dir}:/config/fonts",
|
|
114
|
+
"metanorma/metanorma",
|
|
115
|
+
"metanorma", "compile",
|
|
116
|
+
"--agree-to-terms",
|
|
117
|
+
"-t", "ogc",
|
|
118
|
+
"-x", "pdf",
|
|
119
|
+
"document.adoc",
|
|
120
|
+
]
|
|
121
|
+
print("Compiling PDF via Metanorma Docker...")
|
|
122
|
+
result = subprocess.run(cmd, check=False)
|
|
123
|
+
if result.returncode == 0:
|
|
124
|
+
pdf = output_dir / "document.pdf"
|
|
125
|
+
print(f"PDF written to {pdf}")
|
|
126
|
+
return result.returncode == 0
|
|
@@ -31,6 +31,8 @@ so no system font paths are required.
|
|
|
31
31
|
|
|
32
32
|
from __future__ import annotations
|
|
33
33
|
|
|
34
|
+
import shutil
|
|
35
|
+
import subprocess
|
|
34
36
|
from datetime import datetime
|
|
35
37
|
from pathlib import Path
|
|
36
38
|
|
|
@@ -40,25 +42,83 @@ _PAGE_H = 1754
|
|
|
40
42
|
|
|
41
43
|
_COVER_FILENAME = "cover.png"
|
|
42
44
|
|
|
45
|
+
# Default font family (bundled with Pillow, resolved by name).
|
|
46
|
+
_DEFAULT_REGULAR = "DejaVuSans.ttf"
|
|
47
|
+
_DEFAULT_BOLD = "DejaVuSans-Bold.ttf"
|
|
48
|
+
_DEFAULT_ITALIC = "DejaVuSans-Oblique.ttf"
|
|
43
49
|
|
|
44
|
-
def _load_font(bold: bool, size: int):
|
|
45
|
-
from PIL import ImageFont
|
|
46
50
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return ImageFont.truetype(name, size)
|
|
50
|
-
except Exception:
|
|
51
|
-
# Last-resort fallback; not scalable but keeps rendering from failing.
|
|
52
|
-
return ImageFont.load_default()
|
|
51
|
+
def _resolve_font_file(spec: str | None, *, bold: bool = False, italic: bool = False) -> str | None:
|
|
52
|
+
"""Resolve a user font spec to a usable font file path.
|
|
53
53
|
|
|
54
|
+
``spec`` may be either a path to a ``.ttf``/``.otf`` file or a font family
|
|
55
|
+
name (e.g. ``"Source Sans Pro"``). Family names are resolved via
|
|
56
|
+
``fc-match`` (fontconfig) when available. Returns ``None`` when the spec is
|
|
57
|
+
empty or cannot be resolved, so the caller can fall back to a default.
|
|
58
|
+
"""
|
|
59
|
+
if not spec:
|
|
60
|
+
return None
|
|
61
|
+
p = Path(spec).expanduser()
|
|
62
|
+
if p.is_file():
|
|
63
|
+
return str(p)
|
|
64
|
+
if shutil.which("fc-match"):
|
|
65
|
+
styles = []
|
|
66
|
+
if bold:
|
|
67
|
+
styles.append("Bold")
|
|
68
|
+
if italic:
|
|
69
|
+
styles.append("Italic")
|
|
70
|
+
query = spec if not styles else f"{spec}:style={' '.join(styles)}"
|
|
71
|
+
try:
|
|
72
|
+
out = subprocess.run(
|
|
73
|
+
["fc-match", "-f", "%{file}", query],
|
|
74
|
+
capture_output=True, text=True, timeout=5, check=False,
|
|
75
|
+
)
|
|
76
|
+
path = out.stdout.strip()
|
|
77
|
+
if path and Path(path).is_file():
|
|
78
|
+
return path
|
|
79
|
+
except Exception:
|
|
80
|
+
pass
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class _FontSet:
|
|
85
|
+
"""Resolves cover fonts from optional profile config, with sane defaults.
|
|
86
|
+
|
|
87
|
+
Each of ``regular``/``bold``/``italic`` may be a font-file path or a family
|
|
88
|
+
name; unset styles are derived from ``regular`` (with the matching style) and
|
|
89
|
+
ultimately fall back to the bundled DejaVu family.
|
|
90
|
+
"""
|
|
54
91
|
|
|
55
|
-
def
|
|
56
|
-
|
|
92
|
+
def __init__(self, cover=None):
|
|
93
|
+
self.regular = getattr(cover, "font_regular", None) if cover else None
|
|
94
|
+
self.bold = getattr(cover, "font_bold", None) if cover else None
|
|
95
|
+
self.italic = getattr(cover, "font_italic", None) if cover else None
|
|
96
|
+
|
|
97
|
+
def _font(self, size: int, *, bold: bool = False, italic: bool = False):
|
|
98
|
+
from PIL import ImageFont
|
|
99
|
+
|
|
100
|
+
# Prefer an explicit per-style spec, else derive from the regular family.
|
|
101
|
+
explicit = self.bold if bold else (self.italic if italic else self.regular)
|
|
102
|
+
candidates = [
|
|
103
|
+
_resolve_font_file(explicit, bold=bold, italic=italic),
|
|
104
|
+
_resolve_font_file(self.regular, bold=bold, italic=italic),
|
|
105
|
+
]
|
|
106
|
+
default = _DEFAULT_BOLD if bold else (_DEFAULT_ITALIC if italic else _DEFAULT_REGULAR)
|
|
107
|
+
candidates.append(default)
|
|
108
|
+
for cand in candidates:
|
|
109
|
+
if not cand:
|
|
110
|
+
continue
|
|
111
|
+
try:
|
|
112
|
+
return ImageFont.truetype(cand, size)
|
|
113
|
+
except Exception:
|
|
114
|
+
continue
|
|
115
|
+
return ImageFont.load_default()
|
|
57
116
|
|
|
58
|
-
|
|
59
|
-
return
|
|
60
|
-
|
|
61
|
-
|
|
117
|
+
def regular_font(self, size: int, bold: bool = False):
|
|
118
|
+
return self._font(size, bold=bold)
|
|
119
|
+
|
|
120
|
+
def italic_font(self, size: int):
|
|
121
|
+
return self._font(size, italic=True)
|
|
62
122
|
|
|
63
123
|
|
|
64
124
|
def _format_date(iso_date: str | None) -> str | None:
|
|
@@ -100,6 +160,7 @@ def build_cover_image(profile, output_dir: Path) -> str | None:
|
|
|
100
160
|
|
|
101
161
|
bg = cover.background or "#FFFFFF"
|
|
102
162
|
fg = cover.text_color or "#000000"
|
|
163
|
+
fonts = _FontSet(cover)
|
|
103
164
|
|
|
104
165
|
page = Image.new("RGB", (_PAGE_W, _PAGE_H), bg)
|
|
105
166
|
draw = ImageDraw.Draw(page)
|
|
@@ -129,28 +190,32 @@ def build_cover_image(profile, output_dir: Path) -> str | None:
|
|
|
129
190
|
|
|
130
191
|
# --- Tagline (italic) ---
|
|
131
192
|
if cover.tagline:
|
|
132
|
-
y = centered(cover.tagline,
|
|
193
|
+
y = centered(cover.tagline, fonts.italic_font(30), y, fg) + 60
|
|
133
194
|
|
|
134
195
|
# --- Document number ---
|
|
135
196
|
if m and m.doc_number:
|
|
136
|
-
y = centered(m.doc_number,
|
|
197
|
+
y = centered(m.doc_number, fonts.regular_font(46, bold=True), y, fg) + 40
|
|
137
198
|
|
|
138
199
|
# --- Edition ---
|
|
139
200
|
edition = getattr(profile, "version", None)
|
|
140
201
|
if edition:
|
|
141
|
-
y = centered(f"Edition {edition}",
|
|
202
|
+
y = centered(f"Edition {edition}", fonts.regular_font(28), y, fg) + 24
|
|
142
203
|
|
|
143
204
|
# --- Date ---
|
|
144
205
|
date_str = _format_date(m.publication_date if m else None)
|
|
145
206
|
if date_str:
|
|
146
|
-
y = centered(f"Dated {date_str}",
|
|
207
|
+
y = centered(f"Dated {date_str}", fonts.regular_font(28), y, fg) + 80
|
|
147
208
|
|
|
148
209
|
# --- Title (large, bold, wrapped) ---
|
|
149
210
|
title = getattr(profile, "title", "") or ""
|
|
150
|
-
title_font =
|
|
211
|
+
title_font = fonts.regular_font(54, bold=True)
|
|
151
212
|
for line in _wrap(draw, title, title_font, _PAGE_W - 240):
|
|
152
213
|
y = centered(line, title_font, y, fg) + 16
|
|
153
214
|
|
|
215
|
+
# --- Draft watermark (diagonal, semi-transparent) ---
|
|
216
|
+
if getattr(cover, "watermark", None):
|
|
217
|
+
_stamp_watermark(page, cover.watermark, fonts)
|
|
218
|
+
|
|
154
219
|
target = (output_dir / _COVER_FILENAME).resolve()
|
|
155
220
|
if not str(target).startswith(str(output_dir.resolve())):
|
|
156
221
|
raise RuntimeError(f"Refusing to write cover outside output directory: {target}")
|
|
@@ -158,6 +223,28 @@ def build_cover_image(profile, output_dir: Path) -> str | None:
|
|
|
158
223
|
return _COVER_FILENAME
|
|
159
224
|
|
|
160
225
|
|
|
226
|
+
def _stamp_watermark(page, text: str, fonts: "_FontSet") -> None:
|
|
227
|
+
"""Overlay a large, diagonal, semi-transparent watermark on the cover.
|
|
228
|
+
|
|
229
|
+
Rendered on its own RGBA layer and rotated 45°, then alpha-composited onto
|
|
230
|
+
the (RGB) cover page. Gives a clear 'DRAFT' style indication on the cover;
|
|
231
|
+
an every-page watermark additionally requires the Metanorma layer.
|
|
232
|
+
"""
|
|
233
|
+
from PIL import Image, ImageDraw
|
|
234
|
+
|
|
235
|
+
layer = Image.new("RGBA", page.size, (0, 0, 0, 0))
|
|
236
|
+
ldraw = ImageDraw.Draw(layer)
|
|
237
|
+
font = fonts.regular_font(150, bold=True)
|
|
238
|
+
bbox = ldraw.textbbox((0, 0), text, font=font)
|
|
239
|
+
tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]
|
|
240
|
+
ldraw.text(
|
|
241
|
+
((page.width - tw) / 2, (page.height - th) / 2),
|
|
242
|
+
text, font=font, fill=(200, 0, 0, 70),
|
|
243
|
+
)
|
|
244
|
+
layer = layer.rotate(45, resample=Image.BICUBIC, center=(page.width / 2, page.height / 2))
|
|
245
|
+
page.paste(Image.alpha_composite(page.convert("RGBA"), layer).convert("RGB"), (0, 0))
|
|
246
|
+
|
|
247
|
+
|
|
161
248
|
def _wrap(draw, text: str, font, max_width: int) -> list[str]:
|
|
162
249
|
"""Greedy word-wrap to fit max_width pixels."""
|
|
163
250
|
words = text.split()
|
{oapi_profile_builder-3.2.1 → oapi_profile_builder-3.3.0}/src/oapi_profile_builder/generate.py
RENAMED
|
@@ -1374,18 +1374,249 @@ def _individual_test_adoc(profile: ServiceProfile, test_id: str) -> str:
|
|
|
1374
1374
|
# Metanorma root document
|
|
1375
1375
|
# ---------------------------------------------------------------------------
|
|
1376
1376
|
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1377
|
+
_PDF_OVERRIDE_FILENAME = "pdf_override.xsl"
|
|
1378
|
+
|
|
1379
|
+
# Individual template overrides for the OGC Metanorma flavor's PDF XSL. Each is
|
|
1380
|
+
# keyed to a real template name in metanorma-ogc's ogc.standard.xsl and merged
|
|
1381
|
+
# in via :pdf-stylesheet-override:. These are coupled to the OGC flavor.
|
|
1382
|
+
_OVERRIDE_SUPPRESS_LOGO = """
|
|
1383
|
+
<!-- Suppress the OGC flavor logo on cover/preface (rebranding). -->
|
|
1384
1384
|
<xsl:template name="insertLogoPreface"/>
|
|
1385
1385
|
<xsl:template name="insertLogo"/>
|
|
1386
|
-
</xsl:stylesheet>
|
|
1387
1386
|
"""
|
|
1388
1387
|
|
|
1388
|
+
_OVERRIDE_SUPPRESS_CROSSING_LINES = """
|
|
1389
|
+
<!-- Remove the 'crossing lines' design element by making it invisible. -->
|
|
1390
|
+
<xsl:template name="insertCrossingLines">
|
|
1391
|
+
<fo:block-container absolute-position="fixed" width="0mm" height="0mm" font-size="0">
|
|
1392
|
+
<fo:block/>
|
|
1393
|
+
</fo:block-container>
|
|
1394
|
+
</xsl:template>
|
|
1395
|
+
"""
|
|
1396
|
+
|
|
1397
|
+
_OVERRIDE_SUPPRESS_TITLE_UNDERLINES = """
|
|
1398
|
+
<!-- Remove the horizontal rule drawn beneath section titles. -->
|
|
1399
|
+
<xsl:template name="insertShortHorizontalLine">
|
|
1400
|
+
<fo:block/>
|
|
1401
|
+
</xsl:template>
|
|
1402
|
+
<xsl:template name="insertBigHorizontalLine">
|
|
1403
|
+
<fo:block/>
|
|
1404
|
+
</xsl:template>
|
|
1405
|
+
"""
|
|
1406
|
+
|
|
1407
|
+
# Render the section-divider number as plain text instead of a coloured circle.
|
|
1408
|
+
# Mirrors the flavor's own number computation so annexes still number correctly.
|
|
1409
|
+
# The colour is injected so the number stays readable against whatever divider
|
|
1410
|
+
# background is in effect (white text on the flavor's navy, or a dark DGIWG
|
|
1411
|
+
# colour on a rebranded light page).
|
|
1412
|
+
def _plain_section_numbers_override(color: str) -> str:
|
|
1413
|
+
return f"""
|
|
1414
|
+
<!-- Section-divider number as plain text (no OGC circle), coloured {color}. -->
|
|
1415
|
+
<xsl:template name="insertSectionNumInCircle">
|
|
1416
|
+
<xsl:variable name="sectionNum_"><xsl:call-template name="getSection"/></xsl:variable>
|
|
1417
|
+
<xsl:variable name="sectionNum">
|
|
1418
|
+
<xsl:choose>
|
|
1419
|
+
<xsl:when test="normalize-space($sectionNum_) = '' and self::mn:annex">
|
|
1420
|
+
<xsl:number format="A" count="mn:annex[not(@continue = 'true')]" level="any" lang="en"/>
|
|
1421
|
+
</xsl:when>
|
|
1422
|
+
<xsl:otherwise><xsl:value-of select="$sectionNum_"/></xsl:otherwise>
|
|
1423
|
+
</xsl:choose>
|
|
1424
|
+
</xsl:variable>
|
|
1425
|
+
<fo:block font-size="24pt" font-weight="bold" color="{color}"><xsl:value-of select="$sectionNum"/></fo:block>
|
|
1426
|
+
</xsl:template>
|
|
1427
|
+
"""
|
|
1428
|
+
|
|
1429
|
+
|
|
1430
|
+
# Force the big section-divider title colour. The flavor's divider page-sequence
|
|
1431
|
+
# sets the text colour to white (readable on its navy background); when the
|
|
1432
|
+
# divider page is rebranded to a light background that white title becomes
|
|
1433
|
+
# invisible, so we set an explicit (dark) colour here.
|
|
1434
|
+
def _section_title_color_override(color: str) -> str:
|
|
1435
|
+
return f"""
|
|
1436
|
+
<!-- Section-divider title colour (readable on a rebranded light page). -->
|
|
1437
|
+
<xsl:template name="insertSectionTitleBig">
|
|
1438
|
+
<xsl:param name="title"/>
|
|
1439
|
+
<fo:block font-size="33pt" margin-bottom="6pt" color="{color}">
|
|
1440
|
+
<xsl:apply-templates select="xalan:nodeset($title)" mode="titlebig"/>
|
|
1441
|
+
</fo:block>
|
|
1442
|
+
<xsl:call-template name="insertBigHorizontalLine"/>
|
|
1443
|
+
</xsl:template>
|
|
1444
|
+
"""
|
|
1445
|
+
|
|
1446
|
+
|
|
1447
|
+
# Every-page watermark (e.g. "DRAFT"). The OGC flavor's region-body flow is
|
|
1448
|
+
# painted on top of the header region, so a watermark placed in the header sits
|
|
1449
|
+
# behind opaque tables. The footer region is painted after the body, so we
|
|
1450
|
+
# reproduce the flavor's footer and prepend a page-fixed, rotated, semi-
|
|
1451
|
+
# transparent SVG text mark inside the footer static-content — this overlays
|
|
1452
|
+
# every page, including over tables.
|
|
1453
|
+
def _page_watermark_override(text: str, color: str = "rgb(200,200,200)") -> str:
|
|
1454
|
+
esc = _xml_escape(text)
|
|
1455
|
+
watermark = f"""
|
|
1456
|
+
<fo:block-container absolute-position="fixed" left="0mm" top="0mm" font-size="0">
|
|
1457
|
+
<fo:block>
|
|
1458
|
+
<fo:instream-foreign-object content-height="{{$pageHeight}}mm" content-width="{{$pageWidth}}mm" fox:alt-text="Watermark">
|
|
1459
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="{{$pageWidth}}mm" height="{{$pageHeight}}mm" viewBox="0 0 210 297">
|
|
1460
|
+
<text x="105" y="150" text-anchor="middle" transform="rotate(-45 105 150)" font-size="38" font-family="Lato" fill="{color}" fill-opacity="0.55">{esc}</text>
|
|
1461
|
+
</svg>
|
|
1462
|
+
</fo:instream-foreign-object>
|
|
1463
|
+
</fo:block>
|
|
1464
|
+
</fo:block-container>"""
|
|
1465
|
+
return f"""
|
|
1466
|
+
<!-- Every-page diagonal watermark, prepended to the footer region (painted
|
|
1467
|
+
over the body, so it also overlays tables). Reproduces the OGC footer. -->
|
|
1468
|
+
<xsl:template name="insertFooter">
|
|
1469
|
+
<xsl:param name="num"/>
|
|
1470
|
+
<xsl:param name="color"/>
|
|
1471
|
+
<fo:static-content flow-name="footer" role="artifact">
|
|
1472
|
+
{watermark}
|
|
1473
|
+
<fo:block-container font-size="8pt" color="{{$color}}" padding-top="6mm">
|
|
1474
|
+
<xsl:if test="normalize-space($color) = ''">
|
|
1475
|
+
<xsl:variable name="color_text_title">
|
|
1476
|
+
<xsl:call-template name="getVariable"><xsl:with-param name="variable">color_text_title</xsl:with-param></xsl:call-template>
|
|
1477
|
+
</xsl:variable>
|
|
1478
|
+
<xsl:attribute name="color"><xsl:value-of select="$color_text_title"/></xsl:attribute>
|
|
1479
|
+
</xsl:if>
|
|
1480
|
+
<fo:table table-layout="fixed" width="100%">
|
|
1481
|
+
<fo:table-column column-width="90%"/>
|
|
1482
|
+
<fo:table-column column-width="10%"/>
|
|
1483
|
+
<fo:table-body>
|
|
1484
|
+
<fo:table-row>
|
|
1485
|
+
<fo:table-cell>
|
|
1486
|
+
<fo:block>
|
|
1487
|
+
<fo:inline font-weight="bold">
|
|
1488
|
+
<xsl:call-template name="addLetterSpacing">
|
|
1489
|
+
<xsl:with-param name="text" select="concat($variables/mnx:doc[@num = $num]/copyright-owner, ' ')"/>
|
|
1490
|
+
<xsl:with-param name="letter-spacing" select="0.2"/>
|
|
1491
|
+
</xsl:call-template>
|
|
1492
|
+
</fo:inline>
|
|
1493
|
+
<xsl:call-template name="addLetterSpacing">
|
|
1494
|
+
<xsl:with-param name="text" select="$variables/mnx:doc[@num = $num]/docnumber"/>
|
|
1495
|
+
<xsl:with-param name="letter-spacing" select="0.2"/>
|
|
1496
|
+
</xsl:call-template>
|
|
1497
|
+
</fo:block>
|
|
1498
|
+
</fo:table-cell>
|
|
1499
|
+
<fo:table-cell text-align="right">
|
|
1500
|
+
<fo:block font-weight="bold">
|
|
1501
|
+
<fo:page-number/>
|
|
1502
|
+
</fo:block>
|
|
1503
|
+
</fo:table-cell>
|
|
1504
|
+
</fo:table-row>
|
|
1505
|
+
</fo:table-body>
|
|
1506
|
+
</fo:table>
|
|
1507
|
+
</fo:block-container>
|
|
1508
|
+
</fo:static-content>
|
|
1509
|
+
</xsl:template>
|
|
1510
|
+
"""
|
|
1511
|
+
|
|
1512
|
+
|
|
1513
|
+
def _effective_pdf_config(m) -> dict:
|
|
1514
|
+
"""Resolve effective PDF-styling flags and divider colours from metadata.
|
|
1515
|
+
|
|
1516
|
+
``suppress_design_elements`` is the consolidated "debrand" switch: it turns
|
|
1517
|
+
on crossing-line suppression, plain section numbers and title-underline
|
|
1518
|
+
suppression together, and rebrands the section-divider pages to a light
|
|
1519
|
+
(default white) background with dark, readable numbers/titles so they no
|
|
1520
|
+
longer carry the OGC navy house style.
|
|
1521
|
+
"""
|
|
1522
|
+
debrand = bool(getattr(m, "suppress_design_elements", False))
|
|
1523
|
+
crossing = debrand or bool(getattr(m, "suppress_crossing_lines", False))
|
|
1524
|
+
underlines = debrand or bool(getattr(m, "suppress_title_underlines", False))
|
|
1525
|
+
plain_nums = debrand or bool(getattr(m, "plain_section_numbers", False))
|
|
1526
|
+
|
|
1527
|
+
colors = getattr(m, "colors", None)
|
|
1528
|
+
page_bg = getattr(colors, "page_background", None) if colors else None
|
|
1529
|
+
title_c = getattr(colors, "title", None) if colors else None
|
|
1530
|
+
cover_c = getattr(colors, "cover_text", None) if colors else None
|
|
1531
|
+
|
|
1532
|
+
# Divider background + foreground colours.
|
|
1533
|
+
if debrand:
|
|
1534
|
+
# Rebrand: light divider pages (white unless overridden) + dark text.
|
|
1535
|
+
divider_bg = page_bg or "#FFFFFF"
|
|
1536
|
+
divider_fg = title_c or cover_c or "#1F3864"
|
|
1537
|
+
elif page_bg:
|
|
1538
|
+
# Explicit custom background: assume it is light, use a dark foreground.
|
|
1539
|
+
divider_bg = page_bg
|
|
1540
|
+
divider_fg = title_c or cover_c or "#1F3864"
|
|
1541
|
+
else:
|
|
1542
|
+
# Flavor default: navy divider pages, white number/title.
|
|
1543
|
+
divider_bg = None
|
|
1544
|
+
divider_fg = "white"
|
|
1545
|
+
|
|
1546
|
+
return {
|
|
1547
|
+
"logo": bool(getattr(m, "suppress_flavor_logo", False)),
|
|
1548
|
+
"crossing": crossing,
|
|
1549
|
+
"underlines": underlines,
|
|
1550
|
+
"plain_nums": plain_nums,
|
|
1551
|
+
"body_font": getattr(m, "body_font", None),
|
|
1552
|
+
"watermark": getattr(m, "page_watermark", None),
|
|
1553
|
+
"divider_bg": divider_bg,
|
|
1554
|
+
"divider_fg": divider_fg,
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
|
|
1558
|
+
def _pdf_override_flags(m) -> list[str]:
|
|
1559
|
+
"""Return the override snippets enabled by document_metadata, in order."""
|
|
1560
|
+
if not m:
|
|
1561
|
+
return []
|
|
1562
|
+
cfg = _effective_pdf_config(m)
|
|
1563
|
+
parts: list[str] = []
|
|
1564
|
+
if cfg["logo"]:
|
|
1565
|
+
parts.append(_OVERRIDE_SUPPRESS_LOGO)
|
|
1566
|
+
if cfg["crossing"]:
|
|
1567
|
+
parts.append(_OVERRIDE_SUPPRESS_CROSSING_LINES)
|
|
1568
|
+
if cfg["underlines"]:
|
|
1569
|
+
parts.append(_OVERRIDE_SUPPRESS_TITLE_UNDERLINES)
|
|
1570
|
+
if cfg["plain_nums"]:
|
|
1571
|
+
parts.append(_plain_section_numbers_override(cfg["divider_fg"]))
|
|
1572
|
+
# When the divider background is rebranded (light), force the big title
|
|
1573
|
+
# colour too — the flavor inherits white text, invisible on a light page.
|
|
1574
|
+
if cfg["divider_bg"] is not None:
|
|
1575
|
+
parts.append(_section_title_color_override(cfg["divider_fg"]))
|
|
1576
|
+
if cfg["watermark"]:
|
|
1577
|
+
parts.append(_page_watermark_override(cfg["watermark"]))
|
|
1578
|
+
# Body font override — the OGC flavor hardcodes Lato in the root-style
|
|
1579
|
+
# attribute-set, so :body-font: alone doesn't work; we need an XSL override.
|
|
1580
|
+
body = cfg["body_font"]
|
|
1581
|
+
if body:
|
|
1582
|
+
noto = '<xsl:value-of select="$font_noto_sans"/>'
|
|
1583
|
+
parts.append(
|
|
1584
|
+
f'\n <!-- Override body font family (OGC default is Lato). -->\n'
|
|
1585
|
+
f' <xsl:attribute-set name="root-style">\n'
|
|
1586
|
+
f' <xsl:attribute name="font-family">{body}, STIX Two Math, {noto}</xsl:attribute>\n'
|
|
1587
|
+
f' <xsl:attribute name="font-family-generic">Sans</xsl:attribute>\n'
|
|
1588
|
+
f' <xsl:attribute name="font-size">11pt</xsl:attribute>\n'
|
|
1589
|
+
f' </xsl:attribute-set>\n'
|
|
1590
|
+
)
|
|
1591
|
+
return parts
|
|
1592
|
+
|
|
1593
|
+
|
|
1594
|
+
def _build_pdf_override_xsl(m) -> str | None:
|
|
1595
|
+
"""Assemble the consolidated PDF stylesheet override, or None when unused.
|
|
1596
|
+
|
|
1597
|
+
:pdf-stylesheet-override: accepts a single file, so all requested template
|
|
1598
|
+
overrides (logo suppression, crossing lines, section-number circles, title
|
|
1599
|
+
underlines, divider rebranding and the every-page watermark) are merged into
|
|
1600
|
+
one stylesheet. mn2pdf splices these named-template bodies over the flavor's
|
|
1601
|
+
at equal import precedence, so they take effect.
|
|
1602
|
+
"""
|
|
1603
|
+
parts = _pdf_override_flags(m)
|
|
1604
|
+
if not parts:
|
|
1605
|
+
return None
|
|
1606
|
+
return (
|
|
1607
|
+
'<?xml version="1.0" encoding="UTF-8"?>\n'
|
|
1608
|
+
'<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"\n'
|
|
1609
|
+
' xmlns:fo="http://www.w3.org/1999/XSL/Format"\n'
|
|
1610
|
+
' xmlns:mn="https://www.metanorma.org/ns/standoc"\n'
|
|
1611
|
+
' xmlns:fox="http://xmlgraphics.apache.org/fop/extensions"\n'
|
|
1612
|
+
' xmlns:xalan="http://xml.apache.org/xalan"\n'
|
|
1613
|
+
' xmlns:java="http://xml.apache.org/xalan/java"\n'
|
|
1614
|
+
' xmlns:mnx="https://www.metanorma.org/ns/xslt"\n'
|
|
1615
|
+
' version="1.0">\n'
|
|
1616
|
+
+ "".join(parts)
|
|
1617
|
+
+ "</xsl:stylesheet>\n"
|
|
1618
|
+
)
|
|
1619
|
+
|
|
1389
1620
|
|
|
1390
1621
|
def _xml_escape(text: str) -> str:
|
|
1391
1622
|
return (
|
|
@@ -1489,6 +1720,28 @@ def _build_document_adoc(profile: ServiceProfile) -> str:
|
|
|
1489
1720
|
if m and m.colors:
|
|
1490
1721
|
for attr, value in m.colors.to_metanorma_attributes().items():
|
|
1491
1722
|
lines.append(f":{attr}: {value}")
|
|
1723
|
+
# When debranding (suppress_design_elements) rebrands the section-divider
|
|
1724
|
+
# pages to a light background, emit the background-page colour unless the
|
|
1725
|
+
# profile already set one explicitly via colors.page_background.
|
|
1726
|
+
if m:
|
|
1727
|
+
cfg = _effective_pdf_config(m)
|
|
1728
|
+
explicit_bg = bool(m.colors and m.colors.page_background)
|
|
1729
|
+
if cfg["divider_bg"] is not None and not explicit_bg:
|
|
1730
|
+
lines.append(
|
|
1731
|
+
f":presentation-metadata-color-background-page: {cfg['divider_bg']}"
|
|
1732
|
+
)
|
|
1733
|
+
# The preface/contents pages draw the OGC 'crossing lines with dot'
|
|
1734
|
+
# motif inline (using the secondary-shade-2 / color_design_light colour),
|
|
1735
|
+
# not via a named template, so an XSL override can't remove them. When
|
|
1736
|
+
# crossing lines are being suppressed we white them out via the colour
|
|
1737
|
+
# instead, unless the profile already set colors.cover_lines explicitly.
|
|
1738
|
+
explicit_lines = bool(m.colors and m.colors.cover_lines)
|
|
1739
|
+
if cfg["crossing"] and not explicit_lines:
|
|
1740
|
+
lines.append(":presentation-metadata-color-secondary-shade-2: #FFFFFF")
|
|
1741
|
+
# PDF font overrides (body/header/monospace).
|
|
1742
|
+
if m and m.fonts:
|
|
1743
|
+
for attr, value in m.fonts.to_metanorma_attributes().items():
|
|
1744
|
+
lines.append(f":{attr}: {value}")
|
|
1492
1745
|
# Custom cover page: replace the built-in OGC cover with our generated image.
|
|
1493
1746
|
# The image file (cover.png) is written by generate(); this just references it.
|
|
1494
1747
|
if m and m.cover and m.cover.logo:
|
|
@@ -1497,12 +1750,20 @@ def _build_document_adoc(profile: ServiceProfile) -> str:
|
|
|
1497
1750
|
# Copyright holder → also sets the PDF footer org name (replaces OGC default).
|
|
1498
1751
|
if m and m.copyright_holder:
|
|
1499
1752
|
lines.append(f":copyright-holder: {m.copyright_holder}")
|
|
1753
|
+
# PDF font family overrides (e.g. DGIWG house font 'Source Sans Pro').
|
|
1754
|
+
if m and m.body_font:
|
|
1755
|
+
lines.append(f":body-font: {m.body_font}")
|
|
1756
|
+
if m and m.header_font:
|
|
1757
|
+
lines.append(f":header-font: {m.header_font}")
|
|
1758
|
+
if m and m.monospace_font:
|
|
1759
|
+
lines.append(f":monospace-font: {m.monospace_font}")
|
|
1500
1760
|
# Custom legal boilerplate (page ii). The XML file is written by generate().
|
|
1501
1761
|
if m and m.boilerplate:
|
|
1502
1762
|
lines.append(":boilerplate-authority: boilerplate.xml")
|
|
1503
|
-
#
|
|
1504
|
-
|
|
1505
|
-
|
|
1763
|
+
# Consolidated PDF stylesheet override: logo suppression, crossing lines,
|
|
1764
|
+
# section-number circles, title underlines. The file is written by generate().
|
|
1765
|
+
if _pdf_override_flags(m):
|
|
1766
|
+
lines.append(f":pdf-stylesheet-override: {_PDF_OVERRIDE_FILENAME}")
|
|
1506
1767
|
lines += [
|
|
1507
1768
|
":mn-document-class: ogc",
|
|
1508
1769
|
":mn-output-extensions: xml,html,pdf",
|
|
@@ -1527,6 +1788,21 @@ def _build_document_adoc(profile: ServiceProfile) -> str:
|
|
|
1527
1788
|
return "\n".join(lines) + "\n"
|
|
1528
1789
|
|
|
1529
1790
|
|
|
1791
|
+
def _terms_adoc(profile: ServiceProfile) -> str:
|
|
1792
|
+
"""Render profile-supplied terms as Metanorma term clauses, or empty string."""
|
|
1793
|
+
m = profile.document_metadata
|
|
1794
|
+
terms = getattr(m, "terms", None) if m else None
|
|
1795
|
+
if not terms:
|
|
1796
|
+
return ""
|
|
1797
|
+
out = ["\n"]
|
|
1798
|
+
for t in terms:
|
|
1799
|
+
out.append(f"=== {t.term}\n\n{t.definition}\n")
|
|
1800
|
+
if t.source:
|
|
1801
|
+
out.append(f"\n_{t.source}_\n")
|
|
1802
|
+
out.append("\n")
|
|
1803
|
+
return "".join(out)
|
|
1804
|
+
|
|
1805
|
+
|
|
1530
1806
|
def _build_sections(profile: ServiceProfile) -> dict[str, str]:
|
|
1531
1807
|
"""Return the minimal boilerplate sections required by Metanorma OGC."""
|
|
1532
1808
|
m = profile.document_metadata
|
|
@@ -1655,6 +1931,7 @@ def _build_sections(profile: ServiceProfile) -> dict[str, str]:
|
|
|
1655
1931
|
"which is based on the ISO/IEC Directives, Part 2, Rules for the structure and drafting of International Standards. "
|
|
1656
1932
|
"In particular, the word \"shall\" (not \"must\") is the verb form used to indicate a requirement to be strictly followed to conform to this standard.\n\n"
|
|
1657
1933
|
"This document also uses terms defined in OGC API - EDR Part 1: Core.\n"
|
|
1934
|
+
+ _terms_adoc(profile)
|
|
1658
1935
|
),
|
|
1659
1936
|
"sections/06-requirements.adoc": (
|
|
1660
1937
|
"== Requirements\n\n"
|
|
@@ -1738,8 +2015,10 @@ def generate(profile: ServiceProfile, output_dir: Path) -> None:
|
|
|
1738
2015
|
if boilerplate_xml:
|
|
1739
2016
|
safe_write("boilerplate.xml", boilerplate_xml)
|
|
1740
2017
|
|
|
1741
|
-
# PDF stylesheet override
|
|
1742
|
-
|
|
1743
|
-
|
|
2018
|
+
# Consolidated PDF stylesheet override (logo, crossing lines, section-number
|
|
2019
|
+
# circles, title underlines) — written when any override flag is set.
|
|
2020
|
+
pdf_override_xsl = _build_pdf_override_xsl(m)
|
|
2021
|
+
if pdf_override_xsl:
|
|
2022
|
+
safe_write(_PDF_OVERRIDE_FILENAME, pdf_override_xsl)
|
|
1744
2023
|
|
|
1745
2024
|
print(f"Profile '{profile.name}' written to {output_dir}")
|
{oapi_profile_builder-3.2.1 → oapi_profile_builder-3.3.0}/src/oapi_profile_builder/models.py
RENAMED
|
@@ -353,6 +353,20 @@ class NormativeReference(BaseModel):
|
|
|
353
353
|
title: str = Field(description="Full reference title")
|
|
354
354
|
|
|
355
355
|
|
|
356
|
+
class Term(BaseModel):
|
|
357
|
+
"""A profile-supplied entry for the Terms and Definitions section.
|
|
358
|
+
|
|
359
|
+
Appended after the base terms so a profile can extend the vocabulary (e.g.
|
|
360
|
+
with EDR Part 3 or Pub/Sub terms). Rendered as a Metanorma term clause.
|
|
361
|
+
"""
|
|
362
|
+
term: str = Field(description="The term being defined, e.g. 'publish/subscribe'")
|
|
363
|
+
definition: str = Field(description="The definition text")
|
|
364
|
+
source: str | None = Field(
|
|
365
|
+
default=None,
|
|
366
|
+
description="Optional source/authority for the definition, e.g. 'SOURCE: OGC 19-086r6'",
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
|
|
356
370
|
# Metanorma OGC document stages (`:status:` / `:docstage:`). See
|
|
357
371
|
# https://www.metanorma.org/author/ogc/ref/document-attributes/
|
|
358
372
|
DocStatus = Literal[
|
|
@@ -435,6 +449,29 @@ class CoverPage(BaseModel):
|
|
|
435
449
|
tagline: str | None = Field(default=None, description="Optional line rendered under the logo, e.g. an organisation motto")
|
|
436
450
|
background: str | None = Field(default=None, description="Cover background hex colour (default white)")
|
|
437
451
|
text_color: str | None = Field(default=None, description="Cover text hex colour (default black)")
|
|
452
|
+
font_regular: str | None = Field(
|
|
453
|
+
default=None,
|
|
454
|
+
description=(
|
|
455
|
+
"Cover font for normal text: a font-file path (.ttf/.otf) or an installed "
|
|
456
|
+
"family name (e.g. 'Source Sans Pro'). Family names are resolved via fontconfig. "
|
|
457
|
+
"Defaults to DejaVu Sans."
|
|
458
|
+
),
|
|
459
|
+
)
|
|
460
|
+
font_bold: str | None = Field(
|
|
461
|
+
default=None,
|
|
462
|
+
description="Cover font for bold text (doc number/title). Path or family name; derived from font_regular when unset.",
|
|
463
|
+
)
|
|
464
|
+
font_italic: str | None = Field(
|
|
465
|
+
default=None,
|
|
466
|
+
description="Cover font for the italic tagline. Path or family name; derived from font_regular when unset.",
|
|
467
|
+
)
|
|
468
|
+
watermark: str | None = Field(
|
|
469
|
+
default=None,
|
|
470
|
+
description=(
|
|
471
|
+
"Optional watermark text stamped diagonally across the cover (e.g. 'DRAFT'). "
|
|
472
|
+
"Note: this marks the cover only; an every-page watermark requires the Metanorma layer."
|
|
473
|
+
),
|
|
474
|
+
)
|
|
438
475
|
|
|
439
476
|
@field_validator("background", "text_color")
|
|
440
477
|
@classmethod
|
|
@@ -444,6 +481,25 @@ class CoverPage(BaseModel):
|
|
|
444
481
|
return v
|
|
445
482
|
|
|
446
483
|
|
|
484
|
+
class PdfFonts(BaseModel):
|
|
485
|
+
"""PDF body/heading/monospace font overrides.
|
|
486
|
+
|
|
487
|
+
Maps to Metanorma's ``:body-font:``, ``:header-font:`` and
|
|
488
|
+
``:monospace-font:`` document attributes, so a profile can set the PDF
|
|
489
|
+
typeface (e.g. DGIWG's Source Sans Pro) without a custom flavor. The named
|
|
490
|
+
fonts must be resolvable by the Metanorma container (i.e. present in the
|
|
491
|
+
mounted ``~/.fontist/fonts``). See
|
|
492
|
+
https://www.metanorma.org/author/ref/document-attributes/
|
|
493
|
+
"""
|
|
494
|
+
body: str | None = Field(default=None, description="Body text font family (:body-font:), e.g. 'Source Sans Pro'")
|
|
495
|
+
header: str | None = Field(default=None, description="Heading font family (:header-font:)")
|
|
496
|
+
monospace: str | None = Field(default=None, description="Monospace/code font family (:monospace-font:)")
|
|
497
|
+
|
|
498
|
+
def to_metanorma_attributes(self) -> dict[str, str]:
|
|
499
|
+
mapping = {"body": "body-font", "header": "header-font", "monospace": "monospace-font"}
|
|
500
|
+
return {attr: getattr(self, f) for f, attr in mapping.items() if getattr(self, f, None)}
|
|
501
|
+
|
|
502
|
+
|
|
447
503
|
class Boilerplate(BaseModel):
|
|
448
504
|
"""Front-matter legal boilerplate (page ii) — copyright, license, legal notice.
|
|
449
505
|
|
|
@@ -508,11 +564,22 @@ class DocumentMetadata(BaseModel):
|
|
|
508
564
|
default_factory=list,
|
|
509
565
|
description="Additional normative references appended to the References section.",
|
|
510
566
|
)
|
|
567
|
+
terms: list[Term] = Field(
|
|
568
|
+
default_factory=list,
|
|
569
|
+
description=(
|
|
570
|
+
"Profile-supplied terms appended to the Terms and Definitions section "
|
|
571
|
+
"(e.g. EDR Part 3 / Pub/Sub terms), each with a definition and optional source."
|
|
572
|
+
),
|
|
573
|
+
)
|
|
511
574
|
external_id: str | None = None
|
|
512
575
|
colors: PdfColors | None = Field(
|
|
513
576
|
default=None,
|
|
514
577
|
description="PDF colour-scheme overrides (mapped to Metanorma :presentation-metadata-color-*: attributes).",
|
|
515
578
|
)
|
|
579
|
+
fonts: PdfFonts | None = Field(
|
|
580
|
+
default=None,
|
|
581
|
+
description="PDF font overrides (body/header/monospace) mapped to Metanorma :body-font:/:header-font:/:monospace-font:.",
|
|
582
|
+
)
|
|
516
583
|
cover: CoverPage | None = Field(
|
|
517
584
|
default=None,
|
|
518
585
|
description="Custom cover-page branding (logo + generated cover) replacing the built-in OGC cover.",
|
|
@@ -542,6 +609,62 @@ class DocumentMetadata(BaseModel):
|
|
|
542
609
|
"Metanorma flavor."
|
|
543
610
|
),
|
|
544
611
|
)
|
|
612
|
+
body_font: str | None = Field(
|
|
613
|
+
default=None,
|
|
614
|
+
description=(
|
|
615
|
+
"PDF body-text font family (Metanorma :body-font:), e.g. 'Source Sans Pro'. "
|
|
616
|
+
"The font must be resolvable by fontist/the Metanorma container."
|
|
617
|
+
),
|
|
618
|
+
)
|
|
619
|
+
header_font: str | None = Field(
|
|
620
|
+
default=None,
|
|
621
|
+
description="PDF heading font family (Metanorma :header-font:).",
|
|
622
|
+
)
|
|
623
|
+
monospace_font: str | None = Field(
|
|
624
|
+
default=None,
|
|
625
|
+
description="PDF monospace font family (Metanorma :monospace-font:).",
|
|
626
|
+
)
|
|
627
|
+
suppress_crossing_lines: bool = Field(
|
|
628
|
+
default=False,
|
|
629
|
+
description=(
|
|
630
|
+
"When true, remove the OGC flavor's 'crossing lines' design element (the "
|
|
631
|
+
"blue crossed lines with dots on the cover and section-divider pages) via "
|
|
632
|
+
"the PDF stylesheet override. Coupled to the OGC Metanorma flavor."
|
|
633
|
+
),
|
|
634
|
+
)
|
|
635
|
+
plain_section_numbers: bool = Field(
|
|
636
|
+
default=False,
|
|
637
|
+
description=(
|
|
638
|
+
"When true, render section-divider numbers as plain text instead of the OGC "
|
|
639
|
+
"flavor's coloured circles (e.g. '1 Scope' rather than a circled '1'). "
|
|
640
|
+
"Coupled to the OGC Metanorma flavor."
|
|
641
|
+
),
|
|
642
|
+
)
|
|
643
|
+
suppress_title_underlines: bool = Field(
|
|
644
|
+
default=False,
|
|
645
|
+
description=(
|
|
646
|
+
"When true, remove the short/long horizontal rule the OGC flavor draws "
|
|
647
|
+
"beneath section titles. Coupled to the OGC Metanorma flavor."
|
|
648
|
+
),
|
|
649
|
+
)
|
|
650
|
+
suppress_design_elements: bool = Field(
|
|
651
|
+
default=False,
|
|
652
|
+
description=(
|
|
653
|
+
"When true, strip the OGC flavor's decorative PDF design elements — the "
|
|
654
|
+
"blue 'crossing lines' motif, the circular badges around clause numbers, and "
|
|
655
|
+
"the short rule under clause titles — via a targeted PDF stylesheet override. "
|
|
656
|
+
"Produces a plainer 'i. Abstract' / '1 Scope' heading style for SDOs (e.g. "
|
|
657
|
+
"DGIWG) that don't use the OGC house style. Coupled to the OGC Metanorma flavor."
|
|
658
|
+
),
|
|
659
|
+
)
|
|
660
|
+
page_watermark: str | None = Field(
|
|
661
|
+
default=None,
|
|
662
|
+
description=(
|
|
663
|
+
"Optional text (e.g. 'DRAFT') rendered as a light diagonal watermark on every "
|
|
664
|
+
"PDF page via a PDF stylesheet override. Distinct from cover.watermark, which "
|
|
665
|
+
"only marks the generated cover image. Coupled to the OGC Metanorma flavor."
|
|
666
|
+
),
|
|
667
|
+
)
|
|
545
668
|
|
|
546
669
|
@field_validator("submission_date", "approval_date", "publication_date")
|
|
547
670
|
@classmethod
|
{oapi_profile_builder-3.2.1 → oapi_profile_builder-3.3.0/src/oapi_profile_builder.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oapi-profile-builder
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Summary: Authoritative tooling for creating OGC API Service Profiles (EDR, Features)
|
|
5
5
|
Author-email: Shane Mill <shane.mill@noaa.gov>
|
|
6
6
|
License: Apache License
|
|
@@ -234,18 +234,20 @@ Everything about the document — cover, title-page metadata, legal page, footer
|
|
|
234
234
|
|
|
235
235
|
| What you want to change | Field | Mechanism |
|
|
236
236
|
|---|---|---|
|
|
237
|
-
| Cover logo
|
|
237
|
+
| Cover logo, tagline, fonts, watermark (replaces the OGC cover) | `document_metadata.cover` | Generated cover image via `:coverpage-image:` full replacement |
|
|
238
238
|
| Title, doc number, edition, dates, sub-title/status | `title`, `version`, `document_metadata.{doc_number, doc_type, doc_subtype, status, *_date}` | Metanorma document attributes |
|
|
239
239
|
| PDF colours (titles, tables, cover, backgrounds) | `document_metadata.colors` | `:presentation-metadata-color-*:` |
|
|
240
240
|
| Page-ii legal text (copyright / license / notice) | `document_metadata.boilerplate` | `:boilerplate-authority:` |
|
|
241
241
|
| Footer organisation name | `document_metadata.copyright_holder` | `:copyright-holder:` |
|
|
242
242
|
| Remove the OGC logo from the legal page | `document_metadata.suppress_flavor_logo` | targeted `:pdf-stylesheet-override:` |
|
|
243
|
+
| Remove OGC design (crossing lines, circled numbers, rules, navy divider pages) | `document_metadata.suppress_design_elements` (or the individual `suppress_crossing_lines` / `plain_section_numbers` / `suppress_title_underlines`) | `:pdf-stylesheet-override:` + colour attributes |
|
|
244
|
+
| Every-page DRAFT-style watermark (over content) | `document_metadata.page_watermark` | `:pdf-stylesheet-override:` (footer overlay) |
|
|
243
245
|
| Requirement / conformance identifier namespace | `spec_uri_base` | derives `req_uri` / `conf_uri` |
|
|
244
|
-
| Submitter table, notice paragraph, normative refs | `document_metadata.{submitters, notice, normative_references}` | Metanorma attributes / sections |
|
|
246
|
+
| Submitter table, notice paragraph, normative refs, extra terms | `document_metadata.{submitters, notice, normative_references, terms}` | Metanorma attributes / sections |
|
|
245
247
|
|
|
246
|
-
Custom branding requires Pillow for the cover image: `pip install 'oapi-profile-builder[pdf]'`. Field-by-field detail is in the [`document_metadata`](#document_metadata) reference below; [`examples/nwp_radar.yaml`](examples/nwp_radar.yaml) is a complete DGIWG-branded example.
|
|
248
|
+
Custom branding requires Pillow for the cover image: `pip install 'oapi-profile-builder[pdf]'`. Field-by-field detail is in the [`document_metadata`](#document_metadata) reference below; [`examples/nwp_radar.yaml`](examples/nwp_radar.yaml) is a complete DGIWG-branded example. Omit all of these fields and the output keeps the standard OGC house style.
|
|
247
249
|
|
|
248
|
-
> The `suppress_flavor_logo`
|
|
250
|
+
> The `suppress_flavor_logo`, `suppress_design_elements`/`suppress_*`, and `page_watermark` overrides reach into the OGC Metanorma flavor's XSL templates (e.g. `insertLogoPreface`, `insertCrossingLines`, `insertSectionNumInCircle`, `insertFooter`) and are coupled to that flavor. Everything else uses documented Metanorma attributes. mn2pdf merges our same-named templates over the flavor's at equal precedence (via `merge_override.xsl`), which is what makes these work; a future metanorma-ogc release could require refreshing the template names. For long-term SDO adoption, a dedicated Metanorma "taste"/flavor is the most durable path.
|
|
249
251
|
|
|
250
252
|
---
|
|
251
253
|
|
|
@@ -701,6 +703,10 @@ document_metadata:
|
|
|
701
703
|
tagline: Delivering Military Advantage through multi-national geospatial interoperability
|
|
702
704
|
background: "#FFFFFF" # optional cover background (default white)
|
|
703
705
|
text_color: "#1F3864" # optional cover text colour (default black)
|
|
706
|
+
font_regular: "Source Sans Pro" # optional: .ttf/.otf path OR installed family name (fontconfig)
|
|
707
|
+
font_bold: "Source Sans Pro" # optional bold face; derived from font_regular when unset
|
|
708
|
+
font_italic: "Source Sans Pro" # optional italic face for the tagline
|
|
709
|
+
watermark: "DRAFT" # optional diagonal watermark stamped on the cover
|
|
704
710
|
colors:
|
|
705
711
|
text: "#000000" # body text
|
|
706
712
|
cover_text: "#1F3864" # cover text / section numbers / ToC
|
|
@@ -733,7 +739,41 @@ document_metadata:
|
|
|
733
739
|
feedback: "Comments on this document should be directed to DGIWG."
|
|
734
740
|
```
|
|
735
741
|
|
|
736
|
-
|
|
742
|
+
#### Removing the OGC page design (crossing lines, circled numbers, divider pages)
|
|
743
|
+
|
|
744
|
+
The OGC flavor also draws decorative design elements: the blue "crossing lines with a dot" motif, circled section-divider numbers, a short rule under section titles, and full-navy section-divider pages. One switch removes them all:
|
|
745
|
+
|
|
746
|
+
- **`suppress_design_elements: true`** — a consolidated switch that:
|
|
747
|
+
- removes the blue crossing-lines + dot motif (on divider/cover pages via a template override; on the Contents/preface pages, which draw the lines inline in the flavor's own colour, by whiting that colour out);
|
|
748
|
+
- renders section numbers as plain text (e.g. `1 Scope`, `i Abstract`) instead of coloured circles;
|
|
749
|
+
- removes the short rule beneath section titles;
|
|
750
|
+
- rebrands the full-navy section-divider pages to a clean white page with a DGIWG-navy (from `colors.title`) plain number and title.
|
|
751
|
+
|
|
752
|
+
The individual toggles `suppress_crossing_lines`, `plain_section_numbers`, and `suppress_title_underlines` are still available if you want finer control; `suppress_design_elements` simply turns all three on plus the divider rebrand.
|
|
753
|
+
|
|
754
|
+
- **`page_watermark`** — stamps a light diagonal watermark (e.g. `DRAFT`) on **every page**, rendered *over* the content including tables. This is distinct from `cover.watermark`, which marks only the generated cover image. The cover page is left unmarked.
|
|
755
|
+
|
|
756
|
+
```yaml
|
|
757
|
+
document_metadata:
|
|
758
|
+
suppress_design_elements: true # plain numbers, no crossing lines/rules, white divider pages
|
|
759
|
+
page_watermark: DRAFT # every-page diagonal watermark (over content)
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
With `cover` + `colors` + `copyright_holder` + `boilerplate` + `suppress_flavor_logo` + `suppress_design_elements` (and `spec_uri_base` for the requirement/conformance identifiers), the generated PDF carries no OGC branding — only legitimate citations to the OGC standards the profile conforms to. Omit these fields and you get the standard OGC house style. See [`examples/nwp_radar.yaml`](examples/nwp_radar.yaml).
|
|
763
|
+
|
|
764
|
+
> These design-element overrides (and the `page_watermark` footer overlay) reach into the OGC Metanorma flavor's XSL template names and are coupled to that flavor; a future metanorma-ogc release could require refreshing them.
|
|
765
|
+
|
|
766
|
+
#### Extending Terms and Definitions
|
|
767
|
+
|
|
768
|
+
Add profile-specific terms (e.g. EDR Part 3 or Pub/Sub terms) appended after the base terms in the Terms and Definitions section:
|
|
769
|
+
|
|
770
|
+
```yaml
|
|
771
|
+
document_metadata:
|
|
772
|
+
terms:
|
|
773
|
+
- term: service profile
|
|
774
|
+
definition: A named, referenceable set of constraints and additional requirements applied to an OGC API - EDR implementation.
|
|
775
|
+
source: "SOURCE: OGC API - EDR Part 3: Service Profiles (draft)"
|
|
776
|
+
```
|
|
737
777
|
|
|
738
778
|
---
|
|
739
779
|
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# =================================================================
|
|
2
|
-
#
|
|
3
|
-
# Authors: Shane Mill <shane.mill@noaa.gov>
|
|
4
|
-
#
|
|
5
|
-
# Copyright (c) 2026 Shane Mill
|
|
6
|
-
#
|
|
7
|
-
# Permission is hereby granted, free of charge, to any person
|
|
8
|
-
# obtaining a copy of this software and associated documentation
|
|
9
|
-
# files (the "Software"), to deal in the Software without
|
|
10
|
-
# restriction, including without limitation the rights to use,
|
|
11
|
-
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
-
# copies of the Software, and to permit persons to whom the
|
|
13
|
-
# Software is furnished to do so, subject to the following
|
|
14
|
-
# conditions:
|
|
15
|
-
#
|
|
16
|
-
# The above copyright notice and this permission notice shall be
|
|
17
|
-
# included in all copies or substantial portions of the Software.
|
|
18
|
-
#
|
|
19
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
20
|
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
21
|
-
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
22
|
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
23
|
-
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
24
|
-
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
25
|
-
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
26
|
-
# OTHER DEALINGS IN THE SOFTWARE.
|
|
27
|
-
#
|
|
28
|
-
# =================================================================
|
|
29
|
-
"""
|
|
30
|
-
PDF compilation: shell out to the Metanorma Docker image.
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
from __future__ import annotations
|
|
34
|
-
|
|
35
|
-
import shutil
|
|
36
|
-
import subprocess
|
|
37
|
-
import sys
|
|
38
|
-
from pathlib import Path
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def compile_pdf(output_dir: Path) -> bool:
|
|
42
|
-
"""
|
|
43
|
-
Run `docker run metanorma/metanorma compile` against document.adoc
|
|
44
|
-
in output_dir. Returns True on success.
|
|
45
|
-
"""
|
|
46
|
-
if shutil.which("docker") is None:
|
|
47
|
-
print("docker not found. Install Docker to use --pdf.", file=sys.stderr)
|
|
48
|
-
sys.exit(1)
|
|
49
|
-
|
|
50
|
-
doc = output_dir / "document.adoc"
|
|
51
|
-
if not doc.exists():
|
|
52
|
-
print(f"document.adoc not found in {output_dir}", file=sys.stderr)
|
|
53
|
-
sys.exit(1)
|
|
54
|
-
|
|
55
|
-
fonts_dir = Path.home() / ".fontist" / "fonts"
|
|
56
|
-
fonts_dir.mkdir(parents=True, exist_ok=True)
|
|
57
|
-
|
|
58
|
-
cmd = [
|
|
59
|
-
"docker", "run", "--rm",
|
|
60
|
-
"-v", f"{output_dir.resolve()}:/metanorma",
|
|
61
|
-
"-v", f"{fonts_dir}:/config/fonts",
|
|
62
|
-
"metanorma/metanorma",
|
|
63
|
-
"metanorma", "compile",
|
|
64
|
-
"--agree-to-terms",
|
|
65
|
-
"-t", "ogc",
|
|
66
|
-
"-x", "pdf",
|
|
67
|
-
"document.adoc",
|
|
68
|
-
]
|
|
69
|
-
|
|
70
|
-
print(f"Compiling PDF via Metanorma Docker...")
|
|
71
|
-
result = subprocess.run(cmd, check=False)
|
|
72
|
-
if result.returncode == 0:
|
|
73
|
-
pdf = output_dir / "document.pdf"
|
|
74
|
-
print(f"PDF written to {pdf}")
|
|
75
|
-
return result.returncode == 0
|
|
File without changes
|
|
File without changes
|
{oapi_profile_builder-3.2.1 → oapi_profile_builder-3.3.0}/src/oapi_profile_builder/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{oapi_profile_builder-3.2.1 → oapi_profile_builder-3.3.0}/src/oapi_profile_builder/cite_features.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|