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 mut scaler = context
94 .builder(font.as_swash())
95 .size(f32::from_bits(cache_key.font_size_bits))
96 .hint(!cache_key.flags.contains(CacheKeyFlags::DISABLE_HINTING))
97 .build();
98
99 let mut outline = scaler
101 .scale_outline(cache_key.glyph_id)
102 .or_else(|| scaler.scale_color_outline(cache_key.glyph_id))?;
103
104 if cache_key.flags.contains(CacheKeyFlags::FAKE_ITALIC) {
105 outline.transform(&Transform::skew(
106 Angle::from_degrees(14.0),
107 Angle::from_degrees(0.0),
108 ));
109 }
110
111 let path = outline.path();
113
114 Some(path.commands().collect())
116}
117
118pub struct SwashCache {
120 context: ScaleContext,
121 pub image_cache: HashMap<CacheKey, Option<SwashImage>>,
122 pub outline_command_cache: HashMap<CacheKey, Option<Box<[swash::zeno::Command]>>>,
123}
124
125impl fmt::Debug for SwashCache {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 f.pad("SwashCache { .. }")
128 }
129}
130
131impl SwashCache {
132 pub fn new() -> Self {
134 Self {
135 context: ScaleContext::new(),
136 image_cache: HashMap::default(),
137 outline_command_cache: HashMap::default(),
138 }
139 }
140
141 pub fn get_image_uncached(
143 &mut self,
144 font_system: &mut FontSystem,
145 cache_key: CacheKey,
146 ) -> Option<SwashImage> {
147 swash_image(font_system, &mut self.context, cache_key)
148 }
149
150 pub fn get_image(
152 &mut self,
153 font_system: &mut FontSystem,
154 cache_key: CacheKey,
155 ) -> &Option<SwashImage> {
156 self.image_cache
157 .entry(cache_key)
158 .or_insert_with(|| swash_image(font_system, &mut self.context, cache_key))
159 }
160
161 pub fn get_outline_commands(
163 &mut self,
164 font_system: &mut FontSystem,
165 cache_key: CacheKey,
166 ) -> Option<&[swash::zeno::Command]> {
167 self.outline_command_cache
168 .entry(cache_key)
169 .or_insert_with(|| swash_outline_commands(font_system, &mut self.context, cache_key))
170 .as_deref()
171 }
172
173 pub fn get_outline_commands_uncached(
175 &mut self,
176 font_system: &mut FontSystem,
177 cache_key: CacheKey,
178 ) -> Option<Box<[swash::zeno::Command]>> {
179 swash_outline_commands(font_system, &mut self.context, cache_key)
180 }
181
182 pub fn with_pixels<F: FnMut(i32, i32, Color)>(
184 &mut self,
185 font_system: &mut FontSystem,
186 cache_key: CacheKey,
187 base: Color,
188 mut f: F,
189 ) {
190 if let Some(image) = self.get_image(font_system, cache_key) {
191 let x = image.placement.left;
192 let y = -image.placement.top;
193
194 match image.content {
195 Content::Mask => {
196 let mut i = 0;
197 for off_y in 0..image.placement.height as i32 {
198 for off_x in 0..image.placement.width as i32 {
199 f(
201 x + off_x,
202 y + off_y,
203 Color((u32::from(image.data[i]) << 24) | base.0 & 0xFF_FF_FF),
204 );
205 i += 1;
206 }
207 }
208 }
209 Content::Color => {
210 let mut i = 0;
211 for off_y in 0..image.placement.height as i32 {
212 for off_x in 0..image.placement.width as i32 {
213 f(
215 x + off_x,
216 y + off_y,
217 Color::rgba(
218 image.data[i],
219 image.data[i + 1],
220 image.data[i + 2],
221 image.data[i + 3],
222 ),
223 );
224 i += 4;
225 }
226 }
227 }
228 Content::SubpixelMask => {
229 log::warn!("TODO: SubpixelMask");
230 }
231 }
232 }
233 }
234}