1#[cfg(not(feature = "std"))]
4use alloc::vec::Vec;
5use core::fmt;
6use swash::scale::{image::Content, ScaleContext};
7use swash::scale::{Render, Source, StrikeWith};
8use swash::zeno::{Format, Vector};
9
10use crate::{CacheKey, CacheKeyFlags, Color, FontSystem, HashMap};
11
12pub use swash::scale::image::{Content as SwashContent, Image as SwashImage};
13pub use swash::zeno::{Angle, Command, Placement, Transform};
14
15fn swash_image(
16 font_system: &mut FontSystem,
17 context: &mut ScaleContext,
18 cache_key: CacheKey,
19) -> Option<SwashImage> {
20 let Some(font) = font_system.get_font(cache_key.font_id, cache_key.font_weight) else {
21 log::warn!("did not find font {:?}", cache_key.font_id);
22 return None;
23 };
24
25 let variable_width = font
26 .as_swash()
27 .variations()
28 .find_by_tag(swash::Tag::from_be_bytes(*b"wght"));
29
30 let mut scaler = context
32 .builder(font.as_swash())
33 .size(f32::from_bits(cache_key.font_size_bits))
34 .hint(!cache_key.flags.contains(CacheKeyFlags::DISABLE_HINTING));
35 if let Some(variation) = variable_width {
36 scaler = scaler.variations(std::iter::once(swash::Setting {
37 tag: swash::Tag::from_be_bytes(*b"wght"),
38 value: f32::from(cache_key.font_weight.0)
39 .clamp(variation.min_value(), variation.max_value()),
40 }));
41 }
42 let mut scaler = scaler.build();
43
44 let offset = if cache_key.flags.contains(CacheKeyFlags::PIXEL_FONT) {
47 Vector::new(
48 cache_key.x_bin.as_float().round() + 1.0,
49 cache_key.y_bin.as_float().round(),
50 )
51 } else {
52 Vector::new(cache_key.x_bin.as_float(), cache_key.y_bin.as_float())
53 };
54
55 Render::new(&[
57 Source::ColorOutline(0),
59 Source::ColorBitmap(StrikeWith::BestFit),
61 Source::Outline,
63 ])
64 .format(Format::Alpha)
66 .offset(offset)
68 .transform(if cache_key.flags.contains(CacheKeyFlags::FAKE_ITALIC) {
69 Some(Transform::skew(
70 Angle::from_degrees(14.0),
71 Angle::from_degrees(0.0),
72 ))
73 } else {
74 None
75 })
76 .render(&mut scaler, cache_key.glyph_id)
78}
79
80fn swash_outline_commands(
81 font_system: &mut FontSystem,
82 context: &mut ScaleContext,
83 cache_key: CacheKey,
84) -> Option<Box<[swash::zeno::Command]>> {
85 use swash::zeno::PathData as _;
86
87 let Some(font) = font_system.get_font(cache_key.font_id, cache_key.font_weight) else {
88 log::warn!("did not find font {:?}", cache_key.font_id);
89 return None;
90 };
91
92 let variable_width = font
93 .as_swash()
94 .variations()
95 .find_by_tag(swash::Tag::from_be_bytes(*b"wght"));
96
97 let mut scaler = context
99 .builder(font.as_swash())
100 .size(f32::from_bits(cache_key.font_size_bits))
101 .hint(!cache_key.flags.contains(CacheKeyFlags::DISABLE_HINTING));
102 if let Some(variation) = variable_width {
103 scaler = scaler.variations(std::iter::once(swash::Setting {
104 tag: swash::Tag::from_be_bytes(*b"wght"),
105 value: f32::from(cache_key.font_weight.0)
106 .clamp(variation.min_value(), variation.max_value()),
107 }));
108 }
109 let mut scaler = scaler.build();
110
111 let mut outline = scaler
113 .scale_outline(cache_key.glyph_id)
114 .or_else(|| scaler.scale_color_outline(cache_key.glyph_id))?;
115
116 if cache_key.flags.contains(CacheKeyFlags::FAKE_ITALIC) {
117 outline.transform(&Transform::skew(
118 Angle::from_degrees(14.0),
119 Angle::from_degrees(0.0),
120 ));
121 }
122
123 let path = outline.path();
125
126 Some(path.commands().collect())
128}
129
130pub struct SwashCache {
132 context: ScaleContext,
133 pub image_cache: HashMap<CacheKey, Option<SwashImage>>,
134 pub outline_command_cache: HashMap<CacheKey, Option<Box<[swash::zeno::Command]>>>,
135}
136
137impl fmt::Debug for SwashCache {
138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139 f.pad("SwashCache { .. }")
140 }
141}
142
143impl SwashCache {
144 pub fn new() -> Self {
146 Self {
147 context: ScaleContext::new(),
148 image_cache: HashMap::default(),
149 outline_command_cache: HashMap::default(),
150 }
151 }
152
153 pub fn get_image_uncached(
155 &mut self,
156 font_system: &mut FontSystem,
157 cache_key: CacheKey,
158 ) -> Option<SwashImage> {
159 swash_image(font_system, &mut self.context, cache_key)
160 }
161
162 pub fn get_image(
164 &mut self,
165 font_system: &mut FontSystem,
166 cache_key: CacheKey,
167 ) -> &Option<SwashImage> {
168 self.image_cache
169 .entry(cache_key)
170 .or_insert_with(|| swash_image(font_system, &mut self.context, cache_key))
171 }
172
173 pub fn get_outline_commands(
175 &mut self,
176 font_system: &mut FontSystem,
177 cache_key: CacheKey,
178 ) -> Option<&[swash::zeno::Command]> {
179 self.outline_command_cache
180 .entry(cache_key)
181 .or_insert_with(|| swash_outline_commands(font_system, &mut self.context, cache_key))
182 .as_deref()
183 }
184
185 pub fn get_outline_commands_uncached(
187 &mut self,
188 font_system: &mut FontSystem,
189 cache_key: CacheKey,
190 ) -> Option<Box<[swash::zeno::Command]>> {
191 swash_outline_commands(font_system, &mut self.context, cache_key)
192 }
193
194 pub fn with_pixels<F: FnMut(i32, i32, Color)>(
196 &mut self,
197 font_system: &mut FontSystem,
198 cache_key: CacheKey,
199 base: Color,
200 mut f: F,
201 ) {
202 if let Some(image) = self.get_image(font_system, cache_key) {
203 let x = image.placement.left;
204 let y = -image.placement.top;
205
206 match image.content {
207 Content::Mask => {
208 let mut i = 0;
209 for off_y in 0..image.placement.height as i32 {
210 for off_x in 0..image.placement.width as i32 {
211 f(
213 x + off_x,
214 y + off_y,
215 Color((u32::from(image.data[i]) << 24) | base.0 & 0xFF_FF_FF),
216 );
217 i += 1;
218 }
219 }
220 }
221 Content::Color => {
222 let mut i = 0;
223 for off_y in 0..image.placement.height as i32 {
224 for off_x in 0..image.placement.width as i32 {
225 f(
227 x + off_x,
228 y + off_y,
229 Color::rgba(
230 image.data[i],
231 image.data[i + 1],
232 image.data[i + 2],
233 image.data[i + 3],
234 ),
235 );
236 i += 4;
237 }
238 }
239 }
240 Content::SubpixelMask => {
241 log::warn!("TODO: SubpixelMask");
242 }
243 }
244 }
245 }
246}