canvas-editor-pdf
Version:
pdf exporter to canvas-editor
309 lines (279 loc) • 17.8 kB
Markdown
# Changelog
## 0.6.0 (2026-08-01)
> **Migrating from 0.5.0** — four breaking changes, each detailed below:
> `getValue()` returns `IEditorData` directly (drop the `.data`);
> `table.overflow` now defaults to `false`; `modeRule.print.filterHideElementRow`
> and a set of unused `DrawPdf` methods (`getDataURL`, the `setPaper*` /
> `setPage*` reconfiguration setters, `setPrintData`, `forceUpdate`,
> `getRowCount`, `getCtx`) were removed; and `element.pagingId` / `pagingIndex`
> no longer exist in the data shape.
### Fixed
- LaTeX no longer hangs `setValue()` in the browser. The root cause was a
browser-shim regression (0.4.0, Node-support refactor): `LaTexUtils.svg()`
returns an already-wrapped `data:image/svg+xml;base64,…` URL, but
`svgToPngDataUrl` wrapped it a second time — the resulting `<img>` never
loaded and, with no `onerror` handler, the promise never resolved, stalling
any awaited caller. LaTeX rendering no longer rasterizes at all (see below),
so this path is gone; the shim was also hardened (uses an already-encoded
data URL as-is, rejects on image load failure) for its remaining public use
via `svgString2Image`.
- Nested ordered lists now number each level independently instead of running
the count straight through. `getValue()` strips `listId` from a list's
`valueList` items (only `listLevel` survives serialization), but
`formatElementList` reused a single `listId` for the whole list, so nested
items shared the parent's counter. It now assigns a distinct `listId` per
`listLevel` on re-import (matching canvas-editor), so a child restarts at 1
and the parent resumes afterward.
- `setValue()` no longer aborts early when a document has no header or footer.
It used `return` instead of `continue` while iterating the header/main/footer
zones, so a missing zone bailed out of the whole method before
`setEditorData` ran, leaving the content unset.
- Table row height now adapts correctly for a row-spanning cell with tall
content. When regrouping cells by column, the rowspan target was compared
against the column index instead of the current row index, so a tall
`rowspan` cell could land in the wrong row and its height not propagate.
(Ported from canvas-editor.)
- A table that spans pages no longer corrupts `getValue()`. The old renderer
paginated by *cloning* the table element per page (`pagingId`/`pagingIndex`)
and merging the clones back on serialization; repeated (`pagingRepeat`)
header rows survived the merge, so a 60-row table came back with 63 rows.
Pagination is now render-only and the data layer keeps a single table.
- `new DrawPdf(editor.command.getValue().options, …)` and `updateOptions(...)`
no longer raise a type error. canvas-editor's option types are a separate copy
— and, when consumed from the editor's own source, a different module instance
— so their enum-typed fields are nominally incompatible with this lib's and no
type union can bridge them. The `options` parameter now accepts a loose object
at the boundary (it is normalized by `mergeOption` anyway); pass this lib's
`IEditorOption` for full autocomplete.
### Added
- Nested list rendering (aligned with canvas-editor): `element.listLevel` is now
honored — nested markers and content indent per level, unordered bullets
rotate by depth (disc → circle → square), and ordered-list numbering is
tracked per `listId` so a parent list resumes its sequence after a nested
child. (The editor-side indent/outdent commands were not ported — this
library only renders.)
### Changed
- **Table pagination reworked** (ported from canvas-editor's optimize-table-
pagination change). A table crossing a page boundary is now split into
per-page *fragments* at the render layer — the data layer keeps one table
element — instead of being cloned into several table elements. A row taller
than the remaining space is split mid-row, `pagingRepeat` header rows are
re-shown on continuation pages, and row-spanning cells carry across the
break. New `TablePaging` module plus fragment-aware `TableParticle` and
`Position`. **Breaking (data shape):** `element.pagingId` / `pagingIndex`
are gone — they described the old clone-per-page model.
- LaTeX formulas now render as **vector paths** drawn straight onto the jsPDF
context, instead of being rasterized to a PNG and placed as an image. Output
is crisp at any zoom, the PDF is smaller, and the render path is fully
synchronous (no `Image`/SVG decoding — it can never stall an awaited caller).
The formula's polylines come from `LaTexUtils`; stroke color follows the
element's `color`. `svgString2Image`/`platform.svgToPngDataUrl` remain
exported for back-compat but are no longer used internally.
- A row whose visible content is entirely hidden (`element.hide` /
`control.hide` / `area.hide`) now collapses to zero height in any non-design
render mode, so hidden content leaves no blank gap in the exported PDF.
Empty/newline-only rows are unaffected. Previously this only happened in
PRINT mode, gated behind `modeRule.print.filterHideElementRow`.
- **Breaking (default change):** `table.overflow` now defaults to `false`
(was `true`), matching canvas-editor. When `overflow` is false, a table wider
than the content area is proportionally shrunk to fit (columns compress down
to `table.defaultColMinWidth`, then stop) and its horizontal offset is reset —
so wide tables no longer bleed past the page margin in the exported PDF. Set
`table: { overflow: true }` to restore the old behavior. (Rendering side of
canvas-editor's table-width-autofit feature; the editor-only autofit menu
commands were not ported.)
### Removed
- **Breaking:** the `modeRule.print.filterHideElementRow` option. Hidden-row
collapsing is now unconditional in non-design modes (see above), so the flag
no longer had any effect.
- **Breaking:** a large dead-code cleanup dropped the unused interactive-editor
layer that this PDF-only fork never exercised (cursor, mouse/keyboard events,
`RangeManager`, zone, worker, previewer, table operate/tool, search, the
`Control` module, shortcuts, i18n, clipboard and print helpers, and their
CSS/SVG assets). Along with it, several `DrawPdf` public methods were removed:
`getDataURL` (page-to-PNG export), `setPaperSize` / `setPaperDirection` /
`setPaperMargin` / `setPageScale` / `setPagePixelRatio` / `setPageDevicePixel`
(runtime paper/DPR reconfiguration — configure via the constructor instead),
`setPrintData` / `clearPrintData`, `forceUpdate` (use `render()`),
`getRowCount`, and `getCtx`. `updateOptions` was intentionally **kept**.
- **Breaking:** `getValue()` now returns the `IEditorData` object directly
(`{ header, main, footer, graffiti }`) instead of the `IEditorResult` wrapper
(`{ version, data, options }`). Read the returned object as the data itself —
`getValue().data` is no longer valid.
- `getElementFont(el, scale)` was merged into `getFont(el, scale = 1)` (identical
behavior). This is internal API; callers should use `getFont`.
## 0.5.0 (2026-07-09)
### Added
- **Multi-column layout** via a new `column` option (`count`, `gap`,
`separator`, `separatorColor`, `separatorWidth`). Content flows
column-by-column and only spills to the next page once the last column
fills; optional separator lines are drawn between columns. Ignored in
`CONTINUITY` page mode.
- **Per-page header/footer control**: `disabledPages` (page numbers on which
the header/footer is hidden) and `editable` on the header/footer options,
plus `inactiveAlpha`. Pages with disabled chrome reclaim the vertical space.
- **Nested tables** — tables inside table cells now lay out, render and
paginate correctly (cell/element/position resolved through a table path).
- **Areas inside table cells** — an area's background/border/placeholder is now
honored when the area lives within a `td`, positioned to the cell content box.
- Control `minWidth` is now honored across line wraps (placeholder elements are
inserted to fill the remaining width and restored before each relayout).
- `areaHideDisabled` print-mode rule.
### Fixed
- LaTeX elements clear their target rect before drawing, avoiding overdraw when
a page is re-rendered.
- **Text watermark rendering.** The non-repeating watermark is now centered on
the page (was anchored near the bottom-left and rendered off-center), and
`repeat: true` now actually tiles the text across the page (the previous
code relied on a canvas pattern fill that jsPDF's `Context2d` doesn't
support, so a repeating watermark drew nothing). `gap` spacing applies to the
tiled layout. Image watermarks are unaffected (still single, non-repeating).
- **Page number font.** The page-number renderer rewrote the font family
`Microsoft YaHei` → `Yahei` before drawing, but fonts register with jsPDF
under a lowercased id (e.g. `microsoft yahei`) and no `Yahei` exists, so CJK
page numbers (e.g. `第{pageNo}页/共{pageCount}页`) fell back to a Latin font
and rendered as garbage. Page numbers now resolve the font the same way body
text does — lowercasing the family to match the registered id and calling
`setFont` — and the text is measured with that font so centered/right
alignment stays accurate.
## 0.4.2 (2026-05-27)
### Fixed
- Added a `typesVersions` map for the `./node` subpath so consumers on
TypeScript < 5.0 (classic `moduleResolution: "node"`, which doesn't read the
`exports` `types` condition) can still resolve types for
`import { DrawPdf } from 'canvas-editor-pdf/node'`. Without it, those projects
hit `TS2307: Cannot find module 'canvas-editor-pdf/node'`. Modern resolution
(`bundler`/`node16`/`nodenext`) keeps using the `exports` map.
## 0.4.1 (2026-05-27)
### Fixed
- Widened the `@napi-rs/canvas` peer dependency range from `^0.1.0` to
`^0.1.0 || ^1.0.0`. 0.4.0 only accepted the 0.1.x line, so installing
alongside `@napi-rs/canvas@1.x` (the current major) failed with an
`ERESOLVE` peer conflict. Verified the render pipeline works unchanged on
`@napi-rs/canvas@1.0.0`.
## 0.4.0 (2026-05-27)
### Added
- **Node.js support** via subpath export `canvas-editor-pdf/node`.
Browser consumers continue using `import { DrawPdf } from 'canvas-editor-pdf'`;
Node consumers use `import { DrawPdf } from 'canvas-editor-pdf/node'`. Same
public API surface in both environments.
- Internal `src/platform/` shim that swaps DOM canvas / `fetch` / `Image` for
`@napi-rs/canvas` / `node:fs` / `@resvg/resvg-js` in the Node build (selected
via a `vite.config.ts` `resolve.alias` regex). `DrawPdf`, `Watermark` and
`ImageParticle` are the only call sites that import the shim.
- `fontSource` option on `DrawPdf` constructor (`'cdn' | 'bundled' | { dir }`).
Default is `'bundled'` in Node (reads `dist/font/` from the installed package)
and `'cdn'` in the browser (preserves pre-0.4.0 behavior).
- `@napi-rs/canvas` and `@resvg/resvg-js` declared as **optional peer deps** —
browser consumers don't install them; Node consumers do.
- Node smoke test at [scripts/smoke/node/run.mjs](scripts/smoke/node/run.mjs)
and a paired browser smoke at [scripts/smoke/browser/](scripts/smoke/browser/).
- Copy-pasteable consumer examples under [examples/](examples/) (Next.js Pages
& App Router, Express, standalone script, browser client).
- README sections **Node usage**, **Fonts / fontSource**, and a short
**Running on a server** note.
### Changed
- **TypeScript** bumped from 4.9 → 5.9 (`skipLibCheck` enabled in `tsconfig`
to silence stricter type errors from `@napi-rs/canvas`'s declaration file
which uses TS 5+ features).
- **Vite** bumped from 2.x → 5.x. Vite 5 preserves `import.meta.url` in lib
mode natively, which let me drop the `generateBundle` workaround plugin
the v0.4.0 prototype needed. `src/platform/node.ts#getBundledFontPath`
now reads `import.meta.url` directly.
- **prismjs** bumped to `^1.30.0` (fixes CVE-2022-23647 — not on the PDF render
path but worth patching while we're here).
- TypeScript output now lands in `dist/types/` (browser entry types at
`dist/types/index.d.ts`, Node entry at `dist/types/node.d.ts`). `vite-plugin-dts`
removed in favor of a single `tsc` step.
- `engines.node` raised to `>=18.0.0` (Node 18 has native `fetch` + stable
`node:` imports — both required by the Node platform shim).
- `import jsPDF from 'jspdf'` switched to named `import { jsPDF } from 'jspdf'`
for Node ESM compatibility (jsPDF's CJS default-export shape makes the
default import resolve to the namespace object, not the class).
- `src/dataset/enum/{Common,Editor,Element}.ts` switched from re-exporting
`@hufe921/canvas-editor`'s runtime enums to inlining them locally. Required
because the Node ESM build can't introspect canvas-editor's CJS bundle.
- `DrawPdf.getPagePixelRatio` falls back to `1` when `window` is undefined
(Node has no `window.devicePixelRatio`).
- `build.sourcemap: true` moved out of `rollupOptions.output` (deprecated in
Vite 5+).
### Fixed
- `window.btoa` calls in `src/utils/index.ts` and
`src/core/draw/particle/latex/utils/LaTexUtils.ts` swapped for the global
`btoa` (available in Node 16+ and all browsers).
- `HyperlinkParticle`, `DateParticle`, `BlockParticle` constructors now skip
their DOM popup/container setup when `typeof document === 'undefined'`. PDF
render path never touches those nodes, so this is safe and unblocks the
Node bundle.
- `Watermark` no longer assumes `temporaryCanvas.style` exists
(`@napi-rs/canvas` has no DOM-style accessor).
### Removed
- `vite-plugin-dts` (`tsc` covers the same job and avoids dual-emission to
`dist/` and `dist/types/`).
- `vitepress` and `vue` from devDependencies (unused — no docs site is built
from this repo). Removes 15+ transitive vulnerable packages from the
install tree.
## 0.3.2 (2026-05-22)
### Changed
- Upgraded **jspdf** 3.0.4 → 4.2.1. No public API changes affecting the lib's
use of `Context2d`, `addImage`, `addPage` or font registration.
### Fixed
- **PDF size growth on repeated export.** Reusing a `DrawPdf` instance and
calling `render()` + `save()` multiple times produced increasingly large
PDFs (1st ~700 KB, 2nd+ ~2 MB when images were present). Root cause: jsPDF's
`putImage` splices `"FlateEncode"` out of the *live* array returned by
`getFilters()`, so after the first save with an image the filter array
becomes `[]` and later saves emit page content uncompressed. Fix: recreate
the jsPDF instance at the start of every `render()` so each export starts
from a clean filter state; fonts are cached as base64 and reapplied
synchronously (no CDN refetch). Constructor now delegates jsPDF init to
`_resetPdf`.
- ⚠️ **Breaking for some callers:** `getPdf()` must be called **after**
`render()`, since the instance reference changes each render.
## 0.3.1 (2026-05-21)
### Fixed
- Build/type errors surfaced by the canvas-editor 0.9.133 sync. Build- and
types-only, no behavior changes:
- Replaced the `package.json` import in `DrawPdf` with a `__VERSION__` token
injected via Vite `define` (the import escaped `tsconfig` `rootDir`).
- Added `filterEmptyControl` / `filterHideElementRow` to the print-mode
defaults (`IPrintModeRule` gained them upstream).
- `Control.ts` now iterates `IEditorData` over `header`/`main`/`footer` only,
skipping the new `graffiti` key whose value isn't `IElement[]`.
- Lint cleanup (unused vars in `Badge.ts`, `BlockParticle.ts`).
## 0.3.0 (2026-05-21)
### Added
- **Sync with canvas-editor 0.9.133** (catch-up port from 0.9.94; peer dep
bumped accordingly). Render-relevant changes only — editor APIs, event
handlers, search, cursor and other interactive paths are intentionally not
ported since this lib only produces PDF output. Highlights:
- New element types: **AREA** (content block with background/border/
placeholder + hide mode), **Badge** (page-level overlay), **Label**
(inline label), **Graffiti** (freehand layer), and **image captions**
(optional caption below images with `{imageNo}` token).
- Render/layout fixes pulled from upstream: text ascent/descender handling,
zero-width elements, punctuation/tab widths; list spacing and numbering;
table colgroup/adaptive height/page-break rendering; surrounding-image
scaling; area positioning; watermark layering and format tokens; print
mode options.
- `IElement` / `IEditorOption` gained the matching upstream fields
(`hide`, `areaIndex`, `label`, `imgCaption`, `graffiti`, `badge`, …) and
new enums/constants (Area, Label, Graffiti, FlexDirection, …).
- `AGENTS.md` with architecture and dev-workflow guidance.
- `dev` script for watch-mode library builds (`vite build --mode lib --watch`),
for testing via `npm link` from a consumer without re-running build manually.
### Changed
- **`DrawPdf` accepts canvas-editor types directly** (constructor + `setValue`),
removing the need for `JSON.parse(JSON.stringify(...))` at the consumer
boundary. Options param widened `DeepRequired<IEditorOption>` → `IEditorOption`
(defaults filled internally by `mergeOption`); data/payload widened to a union
with canvas-editor's `IEditorData`.
- **`npm run build` now produces the library** (was `npm run lib`). Previously
`build` produced the demo and could overwrite the library `dist/` by accident.
The demo entrypoint and its scripts were inherited from upstream, never used,
and have been removed entirely.
- Expose the types entry in `package.json#exports`.
### Fixed
- Infinite recursion when rendering `float-bottom` images.
## Earlier versions
See the git history for changes prior to 0.3.0.