Just a quick experiment trying to use the Taffy layout library and image-builder crates to layout and build images in Rust.

This worked well enough to satiate my curiosity and could, for example, generate social share images à la @vercel/og.

Note: image-builder currently has a bug that causes text rendering to look a bit mushy.

The code below references this super pretty logo and IBM Plex Sans Bold.

use std::borrow::BorrowMut;
use std::fs;
use std::io::Cursor;

use image::io::Reader as ImageReader;
use image::DynamicImage;
use image_builder::{colors, FilterType, Image, Picture, Rect, Text};

use taffy::prelude::*;

fn main() {
    let width = 1200;
    let height = 560;

    let mut tree: TaffyTree<()> = TaffyTree::new();

    let mut image = Image::new(width, height, colors::GRAY);

    let plex_bold = fs::read("fonts/ibm-plex-sans/IBMPlexSans-Bold.ttf").unwrap();

    image.add_custom_font("IBM Plex Sans Bold", plex_bold);

    image.add_rect(
        Rect::new()
            .size(width, height)
            .position(0, 0)
            .color(colors::WHITE),
    );

    let logo = fs::read("images/logo.png").unwrap();
    let img: DynamicImage = ImageReader::new(Cursor::new(logo))
        .with_guessed_format()
        .expect("jpg or png")
        .decode()
        .unwrap();
    let mut logo_picture = Picture::new(img).resize(160, 160, FilterType::Triangle);
    let logo_node = tree
        .new_leaf(Style {
            size: Size {
                width: length(160.0),
                height: length(160.0),
            },
            ..Default::default()
        })
        .unwrap();

    let mut title = Text::new("taffy-image")
        .size(90)
        .font("IBM Plex Sans Bold")
        .color(colors::BLACK);

    let (title_width, title_height) = image.borrow_mut().text_size(&title);
    let title_node = tree
        .new_leaf(Style {
            size: Size {
                width: length(title_width as f32),
                height: length(title_height as f32),
            },
            ..Default::default()
        })
        .unwrap();

    let mut description = Text::new("Generated with taffy-image")
        .size(30)
        .color(colors::GRAY);
    let (description_width, description_height) = image.borrow_mut().text_size(&description);
    let description_node = tree
        .new_leaf(Style {
            size: Size {
                width: length(description_width as f32),
                height: length(description_height as f32),
            },
            ..Default::default()
        })
        .unwrap();

    let root_node = tree
        .new_with_children(
            Style {
                flex_direction: FlexDirection::Column,
                align_content: Some(AlignContent::SpaceBetween),
                align_items: Some(AlignItems::Center),
                justify_content: Some(AlignContent::SpaceEvenly),
                size: Size {
                    width: length(width as f32),
                    height: length(height as f32),
                },
                ..Default::default()
            },
            &[logo_node, title_node, description_node],
        )
        .unwrap();

    tree.compute_layout(root_node, Size::MAX_CONTENT).unwrap();

    image.add_picture(logo_picture.position(
        tree.layout(logo_node).unwrap().location.x as u32,
        tree.layout(logo_node).unwrap().location.y as u32,
    ));

    image.add_text(title.position(
        tree.layout(title_node).unwrap().location.x as u32,
        tree.layout(title_node).unwrap().location.y as u32,
    ));

    image.add_text(description.position(
        tree.layout(description_node).unwrap().location.x as u32,
        tree.layout(description_node).unwrap().location.y as u32,
    ));

    image.save("example.png");
}

That code produces this masterpiece:

A smiley emoji at the top, taffy-image in the middle, ‘Generated with taffy-image’ at the bottom

Wow. Such art.