The Font Size Problem
You build an image template. The title looks great at 48px: “Q1 Report.” Clean, readable, plenty of space. Then someone feeds in “Q1 Quarterly Performance and Revenue Analysis Report” and the text overflows its container, gets cut off, or wraps into an unreadable mess.
This is the fundamental tension in programmatic image generation. You design for one text length. Real data comes in every length.
Static designs don’t have this problem — a designer adjusts the font size for each piece of content. But when you’re generating hundreds or thousands of images from a template, every title, subtitle, and label needs to fit its container regardless of length.
The Image Generation API handles part of this automatically. The text layer wraps text within its container dimensions when is_splitting_lines is set to true (which is the default). But font_size_in_px is a fixed value — the API renders text at exactly the size you specify. For variable-length content where you need the font size itself to adapt, you implement a size selection strategy before making the API call.
Auto-Wrapping: The First Line of Defense
Before you reach for font size logic, understand what the text layer already does. When is_splitting_lines is true, the API wraps text to fit the container width. Set a dimensions with your desired width and height, and the text flows within those bounds.
{
index: 1,
type: "text",
text: "This is a longer title that would overflow a single line but wraps neatly within the container boundaries",
font_name: "Inter",
font_weight: "Bold",
font_size_in_px: 36,
text_color: "#ffffff",
text_align: "left",
vertical_align: "top",
is_splitting_lines: true,
position: { x: 60, y: 100 },
dimensions: { width: 680, height: 200 },
}
For many use cases, this is enough. A title container that’s 680px wide and 200px tall can fit several lines of 36px text. Short titles use one line. Longer titles wrap to two or three. The container height accommodates the variation.
But wrapping has limits. At 36px, a 15-word title wraps into 3 lines and fits. A 30-word title wraps into 6 lines and overflows the container. At some point, you need a smaller font size.
Built-In Auto-Scaling
Before writing any font size logic, try the API’s native auto-scaling. The text layer has a should_auto_scale boolean that automatically shrinks the font size to fit the text within its container dimensions. Set it to true, define your maximum font_size_in_px, and the API handles the rest:
{
index: 1,
type: "text",
text: "Q1 Quarterly Performance and Revenue Analysis Report",
font_name: "Inter",
font_weight: "Bold",
font_size_in_px: 48,
text_color: "#ffffff",
text_align: "left",
vertical_align: "top",
is_splitting_lines: true,
should_auto_scale: true,
position: { x: 60, y: 100 },
dimensions: { width: 680, height: 200 },
}
With should_auto_scale: true, a short title like “Q1 Report” renders at the full 48px. A longer title automatically scales down until it fits within the 680x200 container. No client-side logic needed.
For many use cases, this is all you need. The API picks the right size for whatever text you throw at it, respecting both the container width (with wrapping via is_splitting_lines) and the container height.
Font Size Selection for More Control
If you need precise control over which font sizes are used at which text lengths — for example, to enforce design system tiers or to keep visual consistency across a batch of images — you can implement a tier-based approach instead.
Pick the font size based on character count. Short text gets a large size. Long text gets a smaller one. Define tiers that match your template’s container.
type FontSizeTier = {
maxCharacterCount: number;
font_size_in_px: number;
};
const TITLE_FONT_SIZE_TIERS: FontSizeTier[] = [
{ maxCharacterCount: 30, font_size_in_px: 48 },
{ maxCharacterCount: 60, font_size_in_px: 36 },
{ maxCharacterCount: 100, font_size_in_px: 28 },
{ maxCharacterCount: 200, font_size_in_px: 22 },
];
const DEFAULT_FONT_SIZE_IN_PX = 18;
const getFontSizeForText = (text: string, tiers: FontSizeTier[]): number => {
const maybeTier = tiers.find((tier) => text.length <= tier.maxCharacterCount);
return maybeTier?.font_size_in_px ?? DEFAULT_FONT_SIZE_IN_PX;
};
Use it when building the text layer:
const title = "Q1 Quarterly Performance and Revenue Analysis Report";
const font_size_in_px = getFontSizeForText(title, TITLE_FONT_SIZE_TIERS);
const titleLayer = {
index: 1,
type: "text",
text: title,
font_name: "Inter",
font_weight: "Bold",
font_size_in_px,
text_color: "#ffffff",
text_align: "left",
vertical_align: "top",
is_splitting_lines: true,
position: { x: 60, y: 100 },
dimensions: { width: 680, height: 200 },
};
The tier thresholds aren’t magic — they depend on your container dimensions, font, and design. Start with rough estimates and adjust after seeing the output with real data.
Tuning Your Tiers
Character count is a rough proxy for rendered width. A string of “W”s is much wider than the same number of “i”s. But for most body text — product names, article titles, descriptions — character count correlates well enough.
To calibrate your tiers:
- Start with your largest font size and a container width
- Write a title at the max length you’d expect for that size — does it fit in 1-2 lines?
- Write a title just past that length — does it need to drop to the next tier?
- Repeat for each tier down to your minimum readable size
For a 680px-wide container with Inter at 48px, roughly 25-30 characters fit per line. A 30-character max for the 48px tier means most titles stay on one line. At 36px, you get ~35 characters per line, so a 60-character max gives you comfortable two-line titles.
The exact numbers vary by font. A condensed typeface fits more characters per line. A wide typeface fits fewer. Test with your actual font.
A Complete Example: Blog Post Social Cards
Social cards for blog posts are a common case. Titles range from “Launch Day” (10 characters) to “How We Migrated Our Entire Infrastructure to Kubernetes Without Downtime” (72 characters). The card dimensions are fixed at 1200x630.
const SOCIAL_CARD_TITLE_TIERS: FontSizeTier[] = [
{ maxCharacterCount: 30, font_size_in_px: 56 },
{ maxCharacterCount: 50, font_size_in_px: 44 },
{ maxCharacterCount: 80, font_size_in_px: 36 },
{ maxCharacterCount: 120, font_size_in_px: 28 },
];
const client = new IterationLayer({ apiKey: "YOUR_API_KEY" });
const generateSocialCard = async (title: string, author: string) => {
const titleFontSizeInPx = getFontSizeForText(title, SOCIAL_CARD_TITLE_TIERS);
const { data: { buffer } } = await client.generateImage({
dimensions: { width: 1200, height: 630 },
output_format: "png",
layers: [
{
index: 0,
type: "solid-color-background",
hex_color: "#0f172a",
},
{
index: 1,
type: "static-image",
file: { type: "url", name: "logo.png", url: "https://example.com/logo.png" },
position: { x: 60, y: 40 },
dimensions: { width: 140, height: 45 },
},
{
index: 2,
type: "text",
text: title,
font_name: "Inter",
font_weight: "Bold",
font_size_in_px: titleFontSizeInPx,
text_color: "#f8fafc",
text_align: "left",
vertical_align: "center",
is_splitting_lines: true,
position: { x: 60, y: 120 },
dimensions: { width: 1080, height: 350 },
},
{
index: 3,
type: "text",
text: `By ${author}`,
font_name: "Inter",
font_weight: "Regular",
font_size_in_px: 22,
text_color: "#94a3b8",
text_align: "left",
vertical_align: "bottom",
position: { x: 60, y: 540 },
dimensions: { width: 1080, height: 40 },
},
],
});
return Buffer.from(buffer, "base64");
};
The title container is 1080px wide and 350px tall — plenty of room. Combined with vertical_align: "center", short titles sit centered in the space while longer titles fill more of it. The font size tiers ensure nothing overflows.
Multiple Dynamic Text Fields
Templates often have more than one variable text field. A product card might have a title, description, and price — each with different length constraints.
Define separate tiers for each field:
const PRODUCT_TITLE_TIERS: FontSizeTier[] = [
{ maxCharacterCount: 25, font_size_in_px: 32 },
{ maxCharacterCount: 50, font_size_in_px: 24 },
{ maxCharacterCount: 80, font_size_in_px: 20 },
];
const PRODUCT_DESCRIPTION_TIERS: FontSizeTier[] = [
{ maxCharacterCount: 60, font_size_in_px: 18 },
{ maxCharacterCount: 120, font_size_in_px: 15 },
{ maxCharacterCount: 200, font_size_in_px: 13 },
];
Each field scales independently. The title might be at 32px while the description is at 13px. That’s fine — they’re different visual elements with different roles.
When Wrapping Alone Is Enough
Not every template needs font size selection. If your text length is bounded — say, user names (rarely over 30 characters) or product prices (“$1,234.56”) — a fixed font size with auto-wrapping handles the variation.
The rule of thumb: if your shortest text and longest text differ by less than 3x in character count, wrapping alone is probably sufficient. Set the font size for the longest expected input and let shorter text have extra whitespace.
Font size selection becomes important when:
- Text length varies by more than 3x (e.g., 10 to 100 characters)
- The container is constrained (small cards, narrow banners)
- Visual impact matters (you want short titles to be large and punchy)
Keep It Simple
The tier-based approach is deliberately simple. You could build something more sophisticated — measure estimated text width using font metrics, binary-search for the largest size that fits, account for word-break points. But for generated images, the simple approach works. You define 3-5 tiers, test with representative data, and adjust. It takes 15 minutes to calibrate and handles 95% of real-world inputs.
The API’s auto-wrapping handles the rest. Between is_splitting_lines and a few font size tiers, dynamic text fits its container at any length.
What’s Next
Generated images work with the same auth and credit pool as Document Extraction and Image Transformation — chain them in a single pipeline.
Get Started
Check out the Image Generation API docs for the full text layer reference. The is_splitting_lines, vertical_align, and text_align properties give you precise control over how text flows within its container. Pair that with the font size selection pattern from this post, and variable-length text stops being a problem.
Sign up for a free account — no credit card required.