[wasm-api] Add ergonomic Rust interface
This commit is contained in:
parent
74deaa9e78
commit
514a8d58d8
|
@ -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]
|
|
@ -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<T: BufferItem> {
|
||||||
|
_ptr: u32,
|
||||||
|
/// Glyphs in the buffer
|
||||||
|
pub glyphs: Vec<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: BufferItem> Buffer<T> {
|
||||||
|
/// 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<CGlyphPosition> = unsafe {
|
||||||
|
std::slice::from_raw_parts(c_contents.position, c_contents.length as usize).to_vec()
|
||||||
|
};
|
||||||
|
let infos: Vec<CGlyphInfo> = 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<T: BufferItem> Drop for Buffer<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let mut positions: Vec<CGlyphPosition>;
|
||||||
|
let mut infos: Vec<CGlyphInfo>;
|
||||||
|
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<Glyph>;
|
||||||
|
|
||||||
|
/// 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);
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue