Allow WASM shaper code to access features (#6139)
* Add WASM API to wrap features
* Access features through WASM FFI
diff --git a/src/hb-wasm-api-list.hh b/src/hb-wasm-api-list.hh
index 5db45c7..62e3bc6 100644
--- a/src/hb-wasm-api-list.hh
+++ b/src/hb-wasm-api-list.hh
@@ -92,6 +92,8 @@
/* shape */
NATIVE_SYMBOL ("(iiii$)i", shape_with),
+ NATIVE_SYMBOL ("(iii)i", feature_copy_contents),
+ NATIVE_SYMBOL ("(i)", features_free),
/* debug */
#ifdef HB_DEBUG_WASM
diff --git a/src/hb-wasm-api-shape.hh b/src/hb-wasm-api-shape.hh
index 622ff05..7bd45d0 100644
--- a/src/hb-wasm-api-shape.hh
+++ b/src/hb-wasm-api-shape.hh
@@ -64,6 +64,52 @@
shaper_list);
}
+HB_WASM_API (bool_t, feature_copy_contents) (HB_WASM_EXEC_ENV
+ ptr_d(const feature_t, features),
+ uint32_t num_features,
+ ptr_d(features_t, output))
+{
+ HB_PTR_PARAM (features_t, output);
+ if (unlikely (!output))
+ return false;
+
+ unsigned bytes;
+ if (unlikely (hb_unsigned_mul_overflows (num_features, sizeof (feature_t), &bytes)))
+ return false;
+
+ if (!num_features)
+ {
+ output->length = 0;
+ output->features = nullref;
+ return true;
+ }
+
+ HB_ARRAY_PARAM (const feature_t, features, num_features);
+ if (unlikely (!features))
+ return false;
+
+ feature_t *dst = nullptr;
+ uint32_t dstref = module_malloc (bytes, (void **) &dst);
+ if (unlikely (!dstref))
+ return false;
+
+ hb_memcpy (dst, features, bytes);
+ output->length = num_features;
+ output->features = dstref;
+ return true;
+}
+
+HB_WASM_API (void, features_free) (HB_WASM_EXEC_ENV
+ ptr_d(features_t, features))
+{
+ HB_PTR_PARAM (features_t, features);
+ if (unlikely (!features))
+ return;
+
+ module_free (features->features);
+ features->features = nullref;
+ features->length = 0;
+}
}}
diff --git a/src/hb-wasm-api.h b/src/hb-wasm-api.h
index bb977ee..71afea2 100644
--- a/src/hb-wasm-api.h
+++ b/src/hb-wasm-api.h
@@ -297,6 +297,21 @@
#define FEATURE_GLOBAL_START 0
#define FEATURE_GLOBAL_END ((uint32_t) -1)
+typedef struct
+{
+ uint32_t length;
+ ptr_t(feature_t) features;
+} features_t;
+#define FEATURES_INIT {0, 0}
+
+HB_WASM_API (bool_t, feature_copy_contents) (HB_WASM_EXEC_ENV
+ ptr_d(const feature_t, features),
+ uint32_t num_features,
+ ptr_d(features_t, output));
+
+HB_WASM_API (void, features_free) (HB_WASM_EXEC_ENV
+ ptr_d(features_t, features));
+
HB_WASM_API (bool_t, shape_with) (HB_WASM_EXEC_ENV
ptr_d(font_t, font),
ptr_d(buffer_t, buffer),
diff --git a/src/wasm/rust/harfbuzz-wasm/src/lib.rs b/src/wasm/rust/harfbuzz-wasm/src/lib.rs
index ea20ce5..5610b5a 100644
--- a/src/wasm/rust/harfbuzz-wasm/src/lib.rs
+++ b/src/wasm/rust/harfbuzz-wasm/src/lib.rs
@@ -45,6 +45,8 @@
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 feature_copy_contents(features: u32, num_features: u32, output: *mut CFFeatures) -> bool;
+ fn features_free(features: *mut CFFeatures);
fn debugprint(s: *const u8);
fn shape_with(
font: u32,
@@ -363,6 +365,24 @@
pub height: i32,
}
+/// An OpenType feature passed to the shaper.
+#[derive(Debug, Clone, Copy)]
+pub struct Feature {
+ /// The feature tag (e.g. `TAG(b'k', b'e', b'r', b'n')`)
+ pub tag: [u8; 4],
+ /// The feature value (0 = off, 1 = on, etc.)
+ pub value: u32,
+ /// Start index of the feature range, or `FEATURE_GLOBAL_START`
+ pub start: u32,
+ /// End index of the feature range, or `FEATURE_GLOBAL_END`
+ pub end: u32,
+}
+
+/// Start index for features that apply globally.
+pub const FEATURE_GLOBAL_START: u32 = 0;
+/// End index for features that apply globally.
+pub const FEATURE_GLOBAL_END: u32 = u32::MAX;
+
#[derive(Debug)]
#[repr(C)]
struct CBufferContents {
@@ -371,6 +391,22 @@
position: *mut CGlyphPosition,
}
+#[derive(Debug)]
+#[repr(C)]
+struct CFFeatures {
+ length: u32,
+ features: *mut CFeature,
+}
+
+#[derive(Debug, Clone, Copy)]
+#[repr(C)]
+struct CFeature {
+ tag: u32,
+ value: u32,
+ start: u32,
+ end: u32,
+}
+
/// Ergonomic representation of a Harfbuzz buffer item
///
/// Harfbuzz buffers are normally split into two arrays,
@@ -455,6 +491,78 @@
/// Our default buffer item struct. See also [`Glyph`].
pub type GlyphBuffer = Buffer<Glyph>;
+/// A collection of OpenType features passed to the shaper.
+#[derive(Debug)]
+pub struct Features {
+ features: Vec<Feature>,
+}
+
+fn u32_to_tag(tag: u32) -> [u8; 4] {
+ [
+ ((tag >> 24) & 0xFF) as u8,
+ ((tag >> 16) & 0xFF) as u8,
+ ((tag >> 8) & 0xFF) as u8,
+ (tag & 0xFF) as u8,
+ ]
+}
+
+impl Features {
+ /// Construct a `Features` from the raw pointer and count
+ /// passed to the `shape` function by the WASM host.
+ pub fn from_raw(ptr: u32, count: u32) -> Self {
+ let mut c_features = CFFeatures {
+ length: 0,
+ features: std::ptr::null_mut(),
+ };
+ if count > 0 {
+ unsafe {
+ feature_copy_contents(ptr, count, &mut c_features);
+ }
+ }
+ let slice =
+ unsafe { std::slice::from_raw_parts(c_features.features, c_features.length as usize) };
+ let features: Vec<Feature> = slice
+ .iter()
+ .map(|f| Feature {
+ tag: u32_to_tag(f.tag),
+ value: f.value,
+ start: f.start,
+ end: f.end,
+ })
+ .collect();
+ Features { features }
+ }
+
+ /// Returns the number of features.
+ pub fn len(&self) -> usize {
+ self.features.len()
+ }
+
+ /// Returns true if there are no features.
+ pub fn is_empty(&self) -> bool {
+ self.features.is_empty()
+ }
+
+ /// Returns a slice of all features.
+ pub fn as_slice(&self) -> &[Feature] {
+ &self.features
+ }
+
+ /// Iterate over the features.
+ pub fn iter(&self) -> impl Iterator<Item = &Feature> {
+ self.features.iter()
+ }
+}
+
+impl Drop for Features {
+ fn drop(&mut self) {
+ // CFFeatures is stack-allocated but it points to WASM memory.
+ // We don't need to free since feature_copy_contents allocates
+ // in WASM heap and we copied values out. The WASM module
+ // frees its heap on deinstantiation.
+ }
+}
+
/// Write a string to the Harfbuzz debug log.
pub fn debug(s: &str) {
let c_s = CString::new(s).unwrap();