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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
use crate::{
Aabb, OutlineProvider, ReadGlyphOutlineError,
math_segment::Segment,
msdf::{compute_msdf, compute_mtsdf},
polygon_clipping::{IntersectRemoveState, remove_intersecting_shapes},
segment::SegmentCollector,
segment_soa::SegmentSoa,
};
/// MSDF/MTSDF generator.
///
/// This stores all allocated collections necessary for glyph generation. It can be used to reads glyphs
/// and produce a [`GlyphOutline`], from which you can generate distance fields.
#[derive(Default)]
pub struct MsdfGenerator {
pub(crate) cached_segment_vec: Vec<Segment>,
pub(crate) cached_soa: SegmentSoa,
cached_color: Vec<u8>,
intersect_remove_state: IntersectRemoveState,
segment_deviation_threshold: f32,
}
impl MsdfGenerator {
/// Create a new instance.
pub fn new() -> Self {
Self {
cached_segment_vec: vec![],
cached_soa: SegmentSoa::new(),
cached_color: vec![],
intersect_remove_state: Default::default(),
segment_deviation_threshold: 0.05,
}
}
/// Sets the maximum deviation of bezier curve, unit is em.
///
/// # Panics
/// Panics if provided value is negative or zero.
///
/// # Remarks
/// The deviation is the distance from bezier control points to the line connecting its start
/// and end points. Bezier curve with smaller deviation is flatter.
///
/// . p1 (control point)
/// deviation -> |
/// . |
/// p0 ----------x--------- p2
///
/// Bezier curve is recursively split until each segment is flat enough, i.e its deviation is
/// smaller than the value provided in this method.
pub fn set_segment_deviation_threshold(&mut self, em_ratio: f32) {
assert!(em_ratio > 0.);
self.segment_deviation_threshold = em_ratio;
}
/// Reads outline data of a single glyph.
///
/// # Example
/// ```rust
/// # #[cfg(feature = "skrifa")]
/// # {
/// use klyff_msdf::{MsdfGenerator, AtlasRegionSize};
/// use klyff_msdf::SkrifaOutlineProvider;
/// use klyff_msdf::skrifa::{FontRef, GlyphId};
///
/// fn gen_msdf(font: FontRef<'_>, glyph_id: GlyphId){
/// let mut generator = MsdfGenerator::new();
/// let outline = match generator.read_glyph(&SkrifaOutlineProvider::new(font, glyph_id)) {
/// Ok(outline) => outline,
/// Err(error) => todo!("Handle error case"),
/// };
///
/// let size: AtlasRegionSize = todo!("Allocate a region in your texture atlas");
/// let bytes = outline.generate_mtsdf(size);
/// todo!("Write the bytes to your texture atlas");
/// }
/// # }
/// ```
///
/// # Errors
/// See [`ReadGlyphOutlineError`]. Notably, this returns an error if a glyph is empty, or if a
/// glyph can't be rendered with MSDF.
///
/// # Returns
/// An instance of [`GlyphOutline`], which contains sizing information of a glyph and other
/// data to compute the final distance field.
/// Note that to reuse allocations, the instance of [`GlyphOutline`] contains a reference
/// to this instance, so you must drop it first before using this instance to read another glyph.
pub fn read_glyph<'a, Provider: OutlineProvider>(
&'a mut self,
provider: &Provider,
) -> Result<GlyphOutline<'a>, ReadGlyphOutlineError<Provider::ReadFontError>> {
profiling::scope!("MsdfGenerator::read_glyph");
self.cached_segment_vec.clear();
let units_per_em = provider.length_per_em();
let mut collector = SegmentCollector::new(
&mut self.cached_segment_vec,
units_per_em * self.segment_deviation_threshold,
);
let bound = provider.collect_outline(&mut collector)?;
if self.cached_segment_vec.is_empty() {
return Err(ReadGlyphOutlineError::EmptyGlyph);
}
let closest_non_intesecting_dist = remove_intersecting_shapes(
&mut self.cached_segment_vec,
&mut self.intersect_remove_state,
);
self.cached_soa.populate(&self.cached_segment_vec);
let bound_size = bound.max - bound.min;
Ok(GlyphOutline {
generator: self,
width: bound_size.x,
height: bound_size.y,
units_per_em,
closest_non_intesecting_dist,
bound,
})
}
}
/// Parsed outline of a glyph.
///
/// The lifetime is bound to the [`MsdfGenerator`] that produced this instance: you can not use the
/// generator again until this instance is dropped.
pub struct GlyphOutline<'a> {
pub(crate) generator: &'a mut MsdfGenerator,
pub(crate) units_per_em: f32,
pub(crate) height: f32,
pub(crate) width: f32,
pub(crate) closest_non_intesecting_dist: f32,
pub(crate) bound: Aabb,
}
/// Region of a texture atlas.
///
/// The total region uploaded to the atlas is `inner_width + padding_x * 2`
/// wide and `inner_height + padding_y * 2` tall. The inner region holds the
/// glyph; the padding around it carries extra signed-distance information.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AtlasRegionSize {
/// Width of the inner (glyph) region, in pixels.
pub inner_width: usize,
/// Height of the inner (glyph) region, in pixels.
pub inner_height: usize,
/// Padding around the inner region on the x axis, in pixels.
pub padding_x: usize,
/// Padding around the inner region on the y axis, in pixels.
pub padding_y: usize,
}
impl AtlasRegionSize {
/// Total width of the region (inner + padding on both sides).
pub fn total_width(&self) -> usize {
self.inner_width + self.padding_x * 2
}
/// Total height of the region (inner + padding on both sides).
pub fn total_height(&self) -> usize {
self.inner_height + self.padding_y * 2
}
}
impl<'a> GlyphOutline<'a> {
/// Width of the glyph bounding box, in em.
pub fn width_em(&self) -> f32 {
self.width / self.units_per_em
}
/// Height of the glyph bounding box, in em.
pub fn height_em(&self) -> f32 {
self.height / self.units_per_em
}
/// X coordinate of the left edge of the glyph bounding box, in em, relative to the pen origin.
pub fn min_x_em(&self) -> f32 {
self.bound.min.x / self.units_per_em
}
/// Y coordinate of the bottom edge of the glyph bounding box, in em, relative to the baseline.
/// Positive is above baseline, negative is below.
pub fn min_y_em(&self) -> f32 {
self.bound.min.y / self.units_per_em
}
/// Length in font unit corresponding to 1em.
pub fn font_unit_length_per_em(&self) -> f32 {
self.units_per_em
}
/// Closest distance between two segments of the glyph that don't intersect, in em.
///
/// You may decide to allocate a larger region to a glyph if this distance is small,
/// since a smaller distance indicates finer details.
pub fn closest_non_intesecting_dist_em(&self) -> f32 {
self.closest_non_intesecting_dist / self.units_per_em
}
/// Generate MSDF for the glyph.
///
/// # Parameters
/// - `size`: Specifies size and padding of the atlas region this glyph belongs to.
/// - `msdf_pixel_type`: Format of each pixel. For uploading to GPU, use
/// [`MsdfPixelFormat::Rgba8`].
///
/// # Returns
/// The raw bytes data in row-major, RGB format (indexed by `(x + y * width) * 3`).
pub fn generate_msdf(&mut self, size: AtlasRegionSize, pixel_format: MsdfPixelFormat) -> &[u8] {
profiling::scope!("GlyphOutline::generate_msdf");
let total_w = size.total_width();
let total_h = size.total_height();
self.generator
.cached_color
.resize(total_w * total_h * pixel_format.bytes_per_pixel(), 0);
self.generator.cached_color.fill(0);
let bound = self.bound;
let bound_size = self.bound.max - self.bound.min;
for x in 0..total_w {
let xt = (x as f32 + 0.5 - size.padding_x as f32) / size.inner_width as f32;
let xbound = bound.min.x + bound_size.x * xt;
for y in 0..total_h {
let yt = (y as f32 + 0.5 - size.padding_y as f32) / size.inner_height as f32;
let ybound = bound.min.y + bound_size.y * yt;
let pos = glam::vec2(xbound, ybound);
let dists = compute_msdf(
pos,
&self.generator.cached_soa,
&self.generator.cached_segment_vec,
self.units_per_em,
);
let offset = ((total_h - y - 1) * total_w + x) * pixel_format.bytes_per_pixel();
pixel_format.write(
&mut self.generator.cached_color,
offset,
dists[0],
dists[1],
dists[2],
);
}
}
&self.generator.cached_color
}
/// Generate MTSDF for the glyph.
///
/// # Parameters
/// - `size`: Specifies size and padding of the atlas region this glyph belongs to.
///
/// # Returns
/// The raw bytes data in row-major, RGBA format (indexed by `(x + y * width) * 4`).
pub fn generate_mtsdf(&mut self, size: AtlasRegionSize) -> &[u8] {
profiling::scope!("GlyphOutline::generate_mtsdf");
let total_w = size.total_width();
let total_h = size.total_height();
self.generator.cached_color.resize(total_w * total_h * 4, 0);
self.generator.cached_color.fill(0);
let bound = self.bound;
let bound_size = self.bound.max - self.bound.min;
for x in 0..total_w {
let xt = (x as f32 + 0.5 - size.padding_x as f32) / size.inner_width as f32;
let xbound = bound.min.x + bound_size.x * xt;
for y in 0..total_h {
let yt = (y as f32 + 0.5 - size.padding_y as f32) / size.inner_height as f32;
let ybound = bound.min.y + bound_size.y * yt;
let pos = glam::vec2(xbound, ybound);
let color = compute_mtsdf(
pos,
&self.generator.cached_soa,
&self.generator.cached_segment_vec,
self.units_per_em,
);
let offset = ((total_h - y - 1) * total_w + x) * 4;
self.generator.cached_color[offset] = color[0];
self.generator.cached_color[offset + 1] = color[1];
self.generator.cached_color[offset + 2] = color[2];
self.generator.cached_color[offset + 3] = color[3];
}
}
&self.generator.cached_color
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum MsdfPixelFormat {
/// 32 bits per pixel, RGBA format, with unused alpha channel.
/// Useful for directly uploading to GPU.
#[default]
Rgba8,
/// 24 bits per pixel, 8 bits for RGB channels.
/// Useful for outputting to images.
PackedRgb8,
// TODO: Consider suppporting other formats like Rgb10a2, Rg11b10 or Rgb9e5.
}
impl MsdfPixelFormat {
pub fn bytes_per_pixel(&self) -> usize {
match self {
MsdfPixelFormat::Rgba8 => 4,
MsdfPixelFormat::PackedRgb8 => 3,
}
}
pub fn write(&self, bytes: &mut [u8], offset_bytes: usize, vr: f32, vg: f32, vb: f32) {
match self {
MsdfPixelFormat::Rgba8 => {
bytes[offset_bytes] = (vr * 255.) as u8;
bytes[offset_bytes + 1] = (vg * 255.) as u8;
bytes[offset_bytes + 2] = (vb * 255.) as u8;
bytes[offset_bytes + 3] = 0;
}
MsdfPixelFormat::PackedRgb8 => {
bytes[offset_bytes] = (vr * 255.) as u8;
bytes[offset_bytes + 1] = (vg * 255.) as u8;
bytes[offset_bytes + 2] = (vb * 255.) as u8;
}
}
}
}