Fix font-location
This commit is contained in:
@@ -0,0 +1,638 @@
|
||||
{
|
||||
"version": 1,
|
||||
"baseUrl": "https://github.com/jpirnay/crosspoint-reader/blob/master/assets/sd-fonts/",
|
||||
"families": [
|
||||
{
|
||||
"name": "Alegreya",
|
||||
"description": "Calligraphic serif/display (Latin, Greek, Cyrillic)",
|
||||
"styles": [
|
||||
"regular",
|
||||
"bold",
|
||||
"italic",
|
||||
"bolditalic"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "Alegreya_10.cpfont",
|
||||
"size": 314326
|
||||
},
|
||||
{
|
||||
"name": "Alegreya_12.cpfont",
|
||||
"size": 393854
|
||||
},
|
||||
{
|
||||
"name": "Alegreya_14.cpfont",
|
||||
"size": 485840
|
||||
},
|
||||
{
|
||||
"name": "Alegreya_16.cpfont",
|
||||
"size": 595370
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "AtkinsonHyperlegibleNext",
|
||||
"description": "Accessibility font for low vision (Latin)",
|
||||
"styles": [
|
||||
"regular",
|
||||
"bold",
|
||||
"italic",
|
||||
"bolditalic"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "AtkinsonHyperlegibleNext_10.cpfont",
|
||||
"size": 118633
|
||||
},
|
||||
{
|
||||
"name": "AtkinsonHyperlegibleNext_12.cpfont",
|
||||
"size": 140769
|
||||
},
|
||||
{
|
||||
"name": "AtkinsonHyperlegibleNext_14.cpfont",
|
||||
"size": 166585
|
||||
},
|
||||
{
|
||||
"name": "AtkinsonHyperlegibleNext_16.cpfont",
|
||||
"size": 194397
|
||||
},
|
||||
{
|
||||
"name": "AtkinsonHyperlegibleNext_18.cpfont",
|
||||
"size": 230872
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Bitter",
|
||||
"description": "Slab serif designed for e-ink (Latin, Cyrillic)",
|
||||
"styles": [
|
||||
"regular",
|
||||
"bold",
|
||||
"italic",
|
||||
"bolditalic"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "Bitter_10.cpfont",
|
||||
"size": 283762
|
||||
},
|
||||
{
|
||||
"name": "Bitter_12.cpfont",
|
||||
"size": 352284
|
||||
},
|
||||
{
|
||||
"name": "Bitter_14.cpfont",
|
||||
"size": 430628
|
||||
},
|
||||
{
|
||||
"name": "Bitter_16.cpfont",
|
||||
"size": 517782
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Gelasio",
|
||||
"description": "Readable old-style serif (Latin, Cyrillic)",
|
||||
"styles": [
|
||||
"regular",
|
||||
"bold",
|
||||
"italic",
|
||||
"bolditalic"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "Gelasio_10.cpfont",
|
||||
"size": 214500
|
||||
},
|
||||
{
|
||||
"name": "Gelasio_12.cpfont",
|
||||
"size": 280544
|
||||
},
|
||||
{
|
||||
"name": "Gelasio_14.cpfont",
|
||||
"size": 357582
|
||||
},
|
||||
{
|
||||
"name": "Gelasio_16.cpfont",
|
||||
"size": 447480
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "GentiumBookPlus",
|
||||
"description": "Scholarly serif with wide Unicode coverage (Latin, Greek, Cyrillic, IPA)",
|
||||
"styles": [
|
||||
"regular",
|
||||
"bold",
|
||||
"italic",
|
||||
"bolditalic"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "GentiumBookPlus_10.cpfont",
|
||||
"size": 398510
|
||||
},
|
||||
{
|
||||
"name": "GentiumBookPlus_12.cpfont",
|
||||
"size": 519063
|
||||
},
|
||||
{
|
||||
"name": "GentiumBookPlus_14.cpfont",
|
||||
"size": 645720
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "IBMPlexMono",
|
||||
"description": "Monospace for code and technical reading (Latin, Greek, Cyrillic)",
|
||||
"styles": [
|
||||
"regular",
|
||||
"bold",
|
||||
"italic",
|
||||
"bolditalic"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "IBMPlexMono_10.cpfont",
|
||||
"size": 165232
|
||||
},
|
||||
{
|
||||
"name": "IBMPlexMono_12.cpfont",
|
||||
"size": 210770
|
||||
},
|
||||
{
|
||||
"name": "IBMPlexMono_14.cpfont",
|
||||
"size": 267380
|
||||
},
|
||||
{
|
||||
"name": "IBMPlexMono_16.cpfont",
|
||||
"size": 332010
|
||||
},
|
||||
{
|
||||
"name": "IBMPlexMono_18.cpfont",
|
||||
"size": 425525
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "IBMPlexSans",
|
||||
"description": "IBM corporate sans-serif (Latin, Greek, Cyrillic)",
|
||||
"styles": [
|
||||
"regular",
|
||||
"bold",
|
||||
"italic",
|
||||
"bolditalic"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "IBMPlexSans_10.cpfont",
|
||||
"size": 346854
|
||||
},
|
||||
{
|
||||
"name": "IBMPlexSans_12.cpfont",
|
||||
"size": 402760
|
||||
},
|
||||
{
|
||||
"name": "IBMPlexSans_14.cpfont",
|
||||
"size": 467012
|
||||
},
|
||||
{
|
||||
"name": "IBMPlexSans_16.cpfont",
|
||||
"size": 544046
|
||||
},
|
||||
{
|
||||
"name": "IBMPlexSans_18.cpfont",
|
||||
"size": 658695
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "IBMPlexSerif",
|
||||
"description": "Professional serif (Latin, Greek, Cyrillic)",
|
||||
"styles": [
|
||||
"regular",
|
||||
"bold",
|
||||
"italic",
|
||||
"bolditalic"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "IBMPlexSerif_10.cpfont",
|
||||
"size": 261436
|
||||
},
|
||||
{
|
||||
"name": "IBMPlexSerif_12.cpfont",
|
||||
"size": 311135
|
||||
},
|
||||
{
|
||||
"name": "IBMPlexSerif_14.cpfont",
|
||||
"size": 371513
|
||||
},
|
||||
{
|
||||
"name": "IBMPlexSerif_16.cpfont",
|
||||
"size": 441596
|
||||
},
|
||||
{
|
||||
"name": "IBMPlexSerif_18.cpfont",
|
||||
"size": 542532
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Inter",
|
||||
"description": "Modern sans-serif (Latin, Greek, Cyrillic)",
|
||||
"styles": [
|
||||
"regular",
|
||||
"bold",
|
||||
"italic",
|
||||
"bolditalic"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "Inter_10.cpfont",
|
||||
"size": 530150
|
||||
},
|
||||
{
|
||||
"name": "Inter_12.cpfont",
|
||||
"size": 659788
|
||||
},
|
||||
{
|
||||
"name": "Inter_14.cpfont",
|
||||
"size": 807250
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "LexicaUltralegible",
|
||||
"description": "Accessibility font for low vision / dyslexia (Latin)",
|
||||
"styles": [
|
||||
"regular",
|
||||
"bold",
|
||||
"italic",
|
||||
"bolditalic"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "LexicaUltralegible_10.cpfont",
|
||||
"size": 155718
|
||||
},
|
||||
{
|
||||
"name": "LexicaUltralegible_12.cpfont",
|
||||
"size": 195978
|
||||
},
|
||||
{
|
||||
"name": "LexicaUltralegible_14.cpfont",
|
||||
"size": 244278
|
||||
},
|
||||
{
|
||||
"name": "LexicaUltralegible_16.cpfont",
|
||||
"size": 297862
|
||||
},
|
||||
{
|
||||
"name": "LexicaUltralegible_18.cpfont",
|
||||
"size": 358357
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Literata",
|
||||
"description": "Screen-optimized serif (Latin, Greek, Cyrillic)",
|
||||
"styles": [
|
||||
"regular",
|
||||
"bold",
|
||||
"italic",
|
||||
"bolditalic"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "Literata_10.cpfont",
|
||||
"size": 497119
|
||||
},
|
||||
{
|
||||
"name": "Literata_12.cpfont",
|
||||
"size": 583306
|
||||
},
|
||||
{
|
||||
"name": "Literata_14.cpfont",
|
||||
"size": 691897
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Lora",
|
||||
"description": "Calligraphic serif for literary reading (Latin, Cyrillic)",
|
||||
"styles": [
|
||||
"regular",
|
||||
"bold",
|
||||
"italic",
|
||||
"bolditalic"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "Lora_10.cpfont",
|
||||
"size": 244674
|
||||
},
|
||||
{
|
||||
"name": "Lora_12.cpfont",
|
||||
"size": 302601
|
||||
},
|
||||
{
|
||||
"name": "Lora_14.cpfont",
|
||||
"size": 369710
|
||||
},
|
||||
{
|
||||
"name": "Lora_16.cpfont",
|
||||
"size": 453301
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Merriweather",
|
||||
"description": "Warm serif for long-form reading (Latin, Cyrillic)",
|
||||
"styles": [
|
||||
"regular",
|
||||
"bold",
|
||||
"italic",
|
||||
"bolditalic"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "Merriweather_10.cpfont",
|
||||
"size": 380502
|
||||
},
|
||||
{
|
||||
"name": "Merriweather_12.cpfont",
|
||||
"size": 469405
|
||||
},
|
||||
{
|
||||
"name": "Merriweather_14.cpfont",
|
||||
"size": 570179
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "NotoSansArmenian",
|
||||
"description": "Armenian script",
|
||||
"styles": [
|
||||
"regular"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "NotoSansArmenian_10.cpfont",
|
||||
"size": 8524
|
||||
},
|
||||
{
|
||||
"name": "NotoSansArmenian_12.cpfont",
|
||||
"size": 10057
|
||||
},
|
||||
{
|
||||
"name": "NotoSansArmenian_14.cpfont",
|
||||
"size": 12146
|
||||
},
|
||||
{
|
||||
"name": "NotoSansArmenian_16.cpfont",
|
||||
"size": 14418
|
||||
},
|
||||
{
|
||||
"name": "NotoSansArmenian_18.cpfont",
|
||||
"size": 16846
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "NotoSansEthiopic",
|
||||
"description": "Ethiopic/Ge'ez script",
|
||||
"styles": [
|
||||
"regular"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "NotoSansEthiopic_10.cpfont",
|
||||
"size": 35426
|
||||
},
|
||||
{
|
||||
"name": "NotoSansEthiopic_12.cpfont",
|
||||
"size": 47174
|
||||
},
|
||||
{
|
||||
"name": "NotoSansEthiopic_14.cpfont",
|
||||
"size": 60619
|
||||
},
|
||||
{
|
||||
"name": "NotoSansEthiopic_16.cpfont",
|
||||
"size": 76276
|
||||
},
|
||||
{
|
||||
"name": "NotoSansEthiopic_18.cpfont",
|
||||
"size": 94566
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "NotoSansExtended",
|
||||
"description": "Sans-serif (Latin, Greek, Cyrillic, Georgian, Armenian, Ethiopic)",
|
||||
"styles": [
|
||||
"regular",
|
||||
"bold",
|
||||
"italic",
|
||||
"bolditalic"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "NotoSansExtended_10.cpfont",
|
||||
"size": 503189
|
||||
},
|
||||
{
|
||||
"name": "NotoSansExtended_12.cpfont",
|
||||
"size": 636573
|
||||
},
|
||||
{
|
||||
"name": "NotoSansExtended_14.cpfont",
|
||||
"size": 802460
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "NotoSansGeorgian",
|
||||
"description": "Georgian script",
|
||||
"styles": [
|
||||
"regular"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "NotoSansGeorgian_10.cpfont",
|
||||
"size": 12791
|
||||
},
|
||||
{
|
||||
"name": "NotoSansGeorgian_12.cpfont",
|
||||
"size": 15397
|
||||
},
|
||||
{
|
||||
"name": "NotoSansGeorgian_14.cpfont",
|
||||
"size": 18693
|
||||
},
|
||||
{
|
||||
"name": "NotoSansGeorgian_16.cpfont",
|
||||
"size": 22464
|
||||
},
|
||||
{
|
||||
"name": "NotoSansGeorgian_18.cpfont",
|
||||
"size": 26415
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "NotoSansHangul",
|
||||
"description": "Sans-serif (Korean Hangul)",
|
||||
"styles": [
|
||||
"regular"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "NotoSansHangul_10.cpfont",
|
||||
"size": 1180396
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "NotoSerifExtended",
|
||||
"description": "Serif (Latin, Greek, Cyrillic)",
|
||||
"styles": [
|
||||
"regular",
|
||||
"bold",
|
||||
"italic",
|
||||
"bolditalic"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "NotoSerifExtended_10.cpfont",
|
||||
"size": 525124
|
||||
},
|
||||
{
|
||||
"name": "NotoSerifExtended_12.cpfont",
|
||||
"size": 655418
|
||||
},
|
||||
{
|
||||
"name": "NotoSerifExtended_14.cpfont",
|
||||
"size": 820713
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "OpenDyslexic",
|
||||
"description": "Dyslexia-friendly font (Latin, Latin-Extended)",
|
||||
"styles": [
|
||||
"regular",
|
||||
"bold",
|
||||
"italic",
|
||||
"bolditalic"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "OpenDyslexic_10.cpfont",
|
||||
"size": 288236
|
||||
},
|
||||
{
|
||||
"name": "OpenDyslexic_12.cpfont",
|
||||
"size": 378678
|
||||
},
|
||||
{
|
||||
"name": "OpenDyslexic_14.cpfont",
|
||||
"size": 483820
|
||||
},
|
||||
{
|
||||
"name": "OpenDyslexic_8.cpfont",
|
||||
"size": 212648
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SourceCodePro",
|
||||
"description": "Adobe monospace with excellent hinting (Latin)",
|
||||
"styles": [
|
||||
"regular",
|
||||
"bold",
|
||||
"italic",
|
||||
"bolditalic"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "SourceCodePro_10.cpfont",
|
||||
"size": 143190
|
||||
},
|
||||
{
|
||||
"name": "SourceCodePro_12.cpfont",
|
||||
"size": 188782
|
||||
},
|
||||
{
|
||||
"name": "SourceCodePro_14.cpfont",
|
||||
"size": 237985
|
||||
},
|
||||
{
|
||||
"name": "SourceCodePro_16.cpfont",
|
||||
"size": 292971
|
||||
},
|
||||
{
|
||||
"name": "SourceCodePro_18.cpfont",
|
||||
"size": 359581
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SourceSans3",
|
||||
"description": "Adobe humanist sans-serif (Latin, Greek, Cyrillic)",
|
||||
"styles": [
|
||||
"regular",
|
||||
"bold",
|
||||
"italic",
|
||||
"bolditalic"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "SourceSans3_10.cpfont",
|
||||
"size": 380803
|
||||
},
|
||||
{
|
||||
"name": "SourceSans3_12.cpfont",
|
||||
"size": 470341
|
||||
},
|
||||
{
|
||||
"name": "SourceSans3_14.cpfont",
|
||||
"size": 561177
|
||||
},
|
||||
{
|
||||
"name": "SourceSans3_16.cpfont",
|
||||
"size": 669252
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SourceSerif4",
|
||||
"description": "Adobe transitional serif (Latin, Greek, Cyrillic)",
|
||||
"styles": [
|
||||
"regular",
|
||||
"bold",
|
||||
"italic",
|
||||
"bolditalic"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"name": "SourceSerif4_10.cpfont",
|
||||
"size": 302084
|
||||
},
|
||||
{
|
||||
"name": "SourceSerif4_12.cpfont",
|
||||
"size": 359428
|
||||
},
|
||||
{
|
||||
"name": "SourceSerif4_14.cpfont",
|
||||
"size": 429133
|
||||
},
|
||||
{
|
||||
"name": "SourceSerif4_16.cpfont",
|
||||
"size": 508479
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,293 +0,0 @@
|
||||
# SD Card Font Families for CrossPoint++ Reader
|
||||
#
|
||||
# This file is the single source of truth for which fonts are generated,
|
||||
# how they're sourced, and how they're described in the download manifest.
|
||||
#
|
||||
# Adding a new font family = adding a block here. No code changes needed.
|
||||
#
|
||||
# Fields:
|
||||
# name: Output family name (used in filenames and on-device UI)
|
||||
# description: Human-readable description (shown in download UI and manifest)
|
||||
# intervals: Comma-separated Unicode interval presets for fontconvert_sdcard.py
|
||||
# sizes: Point sizes to generate
|
||||
# force_autohint: (optional) Force FreeType auto-hinter instead of native hinting
|
||||
# styles: Map of style name -> font source
|
||||
# path: relative to lib/EpdFont (for committed fonts)
|
||||
# url: download URL (for fonts not in the repo)
|
||||
#
|
||||
# Variable fonts:
|
||||
# Some fonts (Bitter, Inter, Alegreya) are distributed as variable fonts.
|
||||
# freetype-py can't set variable font axis values, so the build script
|
||||
# uses fonttools.instancer to extract static instances automatically.
|
||||
#
|
||||
# To use a variable font, add a 'variable' key to the style spec with
|
||||
# axis values to pin:
|
||||
#
|
||||
# styles:
|
||||
# regular: {url: "https://...Font[wght].ttf", variable: {wght: 400}}
|
||||
# bold: {url: "https://...Font[wght].ttf", variable: {wght: 700}}
|
||||
#
|
||||
# Extracted static fonts are cached in instanced_fonts/ (gitignored).
|
||||
# Requires fonttools: pip install -r requirements.txt
|
||||
|
||||
families:
|
||||
# ── Serif ──────────────────────────────────────────────────────────────
|
||||
|
||||
- name: Literata
|
||||
description: "Screen-optimized serif (Latin, Greek, Cyrillic)"
|
||||
intervals: latin-ext,greek,cyrillic
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/googlefonts/literata/main/fonts/ttf/Literata-Regular.ttf"}
|
||||
bold: {url: "https://raw.githubusercontent.com/googlefonts/literata/main/fonts/ttf/Literata-Bold.ttf"}
|
||||
italic: {url: "https://raw.githubusercontent.com/googlefonts/literata/main/fonts/ttf/Literata-Italic.ttf"}
|
||||
bolditalic: {url: "https://raw.githubusercontent.com/googlefonts/literata/main/fonts/ttf/Literata-BoldItalic.ttf"}
|
||||
|
||||
- name: SourceSerif4
|
||||
description: "Adobe transitional serif (Latin, Greek, Cyrillic)"
|
||||
intervals: latin-ext,greek,cyrillic
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/adobe-fonts/source-serif/release/TTF/SourceSerif4-Regular.ttf"}
|
||||
bold: {url: "https://raw.githubusercontent.com/adobe-fonts/source-serif/release/TTF/SourceSerif4-Bold.ttf"}
|
||||
italic: {url: "https://raw.githubusercontent.com/adobe-fonts/source-serif/release/TTF/SourceSerif4-It.ttf"}
|
||||
bolditalic: {url: "https://raw.githubusercontent.com/adobe-fonts/source-serif/release/TTF/SourceSerif4-BoldIt.ttf"}
|
||||
|
||||
- name: NotoSerifExtended
|
||||
description: "Serif (Latin, Greek, Cyrillic)"
|
||||
intervals: latin-ext,greek,cyrillic
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/notofonts/NotoSerif/main/fonts/ttf/unhinted/instance_ttf/NotoSerif-Regular.ttf"}
|
||||
bold: {url: "https://raw.githubusercontent.com/notofonts/NotoSerif/main/fonts/ttf/unhinted/instance_ttf/NotoSerif-Bold.ttf"}
|
||||
italic: {url: "https://raw.githubusercontent.com/notofonts/NotoSerif-Italic/main/fonts/ttf/unhinted/instance_ttf/NotoSerif-Italic.ttf"}
|
||||
bolditalic: {url: "https://raw.githubusercontent.com/notofonts/NotoSerif-Italic/main/fonts/ttf/unhinted/instance_ttf/NotoSerif-BoldItalic.ttf"}
|
||||
|
||||
- name: Merriweather
|
||||
description: "Warm serif for long-form reading (Latin, Cyrillic)"
|
||||
intervals: latin-ext,cyrillic
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/SorkinType/Merriweather/master/fonts/ttf/Merriweather-Regular.ttf"}
|
||||
bold: {url: "https://raw.githubusercontent.com/SorkinType/Merriweather/master/fonts/ttf/Merriweather-Bold.ttf"}
|
||||
italic: {url: "https://raw.githubusercontent.com/SorkinType/Merriweather/master/fonts/ttf/Merriweather-Italic.ttf"}
|
||||
bolditalic: {url: "https://raw.githubusercontent.com/SorkinType/Merriweather/master/fonts/ttf/Merriweather-BoldItalic.ttf"}
|
||||
|
||||
- name: Lora
|
||||
description: "Calligraphic serif for literary reading (Latin, Cyrillic)"
|
||||
intervals: latin-ext,cyrillic
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/cyrealtype/Lora-Cyrillic/main/fonts/ttf/Lora-Regular.ttf"}
|
||||
bold: {url: "https://raw.githubusercontent.com/cyrealtype/Lora-Cyrillic/main/fonts/ttf/Lora-Bold.ttf"}
|
||||
italic: {url: "https://raw.githubusercontent.com/cyrealtype/Lora-Cyrillic/main/fonts/ttf/Lora-Italic.ttf"}
|
||||
bolditalic: {url: "https://raw.githubusercontent.com/cyrealtype/Lora-Cyrillic/main/fonts/ttf/Lora-BoldItalic.ttf"}
|
||||
|
||||
- name: Gelasio
|
||||
description: "Readable old-style serif (Latin, Cyrillic)"
|
||||
intervals: latin-ext,cyrillic
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/gelasio/Gelasio%5Bwght%5D.ttf", variable: {wght: 400}}
|
||||
bold: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/gelasio/Gelasio%5Bwght%5D.ttf", variable: {wght: 700}}
|
||||
italic: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/gelasio/Gelasio-Italic%5Bwght%5D.ttf", variable: {wght: 400}}
|
||||
bolditalic: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/gelasio/Gelasio-Italic%5Bwght%5D.ttf", variable: {wght: 700}}
|
||||
|
||||
- name: GentiumBookPlus
|
||||
description: "Scholarly serif with wide Unicode coverage (Latin, Greek, Cyrillic, IPA)"
|
||||
intervals: latin-ext,greek,cyrillic
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/gentiumbookplus/GentiumBookPlus-Regular.ttf"}
|
||||
bold: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/gentiumbookplus/GentiumBookPlus-Bold.ttf"}
|
||||
italic: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/gentiumbookplus/GentiumBookPlus-Italic.ttf"}
|
||||
bolditalic: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/gentiumbookplus/GentiumBookPlus-BoldItalic.ttf"}
|
||||
|
||||
- name: IBMPlexSerif
|
||||
description: "Professional serif (Latin, Greek, Cyrillic)"
|
||||
intervals: latin-ext,greek,cyrillic
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/ibmplexserif/IBMPlexSerif-Regular.ttf"}
|
||||
bold: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/ibmplexserif/IBMPlexSerif-Bold.ttf"}
|
||||
italic: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/ibmplexserif/IBMPlexSerif-Italic.ttf"}
|
||||
bolditalic: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/ibmplexserif/IBMPlexSerif-BoldItalic.ttf"}
|
||||
|
||||
- name: Bitter
|
||||
description: "Slab serif designed for e-ink (Latin, Cyrillic)"
|
||||
intervals: latin-ext,cyrillic
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/bitter/Bitter%5Bwght%5D.ttf", variable: {wght: 400}}
|
||||
bold: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/bitter/Bitter%5Bwght%5D.ttf", variable: {wght: 700}}
|
||||
italic: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/bitter/Bitter-Italic%5Bwght%5D.ttf", variable: {wght: 400}}
|
||||
bolditalic: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/bitter/Bitter-Italic%5Bwght%5D.ttf", variable: {wght: 700}}
|
||||
|
||||
# ── Sans-serif ─────────────────────────────────────────────────────────
|
||||
|
||||
- name: NotoSansExtended
|
||||
description: "Sans-serif (Latin, Greek, Cyrillic, Georgian, Armenian, Ethiopic)"
|
||||
intervals: latin-ext,greek,cyrillic,georgian,armenian,ethiopic
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {path: "builtinFonts/source/NotoSans/NotoSans-Regular.ttf"}
|
||||
bold: {path: "builtinFonts/source/NotoSans/NotoSans-Bold.ttf"}
|
||||
italic: {path: "builtinFonts/source/NotoSans/NotoSans-Italic.ttf"}
|
||||
bolditalic: {path: "builtinFonts/source/NotoSans/NotoSans-BoldItalic.ttf"}
|
||||
|
||||
- name: Inter
|
||||
description: "Modern sans-serif (Latin, Greek, Cyrillic)"
|
||||
intervals: latin-ext,greek,cyrillic
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/inter/Inter%5Bopsz%2Cwght%5D.ttf", variable: {wght: 400, opsz: 14}}
|
||||
bold: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/inter/Inter%5Bopsz%2Cwght%5D.ttf", variable: {wght: 700, opsz: 14}}
|
||||
italic: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/inter/Inter-Italic%5Bopsz%2Cwght%5D.ttf", variable: {wght: 400, opsz: 14}}
|
||||
bolditalic: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/inter/Inter-Italic%5Bopsz%2Cwght%5D.ttf", variable: {wght: 700, opsz: 14}}
|
||||
|
||||
- name: SourceSans3
|
||||
description: "Adobe humanist sans-serif (Latin, Greek, Cyrillic)"
|
||||
intervals: latin-ext,greek,cyrillic
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/adobe-fonts/source-sans/release/TTF/SourceSans3-Regular.ttf"}
|
||||
bold: {url: "https://raw.githubusercontent.com/adobe-fonts/source-sans/release/TTF/SourceSans3-Bold.ttf"}
|
||||
italic: {url: "https://raw.githubusercontent.com/adobe-fonts/source-sans/release/TTF/SourceSans3-It.ttf"}
|
||||
bolditalic: {url: "https://raw.githubusercontent.com/adobe-fonts/source-sans/release/TTF/SourceSans3-BoldIt.ttf"}
|
||||
|
||||
- name: IBMPlexSans
|
||||
description: "IBM corporate sans-serif (Latin, Greek, Cyrillic)"
|
||||
intervals: latin-ext,greek,cyrillic
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/IBM/plex/master/packages/plex-sans/fonts/complete/ttf/IBMPlexSans-Regular.ttf"}
|
||||
bold: {url: "https://raw.githubusercontent.com/IBM/plex/master/packages/plex-sans/fonts/complete/ttf/IBMPlexSans-Bold.ttf"}
|
||||
italic: {url: "https://raw.githubusercontent.com/IBM/plex/master/packages/plex-sans/fonts/complete/ttf/IBMPlexSans-Italic.ttf"}
|
||||
bolditalic: {url: "https://raw.githubusercontent.com/IBM/plex/master/packages/plex-sans/fonts/complete/ttf/IBMPlexSans-BoldItalic.ttf"}
|
||||
|
||||
- name: Alegreya
|
||||
description: "Calligraphic serif/display (Latin, Greek, Cyrillic)"
|
||||
intervals: latin-ext,greek,cyrillic
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/alegreya/Alegreya%5Bwght%5D.ttf", variable: {wght: 400}}
|
||||
bold: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/alegreya/Alegreya%5Bwght%5D.ttf", variable: {wght: 700}}
|
||||
italic: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/alegreya/Alegreya-Italic%5Bwght%5D.ttf", variable: {wght: 400}}
|
||||
bolditalic: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/alegreya/Alegreya-Italic%5Bwght%5D.ttf", variable: {wght: 700}}
|
||||
|
||||
# ── Monospace ──────────────────────────────────────────────────────────
|
||||
|
||||
- name: IBMPlexMono
|
||||
description: "Monospace for code and technical reading (Latin, Greek, Cyrillic)"
|
||||
intervals: latin-ext,greek,cyrillic
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/ibmplexmono/IBMPlexMono-Regular.ttf"}
|
||||
bold: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/ibmplexmono/IBMPlexMono-Bold.ttf"}
|
||||
italic: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/ibmplexmono/IBMPlexMono-Italic.ttf"}
|
||||
bolditalic: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/ibmplexmono/IBMPlexMono-BoldItalic.ttf"}
|
||||
|
||||
- name: SourceCodePro
|
||||
description: "Adobe monospace with excellent hinting (Latin)"
|
||||
intervals: latin-ext
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/adobe-fonts/source-code-pro/release/TTF/SourceCodePro-Regular.ttf"}
|
||||
bold: {url: "https://raw.githubusercontent.com/adobe-fonts/source-code-pro/release/TTF/SourceCodePro-Bold.ttf"}
|
||||
italic: {url: "https://raw.githubusercontent.com/adobe-fonts/source-code-pro/release/TTF/SourceCodePro-It.ttf"}
|
||||
bolditalic: {url: "https://raw.githubusercontent.com/adobe-fonts/source-code-pro/release/TTF/SourceCodePro-BoldIt.ttf"}
|
||||
|
||||
# ── Accessibility ──────────────────────────────────────────────────────
|
||||
|
||||
- name: AtkinsonHyperlegibleNext
|
||||
description: "Accessibility font for low vision (Latin)"
|
||||
intervals: latin-ext
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/googlefonts/atkinson-hyperlegible-next/main/fonts/ttf/AtkinsonHyperlegibleNext-Regular.ttf"}
|
||||
bold: {url: "https://raw.githubusercontent.com/googlefonts/atkinson-hyperlegible-next/main/fonts/ttf/AtkinsonHyperlegibleNext-Bold.ttf"}
|
||||
italic: {url: "https://raw.githubusercontent.com/googlefonts/atkinson-hyperlegible-next/main/fonts/ttf/AtkinsonHyperlegibleNext-Italic.ttf"}
|
||||
bolditalic: {url: "https://raw.githubusercontent.com/googlefonts/atkinson-hyperlegible-next/main/fonts/ttf/AtkinsonHyperlegibleNext-BoldItalic.ttf"}
|
||||
|
||||
- name: LexicaUltralegible
|
||||
description: "Accessibility font for low vision / dyslexia (Latin)"
|
||||
intervals: latin-ext
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/jacobxperez/lexica-ultralegible/main/fonts/ttf/LexicaUltralegible-Regular.ttf"}
|
||||
bold: {url: "https://raw.githubusercontent.com/jacobxperez/lexica-ultralegible/main/fonts/ttf/LexicaUltralegible-Bold.ttf"}
|
||||
italic: {url: "https://raw.githubusercontent.com/jacobxperez/lexica-ultralegible/main/fonts/ttf/LexicaUltralegible-Italic.ttf"}
|
||||
bolditalic: {url: "https://raw.githubusercontent.com/jacobxperez/lexica-ultralegible/main/fonts/ttf/LexicaUltralegible-BoldItalic.ttf"}
|
||||
|
||||
# OpenDyslexic was previously baked into the firmware. It is now distributed
|
||||
# as an SD-card font so users who don't need it don't pay the ~3MB flash cost.
|
||||
# Sizes 8/10 retained for backward compatibility with the previous built-in mapping.
|
||||
- name: OpenDyslexic
|
||||
description: "Dyslexia-friendly font (Latin, Latin-Extended)"
|
||||
intervals: latin-ext
|
||||
sizes: [8, 10, 12, 14, 16]
|
||||
styles:
|
||||
regular: {path: "builtinFonts/source/OpenDyslexic/OpenDyslexic-Regular.otf"}
|
||||
bold: {path: "builtinFonts/source/OpenDyslexic/OpenDyslexic-Bold.otf"}
|
||||
italic: {path: "builtinFonts/source/OpenDyslexic/OpenDyslexic-Italic.otf"}
|
||||
bolditalic: {path: "builtinFonts/source/OpenDyslexic/OpenDyslexic-BoldItalic.otf"}
|
||||
|
||||
# ── CJK & Hangul ──────────────────────────────────────────────────────
|
||||
|
||||
- name: NotoSansCJK
|
||||
description: "Sans-serif (Chinese, Japanese, Korean)"
|
||||
intervals: ascii,latin1,punctuation,cjk
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://github.com/notofonts/noto-cjk/raw/main/Sans/OTF/SimplifiedChinese/NotoSansCJKsc-Regular.otf"}
|
||||
|
||||
- name: NotoSerifCJK
|
||||
description: "Serif (Chinese, Japanese, Korean)"
|
||||
intervals: ascii,latin1,punctuation,cjk
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://github.com/notofonts/noto-cjk/raw/main/Serif/OTF/SimplifiedChinese/NotoSerifCJKsc-Regular.otf"}
|
||||
|
||||
- name: NotoSansHangul
|
||||
description: "Sans-serif (Korean Hangul)"
|
||||
intervals: ascii,latin1,punctuation,hangul
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://github.com/notofonts/noto-cjk/raw/main/Sans/OTF/Korean/NotoSansCJKkr-Regular.otf"}
|
||||
|
||||
# ── Additional scripts (no complex shaping needed) ─────────────────────
|
||||
|
||||
- name: NotoSansArmenian
|
||||
description: "Armenian script"
|
||||
intervals: armenian
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/notosansarmenian/NotoSansArmenian%5Bwdth%2Cwght%5D.ttf", variable: {wght: 400, wdth: 100}}
|
||||
|
||||
- name: NotoSansGeorgian
|
||||
description: "Georgian script"
|
||||
intervals: georgian
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/notosansgeorgian/NotoSansGeorgian%5Bwdth%2Cwght%5D.ttf", variable: {wght: 400, wdth: 100}}
|
||||
|
||||
- name: NotoSansEthiopic
|
||||
description: "Ethiopic/Ge'ez script"
|
||||
intervals: ethiopic
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/notosansethiopic/NotoSansEthiopic%5Bwdth%2Cwght%5D.ttf", variable: {wght: 400, wdth: 100}}
|
||||
|
||||
- name: NotoSansCherokee
|
||||
description: "Cherokee syllabary"
|
||||
intervals: cherokee
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/notosanscherokee/NotoSansCherokee%5Bwght%5D.ttf", variable: {wght: 400}}
|
||||
|
||||
- name: NotoSansTifinagh
|
||||
description: "Tifinagh script"
|
||||
intervals: tifinagh
|
||||
sizes: [10, 12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/notosanstifinagh/NotoSansTifinagh-Regular.ttf"}
|
||||
@@ -225,6 +225,7 @@ def main():
|
||||
parser.add_argument(
|
||||
"--output-dir", default=str(DEFAULT_OUTPUT), help="Output directory for .cpfont files"
|
||||
)
|
||||
parser.add_argument("--skip", action="store_true", help="Skip font generation completely (for testing manifest generation)")
|
||||
parser.add_argument("--only", help="Comma-separated family names to build (default: all)")
|
||||
parser.add_argument("--manifest", action="store_true", help="Also generate fonts.json manifest")
|
||||
parser.add_argument("--base-url", default="", help="Base URL for manifest (required with --manifest)")
|
||||
@@ -250,6 +251,7 @@ def main():
|
||||
print(f"ERROR: Config not found: {config_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
output_base = Path(args.output_dir)
|
||||
with open(config_path) as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
@@ -258,73 +260,73 @@ def main():
|
||||
print("ERROR: No families defined in config", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Filter if --only specified
|
||||
if args.only:
|
||||
only_names = {token.strip() for token in args.only.split(",") if token.strip()}
|
||||
families = [f for f in families if f["name"] in only_names]
|
||||
missing = only_names - {f["name"] for f in families}
|
||||
if missing:
|
||||
print(f"WARNING: families not found in config: {', '.join(missing)}", file=sys.stderr)
|
||||
if not families:
|
||||
print("ERROR: no matching families after --only filter", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
failed = False
|
||||
if not args.skip:
|
||||
# Filter if --only specified
|
||||
if args.only:
|
||||
only_names = {token.strip() for token in args.only.split(",") if token.strip()}
|
||||
families = [f for f in families if f["name"] in only_names]
|
||||
missing = only_names - {f["name"] for f in families}
|
||||
if missing:
|
||||
print(f"WARNING: families not found in config: {', '.join(missing)}", file=sys.stderr)
|
||||
if not families:
|
||||
print("ERROR: no matching families after --only filter", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
output_base = Path(args.output_dir)
|
||||
if args.clean and output_base.exists():
|
||||
print(f"Cleaning {output_base}...")
|
||||
shutil.rmtree(output_base)
|
||||
|
||||
if args.clean and output_base.exists():
|
||||
print(f"Cleaning {output_base}...")
|
||||
shutil.rmtree(output_base)
|
||||
output_base.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
output_base.mkdir(parents=True, exist_ok=True)
|
||||
# Download phase (sequential — avoids hammering servers)
|
||||
print(f"\n=== Resolving {len(families)} font families ===\n")
|
||||
for family in families:
|
||||
for style_name, style_spec in family.get("styles", {}).items():
|
||||
if "url" in style_spec:
|
||||
try:
|
||||
resolve_font_path(style_spec, family["name"], style_name)
|
||||
except Exception as e:
|
||||
print(f"ERROR: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Download phase (sequential — avoids hammering servers)
|
||||
print(f"\n=== Resolving {len(families)} font families ===\n")
|
||||
for family in families:
|
||||
for style_name, style_spec in family.get("styles", {}).items():
|
||||
if "url" in style_spec:
|
||||
try:
|
||||
resolve_font_path(style_spec, family["name"], style_name)
|
||||
except Exception as e:
|
||||
print(f"ERROR: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
# Build phase (parallel)
|
||||
max_workers = args.jobs or len(families)
|
||||
print(f"\n=== Building {len(families)} families ({max_workers} parallel jobs) ===\n")
|
||||
|
||||
# Build phase (parallel)
|
||||
max_workers = args.jobs or len(families)
|
||||
print(f"\n=== Building {len(families)} families ({max_workers} parallel jobs) ===\n")
|
||||
|
||||
failed = []
|
||||
with ProcessPoolExecutor(max_workers=max_workers) as executor:
|
||||
futures = {
|
||||
executor.submit(build_family, family, output_base): family["name"]
|
||||
for family in families
|
||||
}
|
||||
for future in as_completed(futures):
|
||||
name = futures[future]
|
||||
success = False
|
||||
message = ""
|
||||
try:
|
||||
name, success, message = future.result()
|
||||
except Exception as e:
|
||||
failed = []
|
||||
with ProcessPoolExecutor(max_workers=max_workers) as executor:
|
||||
futures = {
|
||||
executor.submit(build_family, family, output_base): family["name"]
|
||||
for family in families
|
||||
}
|
||||
for future in as_completed(futures):
|
||||
name = futures[future]
|
||||
success = False
|
||||
message = str(e)
|
||||
if success:
|
||||
# Count output files
|
||||
family_dir = output_base / name
|
||||
count = len(list(family_dir.glob("*.cpfont")))
|
||||
size = sum(f.stat().st_size for f in family_dir.glob("*.cpfont"))
|
||||
print(f" OK: {name} ({count} files, {size / 1024 / 1024:.1f} MB)")
|
||||
else:
|
||||
print(f" FAILED: {name}: {message}", file=sys.stderr)
|
||||
failed.append(name)
|
||||
message = ""
|
||||
try:
|
||||
name, success, message = future.result()
|
||||
except Exception as e:
|
||||
success = False
|
||||
message = str(e)
|
||||
if success:
|
||||
# Count output files
|
||||
family_dir = output_base / name
|
||||
count = len(list(family_dir.glob("*.cpfont")))
|
||||
size = sum(f.stat().st_size for f in family_dir.glob("*.cpfont"))
|
||||
print(f" OK: {name} ({count} files, {size / 1024 / 1024:.1f} MB)")
|
||||
else:
|
||||
print(f" FAILED: {name}: {message}", file=sys.stderr)
|
||||
failed.append(name)
|
||||
|
||||
# Summary
|
||||
print("\n=== Summary ===\n")
|
||||
total_files = len(list(output_base.rglob("*.cpfont")))
|
||||
total_size = sum(f.stat().st_size for f in output_base.rglob("*.cpfont"))
|
||||
print(f"Total: {total_files} .cpfont files ({total_size / 1024 / 1024:.1f} MB)")
|
||||
# Summary
|
||||
print("\n=== Summary ===\n")
|
||||
total_files = len(list(output_base.rglob("*.cpfont")))
|
||||
total_size = sum(f.stat().st_size for f in output_base.rglob("*.cpfont"))
|
||||
print(f"Total: {total_files} .cpfont files ({total_size / 1024 / 1024:.1f} MB)")
|
||||
|
||||
if failed:
|
||||
print(f"\nFailed families: {', '.join(failed)}", file=sys.stderr)
|
||||
if failed:
|
||||
print(f"\nFailed families: {', '.join(failed)}", file=sys.stderr)
|
||||
|
||||
# Manifest
|
||||
if args.manifest:
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ const char* I18n::get(StrId id) const {
|
||||
|
||||
// Use generated helper function - no hardcoded switch needed!
|
||||
const LangStrings lang = getLanguageStrings(_language);
|
||||
// If bit 15 of the offset is set, apply the offset to the English lookup table
|
||||
// If bit 15 of the offset is set, apply the offset to the English lookup table
|
||||
const uint16_t off = lang.offsets[index];
|
||||
if (off & 0x8000) return STRINGS_EN_DATA + (off & 0x7FFF);
|
||||
return lang.data + off;
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
"""Generate a fonts.json manifest from a directory of .cpfont files.
|
||||
|
||||
Scans the input directory (flat or nested by family) for .cpfont files, reads
|
||||
their binary headers to extract style metadata, and produces a JSON manifest
|
||||
suitable for device-initiated font downloads.
|
||||
|
||||
Usage:
|
||||
python3 scripts/generate-font-manifest.py \
|
||||
--input lib/EpdFont/scripts/output \
|
||||
--base-url "https://example.com/fonts/" \
|
||||
--output dist/fonts.json
|
||||
|
||||
The input directory may be flat (all .cpfont files in one dir) or nested
|
||||
(family subdirectories). Family names are derived from filenames using the
|
||||
convention <FamilyName>_<size>.cpfont.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# --- .cpfont binary format constants ---
|
||||
# Global header: 8s magic, H version, H flags, B styleCount, 19x reserved
|
||||
GLOBAL_HEADER_FORMAT = "<8sHHB19x"
|
||||
GLOBAL_HEADER_SIZE = struct.calcsize(GLOBAL_HEADER_FORMAT) # 32 bytes
|
||||
|
||||
# Style TOC entry: B styleId, 3x pad, I intervalCount, I glyphCount, ...
|
||||
# We only need the first byte (styleId) from each 32-byte entry.
|
||||
STYLE_TOC_ENTRY_SIZE = 32
|
||||
STYLE_TOC_ENTRY_FORMAT = "<B31x"
|
||||
|
||||
CPFONT_MAGIC = b"CPFONT\x00\x00"
|
||||
CPFONT_VERSION = 4
|
||||
|
||||
STYLE_NAMES = {0: "regular", 1: "bold", 2: "italic", 3: "bolditalic"}
|
||||
|
||||
# Family descriptions can be loaded from the sd-fonts.yaml config
|
||||
# (via --descriptions-from) or fall back to the family name.
|
||||
FAMILY_DESCRIPTIONS: dict[str, str] = {}
|
||||
|
||||
|
||||
def load_descriptions_from_yaml(yaml_path: Path) -> dict[str, str]:
|
||||
"""Load family descriptions from sd-fonts.yaml config."""
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
print("WARNING: pyyaml not installed, cannot load descriptions from YAML", file=sys.stderr)
|
||||
return {}
|
||||
|
||||
with open(yaml_path) as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
return {f["name"]: f["description"] for f in config.get("families", []) if "description" in f}
|
||||
|
||||
|
||||
def read_cpfont_styles(filepath: Path) -> list[str]:
|
||||
"""Read style names from a .cpfont file's binary header."""
|
||||
with open(filepath, "rb") as f:
|
||||
header_data = f.read(GLOBAL_HEADER_SIZE)
|
||||
if len(header_data) < GLOBAL_HEADER_SIZE:
|
||||
print(f" WARNING: {filepath.name} too small, skipping", file=sys.stderr)
|
||||
return []
|
||||
|
||||
magic, version, _flags, style_count = struct.unpack(
|
||||
GLOBAL_HEADER_FORMAT, header_data
|
||||
)
|
||||
|
||||
if magic != CPFONT_MAGIC:
|
||||
print(
|
||||
f" WARNING: {filepath.name} bad magic {magic!r}, skipping",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return []
|
||||
|
||||
if version != CPFONT_VERSION:
|
||||
print(
|
||||
f" WARNING: {filepath.name} version {version} != {CPFONT_VERSION}, skipping",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return []
|
||||
|
||||
styles = []
|
||||
for _ in range(style_count):
|
||||
toc_data = f.read(STYLE_TOC_ENTRY_SIZE)
|
||||
if len(toc_data) < STYLE_TOC_ENTRY_SIZE:
|
||||
break
|
||||
(style_id,) = struct.unpack(STYLE_TOC_ENTRY_FORMAT, toc_data)
|
||||
name = STYLE_NAMES.get(style_id, f"unknown({style_id})")
|
||||
styles.append(name)
|
||||
|
||||
return styles
|
||||
|
||||
|
||||
def parse_filename(filename: str) -> tuple[str, str] | None:
|
||||
"""Parse '<FamilyName>_<size>.cpfont' into (family, size_str).
|
||||
|
||||
Returns None if the filename doesn't match the expected pattern.
|
||||
"""
|
||||
if not filename.endswith(".cpfont"):
|
||||
return None
|
||||
stem = filename[: -len(".cpfont")]
|
||||
parts = stem.rsplit("_", 1)
|
||||
if len(parts) != 2:
|
||||
return None
|
||||
family, size_str = parts
|
||||
if not size_str.isdigit():
|
||||
return None
|
||||
return family, size_str
|
||||
|
||||
|
||||
def scan_cpfont_files(input_dir: Path) -> dict[str, list[Path]]:
|
||||
"""Scan input directory for .cpfont files, grouped by family name.
|
||||
|
||||
Handles both flat and nested directory layouts.
|
||||
"""
|
||||
families: dict[str, list[Path]] = {}
|
||||
|
||||
for path in sorted(input_dir.rglob("*.cpfont")):
|
||||
if not path.is_file():
|
||||
continue
|
||||
parsed = parse_filename(path.name)
|
||||
if parsed is None:
|
||||
print(f" WARNING: skipping {path.name} (unexpected filename format)", file=sys.stderr)
|
||||
continue
|
||||
family_name = parsed[0]
|
||||
families.setdefault(family_name, []).append(path)
|
||||
|
||||
return families
|
||||
|
||||
|
||||
def build_manifest(
|
||||
families: dict[str, list[Path]], base_url: str
|
||||
) -> dict:
|
||||
"""Build the manifest dict from discovered font families."""
|
||||
manifest_families = []
|
||||
|
||||
for family_name in sorted(families.keys()):
|
||||
files = families[family_name]
|
||||
|
||||
# Read styles from the first file (all files in a family have the
|
||||
# same styles since they're generated from the same source fonts).
|
||||
styles = read_cpfont_styles(files[0]) if files else []
|
||||
|
||||
# Get description
|
||||
description = FAMILY_DESCRIPTIONS.get(family_name)
|
||||
if description is None:
|
||||
print(
|
||||
f" WARNING: no description for family '{family_name}', "
|
||||
f"consider adding one to FAMILY_DESCRIPTIONS in {__file__}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
description = family_name
|
||||
|
||||
file_entries = []
|
||||
for filepath in sorted(files, key=lambda p: p.name):
|
||||
file_entries.append(
|
||||
{
|
||||
"name": filepath.name,
|
||||
"size": filepath.stat().st_size,
|
||||
}
|
||||
)
|
||||
|
||||
manifest_families.append(
|
||||
{
|
||||
"name": family_name,
|
||||
"description": description,
|
||||
"styles": styles,
|
||||
"files": file_entries,
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"version": 1,
|
||||
"baseUrl": base_url,
|
||||
"families": manifest_families,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate fonts.json manifest from .cpfont files"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--input",
|
||||
required=True,
|
||||
help="Directory containing .cpfont files (flat or nested by family)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--base-url",
|
||||
required=True,
|
||||
help="URL prefix for font downloads (device concatenates baseUrl + filename)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
required=True,
|
||||
help="Output path for fonts.json",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--descriptions-from",
|
||||
default=None,
|
||||
help="Path to sd-fonts.yaml to load family descriptions (default: use family name)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
input_dir = Path(args.input)
|
||||
if not input_dir.is_dir():
|
||||
print(f"ERROR: {input_dir} is not a directory", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Ensure base URL ends with /
|
||||
base_url = args.base_url
|
||||
if not base_url.endswith("/"):
|
||||
base_url += "/"
|
||||
|
||||
# Load descriptions from YAML config if provided
|
||||
global FAMILY_DESCRIPTIONS
|
||||
if args.descriptions_from:
|
||||
desc_path = Path(args.descriptions_from)
|
||||
if desc_path.exists():
|
||||
FAMILY_DESCRIPTIONS = load_descriptions_from_yaml(desc_path)
|
||||
print(f"Loaded {len(FAMILY_DESCRIPTIONS)} descriptions from {desc_path}")
|
||||
else:
|
||||
print(f"WARNING: {desc_path} not found, using family names as descriptions", file=sys.stderr)
|
||||
|
||||
print(f"Scanning {input_dir} for .cpfont files...")
|
||||
families = scan_cpfont_files(input_dir)
|
||||
|
||||
if not families:
|
||||
print("ERROR: no .cpfont files found", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Found {len(families)} font families:")
|
||||
for name, files in sorted(families.items()):
|
||||
print(f" {name}: {len(files)} files")
|
||||
|
||||
manifest = build_manifest(families, base_url)
|
||||
|
||||
output_path = Path(args.output)
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(output_path, "w") as f:
|
||||
json.dump(manifest, f, indent=2)
|
||||
f.write("\n")
|
||||
|
||||
print(f"Wrote {output_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -8,7 +8,8 @@
|
||||
#include "util/ButtonNavigator.h"
|
||||
|
||||
#ifndef FONT_MANIFEST_URL
|
||||
#define FONT_MANIFEST_URL "https://github.com/jpirnay/crosspoint-reader/blob/master/assets/sd-fonts/sd-fonts.yaml"
|
||||
#define FONT_MANIFEST_URL \
|
||||
"https://raw.githubusercontent.com/jpirnay/crosspoint-reader/master/assets/sd-fonts/fonts.json"
|
||||
#endif
|
||||
|
||||
class FontDownloadActivity : public Activity {
|
||||
|
||||
@@ -37,7 +37,8 @@ constexpr uint16_t UDP_PORTS[] = {54982, 48123, 39001, 44044, 59678};
|
||||
constexpr uint16_t LOCAL_UDP_PORT = 8134;
|
||||
|
||||
#ifndef FONT_MANIFEST_URL
|
||||
#define FONT_MANIFEST_URL "https://github.com/jpirnay/crosspoint-reader/blob/master/assets/sd-fonts/sd-fonts.yaml"
|
||||
#define FONT_MANIFEST_URL \
|
||||
"https://raw.githubusercontent.com/jpirnay/crosspoint-reader/master/assets/sd-fonts/fonts.json"
|
||||
#endif
|
||||
|
||||
// Static pointer for WebSocket callback (WebSocketsServer requires C-style callback)
|
||||
|
||||
Reference in New Issue
Block a user