@pixldocs/canvas-renderer 0.3.3 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -67
- package/dist/index.cjs +9 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @pixldocs/canvas-renderer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Client-side template renderer for Pixldocs — render templates in any web app with 1:1 visual parity with the Pixldocs editor. No server round-trip needed for previews.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,57 +8,92 @@ React component + imperative API for rendering Pixldocs templates client-side. N
|
|
|
8
8
|
npm install @pixldocs/canvas-renderer fabric react react-dom
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
> **Private package**: Add `//registry.npmjs.org/:_authToken=${NPM_TOKEN}` to your `.npmrc` and configure `NPM_TOKEN` as a build secret.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Two Rendering Approaches
|
|
16
|
+
|
|
17
|
+
### Approach A: Live Canvas (interactive)
|
|
18
|
+
|
|
19
|
+
Renders a visible `<canvas>` element in your page. Best for editors, interactive tools, or when you need real-time canvas manipulation.
|
|
14
20
|
|
|
15
21
|
```tsx
|
|
16
22
|
import { PixldocsPreview } from '@pixldocs/canvas-renderer';
|
|
17
23
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
24
|
+
<PixldocsPreview
|
|
25
|
+
templateId="your-template-id"
|
|
26
|
+
formSchemaId="your-schema-id"
|
|
27
|
+
sectionState={sectionState}
|
|
28
|
+
supabaseUrl="https://xxx.supabase.co"
|
|
29
|
+
supabaseAnonKey="eyJ..."
|
|
30
|
+
pageIndex={0}
|
|
31
|
+
onReady={() => console.log('Canvas ready')}
|
|
32
|
+
/>
|
|
28
33
|
```
|
|
29
34
|
|
|
30
|
-
|
|
35
|
+
**Pros**: Real-time updates, no re-render flicker
|
|
36
|
+
**Cons**: Heavier DOM footprint, canvas stays in memory
|
|
37
|
+
|
|
38
|
+
### Approach B: Hidden Canvas → Image (recommended for previews)
|
|
31
39
|
|
|
32
|
-
|
|
33
|
-
the template, resolves repeatable sections, applies themes, and renders.
|
|
40
|
+
Renders off-screen using a hidden canvas, captures the result as a data URL, and displays it as a lightweight `<img>`. Best for preview panels, template pickers, and multi-page views.
|
|
34
41
|
|
|
35
42
|
```tsx
|
|
36
|
-
import {
|
|
43
|
+
import { useState, useEffect } from 'react';
|
|
44
|
+
import { PixldocsRenderer } from '@pixldocs/canvas-renderer';
|
|
37
45
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
const renderer = new PixldocsRenderer({
|
|
47
|
+
supabaseUrl: 'https://xxx.supabase.co',
|
|
48
|
+
supabaseAnonKey: 'eyJ...',
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
function TemplatePreview({ templateId, formSchemaId, sectionState, themeId }) {
|
|
52
|
+
const [pages, setPages] = useState([]);
|
|
53
|
+
const [loading, setLoading] = useState(false);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
let cancelled = false;
|
|
57
|
+
async function render() {
|
|
58
|
+
setLoading(true);
|
|
59
|
+
try {
|
|
60
|
+
const results = await renderer.renderFromForm({
|
|
61
|
+
templateId,
|
|
62
|
+
formSchemaId,
|
|
63
|
+
sectionState,
|
|
64
|
+
themeId,
|
|
65
|
+
});
|
|
66
|
+
if (!cancelled) setPages(results);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
console.error('Render failed:', err);
|
|
69
|
+
} finally {
|
|
70
|
+
if (!cancelled) setLoading(false);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
render();
|
|
74
|
+
return () => { cancelled = true; };
|
|
75
|
+
}, [templateId, formSchemaId, sectionState, themeId]);
|
|
76
|
+
|
|
77
|
+
if (loading) return <div>Generating preview...</div>;
|
|
46
78
|
|
|
47
79
|
return (
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
supabaseAnonKey="eyJ..."
|
|
54
|
-
onReady={() => console.log('Rendered!')}
|
|
55
|
-
onError={(err) => console.error(err)}
|
|
56
|
-
/>
|
|
80
|
+
<div>
|
|
81
|
+
{pages.map((page, i) => (
|
|
82
|
+
<img key={i} src={page.dataUrl} width={page.width} height={page.height} alt={`Page ${i + 1}`} />
|
|
83
|
+
))}
|
|
84
|
+
</div>
|
|
57
85
|
);
|
|
58
86
|
}
|
|
59
87
|
```
|
|
60
88
|
|
|
61
|
-
|
|
89
|
+
**Pros**: Lightweight DOM (just `<img>` tags), canvas is disposed after capture, fast for multi-page
|
|
90
|
+
**Cons**: Not interactive, requires re-render to update
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## API Reference
|
|
95
|
+
|
|
96
|
+
### `PixldocsPreview` (React Component)
|
|
62
97
|
|
|
63
98
|
| Prop | Type | Default | Description |
|
|
64
99
|
|------|------|---------|-------------|
|
|
@@ -66,67 +101,69 @@ function App() {
|
|
|
66
101
|
| `templateId` | `string` | — | Template UUID (Mode 2) |
|
|
67
102
|
| `formSchemaId` | `string` | — | Form schema UUID (Mode 2) |
|
|
68
103
|
| `sectionState` | `SectionFormState` | — | V2 section state data (Mode 2) |
|
|
69
|
-
| `themeId` | `string` | `'default'` | Theme variant ID
|
|
104
|
+
| `themeId` | `string` | `'default'` | Theme variant ID |
|
|
70
105
|
| `supabaseUrl` | `string` | — | Supabase URL (Mode 2) |
|
|
71
106
|
| `supabaseAnonKey` | `string` | — | Supabase anon key (Mode 2) |
|
|
72
107
|
| `pageIndex` | `number` | `0` | Page index to render |
|
|
73
108
|
| `zoom` | `number` | auto-fit | Zoom/scale factor |
|
|
74
|
-
| `absoluteZoom` | `boolean` | `false` | Use zoom as-is without auto-fit |
|
|
75
109
|
| `imageProxyUrl` | `string` | — | CORS proxy URL for external images |
|
|
76
|
-
| `className` | `string` | — | CSS class for outer container |
|
|
77
110
|
| `onReady` | `() => void` | — | Called when rendering completes |
|
|
78
111
|
| `onError` | `(error: Error) => void` | — | Called on error |
|
|
79
112
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
### Render from V2 sectionState (primary use case)
|
|
83
|
-
|
|
84
|
-
Same payload format as the Pixldocs server API (`/render-from-form`):
|
|
113
|
+
### `PixldocsRenderer` (Imperative API)
|
|
85
114
|
|
|
86
115
|
```ts
|
|
87
|
-
import { PixldocsRenderer } from '@pixldocs/canvas-renderer';
|
|
88
|
-
|
|
89
116
|
const renderer = new PixldocsRenderer({
|
|
90
|
-
supabaseUrl:
|
|
91
|
-
supabaseAnonKey:
|
|
117
|
+
supabaseUrl: string,
|
|
118
|
+
supabaseAnonKey: string,
|
|
119
|
+
imageProxyUrl?: string, // CORS proxy for external images
|
|
120
|
+
pixelRatio?: number, // default: 2
|
|
92
121
|
});
|
|
122
|
+
```
|
|
93
123
|
|
|
124
|
+
#### `renderer.renderFromForm(options)`
|
|
125
|
+
|
|
126
|
+
Renders all pages from a V2 sectionState payload (same format as the server `/render-from-form` API).
|
|
127
|
+
|
|
128
|
+
```ts
|
|
94
129
|
const results = await renderer.renderFromForm({
|
|
95
|
-
templateId: '
|
|
96
|
-
formSchemaId: '
|
|
97
|
-
sectionState: {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
130
|
+
templateId: 'uuid',
|
|
131
|
+
formSchemaId: 'uuid',
|
|
132
|
+
sectionState: { section_abc: { name: 'John' }, section_def: [...] },
|
|
133
|
+
themeId: 'default', // optional
|
|
134
|
+
format: 'png', // 'png' | 'jpeg' | 'webp'
|
|
135
|
+
quality: 0.92, // for jpeg/webp
|
|
136
|
+
scale: 1, // scale multiplier
|
|
102
137
|
});
|
|
103
|
-
|
|
104
|
-
//
|
|
105
|
-
for (const page of results) {
|
|
106
|
-
console.log(page.dataUrl); // data:image/png;base64,...
|
|
107
|
-
}
|
|
138
|
+
// Returns: RenderResult[] — one per page
|
|
139
|
+
// Each: { dataUrl, width, height, pixelWidth, pixelHeight }
|
|
108
140
|
```
|
|
109
141
|
|
|
110
|
-
|
|
142
|
+
#### `renderer.render(config, options?)`
|
|
143
|
+
|
|
144
|
+
Renders a single page from a pre-resolved template config.
|
|
111
145
|
|
|
112
146
|
```ts
|
|
113
|
-
const { dataUrl } = await renderer.render(templateConfig);
|
|
114
|
-
const allPages = await renderer.renderAllPages(templateConfig);
|
|
147
|
+
const { dataUrl, width, height } = await renderer.render(templateConfig, { pageIndex: 0 });
|
|
115
148
|
```
|
|
116
149
|
|
|
117
|
-
|
|
150
|
+
#### `renderer.renderAllPages(config, options?)`
|
|
151
|
+
|
|
152
|
+
Renders all pages from a pre-resolved config.
|
|
118
153
|
|
|
119
154
|
```ts
|
|
120
|
-
const
|
|
155
|
+
const pages = await renderer.renderAllPages(templateConfig);
|
|
121
156
|
```
|
|
122
157
|
|
|
123
|
-
|
|
158
|
+
### Data Resolution Utilities
|
|
159
|
+
|
|
160
|
+
Resolve template data without rendering — useful for inspecting configs or building custom pipelines.
|
|
124
161
|
|
|
125
162
|
```ts
|
|
126
163
|
import { resolveFromForm, resolveTemplateData } from '@pixldocs/canvas-renderer';
|
|
127
164
|
|
|
128
|
-
// V2: resolve from sectionState
|
|
129
|
-
const { config } = await resolveFromForm({
|
|
165
|
+
// V2: resolve from sectionState
|
|
166
|
+
const { config, totalPages } = await resolveFromForm({
|
|
130
167
|
templateId: 'uuid',
|
|
131
168
|
formSchemaId: 'uuid',
|
|
132
169
|
sectionState: { ... },
|
|
@@ -141,8 +178,14 @@ const { config } = await resolveTemplateData({
|
|
|
141
178
|
});
|
|
142
179
|
```
|
|
143
180
|
|
|
181
|
+
---
|
|
182
|
+
|
|
144
183
|
## Peer Dependencies
|
|
145
184
|
|
|
146
185
|
- `fabric` ^6.0.0
|
|
147
186
|
- `react` ^18.0.0 || ^19.0.0
|
|
148
187
|
- `react-dom` ^18.0.0 || ^19.0.0
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
UNLICENSED — Private package for Pixldocs ecosystem only.
|
package/dist/index.cjs
CHANGED
|
@@ -10276,10 +10276,16 @@ function PixldocsPreview(props) {
|
|
|
10276
10276
|
}
|
|
10277
10277
|
) });
|
|
10278
10278
|
}
|
|
10279
|
+
function normalizeFontFamily(fontStack) {
|
|
10280
|
+
const first = fontStack.split(",")[0].trim();
|
|
10281
|
+
return first.replace(/^['"]|['"]$/g, "");
|
|
10282
|
+
}
|
|
10279
10283
|
const loadedFonts = /* @__PURE__ */ new Set();
|
|
10280
10284
|
const loadingPromises = /* @__PURE__ */ new Map();
|
|
10281
|
-
async function loadGoogleFontCSS(
|
|
10282
|
-
if (!
|
|
10285
|
+
async function loadGoogleFontCSS(rawFontFamily) {
|
|
10286
|
+
if (!rawFontFamily || typeof document === "undefined") return;
|
|
10287
|
+
const fontFamily = normalizeFontFamily(rawFontFamily);
|
|
10288
|
+
if (!fontFamily) return;
|
|
10283
10289
|
if (loadedFonts.has(fontFamily)) return;
|
|
10284
10290
|
const existing = loadingPromises.get(fontFamily);
|
|
10285
10291
|
if (existing) return existing;
|
|
@@ -10623,6 +10629,7 @@ exports.PixldocsRenderer = PixldocsRenderer;
|
|
|
10623
10629
|
exports.applyThemeToConfig = applyThemeToConfig;
|
|
10624
10630
|
exports.collectFontsFromConfig = collectFontsFromConfig;
|
|
10625
10631
|
exports.loadGoogleFontCSS = loadGoogleFontCSS;
|
|
10632
|
+
exports.normalizeFontFamily = normalizeFontFamily;
|
|
10626
10633
|
exports.resolveFromForm = resolveFromForm;
|
|
10627
10634
|
exports.resolveTemplateData = resolveTemplateData;
|
|
10628
10635
|
//# sourceMappingURL=index.cjs.map
|