Fix font-location

This commit is contained in:
jpirnay
2026-05-03 22:42:54 +02:00
parent 8526e58ac1
commit 5617db3ad2
7 changed files with 957 additions and 355 deletions
+638
View File
@@ -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
}
]
}
]
}
-293
View File
@@ -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"}
+61 -59
View File
@@ -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
View File
@@ -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;
+253
View File
@@ -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 {
+2 -1
View File
@@ -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)