Bold, Italic, and Rich Text in Generated Images — No Font Switching Required

6 min read Image Generation

The Inline Formatting Problem

You’re generating an image with text. The design calls for a sentence where one word is bold and another is italic. Something like: “Check out our new feature for faster processing.”

In most image generation approaches, this is painful. Canvas APIs — whether browser <canvas>, Node.js canvas, or Sharp’s text compositing — render text in a single font style per draw call. To mix bold and regular text in one line, you have to:

  1. Measure the width of “Check out our “ in the regular font
  2. Draw it at position x
  3. Measure the width of “new feature” in the bold font
  4. Draw it at position x + previous width
  5. Measure the width of “ for “ in the regular font
  6. Keep going for every style change

Each segment requires a separate measurement and draw call. You’re manually implementing a text layout engine. And if the text wraps to multiple lines, the complexity multiplies — you need to track line breaks, recalculate positions, and handle word boundaries.

The Image Generation API text layer solves this with markdown. Write **bold** and *italic* in the text field, provide the matching font variants, and the API handles the inline switching automatically.

Markdown in the Text Layer

The text layer accepts standard markdown formatting in the text field. Use **double asterisks** for bold and *single asterisks* for italic. The API parses the markdown, identifies the style segments, and renders each segment with the correct font variant — all within a single text layer.

import { IterationLayer } from "iterationlayer";
const client = new IterationLayer({ apiKey: "YOUR_API_KEY" });

const result = await client.generateImage({
  dimensions: { width: 800, height: 400 },
  output_format: "png",
  fonts: [
    {
      name: "Inter",
      weight: "Regular",
      style: "normal",
      file: { type: "url", name: "Inter-Regular.woff2", url: "https://example.com/fonts/Inter-Regular.woff2" },
    },
    {
      name: "Inter",
      weight: "Bold",
      style: "normal",
      file: { type: "url", name: "Inter-Bold.woff2", url: "https://example.com/fonts/Inter-Bold.woff2" },
    },
    {
      name: "Inter",
      weight: "Regular",
      style: "italic",
      file: { type: "url", name: "Inter-Italic.woff2", url: "https://example.com/fonts/Inter-Italic.woff2" },
    },
  ],
  layers: [
    {
      index: 0,
      type: "solid-color-background",
      hex_color: "#ffffff",
    },
    {
      index: 1,
      type: "text",
      text: "Check out our **new feature** for *faster* processing",
      font_name: "Inter",
      font_weight: "Regular",
      font_size_in_px: 32,
      text_color: "#1a1a2e",
      text_align: "left",
      vertical_align: "top",
      is_splitting_lines: true,
      position: { x: 40, y: 40 },
      dimensions: { width: 720, height: 320 },
    },
  ],
});

The text renders as a single flowing paragraph. “Check out our “ appears in Inter Regular. “new feature” renders in Inter Bold. “for” switches back to Regular. “faster” renders in Inter Italic. “processing” returns to Regular. All positioned and wrapped as one continuous text block.

How Font Variant Matching Works

The API resolves markdown formatting to font variants using the fonts array. Here’s the matching logic:

  • Regular text uses the font_weight and fontStyle specified on the text layer. In the example above, that’s weight Regular, style normal — Inter Regular.
  • Bold text (**...**) looks for a font with the same name but weight Bold and the same style. If you specified font_weight: "Regular" on the layer, bold segments use weight Bold.
  • Italic text (*...*) looks for a font with the same name and weight but style "italic".

Each font variant is a separate file. This is how fonts work — Inter Regular and Inter Bold are different font files. The fonts array maps each combination of name, weight, and style to its file.

If a variant is missing from the fonts array, the API falls back to the base variant. So if you use **bold** in the text but don’t provide a Bold weight font file, the bold segments render in the regular weight. No error, no crash — just no visual difference for that segment.

A Practical Example: Quote Cards

Quote cards are a natural fit for markdown text. The quote itself in italic, the attribution in bold, maybe a keyword highlighted.

import { IterationLayer } from "iterationlayer";
const client = new IterationLayer({ apiKey: "YOUR_API_KEY" });

const generateQuoteCard = async (quote: string, author: string) => {
  const result = await client.generateImage({
    dimensions: { width: 1080, height: 1080 },
    output_format: "png",
    fonts: [
      {
        name: "Georgia",
        weight: "Regular",
        style: "normal",
        file: { type: "url", name: "Georgia-Regular.ttf", url: "https://example.com/fonts/Georgia-Regular.ttf" },
      },
      {
        name: "Georgia",
        weight: "Regular",
        style: "italic",
        file: { type: "url", name: "Georgia-Italic.ttf", url: "https://example.com/fonts/Georgia-Italic.ttf" },
      },
      {
        name: "Georgia",
        weight: "Bold",
        style: "normal",
        file: { type: "url", name: "Georgia-Bold.ttf", url: "https://example.com/fonts/Georgia-Bold.ttf" },
      },
    ],
    layers: [
      {
        index: 0,
        type: "solid-color-background",
        hex_color: "#faf9f6",
      },
      {
        index: 1,
        type: "rectangle",
        hex_color: "#c4a35a",
        position: { x: 80, y: 200 },
        dimensions: { width: 4, height: 300 },
      },
      {
        index: 2,
        type: "text",
        text: `*${quote}*`,
        font_name: "Georgia",
        font_weight: "Regular",
        font_size_in_px: 36,
        text_color: "#2d2d2d",
        text_align: "left",
        vertical_align: "center",
        is_splitting_lines: true,
        position: { x: 120, y: 200 },
        dimensions: { width: 840, height: 300 },
      },
      {
        index: 3,
        type: "text",
        text: `— **${author}**`,
        font_name: "Georgia",
        font_weight: "Regular",
        font_size_in_px: 24,
        text_color: "#666666",
        text_align: "left",
        vertical_align: "top",
        position: { x: 120, y: 540 },
        dimensions: { width: 840, height: 40 },
      },
    ],
  });

  const { data: { buffer } } = result;

  return Buffer.from(buffer, "base64");
};

The quote renders in italic Georgia. The author name renders in bold Georgia. The em dash and surrounding text use regular Georgia. One text layer per block, no manual width calculations, no per-segment positioning.

Combining with Auto-Wrap

Markdown formatting works naturally with is_splitting_lines. The API wraps text at word boundaries, and style changes flow across line breaks. A bold phrase that spans a line break stays bold on both lines.

This means you can use markdown in long-form text blocks — product descriptions, testimonial quotes, notification messages — without worrying about how style changes interact with wrapping. The API handles the line-break and style-switch coordination internally.

{
  index: 1,
  type: "text",
  text: "We've been using this tool for **six months** and it has completely changed how we handle *document processing*. The API is straightforward, the results are consistent, and the **support team** responds within hours.",
  font_name: "Inter",
  font_weight: "Regular",
  font_size_in_px: 20,
  text_color: "#333333",
  text_align: "left",
  vertical_align: "top",
  is_splitting_lines: true,
  position: { x: 40, y: 40 },
  dimensions: { width: 720, height: 300 },
}

This paragraph wraps across multiple lines. “six months” is bold wherever it lands. “document processing” is italic. “support team” is bold. The line breaks don’t interfere with the formatting.

What Markdown Supports

The text layer supports two markdown constructs:

  • **bold text** — renders with font weight Bold
  • *italic text* — renders with font style italic

That’s it. No headers, no lists, no links, no images. This is a text rendering layer, not a markdown document renderer. The markdown syntax is a convenient way to express inline style changes — nothing more.

You can nest them too. ***bold and italic*** renders with both weight Bold and italic style, assuming you provide that font variant in the fonts array.

The Alternative: Manual Segments

Without markdown support, here’s what you’d do to render “Check out our new feature for faster processing” in a generated image:

// Without markdown: 5 separate text layers, manually positioned
const layers = [
  { index: 1, type: "text", text: "Check out our ", font_weight: "Regular", position: { x: 40, y: 40 }, /* ... */ },
  { index: 2, type: "text", text: "new feature", font_weight: "Bold", position: { x: 262, y: 40 }, /* ... */ },
  { index: 3, type: "text", text: " for ", font_weight: "Regular", position: { x: 424, y: 40 }, /* ... */ },
  { index: 4, type: "text", text: "faster", font_weight: "Regular", fontStyle: "italic", position: { x: 480, y: 40 }, /* ... */ },
  { index: 5, type: "text", text: " processing", font_weight: "Regular", position: { x: 555, y: 40 }, /* ... */ },
];

Five layers instead of one. Every x position calculated by hand. And if the text changes — different words, different lengths — every position needs recalculating. If the text wraps, you need to split segments across lines manually.

Markdown in a single text layer replaces all of that with "Check out our **new feature** for *faster* processing".

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. Upload your font variants — Regular, Bold, Italic — in the fonts array, and start using markdown in your text layers. What used to take five layers and manual pixel math now takes one layer and a few asterisks.

Sign up for a free account — no credit card required.

Start building in minutes

Free trial included. No credit card required.