1 #![warn(missing_docs)]
2 #![allow(dead_code)]
3 //! Interface to Harfbuzz's WASM exports
4 //!
5 //! This crate is designed to make it easier to write a
6 //! WASM shaper for your font using Rust. It binds the
7 //! functions exported by Harfbuzz into the WASM runtime,
8 //! and wraps them in an ergonomic interface using Rust
9 //! structures. For example, here is a basic shaping engine:
10 //!
11 //!
12 //! ```rust
13 //! #[wasm_bindgen]
14 //! pub fn shape(font_ref: u32, buf_ref: u32) -> i32 {
15 //! let font = Font::from_ref(font_ref);
16 //! let mut buffer = GlyphBuffer::from_ref(buf_ref);
17 //! for mut item in buffer.glyphs.iter_mut() {
18 //! // Map character to glyph
19 //! item.codepoint = font.get_glyph(codepoint, 0);
20 //! // Set advance width
21 //! item.h_advance = font.get_glyph_h_advance(item.codepoint);
22 //! }
23 //! 1
24 //! }
25 //! ```
26 use std::ffi::{c_int, CStr, CString};
27
28 #[cfg(feature = "kurbo")]
29 use kurbo::BezPath;
30
31 // We don't use #[wasm_bindgen] here because that makes
32 // assumptions about Javascript calling conventions. We
33 // really do just want to import some C symbols and run
34 // them in unsafe-land!
35 extern "C" {
face_get_upem(face: u32) -> u3236 fn face_get_upem(face: u32) -> u32;
font_get_face(font: u32) -> u3237 fn font_get_face(font: u32) -> u32;
font_get_glyph(font: u32, unicode: u32, uvs: u32) -> u3238 fn font_get_glyph(font: u32, unicode: u32, uvs: u32) -> u32;
font_get_scale(font: u32, x_scale: *mut i32, y_scale: *mut i32)39 fn font_get_scale(font: u32, x_scale: *mut i32, y_scale: *mut i32);
font_get_glyph_extents(font: u32, glyph: u32, extents: *mut CGlyphExtents) -> bool40 fn font_get_glyph_extents(font: u32, glyph: u32, extents: *mut CGlyphExtents) -> bool;
font_glyph_to_string(font: u32, glyph: u32, str: *const u8, len: u32)41 fn font_glyph_to_string(font: u32, glyph: u32, str: *const u8, len: u32);
font_get_glyph_h_advance(font: u32, glyph: u32) -> i3242 fn font_get_glyph_h_advance(font: u32, glyph: u32) -> i32;
font_get_glyph_v_advance(font: u32, glyph: u32) -> i3243 fn font_get_glyph_v_advance(font: u32, glyph: u32) -> i32;
font_copy_glyph_outline(font: u32, glyph: u32, outline: *mut CGlyphOutline) -> bool44 fn font_copy_glyph_outline(font: u32, glyph: u32, outline: *mut CGlyphOutline) -> bool;
face_copy_table(font: u32, tag: u32, blob: *mut Blob) -> bool45 fn face_copy_table(font: u32, tag: u32, blob: *mut Blob) -> bool;
buffer_copy_contents(buffer: u32, cbuffer: *mut CBufferContents) -> bool46 fn buffer_copy_contents(buffer: u32, cbuffer: *mut CBufferContents) -> bool;
buffer_set_contents(buffer: u32, cbuffer: &CBufferContents) -> bool47 fn buffer_set_contents(buffer: u32, cbuffer: &CBufferContents) -> bool;
debugprint(s: *const u8)48 fn debugprint(s: *const u8);
shape_with( font: u32, buffer: u32, features: u32, num_features: u32, shaper: *const u8, ) -> i3249 fn shape_with(
50 font: u32,
51 buffer: u32,
52 features: u32,
53 num_features: u32,
54 shaper: *const u8,
55 ) -> i32;
56 }
57
58 /// An opaque reference to a font at a given size and
59 /// variation. It is equivalent to the `hb_font_t` pointer
60 /// in Harfbuzz.
61 #[derive(Debug)]
62 pub struct Font(u32);
63
64 impl Font {
65 /// Initialize a `Font` struct from the reference provided
66 /// by Harfbuzz to the `shape` function.
from_ref(ptr: u32) -> Self67 pub fn from_ref(ptr: u32) -> Self {
68 Self(ptr)
69 }
70 /// Call the given Harfbuzz shaper on a buffer reference.
71 ///
72 /// For example, `font.shape_with(buffer_ref, "ot")` will
73 /// run standard OpenType shaping, allowing you to modify
74 /// the buffer contents after glyph mapping, substitution
75 /// and positioning has taken place.
shape_with(&self, buffer_ref: u32, shaper: &str)76 pub fn shape_with(&self, buffer_ref: u32, shaper: &str) {
77 let c_shaper = CString::new(shaper).unwrap();
78 unsafe {
79 shape_with(self.0, buffer_ref, 0, 0, c_shaper.as_ptr() as *const u8);
80 }
81 }
82
83 /// Return the font face object that this font belongs to.
get_face(&self) -> Face84 pub fn get_face(&self) -> Face {
85 Face(unsafe { font_get_face(self.0) })
86 }
87
88 /// Map a Unicode codepoint to a glyph ID.
89 ///
90 /// The `uvs` parameter specifies a Unicode Variation
91 /// Selector codepoint which is used in conjunction with
92 /// [format 14 cmap tables](https://learn.microsoft.com/en-us/typography/opentype/spec/cmap#format-14-unicode-variation-sequences)
93 /// to provide alternate glyph mappings for characters with
94 /// Unicode Variation Sequences. Generally you will pass
95 /// `0`.
get_glyph(&self, unicode: u32, uvs: u32) -> u3296 pub fn get_glyph(&self, unicode: u32, uvs: u32) -> u32 {
97 unsafe { font_get_glyph(self.0, unicode, uvs) }
98 }
99
100 /// Get the extents for a given glyph ID, in its design position.
get_glyph_extents(&self, glyph: u32) -> CGlyphExtents101 pub fn get_glyph_extents(&self, glyph: u32) -> CGlyphExtents {
102 let mut extents = CGlyphExtents::default();
103 unsafe {
104 font_get_glyph_extents(self.0, glyph, &mut extents);
105 }
106 extents
107 }
108
109 /// Get the default advance width for a given glyph ID.
get_glyph_h_advance(&self, glyph: u32) -> i32110 pub fn get_glyph_h_advance(&self, glyph: u32) -> i32 {
111 unsafe { font_get_glyph_h_advance(self.0, glyph) }
112 }
113
114 /// Get the default vertical advance for a given glyph ID.
get_glyph_v_advance(&self, glyph: u32) -> i32115 fn get_glyph_v_advance(&self, glyph: u32) -> i32 {
116 unsafe { font_get_glyph_v_advance(self.0, glyph) }
117 }
118
119 /// Get the name of a glyph.
120 ///
121 /// If no names are provided by the font, names of the form
122 /// `gidXXX` are constructed.
get_glyph_name(&self, glyph: u32) -> String123 pub fn get_glyph_name(&self, glyph: u32) -> String {
124 let mut s = [1u8; 32];
125 unsafe {
126 font_glyph_to_string(self.0, glyph, s.as_mut_ptr(), 32);
127 }
128 unsafe { CStr::from_ptr(s.as_ptr() as *const _) }
129 .to_str()
130 .unwrap()
131 .to_string()
132 }
133
134 /// Get the X and Y scale factor applied to this font.
135 ///
136 /// This should be divided by the units per em value to
137 /// provide a scale factor mapping from design units to
138 /// user units. (See [`Face::get_upem`].)
get_scale(&self) -> (i32, i32)139 pub fn get_scale(&self) -> (i32, i32) {
140 let mut x_scale: i32 = 0;
141 let mut y_scale: i32 = 0;
142 unsafe {
143 font_get_scale(
144 self.0,
145 &mut x_scale as *mut c_int,
146 &mut y_scale as *mut c_int,
147 )
148 };
149 (x_scale, y_scale)
150 }
151
152 #[cfg(feature = "kurbo")]
153 /// Get the outline of a glyph as a vector of bezier paths
get_outline(&self, glyph: u32) -> Vec<BezPath>154 pub fn get_outline(&self, glyph: u32) -> Vec<BezPath> {
155 let mut outline = CGlyphOutline {
156 n_points: 0,
157 points: std::ptr::null_mut(),
158 n_contours: 0,
159 contours: std::ptr::null_mut(),
160 };
161 let end_pts_of_contours: &[usize] = unsafe {
162 font_copy_glyph_outline(self.0, glyph, &mut outline);
163 std::slice::from_raw_parts(outline.contours, outline.n_contours as usize)
164 };
165 let points: &[CGlyphOutlinePoint] =
166 unsafe { std::slice::from_raw_parts(outline.points, outline.n_points as usize) };
167 let mut results: Vec<BezPath> = vec![];
168 let mut start_pt: usize = 0;
169 for end_pt in end_pts_of_contours {
170 let this_contour = &points[start_pt..*end_pt];
171 start_pt = *end_pt;
172 let mut path = BezPath::new();
173 let mut ix = 0;
174 while ix < this_contour.len() {
175 let point = &this_contour[ix];
176 match point.pointtype {
177 PointType::MoveTo => path.move_to((point.x as f64, point.y as f64)),
178 PointType::LineTo => path.line_to((point.x as f64, point.y as f64)),
179 PointType::QuadraticTo => {
180 ix += 1;
181 let end_pt = &this_contour[ix];
182 path.quad_to(
183 (point.x as f64, point.y as f64),
184 (end_pt.x as f64, end_pt.y as f64),
185 );
186 }
187 PointType::CubicTo => {
188 ix += 1;
189 let mid_pt = &this_contour[ix];
190 ix += 1;
191 let end_pt = &this_contour[ix];
192 path.curve_to(
193 (point.x as f64, point.y as f64),
194 (mid_pt.x as f64, mid_pt.y as f64),
195 (end_pt.x as f64, end_pt.y as f64),
196 );
197 }
198 }
199 ix += 1;
200 }
201 path.close_path();
202 results.push(path);
203 }
204 results
205 }
206 }
207
208 /// An opaque reference to a font face, equivalent to the `hb_face_t` pointer
209 /// in Harfbuzz.
210 ///
211 /// This is generally returned from [`Font::get_face`].
212 #[derive(Debug)]
213 pub struct Face(u32);
214
215 impl Face {
216 /// Get a blob containing the contents of the given binary font table.
reference_table(&self, tag: &str) -> Blob217 pub fn reference_table(&self, tag: &str) -> Blob {
218 let mut tag_u: u32 = 0;
219 let mut chars = tag.chars();
220 tag_u |= (chars.next().unwrap() as u32) << 24;
221 tag_u |= (chars.next().unwrap() as u32) << 16;
222 tag_u |= (chars.next().unwrap() as u32) << 8;
223 tag_u |= chars.next().unwrap() as u32;
224 let mut blob = Blob {
225 data: std::ptr::null_mut(),
226 length: 0,
227 };
228 unsafe {
229 face_copy_table(self.0, tag_u, &mut blob);
230 }
231 blob
232 }
233
234 /// Get the face's design units per em.
get_upem(&self) -> u32235 pub fn get_upem(&self) -> u32 {
236 unsafe { face_get_upem(self.0) }
237 }
238 }
239
240 /// Trait implemented by custom structs representing buffer items
241 pub trait BufferItem {
242 /// Construct an item in your preferred representation out of the info and position data provided by Harfbuzz.
from_c(info: CGlyphInfo, position: CGlyphPosition) -> Self243 fn from_c(info: CGlyphInfo, position: CGlyphPosition) -> Self;
244 /// Return info and position data to Harfbuzz.
to_c(self) -> (CGlyphInfo, CGlyphPosition)245 fn to_c(self) -> (CGlyphInfo, CGlyphPosition);
246 }
247
248 /// Generic representation of a Harfbuzz buffer item.
249 ///
250 /// By making this generic, we allow you to implement your own
251 /// representations of buffer items; for example, in your shaper,
252 /// you may want certain fields to keep track of the glyph's name,
253 /// extents, or shape, so you would want a custom struct to represent
254 /// buffer items. If you don't care about any of them, use the
255 /// supplied `GlyphBuffer` struct.
256 #[derive(Debug)]
257 pub struct Buffer<T: BufferItem> {
258 _ptr: u32,
259 /// Glyphs in the buffer
260 pub glyphs: Vec<T>,
261 }
262
263 impl<T: BufferItem> Buffer<T> {
264 /// Construct a buffer from the pointer Harfbuzz provides to the WASM.
265 ///
266 /// The `Buffer` struct implements Drop, meaning that when the shaping
267 /// function is finished, the buffer contents are sent back to Harfbuzz.
from_ref(ptr: u32) -> Self268 pub fn from_ref(ptr: u32) -> Self {
269 let mut c_contents = CBufferContents {
270 info: std::ptr::null_mut(),
271 position: std::ptr::null_mut(),
272 length: 0,
273 };
274
275 unsafe {
276 buffer_copy_contents(ptr, &mut c_contents) || panic!("Couldn't copy buffer contents")
277 };
278 let positions: Vec<CGlyphPosition> = unsafe {
279 std::slice::from_raw_parts(c_contents.position, c_contents.length as usize).to_vec()
280 };
281 let infos: Vec<CGlyphInfo> = unsafe {
282 std::slice::from_raw_parts(c_contents.info, c_contents.length as usize).to_vec()
283 };
284 Buffer {
285 glyphs: infos
286 .into_iter()
287 .zip(positions.into_iter())
288 .map(|(i, p)| T::from_c(i, p))
289 .collect(),
290 _ptr: ptr,
291 }
292 }
293 }
294
295 impl<T: BufferItem> Drop for Buffer<T> {
drop(&mut self)296 fn drop(&mut self) {
297 let mut positions: Vec<CGlyphPosition>;
298 let mut infos: Vec<CGlyphInfo>;
299 let glyphs = std::mem::take(&mut self.glyphs);
300 (infos, positions) = glyphs.into_iter().map(|g| g.to_c()).unzip();
301 let c_contents = CBufferContents {
302 length: positions.len() as u32,
303 info: infos[..].as_mut_ptr(),
304 position: positions[..].as_mut_ptr(),
305 };
306 unsafe {
307 if !buffer_set_contents(self._ptr, &c_contents) {
308 panic!("Couldn't set buffer contents");
309 }
310 }
311 }
312 }
313
314 /// Some data provided by Harfbuzz.
315 #[derive(Debug)]
316 #[repr(C)]
317 pub struct Blob {
318 /// Length of the blob in bytes
319 pub length: u32,
320 /// A raw pointer to the contents
321 pub data: *mut u8,
322 }
323
324 /// Glyph information in a buffer item provided by Harfbuzz
325 ///
326 /// You'll only need to interact with this if you're writing
327 /// your own buffer item structure.
328 #[repr(C)]
329 #[derive(Debug, Clone)]
330 pub struct CGlyphInfo {
331 pub codepoint: u32,
332 pub mask: u32,
333 pub cluster: u32,
334 pub var1: u32,
335 pub var2: u32,
336 }
337
338 /// Glyph positioning information in a buffer item provided by Harfbuzz
339 ///
340 /// You'll only need to interact with this if you're writing
341 /// your own buffer item structure.
342 #[derive(Debug, Clone)]
343 #[repr(C)]
344 pub struct CGlyphPosition {
345 pub x_advance: i32,
346 pub y_advance: i32,
347 pub x_offset: i32,
348 pub y_offset: i32,
349 pub var: u32,
350 }
351
352 /// Glyph extents
353 #[derive(Debug, Clone, Default)]
354 #[repr(C)]
355 pub struct CGlyphExtents {
356 /// The scaled left side bearing of the glyph
357 pub x_bearing: i32,
358 /// The scaled coordinate of the top of the glyph
359 pub y_bearing: i32,
360 /// The width of the glyph
361 pub width: i32,
362 /// The height of the glyph
363 pub height: i32,
364 }
365
366 #[derive(Debug)]
367 #[repr(C)]
368 struct CBufferContents {
369 length: u32,
370 info: *mut CGlyphInfo,
371 position: *mut CGlyphPosition,
372 }
373
374 /// Ergonomic representation of a Harfbuzz buffer item
375 ///
376 /// Harfbuzz buffers are normally split into two arrays,
377 /// one representing glyph information and the other
378 /// representing glyph positioning. In Rust, this would
379 /// require lots of zipping and unzipping, so we zip them
380 /// together into a single structure for you.
381 #[derive(Debug, Clone, Copy)]
382 pub struct Glyph {
383 /// The Unicode codepoint or glyph ID of the item
384 pub codepoint: u32,
385 /// The index of the cluster in the input text where this came from
386 pub cluster: u32,
387 /// The horizontal advance of the glyph
388 pub x_advance: i32,
389 /// The vertical advance of the glyph
390 pub y_advance: i32,
391 /// The horizontal offset of the glyph
392 pub x_offset: i32,
393 /// The vertical offset of the glyph
394 pub y_offset: i32,
395 /// You can use this for whatever you like
396 pub flags: u32,
397 }
398 impl BufferItem for Glyph {
from_c(info: CGlyphInfo, pos: CGlyphPosition) -> Self399 fn from_c(info: CGlyphInfo, pos: CGlyphPosition) -> Self {
400 Self {
401 codepoint: info.codepoint,
402 cluster: info.cluster,
403 x_advance: pos.x_advance,
404 y_advance: pos.y_advance,
405 x_offset: pos.x_offset,
406 y_offset: pos.y_offset,
407 flags: 0,
408 }
409 }
to_c(self) -> (CGlyphInfo, CGlyphPosition)410 fn to_c(self) -> (CGlyphInfo, CGlyphPosition) {
411 let info = CGlyphInfo {
412 codepoint: self.codepoint,
413 cluster: self.cluster,
414 mask: 0,
415 var1: 0,
416 var2: 0,
417 };
418 let pos = CGlyphPosition {
419 x_advance: self.x_advance,
420 y_advance: self.y_advance,
421 x_offset: self.x_offset,
422 y_offset: self.y_offset,
423 var: 0,
424 };
425 (info, pos)
426 }
427 }
428
429 #[repr(C)]
430 #[allow(clippy::enum_variant_names)]
431 #[derive(Clone, Debug)]
432 enum PointType {
433 MoveTo,
434 LineTo,
435 QuadraticTo,
436 CubicTo,
437 }
438
439 #[repr(C)]
440 #[derive(Clone, Debug)]
441 struct CGlyphOutlinePoint {
442 x: f32,
443 y: f32,
444 pointtype: PointType,
445 }
446
447 #[repr(C)]
448 struct CGlyphOutline {
449 n_points: usize,
450 points: *mut CGlyphOutlinePoint,
451 n_contours: usize,
452 contours: *mut usize,
453 }
454
455 /// Our default buffer item struct. See also [`Glyph`].
456 pub type GlyphBuffer = Buffer<Glyph>;
457
458 /// Write a string to the Harfbuzz debug log.
debug(s: &str)459 pub fn debug(s: &str) {
460 let c_s = CString::new(s).unwrap();
461 unsafe {
462 debugprint(c_s.as_ptr() as *const u8);
463 };
464 }
465