Indic · Devanagari · Tamil · Bengali · Gujarati · Gurmukhi

Indic PDF API: conjuncts, matras, and virama all shaped correctly.

Generate Hindi, Marathi, Tamil, Bengali, Gujarati, and Punjabi PDFs from HTML. Chromium's HarfBuzz Indic shaper runs the full OpenType feature pipeline (akhn, half, pstf, vatu, blwf). Verified rendering out of the box for Devanagari, Tamil, Bengali, Gujarati, and Khmer (see font coverage). Upload Noto Sans via Brand Kit for the canonical look and Kamy injects @font-face on every render.

Coverage

Five script families, five font uploads.

HarfBuzz handles every Indic shaping rule. Font availability is the only variable — upload once via Brand Kit, set theme.fontFamily, done.

LanguagesScriptShaping featuresRecommended font
Hindi (hi), Marathi (mr), Sanskrit (sa)DevanagariConjunct clusters (क्ष त्र ज्ञ), half-forms, matras above/below the baseline.Noto Sans Devanagari
Tamil (ta)TamilVowel-sign positioning around base consonant (left, right, both); no conjuncts but heavy ligatures.Noto Sans Tamil
Bengali (bn), Assamese (as)BengaliConjuncts via virama suppression, matras, reph (ra-superscript).Noto Sans Bengali
Gujarati (gu)GujaratiDevanagari-derived but no shirorekha (top line); independent vowels and matras.Noto Sans Gujarati
Punjabi (pa)GurmukhiVowel signs (lagaan), tippi/bindi nasal marks, addhak gemination.Noto Sans Gurmukhi

Code

Two complete examples.

Hindi GST invoice

// Hindi GST invoice — Devanagari conjuncts and matras handled by HarfBuzz Indic shaper

const res = await fetch("https://kamy.dev/api/v1/render", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.env.KAMY_API_KEY}`,
  },
  body: JSON.stringify({
    html: `
      <!doctype html>
      <html lang="hi">
        <head>
          <style>
            body {
              font-family: "Noto Sans Devanagari", sans-serif;
              padding: 40px;
              line-height: 1.8;
            }
            h1 { font-size: 28px; }
            .total { font-size: 24px; font-weight: 600; margin-top: 24px; }
            .gstin { font-family: monospace; font-size: 13px; color: #555; }
          </style>
        </head>
        <body>
          <h1>कर चालान संख्या INV-2026-0042</h1>
          <p>ग्राहक: श्री राहुल शर्मा</p>
          <p class="gstin">GSTIN: 27AABCU9603R1ZM</p>
          <p>दिनांक: 24 अप्रैल 2026</p>
          <p>विवरण: मासिक सदस्यता शुल्क</p>
          <p class="total">कुल योग: ₹1,18,000 (GST सहित)</p>
        </body>
      </html>
    `,
    theme: {
      fontFamily: "Noto Sans Devanagari",
      locale: "hi-IN",
    },
  }),
});

Tamil receipt

// Tamil receipt — vowel signs position correctly around the base consonant

const res = await fetch("https://kamy.dev/api/v1/render", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.env.KAMY_API_KEY}`,
  },
  body: JSON.stringify({
    html: `
      <!doctype html>
      <html lang="ta">
        <head>
          <style>
            body {
              font-family: "Noto Sans Tamil", sans-serif;
              padding: 40px;
              line-height: 1.85;
            }
            table { width: 100%; border-collapse: collapse; margin-top: 16px; }
            th, td { padding: 10px 12px; border-bottom: 1px solid #e5e5e5; text-align: left; }
            th { background: #fafafa; font-weight: 600; }
            .total { font-size: 22px; font-weight: 600; margin-top: 24px; }
          </style>
        </head>
        <body>
          <h1>பற்றுச்சீட்டு எண் #2026-0042</h1>
          <p>வாடிக்கையாளர்: திரு. குமார் ராஜன்</p>
          <p>தேதி: 24 ஏப்ரல் 2026</p>
          <table>
            <thead><tr><th>பொருள்</th><th>அளவு</th><th>விலை</th></tr></thead>
            <tbody>
              <tr><td>மாதாந்திர சந்தா</td><td>1</td><td>₹999</td></tr>
              <tr><td>GST (18%)</td><td></td><td>₹180</td></tr>
            </tbody>
          </table>
          <p class="total">மொத்தம்: ₹1,179</p>
        </body>
      </html>
    `,
    theme: { fontFamily: "Noto Sans Tamil", locale: "ta-IN" },
  }),
});

Production tip — choose your default look

Devanagari renders with Noto by default. Tamil, Bengali, and Gujarati render with bundled Lohit / padmaa.

Out of the box, Tamil and Bengali use Lohit-Tamil / Lohit-Bengali, and Gujarati uses padmaa-Medium. They render correctly with full HarfBuzz shaping, but visually feel dated next to Noto. For the canonical Noto look, grab the matching family from the Common Noto fonts panel in Brand Kit — one upload per script, applied to every render.

Common pitfalls

Why Indic breaks elsewhere — and how Kamy avoids it.

Conjunct clusters break apart

Symptom
क्ष renders as क + ् + ष (three separate glyphs) instead of the joined ligature.

Cause
Conjunct shaping requires the OpenType akhn / half / pstf / vatu / blwf features. Tools that bypass HarfBuzz (older PDF libraries, raster-based renderers) ignore them.

Kamy fix
Kamy uses Chromium's HarfBuzz Indic shaper, which runs the full Indic feature pipeline. As long as the font has the conjuncts (Noto Sans Devanagari does), they render correctly.

Vowel signs (matras) appear in the wrong position

Symptom
Tamil ெ (e-vowel) renders to the right of the consonant when it should appear on the left (or both sides for ௌ).

Cause
Matras need contextual positioning per script — Tamil, Devanagari, and Bengali each have different rules. Renderers without an Indic shaper position them naively.

Kamy fix
HarfBuzz reorders matras during shaping based on the script tag in the font. No CSS or markup tricks needed — just ensure lang attribute matches.

Empty boxes everywhere

Symptom
Document is full of □ □ □ where Devanagari, Tamil, or Bengali should be.

Cause
The render image's default font has no Indic glyph coverage. Latin parts of mixed-script docs still render fine, masking the problem until you check the output.

Kamy fix
On kamy.dev this is verified not to happen — Devanagari, Tamil, Bengali, Gujarati, and Khmer all render with embedded fonts (Noto Sans Devanagari and Noto Sans Khmer; bundled Lohit-Tamil, Lohit-Bengali, and padmaa-Medium for Gujarati). If you're self-hosting on an image without Indic fonts, or you want the canonical Noto look across the board, upload Noto Sans Devanagari / Tamil / Bengali / Gujarati / Gurmukhi via Brand Kit → Fonts. One upload per script family, applied to every render.

Ship your first Indic PDF in 60 seconds.

Send HTML, get a PDF — Indic fonts are pre-loaded. Upload Noto Sans for the canonical look. Same API as every other render.

Indic PDF API — Devanagari, Tamil, Bengali rendering | Kamy — Kamy