declare-render 1.0.2 → 1.0.4

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/src/types.ts CHANGED
@@ -1,25 +1,189 @@
1
- import { ContainerRenderData } from "./canvas-renderers/container-renderer";
2
- import { ImgRenderData } from "./canvas-renderers/img-renderer";
3
- import { TextRenderData } from "./canvas-renderers/text-renderer/types";
4
1
  import type { TextMetrics } from "canvas";
5
2
 
6
- export type { ContainerRenderData, ImgRenderData, TextRenderData };
3
+ export enum RendererType {
4
+ CONTAINER = "container",
5
+ TEXT = "text",
6
+ IMG = "img",
7
+ SHAPE = "shape",
8
+ }
9
+
10
+ // ----- TypeScript types (primitives only) -----
11
+
12
+ export interface TextRenderData {
13
+ id: string | number;
14
+ type: "text";
15
+ x: number;
16
+ y: number;
17
+ width: number;
18
+ height: number;
19
+ content: string;
20
+ rotate?: number;
21
+ style: {
22
+ align?: "center" | "right";
23
+ verticalAlign?: "center" | "top" | "bottom";
24
+ fontName: string;
25
+ fontSize: number | { max: number; min: number };
26
+ backgroundColor?: string;
27
+ padding?: number | { x: number; y: number };
28
+ border?: { color: string; width?: number };
29
+ color: string;
30
+ radius?: number;
31
+ verticalGap?: number;
32
+ horizonalGap?: number;
33
+ fontWeight?: string;
34
+ highlight?: {
35
+ logics?: string;
36
+ color?: string;
37
+ content?: string;
38
+ type?: string;
39
+ style?: { height?: number; offsetY?: number; coverText?: boolean; url: string };
40
+ };
41
+ };
42
+ }
43
+
44
+ export interface ImgRenderData {
45
+ id: string;
46
+ type: "img";
47
+ x: number;
48
+ y: number;
49
+ width?: number;
50
+ height?: number;
51
+ url?: string;
52
+ color?: string;
53
+ objectFit: "contain" | "cover";
54
+ radius?: number;
55
+ rotate?: number;
56
+ globalAlpha?: number;
57
+ shadow?: { color: string; blur: number; X: number; Y: number };
58
+ }
59
+
60
+ export interface ContainerRenderData {
61
+ id: string | number;
62
+ type: "container";
63
+ x: number;
64
+ y: number;
65
+ width: number;
66
+ height: number;
67
+ direction?: "row" | "column";
68
+ itemAlign?: "center";
69
+ gap?: number | { x: number; y: number };
70
+ wrap?: boolean;
71
+ renderers: ChildRenderers;
72
+ }
73
+
74
+ export interface ShapeRenderData {
75
+ id: string | number;
76
+ type: "shape";
77
+ x: number;
78
+ y: number;
79
+ width?: number;
80
+ height?: number;
81
+ rotate?: number;
82
+ style?: {
83
+ fillStyle?: string;
84
+ strokeStyle?: string;
85
+ lineWidth?: number;
86
+ lineCap?: "butt" | "round" | "square";
87
+ lineJoin?: "bevel" | "round" | "miter";
88
+ miterLimit?: number;
89
+ lineDash?: number[];
90
+ lineDashOffset?: number;
91
+ globalAlpha?: number;
92
+ };
93
+ shadow?: {
94
+ color: string;
95
+ blur: number;
96
+ X: number;
97
+ Y: number;
98
+ };
99
+ shapes: ShapeCommand[];
100
+ }
101
+
102
+ export type ShapeCommand =
103
+ | { type: "rect"; x: number; y: number; width: number; height: number }
104
+ | { type: "fillRect"; x: number; y: number; width: number; height: number }
105
+ | { type: "strokeRect"; x: number; y: number; width: number; height: number }
106
+ | { type: "clearRect"; x: number; y: number; width: number; height: number }
107
+ | { type: "beginPath" }
108
+ | { type: "closePath" }
109
+ | { type: "moveTo"; x: number; y: number }
110
+ | { type: "lineTo"; x: number; y: number }
111
+ | {
112
+ type: "arc";
113
+ x: number;
114
+ y: number;
115
+ radius: number;
116
+ startAngle: number;
117
+ endAngle: number;
118
+ counterclockwise?: boolean;
119
+ }
120
+ | {
121
+ type: "arcTo";
122
+ x1: number;
123
+ y1: number;
124
+ x2: number;
125
+ y2: number;
126
+ radius: number;
127
+ }
128
+ | {
129
+ type: "quadraticCurveTo";
130
+ cp1x: number;
131
+ cp1y: number;
132
+ x: number;
133
+ y: number;
134
+ }
135
+ | {
136
+ type: "bezierCurveTo";
137
+ cp1x: number;
138
+ cp1y: number;
139
+ cp2x: number;
140
+ cp2y: number;
141
+ x: number;
142
+ y: number;
143
+ }
144
+ | { type: "fill" }
145
+ | { type: "stroke" }
146
+ | { type: "fillAndStroke" };
147
+
148
+ export type ChildRenderers = (
149
+ | ImgRenderData
150
+ | TextRenderData
151
+ | ContainerRenderData
152
+ | ShapeRenderData
153
+ )[];
7
154
 
8
155
  export interface RenderData {
9
156
  id: string;
10
157
  width: number;
11
158
  height: number;
12
- renderers: (ImgRenderData | TextRenderData | ContainerRenderData)[];
159
+ layers: (
160
+ | ImgRenderData
161
+ | TextRenderData
162
+ | ContainerRenderData
163
+ | ShapeRenderData
164
+ )[];
13
165
  output?: {
14
166
  type?: "png" | "jpg";
15
167
  };
16
168
  }
17
169
 
18
- export enum RendererType {
19
- CONTAINER = "container",
20
- TEXT = "text",
21
- IMG = "img",
22
- }
170
+ // ----- String schema for AI (readable as string) -----
171
+
172
+ export const RENDER_DATA_SCHEMA = `
173
+ RenderData: { "id": string, "width": number, "height": number, "layers": Array<TextRenderData | ImgRenderData | ContainerRenderData | ShapeRenderData>, "output"?: { "type"?: "png" | "jpg" } }
174
+
175
+ TextRenderData: { "id": string|number, "type": "text", "x": number, "y": number, "width": number, "height": number, "content": string, "style": { "fontName": string, "fontSize": number | { "max": number, "min": number }, "color": string, "align"?: "center"|"right", "verticalAlign"?: "center"|"top"|"bottom", "fontWeight"?: string, "verticalGap"?: number, "backgroundColor"?: string, "padding"?: number|{ "x": number, "y": number }, "border"?: { "color": string, "width"?: number }, "radius"?: number }, "rotate"?: number }
176
+
177
+ ImgRenderData: { "id": string, "type": "img", "x": number, "y": number, "width"?: number, "height"?: number, "url"?: string, "color"?: string, "objectFit": "contain"|"cover", "radius"?: number, "rotate"?: number, "globalAlpha"?: number, "shadow"?: { "color": string, "blur": number, "X": number, "Y": number } }
178
+
179
+ ContainerRenderData: { "id": string|number, "type": "container", "x": number, "y": number, "width": number, "height": number, "renderers": ChildRenderers[], "direction"?: "row"|"column", "gap"?: number|{ "x": number, "y": number }, "itemAlign"?: "center", "wrap"?: boolean }
180
+
181
+ ShapeRenderData: { "id": string|number, "type": "shape", "x": number, "y": number, "width"?: number, "height"?: number, "rotate"?: number, "style"?: { "fillStyle"?: string, "strokeStyle"?: string, "lineWidth"?: number, "lineCap"?: "butt"|"round"|"square", "lineJoin"?: "bevel"|"round"|"miter", "miterLimit"?: number, "lineDash"?: number[], "lineDashOffset"?: number, "globalAlpha"?: number }, "shadow"?: { "color": string, "blur": number, "X": number, "Y": number }, "shapes": Array<ShapeCommand> }
182
+
183
+ ShapeCommand: { "type": "rect"|"fillRect"|"strokeRect"|"clearRect"|"beginPath"|"closePath"|"moveTo"|"lineTo"|"arc"|"arcTo"|"quadraticCurveTo"|"bezierCurveTo"|"fill"|"stroke"|"fillAndStroke", ...additional properties based on type }
184
+ `.trim();
185
+
186
+ // ----- Metrics (canvas-dependent) -----
23
187
 
24
188
  export interface MetricsChar {
25
189
  char: string;
@@ -38,5 +202,3 @@ export interface MetricsCharWithCoordinates extends MetricsChar {
38
202
  X: number;
39
203
  Y: number;
40
204
  }
41
-
42
- export type ChildRenderers = (ImgRenderData | TextRenderData | ContainerRenderData)[];
package/tsconfig.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "target": "ES2020",
4
4
  "module": "ESNext",
5
5
  "lib": ["ES2020"],
6
- "moduleResolution": "node",
6
+ "moduleResolution": "bundler",
7
7
  "esModuleInterop": true,
8
8
  "allowSyntheticDefaultImports": true,
9
9
  "strict": true,
@@ -11,12 +11,14 @@
11
11
  "forceConsistentCasingInFileNames": true,
12
12
  "resolveJsonModule": true,
13
13
  "isolatedModules": true,
14
- "noEmit": true
14
+ "noEmit": true,
15
+ "types": ["node"],
16
+ "allowImportingTsExtensions": true
15
17
  },
16
18
  "ts-node": {
17
19
  "esm": true,
18
20
  "experimentalSpecifierResolution": "node"
19
21
  },
20
- "include": ["src/**/*"],
22
+ "include": ["src/**/*", "examples/**/*"],
21
23
  "exclude": ["node_modules"]
22
24
  }
@@ -1,206 +0,0 @@
1
- import { Renderer } from "../src/index";
2
- import { RenderData, RendererType } from "../src/types";
3
- import * as fs from "fs";
4
- import * as path from "path";
5
- import { fileURLToPath } from "url";
6
-
7
- const __filename = fileURLToPath(import.meta.url);
8
- const __dirname = path.dirname(__filename);
9
-
10
- // Content data in JSON format for easy reading and maintenance
11
- const contentData = {
12
- title: "防晒指南",
13
- sections: [
14
- {
15
- question: "该不该防晒:",
16
- answer: "适度防晒"
17
- },
18
- {
19
- question: "怎么防晒:",
20
- answer: "做好物理防晒,选适配防晒霜(矿物款更安全,适合敏感肌/儿童,化学款慎选成分),避开正午强紫外线,早晚适度日晒。"
21
- },
22
- {
23
- question: "为什么适度:",
24
- answer: "紫外线会致皮肤DNA突变、加速老化,防晒对护肤和防病至关重要;但完全避光不利健康,且长波长光能穿透防晒和衣物,足够满足合成需求,不用刻意不防晒。维生素D能够调节激素,还与更好的生活质量和寿命相关。"
25
- }
26
- ]
27
- };
28
-
29
- // Dark & Elegant theme canvas configuration (4:3 aspect ratio)
30
- const CANVAS_CONFIG = {
31
- width: 1200,
32
- height: 900, // 4:3 ratio
33
- padding: 50,
34
- colors: {
35
- background: "#121212", // Deep black
36
- title: "#FFD700", // Gold
37
- question: "#FFFFFF", // White
38
- answer: "#B0B0B0", // Light Gray
39
- cardBg: "#1E1E1E", // Dark Gray Card
40
- accent: "#FFD700", // Gold
41
- highlight: "#333333" // Subtle Dark Circle
42
- },
43
- fonts: {
44
- title: "PingFang SC",
45
- body: "PingFang SC"
46
- }
47
- };
48
-
49
- // Calculate text height based on content and width
50
- function calcTextHeight(text: string, fontSize: number, containerWidth: number, lineGap = 8): number {
51
- // CJK characters are roughly square (width ≈ fontSize)
52
- const charsPerLine = Math.floor(containerWidth / fontSize);
53
- const lines = Math.ceil(text.length / charsPerLine);
54
- return lines * (fontSize + lineGap);
55
- }
56
-
57
- // Build renderers from content data
58
- function buildRenderers() {
59
- const renderers: RenderData["renderers"] = [];
60
- const contentWidth = CANVAS_CONFIG.width - CANVAS_CONFIG.padding * 2;
61
- let currentY = CANVAS_CONFIG.padding;
62
-
63
- // 1. Background
64
- renderers.push({
65
- id: "background",
66
- type: RendererType.IMG,
67
- x: 0,
68
- y: 0,
69
- width: CANVAS_CONFIG.width,
70
- height: CANVAS_CONFIG.height,
71
- color: CANVAS_CONFIG.colors.background,
72
- objectFit: "cover"
73
- });
74
-
75
- // 2. Decorative Dark Sun Circle (top right)
76
- renderers.push({
77
- id: "sun-circle",
78
- type: RendererType.IMG,
79
- x: CANVAS_CONFIG.width - 180,
80
- y: -60,
81
- width: 240,
82
- height: 240,
83
- color: CANVAS_CONFIG.colors.highlight,
84
- radius: 120,
85
- objectFit: "cover"
86
- });
87
-
88
- // 3. Title
89
- const titleHeight = 80;
90
- renderers.push({
91
- id: "title",
92
- type: RendererType.TEXT,
93
- x: CANVAS_CONFIG.padding,
94
- y: currentY,
95
- width: contentWidth,
96
- height: titleHeight,
97
- content: "☀️ " + contentData.title,
98
- style: {
99
- fontName: CANVAS_CONFIG.fonts.title,
100
- fontSize: 48,
101
- color: CANVAS_CONFIG.colors.title,
102
- fontWeight: "bold",
103
- verticalAlign: "center"
104
- }
105
- });
106
- currentY += titleHeight + 20;
107
-
108
- // 4. Content Sections
109
- const questionFontSize = 24;
110
- const answerFontSize = 20;
111
- const cardPadding = 24;
112
-
113
- contentData.sections.forEach((section, index) => {
114
- // Card background first (dark rounded rectangle)
115
- const answerTextWidth = contentWidth - cardPadding * 2;
116
- const textHeight = calcTextHeight(section.answer, answerFontSize, answerTextWidth, 12);
117
- const cardHeight = textHeight + cardPadding * 2 + 50; // Extra space for question
118
-
119
- renderers.push({
120
- id: `card-bg-${index}`,
121
- type: RendererType.IMG,
122
- x: CANVAS_CONFIG.padding,
123
- y: currentY,
124
- width: contentWidth,
125
- height: cardHeight,
126
- color: CANVAS_CONFIG.colors.cardBg,
127
- radius: 16,
128
- objectFit: "cover"
129
- });
130
-
131
- // Question label inside card
132
- const questionY = currentY + cardPadding;
133
- renderers.push({
134
- id: `q-${index}`,
135
- type: RendererType.TEXT,
136
- x: CANVAS_CONFIG.padding + cardPadding,
137
- y: questionY,
138
- width: contentWidth - cardPadding * 2,
139
- height: 32,
140
- content: "▸ " + section.question,
141
- style: {
142
- fontName: CANVAS_CONFIG.fonts.body,
143
- fontSize: questionFontSize,
144
- color: CANVAS_CONFIG.colors.question,
145
- fontWeight: "bold",
146
- verticalAlign: "center"
147
- }
148
- });
149
-
150
- // Answer text inside card
151
- const answerY = questionY + 40;
152
- renderers.push({
153
- id: `a-${index}`,
154
- type: RendererType.TEXT,
155
- x: CANVAS_CONFIG.padding + cardPadding,
156
- y: answerY,
157
- width: answerTextWidth,
158
- height: textHeight + 20,
159
- content: section.answer,
160
- style: {
161
- fontName: CANVAS_CONFIG.fonts.body,
162
- fontSize: answerFontSize,
163
- color: CANVAS_CONFIG.colors.answer,
164
- verticalGap: 12,
165
- verticalAlign: "top"
166
- }
167
- });
168
-
169
- currentY += cardHeight + 24;
170
- });
171
-
172
- return renderers;
173
- }
174
-
175
- // Create canvas schema
176
- const schema: RenderData = {
177
- id: "sunscreen-guide",
178
- width: CANVAS_CONFIG.width,
179
- height: CANVAS_CONFIG.height,
180
- renderers: buildRenderers(),
181
- output: {
182
- type: "png"
183
- }
184
- };
185
-
186
- // Main execution
187
- async function main() {
188
- console.log("Generating fresh sunscreen guide image...");
189
- console.log("Content:", JSON.stringify(contentData, null, 2));
190
-
191
- const renderer = new Renderer(schema);
192
- const buffer = await renderer.draw();
193
-
194
- // Ensure output directory exists
195
- const outputDir = path.join(__dirname, "output");
196
- if (!fs.existsSync(outputDir)) {
197
- fs.mkdirSync(outputDir, { recursive: true });
198
- }
199
-
200
- const outputPath = path.join(outputDir, "sunscreen-guide.png");
201
- fs.writeFileSync(outputPath, buffer);
202
-
203
- console.log(`Image saved to: ${outputPath}`);
204
- }
205
-
206
- main().catch(console.error);