Render PUA icon glyphs via per-codepoint fontconfig family probe
swadhinbiswas committed 2 months ago
fbcf86e
1 parent fb90631
commit fbcf86e
2 files changed
+69 -2
Showing 2 changed files+69-2
▼shell/Commons/Style.qml
+52-0
| @@ -476,6 +476,58 @@ QtObject { | ||
476 | 476 | } |
477 | 477 | } |
478 | 478 | |
479 | + // Icon glyphs are Private Use Area codepoints (Nerd Font, FontAwesome, | |
480 | + // Material Symbols…). No single installed font covers every range, and the | |
481 | + // generic `monospace` alias can resolve to a family missing the exact | |
482 | + // glyph (tofu boxes). fontconfig answers per codepoint, so ask it once per | |
483 | + // distinct glyph, cache forever, and render that glyph in a family that | |
484 | + // actually carries it. OpticalGlyph consults this for every PUA glyph. | |
485 | + property var iconFamilyCache: ({}) | |
486 | + | |
487 | + property var iconQueryQueue: [] | |
488 | + property bool iconQueryInFlight: false | |
489 | + | |
490 | + function iconFamilyFor(text) { | |
491 | + var cp = 0 | |
492 | + if (text && text.length > 0) { | |
493 | + try { cp = text.codePointAt(0) } catch (e) { cp = 0 } | |
494 | + } | |
495 | + // Below the PUA start this is ordinary text — render with the regular | |
496 | + // family; only icon codepoints get the per-glyph probe. | |
497 | + if (cp < 0xE000) return fontFamily | |
498 | + var cache = iconFamilyCache | |
499 | + if (cache[cp] !== undefined) return cache[cp] | |
500 | + if (iconQueryQueue.indexOf(cp) === -1) iconQueryQueue = iconQueryQueue.concat([cp]) | |
501 | + dequeueIconQuery() | |
502 | + return fontFamily // until the probe lands; the cache reassign re-runs us | |
503 | + } | |
504 | + | |
505 | + function dequeueIconQuery() { | |
506 | + if (iconQueryInFlight || iconQueryQueue.length === 0) return | |
507 | + iconQueryInFlight = true | |
508 | + iconFontProbe.command = ["/usr/bin/fc-match", "-f", "%{family[0]}", ":charset=" + iconQueryQueue[0].toString(16)] | |
509 | + iconFontProbe.running = true | |
510 | + } | |
511 | + | |
512 | + property Process iconFontProbe: Process { | |
513 | + id: iconFontProbe | |
514 | + command: [] | |
515 | + stdout: StdioCollector { | |
516 | + waitForEnd: true | |
517 | + onStreamFinished: { | |
518 | + var cp = root.iconQueryQueue.length > 0 ? root.iconQueryQueue[0] : 0 | |
519 | + var family = String(text || "").trim() | |
520 | + if (family.length === 0) family = root.fontFamily | |
521 | + var cache = root.iconFamilyCache | |
522 | + cache[cp] = family | |
523 | + root.iconFamilyCache = cache | |
524 | + root.iconQueryQueue = root.iconQueryQueue.slice(1) | |
525 | + root.iconQueryInFlight = false | |
526 | + root.dequeueIconQuery() | |
527 | + } | |
528 | + } | |
529 | + } | |
530 | + | |
479 | 531 | property FileView fontconfigFile: FileView { |
480 | 532 | path: Quickshell.env("HOME") + "/.config/fontconfig/fonts.conf" |
481 | 533 | watchChanges: true |
482 | 534 | |
▼shell/Ui/OpticalGlyph.qml
+17-2
| @@ -10,6 +10,21 @@ Item { | ||
10 | 10 | property color color: Color.foreground |
11 | 11 | property bool debugBounds: false |
12 | 12 | |
13 | + // Icon codepoints (Nerd Font / FontAwesome / Material Symbols — the | |
14 | + // Private Use Areas) render through Style's per-glyph resolver so a family | |
15 | + // that actually contains the glyph is used; ordinary text keeps the | |
16 | + // caller's family. | |
17 | + readonly property bool iconGlyph: { | |
18 | + var cp = 0 | |
19 | + if (text && text.length > 0) { | |
20 | + try { cp = text.codePointAt(0) } catch (e) { cp = 0 } | |
21 | + } | |
22 | + return cp >= 0xE000 | |
23 | + } | |
24 | + readonly property string resolvedFamily: root.iconGlyph | |
25 | + ? Style.iconFamilyFor(root.text) | |
26 | + : root.fontFamily | |
27 | + | |
13 | 28 | readonly property int renderedFontSize: Math.max(1, Math.round(fontSize)) |
14 | 29 | readonly property real tightWidth: Math.max(1, glyphMetrics.tightBoundingRect.width) |
15 | 30 | readonly property real horizontalCorrection: glyph.implicitWidth / 2 - (glyphMetrics.tightBoundingRect.x + tightWidth / 2) |
| @@ -18,7 +33,7 @@ Item { | ||
18 | 33 | |
19 | 34 | TextMetrics { |
20 | 35 | id: glyphMetrics |
21 | - font.family: root.fontFamily | |
36 | + font.family: root.resolvedFamily | |
22 | 37 | font.pixelSize: root.renderedFontSize |
23 | 38 | text: root.text |
24 | 39 | } |
| @@ -31,7 +46,7 @@ Item { | ||
31 | 46 | anchors.horizontalCenterOffset: root.horizontalCorrection |
32 | 47 | text: root.text |
33 | 48 | color: root.color |
34 | - font.family: root.fontFamily | |
49 | + font.family: root.resolvedFamily | |
35 | 50 | font.pixelSize: root.renderedFontSize |
36 | 51 | renderType: Text.NativeRendering |
37 | 52 | } |
38 | 53 | |