ReoGrid ReoGrid Web

PDF Export

Since v1.4.0 a ReoGrid instance can render the active worksheet to a real, vector PDF — text, borders, fills, merges, and cell-anchored images — with no external library and no server round-trip. Output is generated in-browser from worksheet data.

Note: PDF Export is available in the Pro edition.

A font is REQUIRED. The PDF renderer embeds glyph outlines, so every export needs real font bytes. Name a locale ('ja', 'zh-CN', 'zh-TW', 'ko') and ReoGrid resolves the font for you, or pass your own bytes in font. Without one of the two the export throws. This is what makes Japanese, Chinese and Korean text render correctly.

Quick start

import { createReogrid, preloadPdfFont } from '@reogrid/pro'

const grid = createReogrid({ workspace: '#app' })

// Fetch the locale's font once, at an async point you control
await preloadPdfFont('ja')

// Render + download in one call
grid.saveAsPdf({ locale: 'ja', filename: 'report.pdf' })

saveAsPdf() renders and triggers a browser download. Use exportPdf() when you want the raw bytes (e.g. to upload or preview):

const bytes: Uint8Array = grid.exportPdf({ locale: 'ja' })
// e.g. wrap in a Blob for a preview URL
const url = URL.createObjectURL(new Blob([bytes], { type: 'application/pdf' }))

Why the separate preloadPdfFont step? Export is synchronous on purpose: saveAsPdf ends in a click on a generated link, which loses its user-gesture context — and gets caught by popup blockers — if an await runs first. So the download happens at a point you choose, once, and every later export is instant. It also keeps a multi-MB font fetch from hiding inside an innocuous-looking saveAsPdf().

Locales

locale names a font ReoGrid knows how to fetch. Four are registered out of the box:

LocaleFontUse for
'ja'Noto Sans JPJapanese
'zh-CN'Noto Sans SCSimplified Chinese
'zh-TW'Noto Sans TCTraditional Chinese
'ko'Noto Sans KRKorean
import { preloadPdfFont, registerPdfFont } from '@reogrid/pro'

await preloadPdfFont('zh-CN')                 // once, at app start
grid.saveAsPdf({ locale: 'zh-CN' })           // anywhere, thereafter

Picking the right locale matters: a Japanese font covers only about a third of common simplified-Chinese business vocabulary, so exporting Chinese text with 'ja' produces tofu (□□□) for the rest.

Register your own locale — with a loader, a URL, or bytes you already have:

registerPdfFont('th', () => loadFont('/fonts/NotoSansThai-Subset.ttf'))
await preloadPdfFont('th')
grid.saveAsPdf({ locale: 'th' })

Registering bytes marks the locale loaded immediately, so no preload is needed — that is the way to avoid the network entirely (e.g. a font shipped with your app or installed from a package).

Related helpers, all exported from @reogrid/pro: isPdfFontRegistered, isPdfFontLoaded, getPdfFont, getRegisteredPdfFontTags, clearPdfFontCache.

Font packages (no CDN, works offline)

The built-in locales fetch the full upstream font from a public CDN — Noto Sans SC alone is about 17 MB, and that CDN is slow and intermittently unreachable from mainland China. For production, install the matching subset package instead and register it: the bytes come from npm, so there is no runtime network at all, which also works on the offline intranets common in enterprise deployments.

npm install @reogrid/font-sc     # zh-CN — also font-tc, font-jp, font-kr
import { registerPdfFont, preloadPdfFont } from '@reogrid/pro'
import { loadNotoSansSC } from '@reogrid/font-sc'

registerPdfFont('zh-CN', loadNotoSansSC)   // point the locale at the package
await preloadPdfFont('zh-CN')              // once, at app start

grid.saveAsPdf({ locale: 'zh-CN', filename: 'report.pdf' })
PackageLocaleLoaderCharactersOver the wire
@reogrid/font-sc'zh-CN'loadNotoSansSC7,709 (GB 2312)~2,593 KB
@reogrid/font-tc'zh-TW'loadNotoSansTC13,682 (Big5)~4,840 KB
@reogrid/font-jp'ja'loadNotoSansJP6,974 (JIS X 0208)~2,673 KB
@reogrid/font-kr'ko'loadNotoSansKR3,196 (KS X 1001)~494 KB

Each subset carries its language’s classic national standard including level 2 — that is where personal and place names live, and a customer’s name rendering as tofu is worse than a slightly larger download. The bytes sit behind a dynamic import, so a bundler gives them their own chunk: an app that never exports a PDF never downloads them.

Supplying your own font bytes

Use font for a typeface ReoGrid does not know about. It takes raw TrueType glyf bytes (ArrayBuffer | Uint8Array), and wins when both font and locale are given. Three ways to get them:

import { loadDefaultJapaneseFont, loadFont, DEFAULT_JAPANESE_FONT_URL } from '@reogrid/pro'

// 1. Bundled default — Noto Sans JP (~9.6 MB, fetched from a public CDN, cached)
const a = await loadDefaultJapaneseFont()

// 2. Your own hosted subset (recommended for production — much smaller)
const b = await loadFont('https://cdn.example.com/fonts/NotoSansJP-Subset.ttf')

// 3. Bytes you already have (e.g. from an <input type="file"> or fetch)
const c = new Uint8Array(await file.arrayBuffer())

Both helpers cache the downloaded bytes in memory and in Cache Storage, so the ~9.6 MB default is fetched at most once per browser. For production, host a subset of the glyphs you actually need and pass its URL to loadFontDEFAULT_JAPANESE_FONT_URL is exported if you want to build a subset from the same source.

font is not a font name. font: 'Arial' does not work — the option takes the font file bytes, because the glyph outlines are embedded into the PDF itself (that is what makes it render the same everywhere, with no font installed on the reader’s machine). Load the file first, as above. Passing a name throws:

exportWorksheetPdf: `font` must be TrueType (glyf) font bytes, not a font-family
name (got "Arial"). Either name a locale — …

A locale in the wrong field is recognised and redirected: font: 'zh-CN' says to pass it as { locale: 'zh-CN' }.

The font must be TrueType with glyf outlines. OpenType/CFF files (OTTO — many .otf files) are not supported yet and are rejected with their own message.

ExportPdfOptions

OptionTypeDefaultDescription
locale'ja' | 'zh-CN' | 'zh-TW' | 'ko' or a registered tagFont to embed, resolved through the registry. Preload it first. Required unless font is given.
fontArrayBuffer | Uint8ArrayEmbeddable TrueType (glyf) font bytes. Wins over locale. Required unless locale is given.
pageSize'A3'|'A4'|'A5'|'B4'|'B5'|'Letter'|'Legal'|'Tabloid'|'Executive' or { widthMm, heightMm }'A4'Paper size.
orientation'portrait' | 'landscape''portrait'Page orientation.
marginMmnumber | { top, right, bottom, left }12Margins in mm (number = all sides).
rangePdfExportRangeused rangeCell range to export.
showGridLinesbooleanthe worksheet’s own settingDraw light grid lines. Defaults to whatever worksheet.setShowGridLines() / the on-screen grid shows, so the PDF matches the screen unless you override it.
scalenumber1Uniform content scale (0.1–4). Ignored when fitToWidth.
fitToWidthbooleanfalseScale so all columns fit one page width.
usePageBreaksbooleanfalsePaginate exactly like the on-screen page-break preview (see below).
imagesWorksheetImageInfo[]sheet imagesCell-anchored images to draw.
showImagesbooleantrueDraw cell-anchored images.
titlestringDocument title (PDF metadata).

saveAsPdf additionally accepts filename?: string. When omitted it defaults to the loaded document’s base name (see getDocumentName() / setDocumentName()), or worksheet.pdf when no file has been loaded.

Multi-page output with usePageBreaks

By default exportPdf lays the used range onto pages using the pageSize / orientation / marginMm / scale options. Set usePageBreaks: true to instead paginate using the worksheet’s own paging model — the PDF then breaks pages in exactly the same places as the Page Layout page-break preview, including manual breaks, the printable range, and the fit scale:

// The paper, orientation, margins, scale, and page bands all come from
// worksheet.getPrintSettings() + the computed breaks.
grid.worksheet.setPrintSettings({ paperSize: 'A4', orientation: 'landscape' })
grid.worksheet.insertRowPageBreak(40)   // start a new page at row 40

await preloadPdfFont('ja')
grid.saveAsPdf({ locale: 'ja', usePageBreaks: true, filename: 'ledger.pdf' })

When usePageBreaks is on, the option-driven pageSize / orientation / marginMm / scale / fitToWidth / range fields are ignored — configure them through Page Layout instead. If the sheet has nothing to paginate, it falls back to the option-driven fields.

React example: an Export PDF button

import { useEffect, useRef } from 'react'
import { Reogrid } from '@reogrid/pro/react'
import { preloadPdfFont } from '@reogrid/pro'
import type { ReogridInstance } from '@reogrid/pro/react'

function App() {
  const gridRef = useRef<ReogridInstance>(null)

  // Pay the font download once, on mount — not inside the click handler,
  // where an `await` would cost the user gesture the download link needs.
  useEffect(() => { void preloadPdfFont('ja') }, [])

  function handleExport() {
    gridRef.current?.saveAsPdf({ locale: 'ja', usePageBreaks: true, filename: 'report.pdf' })
  }

  return (
    <>
      <button onClick={handleExport}>Export PDF</button>
      <Reogrid ref={gridRef} style={{ flex: 1 }} />
    </>
  )
}

Vue example: an Export PDF button

The Vue component publishes the grid as instance on its template ref (gridRef.value?.instance), so the export call is the same one as everywhere else:

<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { Reogrid, type ReogridInstance } from '@reogrid/pro/vue'
import { preloadPdfFont } from '@reogrid/pro'

const gridRef = ref<{ instance: ReogridInstance | null } | null>(null)

// Pay the font download once, on mount — not inside the click handler,
// where an `await` would cost the user gesture the download link needs.
onMounted(() => { void preloadPdfFont('ja') })

function exportPdf() {
  gridRef.value?.instance?.saveAsPdf({ locale: 'ja', usePageBreaks: true, filename: 'report.pdf' })
}
</script>

<template>
  <button @click="exportPdf">Export PDF</button>
  <Reogrid ref="gridRef" style="width: 100%; height: 400px" />
</template>
  • Page Layout — page size, margins, and the page-break preview that usePageBreaks follows.
  • Print — overview of all printing routes and how to choose.
  • Print to HTML — quick print via the browser’s native dialog.
  • Images — the cell-anchored images that appear in exported PDFs.
Stay Updated

Be first to know — get updates as they ship

Get notified of new releases, features, and announcements.
No spam — just updates that matter.