textBox = ({ context, update }) => {
  const fontSize = 100;
  let curText;

  const layout = () => {
    context.textAlign = 'center';
    context.textBaseline = 'middle';
    context.font = `${fontSize}px "Arial"`;
  };

  const setText = (text = '') => {
    curText = text;

    // Layout and compute text size
    layout();

    const width = Math.max(1, context.measureText(text).width);
    const height = fontSize; // approximate height

    // This is optional but will give a pixel border to our canvas
    // Otherwise sometimes pixels get cut off near the edge
    const bleed = 10;

    // Then update canvas with new size
    // The update() function will trigger a re-render
    update({
      dimensions: [ width, height ],
      bleed
    });
  };

  // Set an initial state
  setText('');

  return {
    render ({ width, height }) {
      context.clearRect(0, 0, width, height);

      context.fillStyle = 'hsl(0, 0%, 90%)';
      context.fillRect(0, 0, width, height);

      // Re-layout before rendering to ensure sizing works correctly
      layout();

      // Render current text
      context.fillStyle = 'black';
      context.fillText(curText, width / 2, height / 2);
    },
    // Expose the function to parent modules
    setText
  };
}
A proof-of-concept showing one canvasSketch interface
interacting with a child canvasSketch interface.

Here we use an off-screen sketch to draw text to a canvas,
and the main on-screen sketch will render that at a smaller
scale to produce crisper text rendering.

Most likely, this is overkill, and simply creating and manually
scaling a second canvas will be far simpler and easier to manage.
However, this demo proves the capability of using multiple sketches,
if you so desire.

@author Matt DesLauriers (@mattdesl)