Merge pull request #164 from jpirnay/feat-sdfonts-ui
feat: Add download feature to fonts page
This commit is contained in:
@@ -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"}
|
||||
@@ -0,0 +1,293 @@
|
||||
# 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"}
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "util/ButtonNavigator.h"
|
||||
|
||||
#ifndef FONT_MANIFEST_URL
|
||||
#define FONT_MANIFEST_URL "https://github.com/jpirnay/crosspoint-reader/assets/sd-fonts/fonts.json"
|
||||
#define FONT_MANIFEST_URL "https://github.com/jpirnay/crosspoint-reader/blob/master/assets/sd-fonts/sd-fonts.yaml"
|
||||
#endif
|
||||
|
||||
class FontDownloadActivity : public Activity {
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "html/SettingsPageHtml.generated.h"
|
||||
#include "html/WelcomePageHtml.generated.h"
|
||||
#include "html/js/jszip_minJs.generated.h"
|
||||
#include "network/HttpDownloader.h"
|
||||
|
||||
namespace {
|
||||
// Folders/files to hide from the web interface file browser
|
||||
@@ -35,6 +36,10 @@ constexpr size_t HIDDEN_ITEMS_COUNT = sizeof(HIDDEN_ITEMS) / sizeof(HIDDEN_ITEMS
|
||||
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"
|
||||
#endif
|
||||
|
||||
// Static pointer for WebSocket callback (WebSocketsServer requires C-style callback)
|
||||
CrossPointWebServer* wsInstance = nullptr;
|
||||
|
||||
@@ -203,6 +208,8 @@ void CrossPointWebServer::begin() {
|
||||
// Font management endpoints
|
||||
server->on("/fonts", HTTP_GET, [this] { handleFontsPage(); });
|
||||
server->on("/api/fonts", HTTP_GET, [this] { handleFontList(); });
|
||||
server->on("/api/fonts/manifest", HTTP_GET, [this] { handleFontManifest(); });
|
||||
server->on("/api/fonts/download", HTTP_POST, [this] { handleFontDownload(); });
|
||||
server->on("/api/fonts/upload", HTTP_POST, [this] { handleFontUpload(); }, [this] { handleFontUploadData(); });
|
||||
server->on("/api/fonts/delete", HTTP_POST, [this] { handleFontDelete(); });
|
||||
|
||||
@@ -1411,6 +1418,220 @@ void CrossPointWebServer::handlePostSettings() {
|
||||
|
||||
// ---- Font Management API ----
|
||||
|
||||
namespace {
|
||||
struct RemoteManifestFile {
|
||||
std::string name;
|
||||
size_t size = 0;
|
||||
};
|
||||
|
||||
struct RemoteManifestFamily {
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::vector<RemoteManifestFile> files;
|
||||
size_t totalSize = 0;
|
||||
bool installed = false;
|
||||
bool hasUpdate = false;
|
||||
};
|
||||
|
||||
// Cap chosen so that FONTS_DIR + "/" + family + "__staging/" + filename stays
|
||||
// well under the 128-byte path buffers used below. snprintf truncation checks
|
||||
// are the actual guard; this just rejects obviously-bogus entries early.
|
||||
static constexpr size_t MAX_FONT_FILE_NAME_LEN = 60;
|
||||
|
||||
bool isValidFontFileName(const std::string& name) {
|
||||
if (name.empty() || name.size() > MAX_FONT_FILE_NAME_LEN) return false;
|
||||
if (name.find('/') != std::string::npos) return false;
|
||||
if (name.find('\\') != std::string::npos) return false;
|
||||
if (name.find("..") != std::string::npos) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool fetchRemoteFontManifest(FontInstaller& installer, std::vector<RemoteManifestFamily>& outFamilies,
|
||||
std::string& outBaseUrl, std::string& outError) {
|
||||
static constexpr const char* MANIFEST_TMP = "/fonts_manifest_web.tmp";
|
||||
|
||||
auto result = HttpDownloader::downloadToFile(FONT_MANIFEST_URL, MANIFEST_TMP, nullptr);
|
||||
if (result != HttpDownloader::OK) {
|
||||
outError = "Failed to fetch font manifest";
|
||||
Storage.remove(MANIFEST_TMP);
|
||||
return false;
|
||||
}
|
||||
|
||||
FsFile manifestFile;
|
||||
if (!Storage.openFileForRead("WEB", MANIFEST_TMP, manifestFile)) {
|
||||
outError = "Failed to open downloaded manifest";
|
||||
Storage.remove(MANIFEST_TMP);
|
||||
return false;
|
||||
}
|
||||
|
||||
JsonDocument doc;
|
||||
DeserializationError err = deserializeJson(doc, manifestFile);
|
||||
manifestFile.close();
|
||||
Storage.remove(MANIFEST_TMP);
|
||||
if (err) {
|
||||
outError = "Failed to parse font manifest";
|
||||
return false;
|
||||
}
|
||||
|
||||
const int version = doc["version"] | 0;
|
||||
if (version != 1) {
|
||||
outError = "Unsupported manifest version";
|
||||
return false;
|
||||
}
|
||||
|
||||
outBaseUrl = doc["baseUrl"] | "";
|
||||
outFamilies.clear();
|
||||
|
||||
JsonArray familiesArr = doc["families"].as<JsonArray>();
|
||||
outFamilies.reserve(familiesArr.size());
|
||||
|
||||
for (JsonObject fObj : familiesArr) {
|
||||
RemoteManifestFamily family;
|
||||
family.name = fObj["name"] | "";
|
||||
family.description = fObj["description"] | "";
|
||||
|
||||
if (!FontInstaller::isValidFamilyName(family.name.c_str())) {
|
||||
LOG_ERR("WEB", "Manifest entry rejected, invalid family name: %s", family.name.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
bool fileNamesOk = true;
|
||||
for (JsonObject fileObj : fObj["files"].as<JsonArray>()) {
|
||||
RemoteManifestFile file;
|
||||
file.name = fileObj["name"] | "";
|
||||
file.size = static_cast<size_t>(fileObj["size"] | 0);
|
||||
if (!isValidFontFileName(file.name)) {
|
||||
LOG_ERR("WEB", "Manifest entry rejected, invalid file name in %s: %s", family.name.c_str(), file.name.c_str());
|
||||
fileNamesOk = false;
|
||||
break;
|
||||
}
|
||||
family.totalSize += file.size;
|
||||
family.files.push_back(std::move(file));
|
||||
}
|
||||
if (!fileNamesOk) continue;
|
||||
|
||||
family.installed = installer.isFamilyInstalled(family.name.c_str());
|
||||
family.hasUpdate = false;
|
||||
if (family.installed) {
|
||||
for (const auto& file : family.files) {
|
||||
char path[128];
|
||||
FontInstaller::buildFontPath(family.name.c_str(), file.name.c_str(), path, sizeof(path));
|
||||
FsFile f;
|
||||
if (Storage.openFileForRead("WEB", path, f)) {
|
||||
const size_t actual = static_cast<size_t>(f.size());
|
||||
f.close();
|
||||
if (actual != file.size) {
|
||||
family.hasUpdate = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
family.hasUpdate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outFamilies.push_back(std::move(family));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool installRemoteFamily(const RemoteManifestFamily& family, const std::string& baseUrl, FontInstaller& installer,
|
||||
std::string& outError) {
|
||||
if (!FontInstaller::isValidFamilyName(family.name.c_str())) {
|
||||
outError = "Invalid family name";
|
||||
return false;
|
||||
}
|
||||
for (const auto& file : family.files) {
|
||||
if (!isValidFontFileName(file.name)) {
|
||||
outError = std::string("Invalid file name: ") + file.name;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
char liveDir[128];
|
||||
char stagingDir[128];
|
||||
char backupDir[128];
|
||||
int n;
|
||||
n = snprintf(liveDir, sizeof(liveDir), "%s/%s", SdCardFontRegistry::FONTS_DIR, family.name.c_str());
|
||||
if (n < 0 || static_cast<size_t>(n) >= sizeof(liveDir)) {
|
||||
outError = "Family path too long";
|
||||
return false;
|
||||
}
|
||||
n = snprintf(stagingDir, sizeof(stagingDir), "%s/%s__staging", SdCardFontRegistry::FONTS_DIR, family.name.c_str());
|
||||
if (n < 0 || static_cast<size_t>(n) >= sizeof(stagingDir)) {
|
||||
outError = "Family path too long";
|
||||
return false;
|
||||
}
|
||||
n = snprintf(backupDir, sizeof(backupDir), "%s/%s__backup", SdCardFontRegistry::FONTS_DIR, family.name.c_str());
|
||||
if (n < 0 || static_cast<size_t>(n) >= sizeof(backupDir)) {
|
||||
outError = "Family path too long";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Storage.exists(stagingDir) && !Storage.removeDir(stagingDir)) {
|
||||
outError = "Failed to prepare staging area";
|
||||
return false;
|
||||
}
|
||||
if (!Storage.mkdir(stagingDir)) {
|
||||
outError = "Failed to create staging area";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& file : family.files) {
|
||||
esp_task_wdt_reset();
|
||||
|
||||
char stagedPath[128];
|
||||
int sn = snprintf(stagedPath, sizeof(stagedPath), "%s/%s", stagingDir, file.name.c_str());
|
||||
if (sn < 0 || static_cast<size_t>(sn) >= sizeof(stagedPath)) {
|
||||
Storage.removeDir(stagingDir);
|
||||
outError = std::string("File path too long: ") + file.name;
|
||||
return false;
|
||||
}
|
||||
const std::string url = baseUrl + file.name;
|
||||
|
||||
auto result = HttpDownloader::downloadToFile(url, stagedPath, nullptr);
|
||||
if (result != HttpDownloader::OK) {
|
||||
Storage.removeDir(stagingDir);
|
||||
outError = std::string("Download failed: ") + file.name;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!installer.validateCpfontFile(stagedPath)) {
|
||||
Storage.removeDir(stagingDir);
|
||||
outError = std::string("Invalid font file: ") + file.name;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const bool hadLiveDir = Storage.exists(liveDir);
|
||||
if (Storage.exists(backupDir) && !Storage.removeDir(backupDir)) {
|
||||
Storage.removeDir(stagingDir);
|
||||
outError = "Failed to prepare backup area";
|
||||
return false;
|
||||
}
|
||||
if (hadLiveDir && !Storage.rename(liveDir, backupDir)) {
|
||||
Storage.removeDir(stagingDir);
|
||||
outError = "Failed to replace installed font";
|
||||
return false;
|
||||
}
|
||||
if (!Storage.rename(stagingDir, liveDir)) {
|
||||
if (hadLiveDir && Storage.exists(backupDir)) {
|
||||
Storage.rename(backupDir, liveDir);
|
||||
}
|
||||
Storage.removeDir(stagingDir);
|
||||
outError = "Failed to finalize font install";
|
||||
return false;
|
||||
}
|
||||
if (Storage.exists(backupDir)) {
|
||||
Storage.removeDir(backupDir);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void CrossPointWebServer::handleFontsPage() const {
|
||||
sendHtmlContent(server.get(), FontsPageHtml, sizeof(FontsPageHtml));
|
||||
LOG_DBG("WEB", "Served fonts page");
|
||||
@@ -1455,6 +1676,118 @@ void CrossPointWebServer::handleFontList() {
|
||||
server->send(200, "application/json", json);
|
||||
}
|
||||
|
||||
void CrossPointWebServer::handleFontManifest() {
|
||||
FontInstaller installer(sdFontSystem.registry());
|
||||
installer.refreshRegistry();
|
||||
|
||||
std::vector<RemoteManifestFamily> families;
|
||||
std::string baseUrl;
|
||||
std::string error;
|
||||
if (!fetchRemoteFontManifest(installer, families, baseUrl, error)) {
|
||||
JsonDocument errDoc;
|
||||
errDoc["ok"] = false;
|
||||
errDoc["error"] = error;
|
||||
String out;
|
||||
serializeJson(errDoc, out);
|
||||
server->send(500, "application/json", out);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonDocument doc;
|
||||
doc["ok"] = true;
|
||||
doc["baseUrl"] = baseUrl;
|
||||
JsonArray arr = doc["families"].to<JsonArray>();
|
||||
for (const auto& family : families) {
|
||||
JsonObject obj = arr.add<JsonObject>();
|
||||
obj["name"] = family.name;
|
||||
obj["description"] = family.description;
|
||||
obj["installed"] = family.installed;
|
||||
obj["hasUpdate"] = family.hasUpdate;
|
||||
obj["totalSize"] = static_cast<unsigned long>(family.totalSize);
|
||||
obj["fileCount"] = static_cast<unsigned>(family.files.size());
|
||||
}
|
||||
String out;
|
||||
serializeJson(doc, out);
|
||||
server->send(200, "application/json", out);
|
||||
}
|
||||
|
||||
void CrossPointWebServer::handleFontDownload() {
|
||||
String body = server->arg("plain");
|
||||
JsonDocument req;
|
||||
if (deserializeJson(req, body)) {
|
||||
server->send(400, "application/json", "{\"ok\":false,\"error\":\"Invalid request\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
const bool installAll = req["all"] | false;
|
||||
const std::string requestedFamily = req["family"] | "";
|
||||
if (!installAll && requestedFamily.empty()) {
|
||||
server->send(400, "application/json", "{\"ok\":false,\"error\":\"Missing family\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
FontInstaller installer(sdFontSystem.registry());
|
||||
installer.refreshRegistry();
|
||||
|
||||
std::vector<RemoteManifestFamily> families;
|
||||
std::string baseUrl;
|
||||
std::string error;
|
||||
if (!fetchRemoteFontManifest(installer, families, baseUrl, error)) {
|
||||
JsonDocument errDoc;
|
||||
errDoc["ok"] = false;
|
||||
errDoc["error"] = error;
|
||||
String out;
|
||||
serializeJson(errDoc, out);
|
||||
server->send(500, "application/json", out);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<RemoteManifestFamily*> targets;
|
||||
if (installAll) {
|
||||
for (auto& family : families) {
|
||||
if (!family.installed || family.hasUpdate) {
|
||||
targets.push_back(&family);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (auto& family : families) {
|
||||
if (family.name == requestedFamily) {
|
||||
targets.push_back(&family);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (targets.empty()) {
|
||||
server->send(404, "application/json", "{\"ok\":false,\"error\":\"Family not found in manifest\"}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
size_t installedCount = 0;
|
||||
for (auto* family : targets) {
|
||||
esp_task_wdt_reset();
|
||||
if (!installRemoteFamily(*family, baseUrl, installer, error)) {
|
||||
JsonDocument errDoc;
|
||||
errDoc["ok"] = false;
|
||||
errDoc["error"] = error;
|
||||
errDoc["family"] = family->name;
|
||||
errDoc["installedCount"] = static_cast<unsigned>(installedCount);
|
||||
String out;
|
||||
serializeJson(errDoc, out);
|
||||
server->send(500, "application/json", out);
|
||||
return;
|
||||
}
|
||||
installedCount++;
|
||||
}
|
||||
|
||||
installer.refreshRegistry();
|
||||
JsonDocument res;
|
||||
res["ok"] = true;
|
||||
res["installedCount"] = static_cast<unsigned>(installedCount);
|
||||
String out;
|
||||
serializeJson(res, out);
|
||||
server->send(200, "application/json", out);
|
||||
}
|
||||
|
||||
void CrossPointWebServer::handleFontUploadData() {
|
||||
HTTPUpload& up = server->upload();
|
||||
|
||||
|
||||
@@ -113,6 +113,8 @@ class CrossPointWebServer {
|
||||
// Font management handlers
|
||||
void handleFontsPage() const;
|
||||
void handleFontList();
|
||||
void handleFontManifest();
|
||||
void handleFontDownload();
|
||||
void handleFontUpload();
|
||||
void handleFontUploadData();
|
||||
void handleFontDelete();
|
||||
|
||||
+145
-25
@@ -104,6 +104,10 @@
|
||||
color: white;
|
||||
}
|
||||
.btn-primary:hover { background: var(--accent-hover-color); }
|
||||
.btn[disabled] {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
.upload-form {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
@@ -137,6 +141,13 @@
|
||||
font-size: 0.95em;
|
||||
}
|
||||
.notice a { color: inherit; font-weight: 600; }
|
||||
.toolbar {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -154,6 +165,16 @@
|
||||
<div id="families"><p class="empty">Loading...</p></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Download Fonts</h2>
|
||||
<div class="toolbar">
|
||||
<button id="refreshManifest" class="btn btn-primary" type="button">Refresh Catalog</button>
|
||||
<button id="downloadAllRemote" class="btn btn-primary" type="button">Download All Updates</button>
|
||||
</div>
|
||||
<div id="remoteFamilies"><p class="empty">Catalog not loaded</p></div>
|
||||
<div id="remoteStatus"></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Upload Font</h2>
|
||||
<p class="notice">
|
||||
@@ -176,6 +197,13 @@
|
||||
return bytes + ' B';
|
||||
}
|
||||
|
||||
function setStatus(id, text, ok) {
|
||||
const el = document.getElementById(id);
|
||||
el.className = ok === undefined ? '' : (ok ? 'status-ok' : 'status-err');
|
||||
el.style.display = 'block';
|
||||
el.textContent = text;
|
||||
}
|
||||
|
||||
async function loadFonts() {
|
||||
try {
|
||||
const res = await fetch('/api/fonts');
|
||||
@@ -230,10 +258,7 @@
|
||||
|
||||
async function deleteFamily(name) {
|
||||
if (!confirm('Delete font family "' + name + '"?')) return;
|
||||
const status = document.getElementById('status');
|
||||
status.className = '';
|
||||
status.style.display = 'block';
|
||||
status.textContent = 'Deleting ' + name + '...';
|
||||
setStatus('status', 'Deleting ' + name + '...');
|
||||
try {
|
||||
const res = await fetch('/api/fonts/delete', {
|
||||
method: 'POST',
|
||||
@@ -241,17 +266,115 @@
|
||||
body: JSON.stringify({family: name})
|
||||
});
|
||||
if (res.ok) {
|
||||
status.className = 'status-ok';
|
||||
status.textContent = 'Deleted "' + name + '".';
|
||||
setStatus('status', 'Deleted "' + name + '".', true);
|
||||
} else {
|
||||
status.className = 'status-err';
|
||||
status.textContent = 'Failed to delete "' + name + '".';
|
||||
setStatus('status', 'Failed to delete "' + name + '".', false);
|
||||
}
|
||||
} catch (err) {
|
||||
status.className = 'status-err';
|
||||
status.textContent = 'Delete error: ' + err.message;
|
||||
setStatus('status', 'Delete error: ' + err.message, false);
|
||||
}
|
||||
await loadFonts();
|
||||
await loadRemoteManifest();
|
||||
}
|
||||
|
||||
async function loadRemoteManifest() {
|
||||
const el = document.getElementById('remoteFamilies');
|
||||
el.innerHTML = '<p class="empty">Loading catalog...</p>';
|
||||
try {
|
||||
const res = await fetch('/api/fonts/manifest');
|
||||
const data = await res.json();
|
||||
if (!res.ok || !data.ok) {
|
||||
throw new Error(data.error || 'catalog unavailable');
|
||||
}
|
||||
|
||||
el.innerHTML = '';
|
||||
if (!Array.isArray(data.families) || data.families.length === 0) {
|
||||
el.innerHTML = '<p class="empty">No remote fonts found</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
data.families.forEach(f => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'family';
|
||||
|
||||
const info = document.createElement('div');
|
||||
info.className = 'family-info';
|
||||
|
||||
const title = document.createElement('h3');
|
||||
title.textContent = f.name;
|
||||
|
||||
const meta = document.createElement('span');
|
||||
meta.className = 'family-meta';
|
||||
const state = f.installed ? (f.hasUpdate ? 'Installed, update available' : 'Installed, up to date') : 'Not installed';
|
||||
const details = state + ' - ' + (f.fileCount || 0) + ' file(s), ' + formatSize(f.totalSize || 0);
|
||||
meta.textContent = f.description ? (f.description + ' - ' + details) : details;
|
||||
|
||||
info.appendChild(title);
|
||||
info.appendChild(meta);
|
||||
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'btn btn-primary';
|
||||
btn.dataset.family = f.name;
|
||||
btn.textContent = f.installed ? (f.hasUpdate ? 'Update' : 'Reinstall') : 'Download';
|
||||
|
||||
row.appendChild(info);
|
||||
row.appendChild(btn);
|
||||
el.appendChild(row);
|
||||
});
|
||||
|
||||
el.querySelectorAll('button[data-family]').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
btn.disabled = true;
|
||||
await downloadRemoteFamily(btn.dataset.family);
|
||||
btn.disabled = false;
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
el.innerHTML = '<p class="empty">Failed to load catalog: ' + err.message + '</p>';
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadRemoteFamily(family) {
|
||||
setStatus('remoteStatus', 'Downloading "' + family + '"...');
|
||||
try {
|
||||
const res = await fetch('/api/fonts/download', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({family})
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok || !data.ok) {
|
||||
throw new Error(data.error || 'download failed');
|
||||
}
|
||||
setStatus('remoteStatus', 'Installed "' + family + '".', true);
|
||||
await loadFonts();
|
||||
await loadRemoteManifest();
|
||||
} catch (err) {
|
||||
setStatus('remoteStatus', 'Install failed for "' + family + '": ' + err.message, false);
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadAllRemoteUpdates() {
|
||||
setStatus('remoteStatus', 'Downloading all available updates...');
|
||||
const btn = document.getElementById('downloadAllRemote');
|
||||
btn.disabled = true;
|
||||
try {
|
||||
const res = await fetch('/api/fonts/download', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({all: true})
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok || !data.ok) {
|
||||
throw new Error(data.error || 'download failed');
|
||||
}
|
||||
setStatus('remoteStatus', 'Installed/updated ' + (data.installedCount || 0) + ' family(s).', true);
|
||||
await loadFonts();
|
||||
await loadRemoteManifest();
|
||||
} catch (err) {
|
||||
setStatus('remoteStatus', 'Download-all failed: ' + err.message, false);
|
||||
}
|
||||
btn.disabled = false;
|
||||
}
|
||||
|
||||
// Derive family name from a .cpfont filename: take everything before the
|
||||
@@ -293,25 +416,20 @@
|
||||
const status = document.getElementById('status');
|
||||
const files = cpfontFilesOnly(document.getElementById('fontFiles').files);
|
||||
if (files.length === 0) {
|
||||
status.className = 'status-err';
|
||||
status.style.display = 'block';
|
||||
status.textContent = 'No .cpfont files selected.';
|
||||
setStatus('status', 'No .cpfont files selected.', false);
|
||||
return;
|
||||
}
|
||||
|
||||
const families = files.map(f => sanitizeFamily(familyFromFilename(f.name)));
|
||||
const uniqueFamilies = new Set(families);
|
||||
if (uniqueFamilies.size !== 1) {
|
||||
status.className = 'status-err';
|
||||
status.style.display = 'block';
|
||||
status.textContent = 'Selected files belong to multiple families.';
|
||||
setStatus('status', 'Selected files belong to multiple families.', false);
|
||||
return;
|
||||
}
|
||||
|
||||
const family = families[0];
|
||||
|
||||
status.className = '';
|
||||
status.style.display = 'block';
|
||||
setStatus('status', 'Preparing upload...');
|
||||
|
||||
let uploaded = 0;
|
||||
for (const file of files) {
|
||||
@@ -323,27 +441,29 @@
|
||||
const res = await fetch('/api/fonts/upload', { method: 'POST', body: formData });
|
||||
const data = await res.json();
|
||||
if (!data.ok) {
|
||||
status.className = 'status-err';
|
||||
status.textContent = 'Failed on ' + file.name + ': ' + (data.error || 'unknown error');
|
||||
setStatus('status', 'Failed on ' + file.name + ': ' + (data.error || 'unknown error'), false);
|
||||
await loadFonts();
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
status.className = 'status-err';
|
||||
status.textContent = 'Upload error on ' + file.name + ': ' + err.message;
|
||||
setStatus('status', 'Upload error on ' + file.name + ': ' + err.message, false);
|
||||
await loadFonts();
|
||||
return;
|
||||
}
|
||||
uploaded++;
|
||||
}
|
||||
|
||||
status.className = 'status-ok';
|
||||
status.textContent = 'Uploaded ' + uploaded + ' file' + (uploaded === 1 ? '' : 's') +
|
||||
' to family "' + family + '".';
|
||||
setStatus('status', 'Uploaded ' + uploaded + ' file' + (uploaded === 1 ? '' : 's') +
|
||||
' to family "' + family + '".', true);
|
||||
await loadFonts();
|
||||
await loadRemoteManifest();
|
||||
});
|
||||
|
||||
document.getElementById('refreshManifest').addEventListener('click', loadRemoteManifest);
|
||||
document.getElementById('downloadAllRemote').addEventListener('click', downloadAllRemoteUpdates);
|
||||
|
||||
loadFonts();
|
||||
loadRemoteManifest();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user