diff --git a/apps/geolibre-desktop/package.json b/apps/geolibre-desktop/package.json index 85ddf15af7..15a9d651a9 100644 --- a/apps/geolibre-desktop/package.json +++ b/apps/geolibre-desktop/package.json @@ -32,6 +32,7 @@ "@electric-sql/pglite": "^0.5.4", "@electric-sql/pglite-postgis": "^0.2.4", "@fontsource-variable/ibm-plex-sans": "^5.3.0", + "@fontsource-variable/vazirmatn": "^5.3.0", "@fontsource/ibm-plex-mono": "^5.3.0", "@geolibre/core": "*", "@geolibre/embed": "*", diff --git a/apps/geolibre-desktop/src/index.css b/apps/geolibre-desktop/src/index.css index 79daae6f8d..57efe968a0 100644 --- a/apps/geolibre-desktop/src/index.css +++ b/apps/geolibre-desktop/src/index.css @@ -7,10 +7,10 @@ /* App typeface: IBM Plex Sans for UI, IBM Plex Mono for the numeric/coordinate readouts (StatusBar, code, feature IDs). One matched superfamily, so the mono - readouts and the surrounding UI share a voice. The @font-face rules are - loaded from main.tsx — see the note there for why they cannot be @imported - here. Self-hosted rather than CDN-loaded: the desktop build must render - offline, and the Tauri CSP is `default-src 'self'`. + readouts and the surrounding UI share a voice. Fontsource's own @font-face + stylesheets are loaded from main.tsx — see the note there for why they cannot + be @imported here. Self-hosted rather than CDN-loaded: the desktop build must + render offline, and the Tauri CSP is `default-src 'self'`. This block drives both the `font-sans`/`font-mono` utilities and Tailwind's preflight default, so no `font-family` on is needed. @@ -27,6 +27,49 @@ "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; } +/* Persian (fa) is the one locale that needs a second webfont: Plex has no + Arabic-script coverage, so without this the UI falls back to whatever the + system happens to ship. Only the *arabic* subset is loaded — Vazirmatn also + publishes latin and latin-ext, but those duplicate Plex for 56 KB, and the + service worker precaches every emitted asset, so an unused subset is weight + every user downloads on SW install rather than a lazy per-glyph fetch. + + Written out by hand rather than imported from main.tsx because fontsource + ships no subset-scoped entry point: both index.css and wght.css declare all + three subsets. The Tailwind-inlining hazard described in main.tsx does not + apply here — it is specific to @importing fontsource's CSS, whose relative + url(./files/*.woff2) never reaches Vite's asset pipeline. A bare package + specifier in a url() that Tailwind passes through is rewritten normally. + + unicode-range is copied verbatim from the package's wght.css; keep it in sync + if @fontsource-variable/vazirmatn is bumped and the subset boundaries move. */ +@font-face { + font-family: "Vazirmatn Variable"; + font-style: normal; + font-display: swap; + font-weight: 100 900; + src: url("@fontsource-variable/vazirmatn/files/vazirmatn-arabic-wght-normal.woff2") + format("woff2-variations"); + unicode-range: + U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1, U+08E3-08FF, U+200C-200E, + U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC, U+102E0-102FB, + U+10E60-10E7E, U+10EC2-10EC4, U+10EFC-10EFF, U+1EE00-1EE03, U+1EE05-1EE1F, U+1EE21-1EE22, + U+1EE24, U+1EE27, U+1EE29-1EE32, U+1EE34-1EE37, U+1EE39, U+1EE3B, U+1EE42, U+1EE47, U+1EE49, + U+1EE4B, U+1EE4D-1EE4F, U+1EE51-1EE52, U+1EE54, U+1EE57, U+1EE59, U+1EE5B, U+1EE5D, U+1EE5F, + U+1EE61-1EE62, U+1EE64, U+1EE67-1EE6A, U+1EE6C-1EE72, U+1EE74-1EE77, U+1EE79-1EE7C, U+1EE7E, + U+1EE80-1EE89, U+1EE8B-1EE9B, U+1EEA1-1EEA3, U+1EEA5-1EEA9, U+1EEAB-1EEBB, U+1EEF0-1EEF1; +} + +/* Vazirmatn covers the Arabic script; Plex stays second so the Latin text that + remains in a Persian UI (layer names, coordinates, product names) keeps the + same voice as every other locale instead of dropping to system-ui. */ +html:lang(fa) { + --font-sans: + "Vazirmatn Variable", "IBM Plex Sans Variable", ui-sans-serif, system-ui, -apple-system, + "Segoe UI", Roboto, "Noto Sans Arabic", "Noto Sans", "Helvetica Neue", Arial, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji"; +} + /* Map-floating chrome, a.k.a. "instrument glass". The distinction this encodes: chrome docked BESIDE the map (top toolbar, diff --git a/package-lock.json b/package-lock.json index b9df927402..11732a9f2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,6 +45,7 @@ "@electric-sql/pglite": "^0.5.4", "@electric-sql/pglite-postgis": "^0.2.4", "@fontsource-variable/ibm-plex-sans": "^5.3.0", + "@fontsource-variable/vazirmatn": "^5.3.0", "@fontsource/ibm-plex-mono": "^5.3.0", "@geolibre/core": "*", "@geolibre/embed": "*", @@ -3887,6 +3888,15 @@ "url": "https://github.com/sponsors/ayuhito" } }, + "node_modules/@fontsource-variable/vazirmatn": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource-variable/vazirmatn/-/vazirmatn-5.3.0.tgz", + "integrity": "sha512-ZA68W8JHbMdNzzwjYHUWKB2PH22QpxNEnk0ukcXM+XiqYMV6ZWJZAKuNXcrMR2JjuIAwsfAU3KknCXPI6wXUvw==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@fontsource/ibm-plex-mono": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@fontsource/ibm-plex-mono/-/ibm-plex-mono-5.3.0.tgz",