Skip to content

Commit 9863100

Browse files
committed
chore: add package READMEs, repository metadata, and bump to 0.1.1
Add a README.md to each package. Add repository/homepage/bugs fields and a description/keywords so the npm page has real content. Reword a couple of code comments and the root README for clarity.
1 parent c729186 commit 9863100

8 files changed

Lines changed: 197 additions & 8 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# svg-pdf
22

3-
`@svg-pdf/core` — a worker-safe, PDF-engine-agnostic SVG-to-PDF vector parser.
3+
`@svg-pdf/core` — a worker-safe SVG-to-PDF vector parser that isn't tied to any specific PDF library.
44

55
**Status:** pre-release (`0.x`), not yet published to npm.
66

@@ -55,6 +55,7 @@ This is an npm workspaces monorepo (`packages/*`) — no separate per-package in
5555
git clone <repo-url>
5656
cd svg-pdf
5757
npm install # also wires up the local git hooks, see "Contributing"
58+
npm run build # compiles each package's dist/ output, needed before test/typecheck below
5859
npm run test # unit tests
5960
npm run test:visual # SVG-vs-PDF visual regression tests (renders both and diffs pixels)
6061
npm run typecheck # tsc -b across all packages

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# @svg-pdf/core
2+
3+
Parses an SVG document into a flat instruction list that isn't tied to any specific PDF library — no DOM/canvas rendering involved, so it runs the same in Node, the browser, and a Web Worker.
4+
5+
On its own, this package doesn't produce a PDF file — it just reads SVG and hands back a neutral, structured description of what to draw (shapes, gradients, text, etc., plus a list of anything it had to skip). Turning that into an actual PDF is the job of a separate **adapter** package, one per PDF library — for example [`@svg-pdf/libpdf`](https://www.npmjs.com/package/@svg-pdf/libpdf), which targets [`@libpdf/core`](https://libpdf.documenso.com/). This split exists so the parsing logic isn't tied to any one PDF library, and so it can run somewhere a full DOM isn't available, like a Web Worker.
6+
7+
## Install
8+
9+
```
10+
npm install @svg-pdf/core
11+
```
12+
13+
Also available as a standalone browser bundle (no build tool or npm install needed) via CDN:
14+
15+
```html
16+
<script src="https://unpkg.com/@svg-pdf/core"></script>
17+
<script>
18+
const doc = SvgPdfCore.parseSvgDocument(svgText);
19+
</script>
20+
```
21+
22+
## Usage
23+
24+
```ts
25+
import { parseSvgDocument } from '@svg-pdf/core';
26+
27+
const doc = parseSvgDocument('<svg viewBox="0 0 100 100"><rect width="10" height="10"/></svg>');
28+
29+
console.log(doc.instructions); // flat instruction list, ready for an adapter to draw
30+
console.log(doc.warnings); // anything the parser had to skip, with a plain-English reason
31+
```
32+
33+
`parseSvgDocument` never throws just because your SVG uses a feature it doesn't support yet — it skips that one piece and records a warning, so the rest of the document still comes through. The only time it throws is if the SVG text itself isn't valid XML.
34+
35+
## What's supported
36+
37+
Shapes and grouping (`<path>`, `<rect>`, `<circle>`, `<g>`, `<use>`, `<symbol>`, `<switch>`, nested `<svg>`, transforms), linear/radial gradients and `<pattern>`, `<marker>` (arrowheads and dots on line vertices), `<clipPath>`, fills/strokes/blending (`fill-rule`, dash patterns, `mix-blend-mode`, etc.), real CSS selector matching inside `<style>` (not just simple tag/class/id), best-effort `<text>` including `<textPath>`, `<image>` (including an SVG payload embedded inside another SVG), and `<a href>` link regions.
38+
39+
There are a handful of documented limits too — some are deliberate design choices (e.g. `@libpdf/core`-specific constraints on adapters), some are features not implemented yet. The [supported-features doc](https://github.com/delylabs/svg-pdf/blob/main/docs/supported-features.md) in the repo has the full, up-to-date list of what's supported, what's out of scope and why, and what's not supported yet.
40+
41+
## License
42+
43+
MIT.

packages/core/package.json

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,26 @@
11
{
22
"name": "@svg-pdf/core",
3-
"version": "0.1.0",
3+
"version": "0.1.1",
4+
"description": "SVG to real PDF vectors — not locked to one PDF library",
5+
"keywords": [
6+
"svg",
7+
"pdf",
8+
"typescript",
9+
"web-worker",
10+
"vector-graphics",
11+
"pdf-generation",
12+
"svg-to-pdf"
13+
],
414
"license": "MIT",
15+
"repository": {
16+
"type": "git",
17+
"url": "git+https://github.com/delylabs/svg-pdf.git",
18+
"directory": "packages/core"
19+
},
20+
"homepage": "https://github.com/delylabs/svg-pdf#readme",
21+
"bugs": {
22+
"url": "https://github.com/delylabs/svg-pdf/issues"
23+
},
524
"type": "module",
625
"publishConfig": {
726
"access": "public"

packages/core/src/types.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ export type LineJoin = 'miter' | 'round' | 'bevel';
4141

4242
/*
4343
* Matches @libpdf/core's own `BlendMode` type (PascalCase) rather than
44-
* importing it directly — this parsing layer stays library-agnostic, same as
45-
* the rest of this file; embed.ts passes the value straight through.
44+
* importing it directly — this parsing layer stays independent of any one
45+
* PDF library, same as the rest of this file; embed.ts passes the value
46+
* straight through.
4647
*/
4748
export type BlendMode =
4849
| 'Normal'

packages/libpdf/README.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# @svg-pdf/libpdf
2+
3+
Embeds an SVG document as real, sharp vector content into a PDF made with [`@libpdf/core`](https://libpdf.documenso.com/) — not a rasterized image, so it stays crisp at any zoom.
4+
5+
This package is an **adapter**: it parses SVG using [`@svg-pdf/core`](https://www.npmjs.com/package/@svg-pdf/core) (a DOM-free parser that turns SVG into a neutral instruction list) and translates the result into `@libpdf/core` drawing calls. Anything the parser can't handle is skipped individually with a warning, instead of failing the whole conversion.
6+
7+
## Install
8+
9+
`@libpdf/core` — the library that actually writes PDF files — is a **peer dependency**: it won't come along automatically, so install both yourself:
10+
11+
```
12+
npm install @svg-pdf/libpdf @libpdf/core
13+
```
14+
15+
This is done deliberately rather than bundling a private copy of `@libpdf/core`, because _you_ are the one who creates the PDF document object using `@libpdf/core` and then hands it to `@svg-pdf/libpdf` to draw into — if the two ended up using separately-installed copies of `@libpdf/core`, they wouldn't recognize each other's objects as "the same kind of thing," causing confusing bugs.
16+
17+
## Quick start
18+
19+
```ts
20+
import { PDF as LibPDF } from '@libpdf/core';
21+
import { embedSvgInPdf } from '@svg-pdf/libpdf';
22+
import * as fs from 'fs';
23+
24+
const svgText = '<svg viewBox="0 0 200 100"><rect width="10" height="10"/></svg>';
25+
26+
const doc = LibPDF.create();
27+
const { warnings } = await embedSvgInPdf(doc, {
28+
svgText,
29+
rotation: 0,
30+
// optional: pageSize, orientation ('portrait' | 'landscape'), margin
31+
});
32+
33+
if (warnings.length > 0) {
34+
console.warn('Unsupported SVG features were skipped:', warnings);
35+
}
36+
37+
fs.writeFileSync('output.pdf', await doc.save());
38+
```
39+
40+
Create an empty PDF document, call `embedSvgInPdf` to draw your SVG into it as one page, then save the PDF. `embedSvgInPdf` never throws just because your SVG uses an unsupported feature — it skips that one piece and adds a message to `warnings`. The only time it throws is if the SVG text itself can't be parsed (not valid XML).
41+
42+
## Fetching external `<image>` URLs
43+
44+
An `<image href="https://example.com/photo.png">` pointing at the web is **not** fetched by default — it's skipped with a warning instead. This is a deliberate safety default: automatically fetching arbitrary URLs found inside someone else's SVG could be abused to make your server request things it shouldn't reach (a class of attack called SSRF).
45+
46+
If you trust your SVGs, pass a `fetchImage` function and decide for yourself what's safe to fetch:
47+
48+
```ts
49+
await embedSvgInPdf(doc, {
50+
svgText,
51+
rotation: 0,
52+
fetchImage: async (url) => {
53+
const res = await fetch(url);
54+
if (!res.ok) return null;
55+
return {
56+
bytes: new Uint8Array(await res.arrayBuffer()),
57+
mimeType: res.headers.get('content-type') ?? 'image/png',
58+
};
59+
},
60+
});
61+
```
62+
63+
## Embedding custom fonts
64+
65+
By default, text is drawn using one of PDF's 14 built-in "standard" fonts (like Helvetica), matched to your SVG's `font-family`/`font-weight`/`font-style`. To embed a real, custom font instead, pass a `fetchFont` function — called once per distinct font actually used, not once per letter:
66+
67+
```ts
68+
await embedSvgInPdf(doc, {
69+
svgText,
70+
rotation: 0,
71+
fetchFont: async ({ fontFamily, fontWeight, fontStyle }) => {
72+
if (fontFamily === 'Poppins') return fs.readFileSync('./fonts/Poppins-Regular.ttf');
73+
return null; // falls back to the closest standard font
74+
},
75+
});
76+
```
77+
78+
If the SVG already embeds its own font inline via `@font-face { src: url(data:font/ttf;base64,...) }`, that's used automatically — no `fetchFont` needed.
79+
80+
## Embedding non-JPEG/PNG images
81+
82+
JPEG and PNG are embedded as-is. Any other format (WebP, for example) needs converting to PNG first — this happens automatically via `OffscreenCanvas` in a browser/Worker, but that API doesn't exist in plain Node.js. In Node, supply your own `normalizeImage` function (for example, using `sharp`):
83+
84+
```ts
85+
import sharp from 'sharp';
86+
87+
await embedSvgInPdf(doc, {
88+
svgText,
89+
rotation: 0,
90+
normalizeImage: async (bytes, mimeType) => sharp(bytes).png().toBuffer(),
91+
});
92+
```
93+
94+
`@svg-pdf/libpdf` deliberately doesn't bundle `sharp` or any other Node-only image library itself, to stay lightweight and runnable unmodified in a browser.
95+
96+
## Using the parser standalone
97+
98+
If you just want the parsed, structured representation of an SVG — without turning it into a PDF — use [`@svg-pdf/core`](https://www.npmjs.com/package/@svg-pdf/core) directly via its `parseSvgDocument` function.
99+
100+
## What's supported
101+
102+
Shapes and grouping, gradients and `<pattern>`, `<marker>`, `<clipPath>`, fills/strokes/blending, real CSS selector matching inside `<style>`, best-effort `<text>` including `<textPath>`, `<image>`, and `<a href>` link regions. The [supported-features doc](https://github.com/delylabs/svg-pdf/blob/main/docs/supported-features.md) in the repo has the full, up-to-date list of what's supported, what's out of scope and why, and what's not supported yet.
103+
104+
## License
105+
106+
MIT.

packages/libpdf/package.json

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,26 @@
11
{
22
"name": "@svg-pdf/libpdf",
3-
"version": "0.1.0",
3+
"version": "0.1.1",
4+
"description": "SVG to real PDF vectors, embedded via @libpdf/core",
5+
"keywords": [
6+
"svg",
7+
"pdf",
8+
"typescript",
9+
"web-worker",
10+
"vector-graphics",
11+
"pdf-generation",
12+
"svg-to-pdf"
13+
],
414
"license": "MIT",
15+
"repository": {
16+
"type": "git",
17+
"url": "git+https://github.com/delylabs/svg-pdf.git",
18+
"directory": "packages/libpdf"
19+
},
20+
"homepage": "https://github.com/delylabs/svg-pdf#readme",
21+
"bugs": {
22+
"url": "https://github.com/delylabs/svg-pdf/issues"
23+
},
524
"type": "module",
625
"publishConfig": {
726
"access": "public"

packages/libpdf/src/draw/drawContext.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { type CharLayout } from './textLayout';
2626
export const concat = (m: Matrix2D): ReturnType<typeof ops.concatMatrix> =>
2727
ops.concatMatrix(m.a, m.b, m.c, m.d, m.e, m.f);
2828

29-
// Counter-flips the ambient CTM's inherited Y-flip for anything (text glyphs, image XObjects) whose own "up" direction isn't transform-agnostic like a filled path is — see the doc comments at each call site.
29+
// Counter-flips the ambient CTM's inherited Y-flip for anything (text glyphs, image XObjects) whose own "up" direction changes under that flip, unlike a filled path — see the doc comments at each call site.
3030
export const FLIP_Y: Matrix2D = { a: 1, b: 0, c: 0, d: -1, e: 0, f: 0 };
3131

3232
/*

0 commit comments

Comments
 (0)