diff --git a/examples/brand-typography-minimal.yml b/examples/brand-typography-minimal.yml new file mode 100644 index 00000000..8ff78f73 --- /dev/null +++ b/examples/brand-typography-minimal.yml @@ -0,0 +1,9 @@ +meta: + name: examples/brand-typography-simple.yml +typography: + fonts: + - family: Open Sans + source: file + base: Open Sans + headings: Roboto Slab + monospace: Fira Code \ No newline at end of file diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index d4e75e97..2561c942 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -146,7 +146,7 @@ class BrandTypographyFontFiles(BaseModel): source: Literal["file"] = "file" family: str - files: list[BrandTypographyFontFilesPath] + files: list[BrandTypographyFontFilesPath] = Field(default_factory=list) class BrandTypographyFontFilesPath(BaseModel): @@ -413,6 +413,59 @@ class BrandTypography(BrandBase): ) link: BrandTypographyLink | None = None + @model_validator(mode="before") + @classmethod + def simple_google_fonts(cls, data: Any): + if not isinstance(data, dict): + return data + + defined_families = set() + file_families = set() + + if ( + "fonts" in data + and isinstance(data["fonts"], list) + and len(data["fonts"]) > 0 + ): + for font in data["fonts"]: + defined_families.add(font["family"]) + if font["source"] == "file": + file_families.add(font["family"]) + else: + data["fonts"] = [] + + for field in ( + "base", + "headings", + "monospace", + "monospace_inline", + "monospace_block", + ): + if field not in data: + continue + + if not isinstance(data[field], (str, dict)): + continue + + if isinstance(data[field], str): + data[field] = {"family": data[field]} + + if "family" not in data[field]: + continue + + if data[field]["family"] in defined_families: + continue + + data["fonts"].append( + { + "family": data[field]["family"], + "source": "google", + } + ) + defined_families.add(data[field]["family"]) + + return data + @model_validator(mode="after") def forward_monospace_values(self): """ diff --git a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_color.json b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_color.json index 1c74f306..ba1b90f7 100644 --- a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_color.json +++ b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_color.json @@ -18,6 +18,7 @@ "base": { "color": "#1b1818" }, + "fonts": [], "headings": { "color": "#87CEEB" }, diff --git a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_minimal.json b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_minimal.json new file mode 100644 index 00000000..1849fccb --- /dev/null +++ b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_minimal.json @@ -0,0 +1,38 @@ +{ + "meta": { + "name": { + "full": "examples/brand-typography-simple.yml" + } + }, + "typography": { + "base": { + "family": "Open Sans" + }, + "fonts": [ + { + "family": "Open Sans", + "source": "file" + }, + { + "family": "Roboto Slab", + "source": "google" + }, + { + "family": "Fira Code", + "source": "google" + } + ], + "headings": { + "family": "Roboto Slab" + }, + "monospace": { + "family": "Fira Code" + }, + "monospace-block": { + "family": "Fira Code" + }, + "monospace-inline": { + "family": "Fira Code" + } + } +} diff --git a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_simple.json b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_simple.json index 5523c38f..492aa61e 100644 --- a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_simple.json +++ b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_simple.json @@ -10,6 +10,20 @@ "line-height": 1.25, "size": "1rem" }, + "fonts": [ + { + "family": "Open Sans", + "source": "google" + }, + { + "family": "Roboto Slab", + "source": "google" + }, + { + "family": "Fira Code", + "source": "google" + } + ], "headings": { "color": "primary", "family": "Roboto Slab", diff --git a/pkg-py/tests/test_typography.py b/pkg-py/tests/test_typography.py index d769163b..659074a5 100644 --- a/pkg-py/tests/test_typography.py +++ b/pkg-py/tests/test_typography.py @@ -258,7 +258,16 @@ def test_brand_typography_ex_simple(snapshot_json): brand = read_brand_yaml(path_examples("brand-typography-simple.yml")) assert isinstance(brand.typography, BrandTypography) - assert brand.typography.fonts == [] + + assert isinstance(brand.typography.fonts, list) + assert len(brand.typography.fonts) == 3 + assert [f.family for f in brand.typography.fonts] == [ + "Open Sans", + "Roboto Slab", + "Fira Code", + ] + assert [f.source for f in brand.typography.fonts] == ["google"] * 3 + assert brand.typography.link is None assert isinstance(brand.typography.base, BrandTypographyBase) assert isinstance(brand.typography.headings, BrandTypographyHeadings) @@ -355,3 +364,22 @@ def test_brand_typography_ex_color(snapshot_json): assert t.link.color == color.palette["red"] assert snapshot_json == pydantic_data_from_json(brand) + + +def test_brand_typography_ex_minimal(snapshot_json): + brand = read_brand_yaml(path_examples("brand-typography-minimal.yml")) + + assert isinstance(brand.typography, BrandTypography) + + assert isinstance(brand.typography.fonts, list) + assert len(brand.typography.fonts) == 3 + assert brand.typography.fonts[0].source == "file" + assert brand.typography.fonts[0].files == [] + + assert isinstance(brand.typography.fonts[1], BrandTypographyFontGoogle) + assert brand.typography.fonts[1].family == "Roboto Slab" + + assert isinstance(brand.typography.fonts[2], BrandTypographyFontGoogle) + assert brand.typography.fonts[2].family == "Fira Code" + + assert snapshot_json == pydantic_data_from_json(brand)