Search code examples
rusthtml5-canvaswasm-bindgen

Create a global font cache


I'm using rustybuzz and wasm-bindgen to typeset text on an HTML5 canvas. I would like to create a global font cache, indexed by Strings, so that I can load and parse font files once and then reuse them multiple times (cue comments about how global variables are bad... if anyone has a better way to do this, please let me know). Specifically, I want some variation of a HashMap<String, rustybuzz::Face> that I can access anywhere. I then want to expose a register_font function to the JavaScript side so I can load in ArrayBuffers, something like:

#[wasm_bindgen]
pub fn register_font(name: &str, font_data: &[u8]) {
  MY_FONT_CACHE_SOMEHOW.insert(
    name.to_string(),
    rustybuzz::Face::from_slice(&font_data, 0).unwrap()
  );
}

And, for completion, I'd like an internal get_font function to retrieve:

fn get_font(name: &String) -> rustybuzz::Face {
  MY_FONT_CACHE_SOMEHOW.get(name).unwrap()
}

I know I've got issues with variable lifetimes, I just don't know how to solve them. The rustybuzz::Face struct just references its internal data, it doesn't own it. wasm-bindgen doesn't support marking the incoming ArrayBuffer/[u8] on the register_font function as 'static, which seems to be one of the main problems. But I'm new to this whole rust thing. Anyone know how to do this (or a better way to do this)?


Solution

  • If you only add to the hash table and never delete you could copy and leak font_data to make it static:

    #[wasm_bindgen]
    pub fn register_font(name: &str, font_data: &[u8]) {
        let font_data: &'static [u8] = font_data.to_owned().leak();
        //...
    }
    

    If you want to be able to delete fonts from the cache you have to keep the font_data and the Face together. That is the infamous issue of a self-referential type.

    You can go the unsafe way and keep a pointer to a dynamically allocated *const [u8] type, or you can use one of the many crates that try to solve this issue.

    Currently my favourite is ouroboros: it would be something like this (untested):

    #[self_referencing]
    struct MyFace {
        font_data: Vec<u8>,
        #[borrows(font_data)]
        face: Face<'this>,
    }
    
    #[wasm_bindgen]
    pub fn register_font(name: &str, font_data: &[u8]) {
        let face = MyFaceBuilder {
            font_data: font_data.to_owned(),
            face_builder: |font_data: &[u8]| Face::from_slice(font_data).unwrap(),
        }.build();
        MY_FONT_CACHE_SOMEHOW.insert(
            name.to_string(),
            face,
        );
    }