Search code examples
javascriptfontsweb-worker

Can not clone object to web worker


<!DOCTYPE html>
<html>

<body>
  <button type="button" onclick='UseLocalFontsButton()'>Click Me!</button>
  <script>

    async function UseLocalFontsButton() {
      try {
        const array = await self.queryLocalFonts();

        for (const font of array) {
          // blob() returns a Blob containing the bytes of the font.
          const bytes = await font.blob();
          console.log(`bytes.size = ${bytes.size}`);
          console.log(`bytes.type = ${bytes.type}`);
          // process font
        };
      } 
      catch (e) {
        console.warn(`Error: ${e.message}`);
      }
    };

</script>

</body>
</html>

Here I am getting local fonts and processing it. It works but I need to move font processing part to worker thread. I cant move everything because queryLocalFonts only works on main thread.

Here is my try:

<!DOCTYPE html>
<html>

<body>
  <button type="button" onclick='UseLocalFontsButton()'>Click Me!</button>

  <script>

    async function UseLocalFontsButton() {
      try {
        const array = await self.queryLocalFonts();

        let worker = new Worker('worker.js');
        worker.postMessage(JSON.stringify(array));
      } 
      catch (e) {
        console.warn(`Error: ${e.message}`);
      }
    };

</script>

</body>
</html>

And the worker:

(function () {
    onmessage = async function handleMessageFromMain(msg) 
    {
        var array = JSON.parse(msg.data);

        console.log('array = ', array);

        try {
            for (const font of array) {
                // blob() returns a Blob containing the bytes of the font.
                const bytes = await font.blob();
                console.log(`bytes.size = ${bytes.size}`);
                console.log(`bytes.type = ${bytes.type}`);
                // process font
            };
        } catch (e) {
            console.warn(`Error: ${e.message}`);
        }
    };
})();

I am getting error: Error: font.blob is not a function. Looks like font object is not properly copied to worker thread. Can you hint how that can be done?


Solution

  • You don't need to serialize your data to JSON when you post it to a Worker (or other MessagePort objects for that matter); the postMessage() method has its own cloning algorithm able to pass many different JS object types that JSON doesn't support.

    And while FontData is not part of these many object types, Blob is. So what you need to do is to extract all the your FontData instances as Blob and send these Blob objects to your worker.

    That would give:

    // main script
    async function UseLocalFontsButton() {
      try {
        const fonts = await self.queryLocalFonts();
        const toTransfer = [];
        for(const font of fonts) {
          toTransfer.push({
            // You're free to add whatever other info you need
            name: font.postscriptName,
            blob: await font.blob()
          });
        }
        const worker = new Worker("worker.js");
        worker.postMessage(toTransfer);
        worker.onmessage = ({data}) => {
          log(data);
        }
      } 
      catch (e) {
        log(`Error: ${e.message}`);
      }
    };
    
    // worker script
    onmessage = async ({data}) => {
      postMessage(`Received ${data.length} Blobs`);
      for(const {blob, name} of data) {
        const buf = await blob..arrayBuffer();
        // Do your analysis on the font's data
      }
    };
    

    Live example as a Glitch project since StackSnippet's don't allow access to local fonts. (editor).