From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- gfx/harfbuzz/src/wasm/graphite/Makefile | 28 ++ gfx/harfbuzz/src/wasm/graphite/shape.cc | 250 +++++++++++ .../src/wasm/rust/harfbuzz-wasm/Cargo.toml | 9 + .../src/wasm/rust/harfbuzz-wasm/src/lib.rs | 464 +++++++++++++++++++++ gfx/harfbuzz/src/wasm/sample/c/Makefile | 25 ++ gfx/harfbuzz/src/wasm/sample/c/shape-fallback.cc | 60 +++ gfx/harfbuzz/src/wasm/sample/c/shape-ot.cc | 18 + gfx/harfbuzz/src/wasm/sample/c/test.ttf | Bin 0 -> 22116 bytes .../src/wasm/sample/rust/hello-wasm/Cargo.toml | 13 + .../src/wasm/sample/rust/hello-wasm/src/lib.rs | 24 ++ 10 files changed, 891 insertions(+) create mode 100644 gfx/harfbuzz/src/wasm/graphite/Makefile create mode 100644 gfx/harfbuzz/src/wasm/graphite/shape.cc create mode 100644 gfx/harfbuzz/src/wasm/rust/harfbuzz-wasm/Cargo.toml create mode 100644 gfx/harfbuzz/src/wasm/rust/harfbuzz-wasm/src/lib.rs create mode 100644 gfx/harfbuzz/src/wasm/sample/c/Makefile create mode 100644 gfx/harfbuzz/src/wasm/sample/c/shape-fallback.cc create mode 100644 gfx/harfbuzz/src/wasm/sample/c/shape-ot.cc create mode 100644 gfx/harfbuzz/src/wasm/sample/c/test.ttf create mode 100644 gfx/harfbuzz/src/wasm/sample/rust/hello-wasm/Cargo.toml create mode 100644 gfx/harfbuzz/src/wasm/sample/rust/hello-wasm/src/lib.rs (limited to 'gfx/harfbuzz/src/wasm') diff --git a/gfx/harfbuzz/src/wasm/graphite/Makefile b/gfx/harfbuzz/src/wasm/graphite/Makefile new file mode 100644 index 0000000000..cdd44dcb33 --- /dev/null +++ b/gfx/harfbuzz/src/wasm/graphite/Makefile @@ -0,0 +1,28 @@ +FONTS = CharisSIL-R.wasm.ttf Scheherazade-R.wasm.ttf AwamiNastaliq-Regular.wasm.ttf +ADD_TABLE = ../../addTable.py + +all: $(FONTS) + +%.wasm: %.cc ../../hb-wasm-api.h + emcc \ + -I ../.. \ + -I ~/graphite/include/ \ + -fvisibility=hidden \ + -Wl,--allow-undefined \ + -Wl,--no-entry \ + -Wl,--strip-all \ + -sERROR_ON_UNDEFINED_SYMBOLS=0 \ + -Wl,--export=malloc -Wl,--export=free \ + ~/graphite/src/libgraphite2.a \ + ~/wasm/wasi-sdk-19.0/share/wasi-sysroot/lib/wasm32-wasi/libc.a \ + $< \ + -o $@ + + +%.wasm.ttf: %.ttf shape.wasm $(ADD_TABLE) + python $(ADD_TABLE) $< $@ shape.wasm + +clean: + $(RM) shape.wasm $(FONTS) + +.PRECIOUS: shape.wasm diff --git a/gfx/harfbuzz/src/wasm/graphite/shape.cc b/gfx/harfbuzz/src/wasm/graphite/shape.cc new file mode 100644 index 0000000000..f445049a48 --- /dev/null +++ b/gfx/harfbuzz/src/wasm/graphite/shape.cc @@ -0,0 +1,250 @@ +#define HB_WASM_INTERFACE(ret_t, name) __attribute__((export_name(#name))) ret_t name + +#include + +#include + +#include +#include + +void debugprint1 (char *s, int32_t); +void debugprint2 (char *s, int32_t, int32_t); + +static const void *copy_table (const void *data, unsigned int tag, size_t *len) +{ + face_t *face = (face_t *) data; + blob_t blob = BLOB_INIT; + if (!face_copy_table (face, tag, &blob)) + abort (); + + *len = blob.length; + return blob.data; +} + +static void free_table (const void *data, const void *table_data) +{ + blob_t blob; + blob.length = 0; // Doesn't matter + blob.data = (char *) table_data; + blob_free (&blob); +} + +void * +shape_plan_create (face_t *face) +{ + const gr_face_ops ops = {sizeof (gr_face_ops), ©_table, &free_table}; + gr_face *grface = gr_make_face_with_ops (face, &ops, gr_face_preloadAll); + return grface; +} + +void +shape_plan_destroy (void *data) +{ + gr_face_destroy ((gr_face *) data); +} + +bool_t +shape (void *shape_plan, + font_t *font, + buffer_t *buffer, + const feature_t *features, + uint32_t num_features) +{ + face_t *face = font_get_face (font); + gr_face *grface = (gr_face *) shape_plan; + + direction_t direction = buffer_get_direction (buffer); + direction_t horiz_dir = script_get_horizontal_direction (buffer_get_script (buffer)); + /* TODO vertical: + * The only BTT vertical script is Ogham, but it's not clear to me whether OpenType + * Ogham fonts are supposed to be implemented BTT or not. Need to research that + * first. */ + if ((DIRECTION_IS_HORIZONTAL (direction) && + direction != horiz_dir && horiz_dir != DIRECTION_INVALID) || + (DIRECTION_IS_VERTICAL (direction) && + direction != DIRECTION_TTB)) + { + buffer_reverse_clusters (buffer); + direction = DIRECTION_REVERSE (direction); + } + + buffer_contents_t contents = BUFFER_CONTENTS_INIT; + if (!buffer_copy_contents (buffer, &contents)) + return false; + + gr_segment *seg = nullptr; + const gr_slot *is; + unsigned int ci = 0, ic = 0; + unsigned int curradvx = 0, curradvy = 0; + unsigned length = contents.length; + + uint32_t *chars = (uint32_t *) malloc (length * sizeof (uint32_t)); + if (!chars) + return false; + for (unsigned int i = 0; i < contents.length; ++i) + chars[i] = contents.info[i].codepoint; + + seg = gr_make_seg (nullptr, grface, + 0, // https://github.com/harfbuzz/harfbuzz/issues/3439#issuecomment-1442650148 + nullptr, + gr_utf32, chars, contents.length, + 2 | (direction == DIRECTION_RTL ? 1 : 0)); + + free (chars); + + if (!seg) + return false; + + unsigned int glyph_count = gr_seg_n_slots (seg); + + struct cluster_t { + unsigned int base_char; + unsigned int num_chars; + unsigned int base_glyph; + unsigned int num_glyphs; + unsigned int cluster; + int advance; + }; + + length = glyph_count; + if (!buffer_contents_realloc (&contents, length)) + return false; + cluster_t *clusters = (cluster_t *) malloc (length * sizeof (cluster_t)); + uint32_t *gids = (uint32_t *) malloc (length * sizeof (uint32_t)); + if (!clusters || !gids) + { + free (clusters); + free (gids); + return false; + } + + memset (clusters, 0, sizeof (clusters[0]) * length); + codepoint_t *pg = gids; + clusters[0].cluster = contents.info[0].cluster; + unsigned int upem = face_get_upem (face); + int32_t font_x_scale, font_y_scale; + font_get_scale (font, &font_x_scale, &font_y_scale); + float xscale = (float) font_x_scale / upem; + float yscale = (float) font_y_scale / upem; + yscale *= yscale / xscale; + unsigned int curradv = 0; + if (DIRECTION_IS_BACKWARD (direction)) + { + curradv = gr_slot_origin_X(gr_seg_first_slot(seg)) * xscale; + clusters[0].advance = gr_seg_advance_X(seg) * xscale - curradv; + } + else + clusters[0].advance = 0; + for (is = gr_seg_first_slot (seg), ic = 0; is; is = gr_slot_next_in_segment (is), ic++) + { + unsigned int before = gr_slot_before (is); + unsigned int after = gr_slot_after (is); + *pg = gr_slot_gid (is); + pg++; + while (clusters[ci].base_char > before && ci) + { + clusters[ci-1].num_chars += clusters[ci].num_chars; + clusters[ci-1].num_glyphs += clusters[ci].num_glyphs; + clusters[ci-1].advance += clusters[ci].advance; + ci--; + } + + if (gr_slot_can_insert_before (is) && clusters[ci].num_chars && before >= clusters[ci].base_char + clusters[ci].num_chars) + { + cluster_t *c = clusters + ci + 1; + c->base_char = clusters[ci].base_char + clusters[ci].num_chars; + c->cluster = contents.info[c->base_char].cluster; + c->num_chars = before - c->base_char; + c->base_glyph = ic; + c->num_glyphs = 0; + if (DIRECTION_IS_BACKWARD (direction)) + { + c->advance = curradv - gr_slot_origin_X(is) * xscale; + curradv -= c->advance; + } + else + { + auto origin_X = gr_slot_origin_X (is) * xscale; + c->advance = 0; + clusters[ci].advance += origin_X - curradv; + curradv = origin_X; + } + ci++; + } + clusters[ci].num_glyphs++; + + if (clusters[ci].base_char + clusters[ci].num_chars < after + 1) + clusters[ci].num_chars = after + 1 - clusters[ci].base_char; + } + + if (DIRECTION_IS_BACKWARD (direction)) + clusters[ci].advance += curradv; + else + clusters[ci].advance += gr_seg_advance_X(seg) * xscale - curradv; + ci++; + + for (unsigned int i = 0; i < ci; ++i) + { + for (unsigned int j = 0; j < clusters[i].num_glyphs; ++j) + { + glyph_info_t *info = &contents.info[clusters[i].base_glyph + j]; + info->codepoint = gids[clusters[i].base_glyph + j]; + info->cluster = clusters[i].cluster; + info->var1 = (unsigned) clusters[i].advance; // all glyphs in the cluster get the same advance + } + } + contents.length = glyph_count; + + /* Positioning. */ + unsigned int currclus = 0xFFFFFFFF; + const glyph_info_t *info = contents.info; + glyph_position_t *pPos = contents.pos; + if (!DIRECTION_IS_BACKWARD (direction)) + { + curradvx = 0; + for (is = gr_seg_first_slot (seg); is; pPos++, ++info, is = gr_slot_next_in_segment (is)) + { + pPos->x_offset = gr_slot_origin_X (is) * xscale - curradvx; + pPos->y_offset = gr_slot_origin_Y (is) * yscale - curradvy; + if (info->cluster != currclus) { + pPos->x_advance = (int) info->var1; + curradvx += pPos->x_advance; + currclus = info->cluster; + } else + pPos->x_advance = 0.; + + pPos->y_advance = gr_slot_advance_Y (is, grface, nullptr) * yscale; + curradvy += pPos->y_advance; + } + buffer_set_contents (buffer, &contents); + } + else + { + curradvx = gr_seg_advance_X(seg) * xscale; + for (is = gr_seg_first_slot (seg); is; pPos++, info++, is = gr_slot_next_in_segment (is)) + { + if (info->cluster != currclus) + { + pPos->x_advance = (int) info->var1; + curradvx -= pPos->x_advance; + currclus = info->cluster; + } else + pPos->x_advance = 0.; + + pPos->y_advance = gr_slot_advance_Y (is, grface, nullptr) * yscale; + curradvy -= pPos->y_advance; + pPos->x_offset = gr_slot_origin_X (is) * xscale - (int) info->var1 - curradvx + pPos->x_advance; + pPos->y_offset = gr_slot_origin_Y (is) * yscale - curradvy; + } + buffer_set_contents (buffer, &contents); + buffer_reverse_clusters (buffer); + } + + gr_seg_destroy (seg); + free (clusters); + free (gids); + + bool ret = glyph_count; + + return ret; +} diff --git a/gfx/harfbuzz/src/wasm/rust/harfbuzz-wasm/Cargo.toml b/gfx/harfbuzz/src/wasm/rust/harfbuzz-wasm/Cargo.toml new file mode 100644 index 0000000000..fa17de36be --- /dev/null +++ b/gfx/harfbuzz/src/wasm/rust/harfbuzz-wasm/Cargo.toml @@ -0,0 +1,9 @@ +[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] +kurbo = { version = "0.9.0", optional = true } diff --git a/gfx/harfbuzz/src/wasm/rust/harfbuzz-wasm/src/lib.rs b/gfx/harfbuzz/src/wasm/rust/harfbuzz-wasm/src/lib.rs new file mode 100644 index 0000000000..ea20ce507f --- /dev/null +++ b/gfx/harfbuzz/src/wasm/rust/harfbuzz-wasm/src/lib.rs @@ -0,0 +1,464 @@ +#![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}; + +#[cfg(feature = "kurbo")] +use kurbo::BezPath; + +// 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 font_copy_glyph_outline(font: u32, glyph: u32, outline: *mut CGlyphOutline) -> bool; + fn face_copy_table(font: u32, tag: u32, blob: *mut Blob) -> bool; + fn buffer_copy_contents(buffer: u32, cbuffer: *mut CBufferContents) -> bool; + 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) + } + + #[cfg(feature = "kurbo")] + /// Get the outline of a glyph as a vector of bezier paths + pub fn get_outline(&self, glyph: u32) -> Vec { + let mut outline = CGlyphOutline { + n_points: 0, + points: std::ptr::null_mut(), + n_contours: 0, + contours: std::ptr::null_mut(), + }; + let end_pts_of_contours: &[usize] = unsafe { + font_copy_glyph_outline(self.0, glyph, &mut outline); + std::slice::from_raw_parts(outline.contours, outline.n_contours as usize) + }; + let points: &[CGlyphOutlinePoint] = + unsafe { std::slice::from_raw_parts(outline.points, outline.n_points as usize) }; + let mut results: Vec = vec![]; + let mut start_pt: usize = 0; + for end_pt in end_pts_of_contours { + let this_contour = &points[start_pt..*end_pt]; + start_pt = *end_pt; + let mut path = BezPath::new(); + let mut ix = 0; + while ix < this_contour.len() { + let point = &this_contour[ix]; + match point.pointtype { + PointType::MoveTo => path.move_to((point.x as f64, point.y as f64)), + PointType::LineTo => path.line_to((point.x as f64, point.y as f64)), + PointType::QuadraticTo => { + ix += 1; + let end_pt = &this_contour[ix]; + path.quad_to( + (point.x as f64, point.y as f64), + (end_pt.x as f64, end_pt.y as f64), + ); + } + PointType::CubicTo => { + ix += 1; + let mid_pt = &this_contour[ix]; + ix += 1; + let end_pt = &this_contour[ix]; + path.curve_to( + (point.x as f64, point.y as f64), + (mid_pt.x as f64, mid_pt.y as f64), + (end_pt.x as f64, end_pt.y as f64), + ); + } + } + ix += 1; + } + path.close_path(); + results.push(path); + } + results + } +} + +/// 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; + let mut blob = Blob { + data: std::ptr::null_mut(), + length: 0, + }; + unsafe { + face_copy_table(self.0, tag_u, &mut blob); + } + blob + } + + /// 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 mut c_contents = CBufferContents { + info: std::ptr::null_mut(), + position: std::ptr::null_mut(), + length: 0, + }; + + unsafe { + buffer_copy_contents(ptr, &mut c_contents) || panic!("Couldn't copy buffer contents") + }; + 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 = std::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 { + if !buffer_set_contents(self._ptr, &c_contents) { + panic!("Couldn't set buffer 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. +#[derive(Debug, Clone, Copy)] +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) + } +} + +#[repr(C)] +#[allow(clippy::enum_variant_names)] +#[derive(Clone, Debug)] +enum PointType { + MoveTo, + LineTo, + QuadraticTo, + CubicTo, +} + +#[repr(C)] +#[derive(Clone, Debug)] +struct CGlyphOutlinePoint { + x: f32, + y: f32, + pointtype: PointType, +} + +#[repr(C)] +struct CGlyphOutline { + n_points: usize, + points: *mut CGlyphOutlinePoint, + n_contours: usize, + contours: *mut usize, +} + +/// 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); + }; +} diff --git a/gfx/harfbuzz/src/wasm/sample/c/Makefile b/gfx/harfbuzz/src/wasm/sample/c/Makefile new file mode 100644 index 0000000000..4ee073b645 --- /dev/null +++ b/gfx/harfbuzz/src/wasm/sample/c/Makefile @@ -0,0 +1,25 @@ +ADD_TABLE = ../../../addTable.py + +all: test-fallback.wasm.ttf test-ot.wasm.ttf + +%.wasm: %.cc ../../../hb-wasm-api.h + clang \ + --target=wasm32-unknown-wasi \ + -Wl,--no-entry \ + -fvisibility=hidden \ + -Wl,--allow-undefined \ + -nostdlib \ + -I ../../.. \ + $< \ + -o $@ + +test-fallback.wasm.ttf: test.ttf shape-fallback.wasm $(ADD_TABLE) + python $(ADD_TABLE) $< $@ shape-fallback.wasm + +test-ot.wasm.ttf: test.ttf shape-ot.wasm $(ADD_TABLE) + python $(ADD_TABLE) $< $@ shape-ot.wasm + +clean: + $(RM) test-fallback.wasm.ttf test-ot.wasm.ttf shape-fallback.wasm shape-ot.wasm + +.PRECIOUS: *.wasm diff --git a/gfx/harfbuzz/src/wasm/sample/c/shape-fallback.cc b/gfx/harfbuzz/src/wasm/sample/c/shape-fallback.cc new file mode 100644 index 0000000000..7787bbae7a --- /dev/null +++ b/gfx/harfbuzz/src/wasm/sample/c/shape-fallback.cc @@ -0,0 +1,60 @@ +#define HB_WASM_INTERFACE(ret_t, name) __attribute__((export_name(#name))) ret_t name + +#include + +extern "C" { +void debugprint (const char *s); +void debugprint1 (const char *s, int32_t); +void debugprint2 (const char *s, int32_t, int32_t); +} + +bool_t +shape (void *shape_plan, + font_t *font, + buffer_t *buffer, + const feature_t *features, + uint32_t num_features) +{ + face_t *face = font_get_face (font); + + blob_t blob = BLOB_INIT; + face_copy_table (face, TAG ('c','m','a','p'), &blob); + + debugprint1 ("cmap length", blob.length); + + blob_free (&blob); + + buffer_contents_t contents = BUFFER_CONTENTS_INIT; + if (!buffer_copy_contents (buffer, &contents)) + return false; + + debugprint1 ("buffer length", contents.length); + + glyph_outline_t outline = GLYPH_OUTLINE_INIT; + + for (unsigned i = 0; i < contents.length; i++) + { + char name[64]; + + debugprint1 ("glyph at", i); + + font_glyph_to_string (font, contents.info[i].codepoint, name, sizeof (name)); + + debugprint (name); + + contents.info[i].codepoint = font_get_glyph (font, contents.info[i].codepoint, 0); + contents.pos[i].x_advance = font_get_glyph_h_advance (font, contents.info[i].codepoint); + + font_copy_glyph_outline (font, contents.info[i].codepoint, &outline); + debugprint1 ("num outline points", outline.n_points); + debugprint1 ("num outline contours", outline.n_contours); + } + + glyph_outline_free (&outline); + + bool_t ret = buffer_set_contents (buffer, &contents); + + buffer_contents_free (&contents); + + return ret; +} diff --git a/gfx/harfbuzz/src/wasm/sample/c/shape-ot.cc b/gfx/harfbuzz/src/wasm/sample/c/shape-ot.cc new file mode 100644 index 0000000000..9081cfebcc --- /dev/null +++ b/gfx/harfbuzz/src/wasm/sample/c/shape-ot.cc @@ -0,0 +1,18 @@ +#define HB_WASM_INTERFACE(ret_t, name) __attribute__((export_name(#name))) ret_t name + +#include + +extern "C" { +void debugprint1 (const char *s, int32_t); +void debugprint2 (const char *s, int32_t, int32_t); +} + +bool_t +shape (void *shape_plan, + font_t *font, + buffer_t *buffer, + const feature_t *features, + uint32_t num_features) +{ + return shape_with (font, buffer, features, num_features, "ot"); +} diff --git a/gfx/harfbuzz/src/wasm/sample/c/test.ttf b/gfx/harfbuzz/src/wasm/sample/c/test.ttf new file mode 100644 index 0000000000..2ba04f611b Binary files /dev/null and b/gfx/harfbuzz/src/wasm/sample/c/test.ttf differ diff --git a/gfx/harfbuzz/src/wasm/sample/rust/hello-wasm/Cargo.toml b/gfx/harfbuzz/src/wasm/sample/rust/hello-wasm/Cargo.toml new file mode 100644 index 0000000000..53357ea908 --- /dev/null +++ b/gfx/harfbuzz/src/wasm/sample/rust/hello-wasm/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "hello-wasm" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +#externref = "0.1.0" +wasm-bindgen = "0.2.0" +tiny-rng = "0.2.0" +harfbuzz-wasm = { path="../../../rust/harfbuzz-wasm"} diff --git a/gfx/harfbuzz/src/wasm/sample/rust/hello-wasm/src/lib.rs b/gfx/harfbuzz/src/wasm/sample/rust/hello-wasm/src/lib.rs new file mode 100644 index 0000000000..ab8f153684 --- /dev/null +++ b/gfx/harfbuzz/src/wasm/sample/rust/hello-wasm/src/lib.rs @@ -0,0 +1,24 @@ +use harfbuzz_wasm::{Font, GlyphBuffer}; +use tiny_rng::{Rand, Rng}; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn shape( + _shape_plan: u32, + font_ref: u32, + buf_ref: u32, + _features: u32, + _num_features: u32, +) -> i32 { + let mut rng = Rng::from_seed(123456); + let font = Font::from_ref(font_ref); + font.shape_with(buf_ref, "ot"); + let mut buffer = GlyphBuffer::from_ref(buf_ref); + for mut item in buffer.glyphs.iter_mut() { + // Randomize it! + item.x_offset = ((rng.rand_u32() as i32) >> 24) - 120; + item.y_offset = ((rng.rand_u32() as i32) >> 24) - 120; + } + // Buffer is written back to HB on drop + 1 +} -- cgit v1.2.3