summaryrefslogtreecommitdiffstats
path: root/gfx/harfbuzz/src/wasm
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /gfx/harfbuzz/src/wasm
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/harfbuzz/src/wasm')
-rw-r--r--gfx/harfbuzz/src/wasm/graphite/Makefile28
-rw-r--r--gfx/harfbuzz/src/wasm/graphite/shape.cc250
-rw-r--r--gfx/harfbuzz/src/wasm/rust/harfbuzz-wasm/Cargo.toml9
-rw-r--r--gfx/harfbuzz/src/wasm/rust/harfbuzz-wasm/src/lib.rs464
-rw-r--r--gfx/harfbuzz/src/wasm/sample/c/Makefile25
-rw-r--r--gfx/harfbuzz/src/wasm/sample/c/shape-fallback.cc60
-rw-r--r--gfx/harfbuzz/src/wasm/sample/c/shape-ot.cc18
-rw-r--r--gfx/harfbuzz/src/wasm/sample/c/test.ttfbin0 -> 22116 bytes
-rw-r--r--gfx/harfbuzz/src/wasm/sample/rust/hello-wasm/Cargo.toml13
-rw-r--r--gfx/harfbuzz/src/wasm/sample/rust/hello-wasm/src/lib.rs24
10 files changed, 891 insertions, 0 deletions
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 <hb-wasm-api.h>
+
+#include <graphite2/Segment.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+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), &copy_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<BezPath> {
+ 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<BezPath> = 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<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 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<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 = 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<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);
+ };
+}
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 <hb-wasm-api.h>
+
+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 <hb-wasm-api.h>
+
+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
--- /dev/null
+++ b/gfx/harfbuzz/src/wasm/sample/c/test.ttf
Binary files 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
+}