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 font = match font_system.get_font(cache_key.font_id) {
21 Some(some) => some,
22 None => {
23 log::warn!("did not find font {:?}", cache_key.font_id);
24 return None;
25 }
26 };
27
28 let mut scaler = context
30 .builder(font.as_swash())
31 .size(f32::from_bits(cache_key.font_size_bits))
32 .hint(true)
33 .build();
34
35 let offset = Vector::new(cache_key.x_bin.as_float(), cache_key.y_bin.as_float());
38
39 Render::new(&[
41 Source::ColorOutline(0),
43 Source::ColorBitmap(StrikeWith::BestFit),
45 Source::Outline,
47 ])
48 .format(Format::Alpha)
50 .offset(offset)
52 .transform(if cache_key.flags.contains(CacheKeyFlags::FAKE_ITALIC) {
53 Some(Transform::skew(
54 Angle::from_degrees(14.0),
55 Angle::from_degrees(0.0),
56 ))
57 } else {
58 None
59 })
60 .render(&mut scaler, cache_key.glyph_id)
62}
63
64fn swash_outline_commands(
65 font_system: &mut FontSystem,
66 context: &mut ScaleContext,
67 cache_key: CacheKey,
68) -> Option<Box<[swash::zeno::Command]>> {
69 use swash::zeno::PathData as _;
70
71 let font = match font_system.get_font(cache_key.font_id) {
72 Some(some) => some,
73 None => {
74 log::warn!("did not find font {:?}", cache_key.font_id);
75 return None;
76 }
77 };
78
79 let mut scaler = context
81 .builder(font.as_swash())
82 .size(f32::from_bits(cache_key.font_size_bits))
83 .hint(true)
84 .build();
85
86 let outline = scaler
88 .scale_outline(cache_key.glyph_id)
89 .or_else(|| scaler.scale_color_outline(cache_key.glyph_id))?;
90
91 let path = outline.path();
93
94 Some(path.commands().collect())
96}
97
98pub struct SwashCache {
100 context: ScaleContext,
101 pub image_cache: HashMap<CacheKey, Option<SwashImage>>,
102 pub outline_command_cache: HashMap<CacheKey, Option<Box<[swash::zeno::Command]>>>,
103}
104
105impl fmt::Debug for SwashCache {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 f.pad("SwashCache { .. }")
108 }
109}
110
111impl SwashCache {
112 pub fn new() -> Self {
114 Self {
115 context: ScaleContext::new(),
116 image_cache: HashMap::default(),
117 outline_command_cache: HashMap::default(),
118 }
119 }
120
121 pub fn get_image_uncached(
123 &mut self,
124 font_system: &mut FontSystem,
125 cache_key: CacheKey,
126 ) -> Option<SwashImage> {
127 swash_image(font_system, &mut self.context, cache_key)
128 }
129
130 pub fn get_image(
132 &mut self,
133 font_system: &mut FontSystem,
134 cache_key: CacheKey,
135 ) -> &Option<SwashImage> {
136 self.image_cache
137 .entry(cache_key)
138 .or_insert_with(|| swash_image(font_system, &mut self.context, cache_key))
139 }
140
141 pub fn get_outline_commands(
143 &mut self,
144 font_system: &mut FontSystem,
145 cache_key: CacheKey,
146 ) -> Option<&[swash::zeno::Command]> {
147 self.outline_command_cache
148 .entry(cache_key)
149 .or_insert_with(|| swash_outline_commands(font_system, &mut self.context, cache_key))
150 .as_deref()
151 }
152
153 pub fn get_outline_commands_uncached(
155 &mut self,
156 font_system: &mut FontSystem,
157 cache_key: CacheKey,
158 ) -> Option<Box<[swash::zeno::Command]>> {
159 swash_outline_commands(font_system, &mut self.context, cache_key)
160 }
161
162 pub fn with_pixels<F: FnMut(i32, i32, Color)>(
164 &mut self,
165 font_system: &mut FontSystem,
166 cache_key: CacheKey,
167 base: Color,
168 mut f: F,
169 ) {
170 if let Some(image) = self.get_image(font_system, cache_key) {
171 let x = image.placement.left;
172 let y = -image.placement.top;
173
174 match image.content {
175 Content::Mask => {
176 let mut i = 0;
177 for off_y in 0..image.placement.height as i32 {
178 for off_x in 0..image.placement.width as i32 {
179 f(
181 x + off_x,
182 y + off_y,
183 Color(((image.data[i] as u32) << 24) | base.0 & 0xFF_FF_FF),
184 );
185 i += 1;
186 }
187 }
188 }
189 Content::Color => {
190 let mut i = 0;
191 for off_y in 0..image.placement.height as i32 {
192 for off_x in 0..image.placement.width as i32 {
193 f(
195 x + off_x,
196 y + off_y,
197 Color::rgba(
198 image.data[i],
199 image.data[i + 1],
200 image.data[i + 2],
201 image.data[i + 3],
202 ),
203 );
204 i += 4;
205 }
206 }
207 }
208 Content::SubpixelMask => {
209 log::warn!("TODO: SubpixelMask");
210 }
211 }
212 }
213 }
214}