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
▼shell/Commons/Style.qml
+52-0
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
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
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
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