ltk/render/
setup.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>

//! Construction + accessors for [`SoftwareCanvas`]: new / sub_canvas
//! / resize plus the font-registry installer and `blit`.

use std::collections::HashMap;
use std::sync::{ Arc, OnceLock };

use fontdue::{ Font, FontSettings };
use tiny_skia::{ Pixmap, PixmapPaint, Transform };

use crate::system_fonts::FontHandle;
use crate::theme::{ FontRegistry, FontStyle };

use super::helpers::load_default_font_bytes;
use super::SoftwareCanvas;

/// Process-wide cache of the default font face. The handle keeps the
/// raw bytes (`Arc<Vec<u8>>`) alongside the fontdue `Arc<Font>` so
/// the HarfBuzz shaper can be invoked without re-reading the file.
/// Sora is small (~50 KB) so the cost was minor in absolute terms —
/// but a layer shell that brings up a launcher overlay, a QS panel,
/// a calendar popup and a handful of toast surfaces would still pay
/// the parse cost a dozen times in a single session, all of which
/// is wasted work.
static DEFAULT_FONT: OnceLock<FontHandle> = OnceLock::new();

fn default_handle() -> FontHandle
{
	DEFAULT_FONT.get_or_init( ||
	{
		let bytes = load_default_font_bytes();
		let font  = Font::from_bytes( bytes.as_slice(), FontSettings::default() )
			.expect( "bad font" );
		FontHandle
		{
			font:  Arc::new( font ),
			bytes: Arc::new( bytes ),
			face:  0,
		}
	} ).clone()
}

impl SoftwareCanvas
{
	/// Create a canvas of the given pixel dimensions, loading a system font.
	///
	/// Fallback fonts (Noto Sans / CJK / Devanagari / …) are NOT loaded
	/// here — they are owned by the crate-private system-fonts chain
	/// and loaded lazily per codepoint. A canvas that only ever paints
	/// Latin text will never touch those files; the first non-Latin
	/// glyph triggers a single targeted load and the rest of the
	/// process reuses the cached `Arc<Font>`.
	pub fn new( width: u32, height: u32 ) -> Self
	{
		let handle = default_handle();
		Self
		{
			pixmap:          Pixmap::new( width, height ).expect( "pixmap" ),
			font:            handle.font.clone(),
			font_bytes:      handle.bytes.clone(),
			font_face:       handle.face,
			font_registry:   None,
			dpi_scale:       1.0,
			global_alpha:    1.0,
			glyph_cache:     HashMap::new(),
			clip_mask:       None,
			clip_bounds:     Vec::new(),
		}
	}

	/// Create a blank sub-canvas sharing the same font and DPI scale.
	pub fn sub_canvas( &self, width: u32, height: u32 ) -> SoftwareCanvas
	{
		SoftwareCanvas
		{
			pixmap:         Pixmap::new( width, height ).expect( "pixmap" ),
			font:           Arc::clone( &self.font ),
			font_bytes:     Arc::clone( &self.font_bytes ),
			font_face:      self.font_face,
			font_registry:  self.font_registry.as_ref().map( Arc::clone ),
			dpi_scale:      self.dpi_scale,
			global_alpha:   self.global_alpha,
			glyph_cache:    HashMap::new(),
			clip_mask:      None,
			clip_bounds:    Vec::new(),
		}
	}

	/// Install a theme font registry so [`Self::font_for`] can
	/// resolve family+weight+style triples declared by the theme's
	/// `fonts` block. The default [`Self::font`] stays in place as a
	/// fallback.
	pub fn set_font_registry( &mut self, registry: Arc<FontRegistry> )
	{
		self.font_registry = Some( registry );
	}

	/// Resolve a specific font from the theme registry, falling back
	/// to the canvas' default [`Self::font`] when no registry is
	/// installed or the triple cannot be satisfied.
	pub fn font_for( &self, family: &str, weight: u16, style: FontStyle ) -> Arc<Font>
	{
		self.font_registry
			.as_ref()
			.and_then( |r| r.resolve( family, weight, style ) )
			.unwrap_or_else( || Arc::clone( &self.font ) )
	}

	/// Pick the right font for `ch`. Tries the primary [`Self::font`]
	/// first; on a miss, delegates to the crate-private system-fonts
	/// fallback chain (lazy load of the relevant Noto pack). Falls
	/// back to the primary (which paints a `.notdef` box) when no
	/// installed fallback covers the codepoint.
	pub fn font_for_char( &self, ch: char ) -> Arc<Font>
	{
		if self.font.lookup_glyph_index( ch ) != 0
		{
			return Arc::clone( &self.font );
		}
		crate::system_fonts::lookup( ch ).unwrap_or_else( || Arc::clone( &self.font ) )
	}

	/// Bytes-aware variant of [`Self::font_for_char`]. Returns the
	/// full `FontHandle` so callers that invoke the HarfBuzz shaper
	/// can hand the raw bytes directly to rustybuzz. The primary
	/// font supplies the bytes from
	/// [`Self::font_bytes`]; fallback chars borrow the bytes that
	/// were loaded into the system fallback cache.
	pub fn font_handle_for_char( &self, ch: char ) -> crate::system_fonts::FontHandle
	{
		if self.font.lookup_glyph_index( ch ) != 0
		{
			return crate::system_fonts::FontHandle
			{
				font:  Arc::clone( &self.font ),
				bytes: Arc::clone( &self.font_bytes ),
				face:  self.font_face,
			};
		}
		crate::system_fonts::lookup_handle( ch ).unwrap_or_else( ||
			crate::system_fonts::FontHandle
			{
				font:  Arc::clone( &self.font ),
				bytes: Arc::clone( &self.font_bytes ),
				face:  self.font_face,
			}
		)
	}

	pub fn blit( &mut self, src: &SoftwareCanvas, dest_x: i32, dest_y: i32 )
	{
		let paint = PixmapPaint::default();
		let t     = Transform::from_translate( dest_x as f32, dest_y as f32 );
		self.pixmap.draw_pixmap( 0, 0, src.pixmap.as_ref(), &paint, t, self.clip_mask.as_ref() );
	}

	pub fn resize( &mut self, width: u32, height: u32 )
	{
		self.pixmap = Pixmap::new( width, height ).expect( "pixmap" );
	}
}