@neatico/visual-core 0.14.0 → 0.14.2

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.
Files changed (2) hide show
  1. package/README.md +173 -66
  2. package/package.json +14 -4
package/README.md CHANGED
@@ -1,16 +1,49 @@
1
- # @neatico/visual-core
2
-
3
- Framework-independent building blocks for editable visual products, extracted from [Neatico](https://www.neatico.com/?utm_source=github&utm_medium=readme&utm_campaign=visual-core).
4
-
5
- Create visuals with AI at **[neatico.com](https://www.neatico.com/?utm_source=npm&utm_medium=readme&utm_campaign=visual-core)**, or use this package to build your own editor, layout pipeline, or scene extension.
6
-
7
- ## Install
1
+ <div align="center">
2
+ <a href="https://www.neatico.com/?utm_source=npm&utm_medium=readme&utm_campaign=visual-core">
3
+ <img src="https://www.neatico.com/brand/neatico-logo.svg" width="240" alt="Neatico" />
4
+ </a>
5
+ <h1>@neatico/visual-core</h1>
6
+ <p>Framework-independent geometry, measured text wrapping, and extensible scene registries for visual editors.</p>
7
+ <p>
8
+ <a href="https://www.npmjs.com/package/@neatico/visual-core"><img src="https://img.shields.io/npm/v/@neatico/visual-core.svg?style=flat-square&label=npm" alt="npm version" /></a>
9
+ <a href="https://github.com/neatico/visual-core"><img src="https://img.shields.io/badge/source-github-111827?style=flat-square" alt="source" /></a>
10
+ <img src="https://img.shields.io/badge/license-MIT-10b981?style=flat-square" alt="license: MIT" />
11
+ <img src="https://img.shields.io/badge/node-%3E%3D20-6366f1?style=flat-square" alt="node >=20" />
12
+ </p>
13
+ </div>
14
+
15
+ ---
16
+
17
+ `@neatico/visual-core` is the layout engine that powers Neatico's AI design canvas. It is pure, data-first, and free of React, the DOM, a database, and any AI provider — so you can run it on a server, in a worker, or inside your own editor.
18
+
19
+ It provides the pieces a serious visual editor needs:
20
+
21
+ - **Affine geometry** — group/child transforms that survive ungrouping.
22
+ - **Measured text wrapping** — UAX #14 line breaks driven by *your* font metrics.
23
+ - **Source-backed content model** — validate untrusted AI output without trusting generated code or asset URLs.
24
+ - **Data-only compositions & blueprints** — portable, versioned layout descriptions with no executable markup.
25
+ - **Layout eligibility** — 97 presentation and 97 social layouts with semantic contracts.
26
+ - **Themes, fonts, and pagination** — deterministic token sets and lossless content splitting.
27
+
28
+ ## Installation
8
29
 
9
30
  ```sh
10
31
  npm install @neatico/visual-core
11
32
  ```
12
33
 
13
- This package provides native ES modules and TypeScript declarations. It uses the MIT-licensed `linebreak` package for Unicode line opportunities and does not require React, a browser, a database, or an AI provider. Node.js 20 or later is supported. CommonJS projects can use `require()` or dynamic `import()`.
34
+ Ships as native ES modules, CommonJS, and TypeScript declarations. Requires Node.js 20+ (the only runtime dependency is the MIT-licensed [`linebreak`](https://github.com/foliojs/linebreak) for Unicode line opportunities).
35
+
36
+ ## Contents
37
+
38
+ - [Preserve transforms when ungrouping](#preserve-transforms-when-ungrouping)
39
+ - [Wrap text with your font measurements](#wrap-text-with-your-font-measurements)
40
+ - [Add custom scenes](#add-custom-scenes)
41
+ - [Validate AI-authored content](#validate-ai-authored-content)
42
+ - [Validate compositions and blueprints](#validate-compositions-and-blueprints)
43
+ - [Select layout candidates](#select-layout-candidates)
44
+ - [Paginate without losing content](#paginate-without-losing-content)
45
+ - [Themes, fonts, and fingerprints](#themes-fonts-and-fingerprints)
46
+ - [API surface](#api-surface)
14
47
 
15
48
  ## Preserve transforms when ungrouping
16
49
 
@@ -22,30 +55,31 @@ const child = { x: 20, y: 30, width: 120, height: 60, rotation: 15 };
22
55
  const ungrouped = flattenGroupLayout(group, child);
23
56
  ```
24
57
 
25
- Coordinates are in logical canvas units. Rotations and skew angles are in degrees. Transforms use the center of the element as their origin. `flattenGroupLayout` preserves additional child properties and returns a new object. It supports translation, rotation, scaling, skew, and horizontal or vertical flips. Nonfinite values, negative dimensions, and degenerate ungrouping transforms fail explicitly.
58
+ Coordinates are in logical canvas units; rotations and skew are in degrees with the element center as the origin. `flattenGroupLayout` preserves additional child properties and returns a new object. It supports translation, rotation, scaling, skew, and horizontal/vertical flips. Nonfinite values, negative dimensions, and degenerate transforms fail explicitly.
26
59
 
27
- Additional exports include `layoutMatrix`, `multiplyMatrix`, `layoutBounds`, and the `Layout` and `Matrix` types. Matrices use the six-value CSS affine format `[a, b, c, d, e, f]`.
60
+ Also exported: `layoutMatrix`, `multiplyMatrix`, `layoutBounds`, plus the `Layout` and `Matrix` types. Matrices use the six-value CSS affine format `[a, b, c, d, e, f]`.
28
61
 
29
- ## Wrap text using your font measurements
62
+ ## Wrap text with your font measurements
30
63
 
31
64
  ```ts
32
65
  import { wrapCopy } from "@neatico/visual-core";
33
66
 
34
- const context = document.createElement("canvas").getContext("2d")!;
67
+ const ctx = document.createElement("canvas").getContext("2d")!;
35
68
  await document.fonts.load("400 24px Inter");
69
+
36
70
  const lines = wrapCopy(
37
71
  "One idea. Every format.",
38
72
  240,
39
73
  24,
40
74
  false,
41
75
  (text, size, bold) => {
42
- context.font = `${bold ? 700 : 400} ${size}px Inter`;
43
- return context.measureText(text).width;
76
+ ctx.font = `${bold ? 700 : 400} ${size}px Inter`;
77
+ return ctx.measureText(text).width;
44
78
  },
45
79
  );
46
80
  ```
47
81
 
48
- Supply measurements for the actual font used by your renderer. Explicit line breaks are preserved. Break opportunities follow UAX #14 through [linebreak](https://github.com/foliojs/linebreak) (Unicode 13); oversized tokens use grapheme boundaries where `Intl.Segmenter` is available. A grapheme wider than the available line fails explicitly. Tiny trailing lines are balanced without adding lines; pass `{ balance: false }` as the sixth argument to disable this. Titles may opt into `{ keepWords: true, preferClauseBreaks: true }` to respect available word segmentation and a fitting topic prefix before a colon. Dictionary segmentation is language-dependent and is not a semantic guarantee. Hyphenation, font shaping and cross-device font installation remain the renderer’s responsibility. Returned lines are plain text; escape them before inserting them into HTML.
82
+ You supply measurements for the actual font the renderer uses. Explicit line breaks are preserved; opportunities follow UAX #14 (Unicode 13), oversized tokens use grapheme boundaries via `Intl.Segmenter`, and a grapheme wider than the line fails rather than silently clipping. Tiny trailing lines are balanced unless you pass `{ balance: false }`. Titles can use `{ keepWords: true, preferClauseBreaks: true }`. Returned lines are plain text — escape them before inserting into HTML.
49
83
 
50
84
  ## Add custom scenes
51
85
 
@@ -54,85 +88,158 @@ import { createSceneRegistry } from "@neatico/visual-core";
54
88
 
55
89
  type Brief = { headline: string };
56
90
  type Scene = { text: string; accent: string };
91
+
57
92
  const scenes = createSceneRegistry<Brief, Scene>();
58
93
  const unregister = scenes.register("product-launch", (brief) => ({
59
94
  text: brief.headline,
60
95
  accent: "#6950e8",
61
96
  }));
62
- const scene = scenes.render("product-launch", {
63
- headline: "Meet your next idea.",
64
- });
65
- unregister();
66
- ```
67
-
68
- Registries are isolated instances. Duplicate names and unknown scenes throw errors. Renderers own their input validation and output format, so you can return your own canvas schema, SVG tree, or other representation. Async renderers are supported by choosing a promise as the output type. No global registry is created.
69
97
 
70
- ## Development
71
-
72
- ```sh
73
- npm ci
74
- npm test
75
- npm pack --dry-run
98
+ const scene = scenes.render("product-launch", { headline: "Meet your next idea." });
99
+ unregister();
76
100
  ```
77
101
 
78
- Tests cover transform equivalence, invalid geometry, text wrapping, and extension isolation. `npm pack` runs the build and tests before assembling the package. Only compiled output, this README, and the license are published.
79
-
80
- ## Contributing
102
+ Each registry is isolated; duplicate names and unknown scenes throw. Renderers own their input validation and output schema, so async renderers and custom canvas/SVG representations are supported. No global registry is created.
81
103
 
82
- Open an issue or pull request at [neatico/visual-core](https://github.com/neatico/visual-core). Include a minimal reproduction and a regression test. Keep APIs independent of application state and use American English in documentation and code comments.
104
+ ## Validate AI-authored content
83
105
 
84
- ## License
106
+ ```ts
107
+ import { parseSource, validateContentPlan } from "@neatico/visual-core";
85
108
 
86
- MIT. See [LICENSE](./LICENSE).
109
+ const source = parseSource(rawInputText);
110
+ const plan = validateContentPlan(aiJson, source, "presentation");
111
+ ```
87
112
 
88
- ## Portable template blueprints
113
+ `validateContentPlan` is a hard boundary for untrusted model output. It strips unknown properties, never trusts generated code or asset URLs, and verifies content against the source ranges. File-name placeholders are treated as references, not fabricated assets. Use `validateEditableContentPage` for editor-authored pages (pass `{ allowIncomplete: true }` while the user is still editing).
89
114
 
90
- `validateBlueprint` accepts a versioned, data-only template with an `id`, localized
91
- `labels` (including `en` as the fallback), required `fields`, supported `ratios`,
92
- and a `composition`. Every required field must be bound exactly once. The
93
- composition validator returns only supported structural properties; executable
94
- markup and arbitrary style properties are not part of this contract.
115
+ ## Validate compositions and blueprints
95
116
 
96
117
  ```ts
97
- import { createBlueprintRegistry, blueprintLabel } from '@neatico/visual-core';
118
+ import {
119
+ validateComposition,
120
+ compositionFields,
121
+ createBlueprintRegistry,
122
+ blueprintLabel,
123
+ } from "@neatico/visual-core";
124
+
125
+ const fields = compositionFields(aiContent);
126
+ const composition = validateComposition(aiComposition, fields);
98
127
 
99
128
  const catalog = createBlueprintRegistry();
100
129
  const unregister = catalog.register({
101
130
  version: 1,
102
- id: 'editorial-introduction',
103
- labels: { en: 'Editorial introduction', zh: '编辑式导言' },
104
- fields: ['title', 'body'],
131
+ id: "editorial-introduction",
132
+ labels: { en: "Editorial introduction", zh: "编辑式导言" },
133
+ fields: ["title", "body"],
105
134
  ratios: [1, 16 / 9],
135
+ intents: ["cover"],
106
136
  composition: {
107
137
  version: 1,
108
- root: { type: 'column', children: [
109
- { type: 'text', bind: 'title', role: 'hero' },
110
- { type: 'text', bind: 'body', role: 'body' },
111
- ] },
138
+ root: {
139
+ type: "column",
140
+ children: [
141
+ { type: "text", bind: "title", role: "hero" },
142
+ { type: "text", bind: "body", role: "body" },
143
+ ],
144
+ },
112
145
  },
113
146
  });
114
- const choices = catalog.find(['title', 'body'], 16 / 9);
115
- const name = blueprintLabel(choices[0], 'zh-CN');
147
+
148
+ const choices = catalog.find(["title", "body"], 16 / 9, "cover");
149
+ const name = blueprintLabel(choices[0], "zh-CN");
116
150
  unregister();
117
151
  ```
118
152
 
119
- Registries are isolated, reject duplicate IDs, and return defensive copies.
120
- Field matching is exact: a template cannot silently omit extra user content.
121
- Locale lookup tries the exact locale, then its language, then English.
153
+ Compositions and blueprints are **data-only** (`version: 1`). There are no coordinates, CSS, HTML, or URLs in the tree; every text node binds exactly one existing, nonempty content field. Blueprints can declare semantic `intents`; a cover-only template will never be picked for a closing slide. Field matching is exact, so a template cannot silently drop extra user content. Locale lookup tries the exact locale, then the language, then English.
154
+
155
+ Structural validation is not visual approval. Before registering AI-authored templates in production, render representative samples at every supported ratio with real font metrics and check bounds, collisions, readability, and content preservation. The host owns persistence, permissions, review, and migration.
122
156
 
123
- Structural validation is not visual approval. Before registering AI-authored
124
- blueprints in a production catalog, the host must render representative samples
125
- at every supported ratio with the actual font metrics and check bounds, text
126
- collisions, readability, and content preservation. The host also owns persistence,
127
- workspace permissions, review status, and migration between blueprint versions.
157
+ ## Select layout candidates
128
158
 
129
- ## Content-aware layout eligibility (0.6)
159
+ ```ts
160
+ import { eligibleLayouts, layoutCandidates } from "@neatico/visual-core";
161
+
162
+ const eligible = eligibleLayouts("presentation", contentPage, 16 / 9);
163
+ const shortlist = layoutCandidates("presentation", "cover", contentPage, 16 / 9, 5);
164
+ ```
130
165
 
131
- `LAYOUT_CONTRACTS` covers all 97 slide and 97 social IDs with semantic intent,
132
- item capacity, required fields and ratio bounds. `eligibleLayouts` filters the
133
- catalog, and `layoutCandidates` returns a bounded deterministic shortlist.
134
- These are structural eligibility checks, not a guarantee that arbitrary copy
135
- fits. Hosts must measure emitted geometry, verify all source fields remain
136
- visible and reject or paginate unreadable output. No text is rewritten here.
166
+ `LAYOUT_CONTRACTS` covers all 97 presentation and 97 social layout IDs with semantic intent, item capacity, required fields, and ratio bounds. Eligibility is a first filter only — it never replaces measured fit. The host must still measure emitted geometry, confirm every source field is visible, and reject or paginate unreadable output.
137
167
 
138
- Blueprints may declare `intents` (0.8.0), such as `['cover']` for presentation layouts or `['features']` for social scenes. Registry lookup accepts the semantic intent as its third argument and excludes scoped templates when the intent is absent or incompatible. Omit this property only for deliberately generic layouts. This keeps identical field shapes from turning a cover-only layout into a closing slide or unrelated social format.
168
+ ## Paginate without losing content
169
+
170
+ ```ts
171
+ import { splitCopy, splitContent } from "@neatico/visual-core";
172
+
173
+ const copySplit = splitCopy(longParagraph); // [head, tail] | undefined
174
+ const pageSplit = splitContent(overflowingPage); // [pageA, pageB] | undefined
175
+ ```
176
+
177
+ Splits happen only at explicit word/sentence boundaries. Identifiers, decimal values, and unbroken URLs are never cut to force them onto a smaller canvas. The host keeps the title and context on each emitted page and remeasures.
178
+
179
+ ## Themes, fonts, and fingerprints
180
+
181
+ ```ts
182
+ import {
183
+ designTheme,
184
+ designCandidates,
185
+ structureFingerprint,
186
+ recommendedFonts,
187
+ } from "@neatico/visual-core";
188
+
189
+ const theme = designTheme("ink");
190
+ const recipes = designCandidates(page, { kind: "social", ratio: 1, limit: 5 });
191
+
192
+ const fp = structureFingerprint(placedItems, canvasWidth, canvasHeight);
193
+ const fonts = recommendedFonts("ja-JP", japaneseHeadline);
194
+ ```
195
+
196
+ - `DESIGN_THEMES` / `designTheme` expose complete, deterministic palette, typography, and geometry tokens.
197
+ - `designCandidates` ranks authored catalog recipes against a page, excluding already-used families.
198
+ - `structureFingerprint` ignores color, copy, and generated IDs — a preview that only recolors has the same fingerprint.
199
+ - `recommendedFonts`, `contentScript`, and `languageFont` resolve the curated Google Fonts catalog to scripts and supported weights.
200
+
201
+ ## API surface
202
+
203
+ | Module | Key exports |
204
+ |---|---|
205
+ | Geometry | `layoutMatrix`, `multiplyMatrix`, `flattenGroupLayout`, `layoutBounds`, `Layout`, `Matrix` |
206
+ | Text | `wrapCopy`, `Measure`, `WrapOptions` |
207
+ | Scenes | `createSceneRegistry`, `SceneRenderer` |
208
+ | Content | `parseSource`, `validateContentPlan`, `validateEditableContentPage`, `contentBlockText`, `ContentPlan`, `ContentPage`, `ContentBlock` |
209
+ | Composition | `validateComposition`, `compositionFields`, `COMPOSITION_PROMPT`, `Composition` |
210
+ | Blueprints | `validateBlueprint`, `createBlueprintRegistry`, `blueprintLabel` |
211
+ | Design blueprints | `validateDesignBlueprint`, `blueprintFitsContent`, `DesignBlueprint` |
212
+ | Layout contracts | `LAYOUT_CONTRACTS`, `eligibleLayouts`, `layoutCandidates` |
213
+ | Layout catalogs | `PRESENTATION_LAYOUT_IDS`, `SOCIAL_LAYOUT_CATALOG`, `COMPOSITION_SCENES` |
214
+ | Pagination | `splitCopy`, `splitContent` |
215
+ | Design system | `DESIGN_THEMES`, `designTheme`, `DESIGN_RECIPES`, `designCandidates`, `structureFingerprint` |
216
+ | Editorial | `readingUnits`, `needsEditorialPagination`, `ensurePresentationBookends` |
217
+ | Fonts | `GOOGLE_FONT_CATALOG`, `contentScript`, `recommendedFonts`, `languageFont` |
218
+
219
+ ## Design philosophy
220
+
221
+ Neatico generates genuinely different designs for different stories, not the same template recolored. That principle is encoded here: eligibility is structural but measured fit stays mandatory, compositions are data-only, and registries are isolated so an application can bring its own renderers and approve its own templates. Every validator fails explicitly rather than guessing.
222
+
223
+ ## Development
224
+
225
+ ```sh
226
+ npm install
227
+ npm test # builds ESM/CJS, then runs node:test
228
+ npm pack --dry-run
229
+ ```
230
+
231
+ Tests cover transform equivalence, invalid geometry, text wrapping, content validation, registry isolation, and pagination. `npm pack` runs the build and tests before assembling the package; only compiled output, this README, and the license are published.
232
+
233
+ ## Meet Neatico
234
+
235
+ This package is the open-source layout core of **[Neatico](https://www.neatico.com/?utm_source=npm&utm_medium=readme&utm_campaign=visual-core)** — the AI workspace that turns a single idea into pixel-perfect, fully editable visuals. Describe it once and Neatico designs social posts, presentations, and marketing graphics with real layouts, measured typography, your brand kit, AI imagery and background removal, and one-click PPTX/PNG export.
236
+
237
+ The `@neatico/*` packages are the framework-independent pieces extracted from that product so you can build your own editor, generate-and-go pipeline, or scene extension. Want the finished experience instead of the primitives? **[Start creating free on neatico.com →](https://www.neatico.com/?utm_source=npm&utm_medium=readme&utm_campaign=visual-core)**
238
+
239
+ ## Contributing
240
+
241
+ Open an issue or pull request at [neatico/visual-core](https://github.com/neatico/visual-core) with a minimal reproduction and a regression test. Keep APIs independent of application state and use American English in documentation and code comments.
242
+
243
+ ## License
244
+
245
+ MIT. See [LICENSE](./LICENSE).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neatico/visual-core",
3
- "version": "0.14.0",
3
+ "version": "0.14.2",
4
4
  "description": "Framework-independent geometry, measured text wrapping, and extensible scene registries for visual editors.",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -31,11 +31,14 @@
31
31
  "type": "git",
32
32
  "url": "git+https://github.com/neatico/visual-core.git"
33
33
  },
34
- "homepage": "https://www.neatico.com",
34
+ "homepage": "https://www.neatico.com/?utm_source=npm&utm_medium=package&utm_campaign=visual-core",
35
35
  "bugs": {
36
36
  "url": "https://github.com/neatico/visual-core/issues"
37
37
  },
38
- "author": "PowerDuckie",
38
+ "author": {
39
+ "name": "POWERDUCK LIMITED",
40
+ "url": "https://www.neatico.com/"
41
+ },
39
42
  "license": "MIT",
40
43
  "publishConfig": {
41
44
  "access": "public",
@@ -46,7 +49,14 @@
46
49
  "layout",
47
50
  "typography",
48
51
  "affine-transform",
49
- "neatico"
52
+ "neatico",
53
+ "geometry",
54
+ "text-wrapping",
55
+ "line-break",
56
+ "canvas",
57
+ "design",
58
+ "editor",
59
+ "visual"
50
60
  ],
51
61
  "devDependencies": {
52
62
  "typescript": "^5.9.3"