From 514a8d58d8820b7b21e2f25933bc9b0d6ea70672 Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Sat, 25 Feb 2023 14:42:13 +0000 Subject: [PATCH] [wasm-api] Add ergonomic Rust interface --- src/wasm/rust/harfbuzz-wasm/Cargo.toml | 8 + src/wasm/rust/harfbuzz-wasm/src/lib.rs | 364 +++++++++++++++++++++++++ 2 files changed, 372 insertions(+) create mode 100644 src/wasm/rust/harfbuzz-wasm/Cargo.toml create mode 100644 src/wasm/rust/harfbuzz-wasm/src/lib.rs diff --git a/src/wasm/rust/harfbuzz-wasm/Cargo.toml b/src/wasm/rust/harfbuzz-wasm/Cargo.toml new file mode 100644 index 000000000..0dfb3e808 --- /dev/null +++ b/src/wasm/rust/harfbuzz-wasm/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "harfbuzz-wasm" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/wasm/rust/harfbuzz-wasm/src/lib.rs b/src/wasm/rust/harfbuzz-wasm/src/lib.rs new file mode 100644 index 000000000..b65538c7f --- /dev/null +++ b/src/wasm/rust/harfbuzz-wasm/src/lib.rs @@ -0,0 +1,364 @@ +#![warn(missing_docs)] +#![allow(dead_code)] +//! Interface to Harfbuzz's WASM exports +//! +//! This crate is designed to make it easier to write a +//! WASM shaper for your font using Rust. It binds the +//! functions exported by Harfbuzz into the WASM runtime, +//! and wraps them in an ergonomic interface using Rust +//! structures. For example, here is a basic shaping engine: +//! +//! +//! ```rust +//! #[wasm_bindgen] +//! pub fn shape(font_ref: u32, buf_ref: u32) -> i32 { +//! let font = Font::from_ref(font_ref); +//! let mut buffer = GlyphBuffer::from_ref(buf_ref); +//! for mut item in buffer.glyphs.iter_mut() { +//! // Map character to glyph +//! item.codepoint = font.get_glyph(codepoint, 0); +//! // Set advance width +//! item.h_advance = font.get_glyph_h_advance(item.codepoint); +//! } +//! 1 +//! } +//! ``` +use std::{ + ffi::{c_int, CStr, CString}, + mem, +}; + +// We don't use #[wasm_bindgen] here because that makes +// assumptions about Javascript calling conventions. We +// really do just want to import some C symbols and run +// them in unsafe-land! +extern "C" { + fn face_get_upem(face: u32) -> u32; + fn font_get_face(font: u32) -> u32; + fn font_get_glyph(font: u32, unicode: u32, uvs: u32) -> u32; + fn font_get_scale(font: u32, x_scale: *mut i32, y_scale: *mut i32); + fn font_get_glyph_extents(font: u32, glyph: u32, extents: *mut CGlyphExtents) -> bool; + fn font_glyph_to_string(font: u32, glyph: u32, str: *const u8, len: u32); + fn font_get_glyph_h_advance(font: u32, glyph: u32) -> i32; + fn font_get_glyph_v_advance(font: u32, glyph: u32) -> i32; + fn face_reference_table(font: u32, tag: u32) -> Blob; + fn buffer_copy_contents(buffer: u32) -> CBufferContents; + fn buffer_set_contents(buffer: u32, cbuffer: &CBufferContents) -> bool; + fn debugprint(s: *const u8); + fn shape_with( + font: u32, + buffer: u32, + features: u32, + num_features: u32, + shaper: *const u8, + ) -> i32; +} + +/// An opaque reference to a font at a given size and +/// variation. It is equivalent to the `hb_font_t` pointer +/// in Harfbuzz. +#[derive(Debug)] +pub struct Font(u32); + +impl Font { + /// Initialize a `Font` struct from the reference provided + /// by Harfbuzz to the `shape` function. + pub fn from_ref(ptr: u32) -> Self { + Self(ptr) + } + /// Call the given Harfbuzz shaper on a buffer reference. + /// + /// For example, `font.shape_with(buffer_ref, "ot")` will + /// run standard OpenType shaping, allowing you to modify + /// the buffer contents after glyph mapping, substitution + /// and positioning has taken place. + pub fn shape_with(&self, buffer_ref: u32, shaper: &str) { + let c_shaper = CString::new(shaper).unwrap(); + unsafe { + shape_with(self.0, buffer_ref, 0, 0, c_shaper.as_ptr() as *const u8); + } + } + + /// Return the font face object that this font belongs to. + pub fn get_face(&self) -> Face { + Face(unsafe { font_get_face(self.0) }) + } + + /// Map a Unicode codepoint to a glyph ID. + /// + /// The `uvs` parameter specifies a Unicode Variation + /// Selector codepoint which is used in conjunction with + /// [format 14 cmap tables](https://learn.microsoft.com/en-us/typography/opentype/spec/cmap#format-14-unicode-variation-sequences) + /// to provide alternate glyph mappings for characters with + /// Unicode Variation Sequences. Generally you will pass + /// `0`. + pub fn get_glyph(&self, unicode: u32, uvs: u32) -> u32 { + unsafe { font_get_glyph(self.0, unicode, uvs) } + } + + /// Get the extents for a given glyph ID, in its design position. + pub fn get_glyph_extents(&self, glyph: u32) -> CGlyphExtents { + let mut extents = CGlyphExtents::default(); + unsafe { + font_get_glyph_extents(self.0, glyph, &mut extents); + } + extents + } + + /// Get the default advance width for a given glyph ID. + pub fn get_glyph_h_advance(&self, glyph: u32) -> i32 { + unsafe { font_get_glyph_h_advance(self.0, glyph) } + } + + /// Get the default vertical advance for a given glyph ID. + fn get_glyph_v_advance(&self, glyph: u32) -> i32 { + unsafe { font_get_glyph_v_advance(self.0, glyph) } + } + + /// Get the name of a glyph. + /// + /// If no names are provided by the font, names of the form + /// `gidXXX` are constructed. + pub fn get_glyph_name(&self, glyph: u32) -> String { + let mut s = [1u8; 32]; + unsafe { + font_glyph_to_string(self.0, glyph, s.as_mut_ptr(), 32); + } + unsafe { CStr::from_ptr(s.as_ptr() as *const _) } + .to_str() + .unwrap() + .to_string() + } + + /// Get the X and Y scale factor applied to this font. + /// + /// This should be divided by the units per em value to + /// provide a scale factor mapping from design units to + /// user units. (See [`Face::get_upem`].) + pub fn get_scale(&self) -> (i32, i32) { + let mut x_scale: i32 = 0; + let mut y_scale: i32 = 0; + unsafe { + font_get_scale( + self.0, + &mut x_scale as *mut c_int, + &mut y_scale as *mut c_int, + ) + }; + (x_scale, y_scale) + } +} + +/// An opaque reference to a font face, equivalent to the `hb_face_t` pointer +/// in Harfbuzz. +/// +/// This is generally returned from [`Font::get_face`]. +#[derive(Debug)] +pub struct Face(u32); + +impl Face { + /// Get a blob containing the contents of the given binary font table. + pub fn reference_table(&self, tag: &str) -> Blob { + let mut tag_u: u32 = 0; + let mut chars = tag.chars(); + tag_u |= (chars.next().unwrap() as u32) << 24; + tag_u |= (chars.next().unwrap() as u32) << 16; + tag_u |= (chars.next().unwrap() as u32) << 8; + tag_u |= chars.next().unwrap() as u32; + unsafe { face_reference_table(self.0, tag_u) } + } + + /// Get the face's design units per em. + pub fn get_upem(&self) -> u32 { + unsafe { face_get_upem(self.0) } + } +} + +/// Trait implemented by custom structs representing buffer items +pub trait BufferItem { + /// Construct an item in your preferred representation out of the info and position data provided by Harfbuzz. + fn from_c(info: CGlyphInfo, position: CGlyphPosition) -> Self; + /// Return info and position data to Harfbuzz. + fn to_c(self) -> (CGlyphInfo, CGlyphPosition); +} + +/// Generic representation of a Harfbuzz buffer item. +/// +/// By making this generic, we allow you to implement your own +/// representations of buffer items; for example, in your shaper, +/// you may want certain fields to keep track of the glyph's name, +/// extents, or shape, so you would want a custom struct to represent +/// buffer items. If you don't care about any of them, use the +/// supplied `GlyphBuffer` struct. +#[derive(Debug)] +pub struct Buffer { + _ptr: u32, + /// Glyphs in the buffer + pub glyphs: Vec, +} + +impl Buffer { + /// Construct a buffer from the pointer Harfbuzz provides to the WASM. + /// + /// The `Buffer` struct implements Drop, meaning that when the shaping + /// function is finished, the buffer contents are sent back to Harfbuzz. + pub fn from_ref(ptr: u32) -> Self { + let c_contents: CBufferContents = unsafe { buffer_copy_contents(ptr) }; + let positions: Vec = unsafe { + std::slice::from_raw_parts(c_contents.position, c_contents.length as usize).to_vec() + }; + let infos: Vec = unsafe { + std::slice::from_raw_parts(c_contents.info, c_contents.length as usize).to_vec() + }; + Buffer { + glyphs: infos + .into_iter() + .zip(positions.into_iter()) + .map(|(i, p)| T::from_c(i, p)) + .collect(), + _ptr: ptr, + } + } +} + +impl Drop for Buffer { + fn drop(&mut self) { + let mut positions: Vec; + let mut infos: Vec; + let glyphs = mem::take(&mut self.glyphs); + (infos, positions) = glyphs.into_iter().map(|g| g.to_c()).unzip(); + let c_contents = CBufferContents { + length: positions.len() as u32, + info: infos[..].as_mut_ptr(), + position: positions[..].as_mut_ptr(), + }; + unsafe { + buffer_set_contents(self._ptr, &c_contents); + } + } +} + +/// Some data provided by Harfbuzz. +#[derive(Debug)] +#[repr(C)] +pub struct Blob { + /// Length of the blob in bytes + pub length: u32, + /// A raw pointer to the contents + pub data: *mut u8, +} + +/// Glyph information in a buffer item provided by Harfbuzz +/// +/// You'll only need to interact with this if you're writing +/// your own buffer item structure. +#[repr(C)] +#[derive(Debug, Clone)] +pub struct CGlyphInfo { + pub codepoint: u32, + pub mask: u32, + pub cluster: u32, + pub var1: u32, + pub var2: u32, +} + +/// Glyph positioning information in a buffer item provided by Harfbuzz +/// +/// You'll only need to interact with this if you're writing +/// your own buffer item structure. +#[derive(Debug, Clone)] +#[repr(C)] +pub struct CGlyphPosition { + pub x_advance: i32, + pub y_advance: i32, + pub x_offset: i32, + pub y_offset: i32, + pub var: u32, +} + +/// Glyph extents +#[derive(Debug, Clone, Default)] +#[repr(C)] +pub struct CGlyphExtents { + /// The scaled left side bearing of the glyph + pub x_bearing: i32, + /// The scaled coordinate of the top of the glyph + pub y_bearing: i32, + /// The width of the glyph + pub width: i32, + /// The height of the glyph + pub height: i32, +} + +#[derive(Debug)] +#[repr(C)] +struct CBufferContents { + length: u32, + info: *mut CGlyphInfo, + position: *mut CGlyphPosition, +} + +/// Ergonomic representation of a Harfbuzz buffer item +/// +/// Harfbuzz buffers are normally split into two arrays, +/// one representing glyph information and the other +/// representing glyph positioning. In Rust, this would +/// require lots of zipping and unzipping, so we zip them +/// together into a single structure for you. +pub struct Glyph { + /// The Unicode codepoint or glyph ID of the item + pub codepoint: u32, + /// The index of the cluster in the input text where this came from + pub cluster: u32, + /// The horizontal advance of the glyph + pub x_advance: i32, + /// The vertical advance of the glyph + pub y_advance: i32, + /// The horizontal offset of the glyph + pub x_offset: i32, + /// The vertical offset of the glyph + pub y_offset: i32, + /// You can use this for whatever you like + pub flags: u32, +} +impl BufferItem for Glyph { + fn from_c(info: CGlyphInfo, pos: CGlyphPosition) -> Self { + Self { + codepoint: info.codepoint, + cluster: info.cluster, + x_advance: pos.x_advance, + y_advance: pos.y_advance, + x_offset: pos.x_offset, + y_offset: pos.y_offset, + flags: 0, + } + } + fn to_c(self) -> (CGlyphInfo, CGlyphPosition) { + let info = CGlyphInfo { + codepoint: self.codepoint, + cluster: self.cluster, + mask: 0, + var1: 0, + var2: 0, + }; + let pos = CGlyphPosition { + x_advance: self.x_advance, + y_advance: self.y_advance, + x_offset: self.x_offset, + y_offset: self.y_offset, + var: 0, + }; + (info, pos) + } +} + +/// Our default buffer item struct. See also [`Glyph`]. +pub type GlyphBuffer = Buffer; + +/// Write a string to the Harfbuzz debug log. +pub fn debug(s: &str) { + let c_s = CString::new(s).unwrap(); + unsafe { + debugprint(c_s.as_ptr() as *const u8); + }; +}