@pixerate/editor 0.12.0 → 0.14.0

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/dist/index.cjs CHANGED
@@ -66,6 +66,8 @@ __export(src_exports, {
66
66
  SmilieReplacer: () => SmilieReplacer,
67
67
  SpreadsheetController: () => SpreadsheetController,
68
68
  StreamingLayoutManager: () => StreamingLayoutManager,
69
+ TEMPLATE_DEPTH_ERROR: () => TEMPLATE_DEPTH_ERROR,
70
+ TEMPLATE_LOOP_ERROR: () => TEMPLATE_LOOP_ERROR,
69
71
  TEMPLATE_REGEX: () => TEMPLATE_REGEX,
70
72
  TemplateSuggestions: () => TemplateSuggestions,
71
73
  VARIABLE_REGEX: () => VARIABLE_REGEX,
@@ -111,6 +113,7 @@ __export(src_exports, {
111
113
  getEditorText: () => getEditorText,
112
114
  getLatestVersion: () => getLatestVersion,
113
115
  getLayoutedNodes: () => getLayoutedNodes,
116
+ getTemplateBody: () => getTemplateBody,
114
117
  getViewportMetrics: () => getViewportMetrics,
115
118
  globalImageCache: () => globalImageCache,
116
119
  handleImageInsertion: () => handleImageInsertion,
@@ -280,6 +283,18 @@ function getLatestVersion(template) {
280
283
  }
281
284
  return template.versions[template.versions.length - 1];
282
285
  }
286
+ var TEMPLATE_LOOP_ERROR = "[Template loop detected]";
287
+ var TEMPLATE_DEPTH_ERROR = "[Template max depth exceeded]";
288
+ function getTemplateBody(template, bindings) {
289
+ const boundVersionId = bindings?.[template.name];
290
+ if (boundVersionId && template.versions) {
291
+ const boundVersion = template.versions.find(
292
+ (v) => v.id === boundVersionId || v.version === boundVersionId
293
+ );
294
+ return boundVersion?.body;
295
+ }
296
+ return getLatestVersion(template)?.body ?? template.body;
297
+ }
283
298
  function resolveTemplates(text, templates = [], options) {
284
299
  const templateMap = /* @__PURE__ */ new Map();
285
300
  for (const t of templates) {
@@ -287,50 +302,27 @@ function resolveTemplates(text, templates = [], options) {
287
302
  }
288
303
  const maxDepth = options?.maxDepth ?? 10;
289
304
  function resolveRecursive(currentText, visited, depth) {
290
- if (depth > maxDepth) {
291
- return "[Template max depth exceeded]";
292
- }
293
- let result = currentText;
294
305
  const regex = new RegExp(TEMPLATE_REGEX.source, TEMPLATE_REGEX.flags);
295
- let match;
296
- while ((match = regex.exec(result)) !== null) {
297
- const [fullMatch, rawTemplateName] = match;
306
+ return currentText.replace(regex, (fullMatch, rawTemplateName) => {
298
307
  const templateName = rawTemplateName.trim();
299
308
  if (visited.has(templateName)) {
300
- return "[Template loop detected]";
309
+ return TEMPLATE_LOOP_ERROR;
301
310
  }
302
311
  const template = templateMap.get(templateName);
303
312
  if (!template) {
304
- continue;
305
- }
306
- let versionBody;
307
- const boundVersionId = options?.bindings?.[templateName];
308
- if (boundVersionId && template.versions) {
309
- const boundVersion = template.versions.find(
310
- (v) => v.id === boundVersionId || v.version === boundVersionId
311
- );
312
- versionBody = boundVersion?.body;
313
- } else {
314
- const latest = getLatestVersion(template);
315
- versionBody = latest?.body ?? template.body;
313
+ return fullMatch;
316
314
  }
315
+ const versionBody = getTemplateBody(template, options?.bindings);
317
316
  if (versionBody === void 0) {
318
- continue;
317
+ return fullMatch;
318
+ }
319
+ if (depth + 1 > maxDepth) {
320
+ return TEMPLATE_DEPTH_ERROR;
319
321
  }
320
322
  const nextVisited = new Set(visited);
321
323
  nextVisited.add(templateName);
322
- const resolvedBody = resolveRecursive(
323
- versionBody,
324
- nextVisited,
325
- depth + 1
326
- );
327
- if (resolvedBody === "[Template loop detected]" || resolvedBody === "[Template max depth exceeded]") {
328
- return resolvedBody;
329
- }
330
- result = result.replace(fullMatch, resolvedBody);
331
- regex.lastIndex = 0;
332
- }
333
- return result;
324
+ return resolveRecursive(versionBody, nextVisited, depth + 1);
325
+ });
334
326
  }
335
327
  return resolveRecursive(text, /* @__PURE__ */ new Set(), 0);
336
328
  }
@@ -379,7 +371,7 @@ function extractInstructionNames(text) {
379
371
  }
380
372
 
381
373
  // src/grammar/sourcemap.ts
382
- function resolveTemplatesWithMapping(text, templates = [], templateBindings) {
374
+ function resolveTemplatesWithMapping(text, templates = [], templateBindings, options) {
383
375
  if (!templates || templates.length === 0 || !text.includes("{{")) {
384
376
  return {
385
377
  text,
@@ -395,7 +387,7 @@ function resolveTemplatesWithMapping(text, templates = [], templateBindings) {
395
387
  };
396
388
  }
397
389
  const templateMap = new Map(templates.map((t) => [t.name, t]));
398
- const regex = /{{\s*([^}]+?)\s*}}/g;
390
+ const regex = new RegExp(TEMPLATE_REGEX.source, TEMPLATE_REGEX.flags);
399
391
  let match;
400
392
  let lastIndex = 0;
401
393
  let resolvedText = "";
@@ -417,25 +409,13 @@ function resolveTemplatesWithMapping(text, templates = [], templateBindings) {
417
409
  }
418
410
  let resolvedBody = fullMatch;
419
411
  let isTemplate = false;
420
- if (templateMap.has(templateName)) {
421
- const template = templateMap.get(templateName);
422
- let versionBody;
423
- const boundVersionId = templateBindings?.[templateName];
424
- if (boundVersionId && template.versions) {
425
- const boundVersion = template.versions.find(
426
- (v) => v.id === boundVersionId || v.version === boundVersionId
427
- );
428
- versionBody = boundVersion?.body;
429
- } else {
430
- const latestVersion = getLatestVersion(template);
431
- versionBody = latestVersion?.body ?? template.body;
432
- }
433
- if (versionBody !== void 0) {
434
- resolvedBody = resolveTemplates(versionBody, templates, {
435
- bindings: templateBindings
436
- });
437
- isTemplate = true;
438
- }
412
+ const template = templateMap.get(templateName);
413
+ if (template && getTemplateBody(template, templateBindings) !== void 0) {
414
+ resolvedBody = resolveTemplates(fullMatch, templates, {
415
+ bindings: templateBindings,
416
+ maxDepth: options?.maxDepth
417
+ });
418
+ isTemplate = true;
439
419
  }
440
420
  if (isTemplate) {
441
421
  map.push({
@@ -480,9 +460,11 @@ function mapResolvedOffsetToRaw(resolvedOffset, map) {
480
460
  if (inRange) {
481
461
  if (seg.source === "raw") {
482
462
  return seg.rawStart + (resolvedOffset - seg.resolvedStart);
483
- } else {
484
- return seg.rawStart;
485
463
  }
464
+ if (resolvedOffset === seg.resolvedEnd) {
465
+ return seg.rawEnd;
466
+ }
467
+ return seg.rawStart;
486
468
  }
487
469
  }
488
470
  return resolvedOffset;
@@ -495,9 +477,11 @@ function mapRawOffsetToResolved(rawOffset, map) {
495
477
  if (inRange) {
496
478
  if (seg.source === "raw") {
497
479
  return seg.resolvedStart + (rawOffset - seg.rawStart);
498
- } else {
499
- return seg.resolvedStart;
500
480
  }
481
+ if (rawOffset === seg.rawEnd) {
482
+ return seg.resolvedEnd;
483
+ }
484
+ return seg.resolvedStart;
501
485
  }
502
486
  }
503
487
  return rawOffset;
@@ -5779,6 +5763,18 @@ var ImageCache = class {
5779
5763
  };
5780
5764
  img.onerror = (e) => reject(new Error(`Failed to load image from: ${src}`));
5781
5765
  img.src = src;
5766
+ if (typeof navigator !== "undefined" && /jsdom/i.test(navigator.userAgent)) {
5767
+ setTimeout(() => {
5768
+ try {
5769
+ Object.defineProperty(img, "naturalWidth", { value: 800, configurable: true });
5770
+ Object.defineProperty(img, "naturalHeight", { value: 600, configurable: true });
5771
+ Object.defineProperty(img, "complete", { value: true, configurable: true });
5772
+ } catch {
5773
+ }
5774
+ this.cache.set(src, img);
5775
+ resolve(img);
5776
+ }, 10);
5777
+ }
5782
5778
  });
5783
5779
  }
5784
5780
  set(src, img) {
@@ -6258,6 +6254,66 @@ var ImageEditorRenderer = class {
6258
6254
  }
6259
6255
  return exportCanvas;
6260
6256
  }
6257
+ /**
6258
+ * Generates a binary mask canvas (white for mask/selected area, black for unmasked).
6259
+ * Useful for AI inpainting and selective generative fill.
6260
+ */
6261
+ async renderMask(state, options) {
6262
+ const naturalWidth = state.imageDimensions.width;
6263
+ const naturalHeight = state.imageDimensions.height;
6264
+ const maskCanvas = this.createCanvas(naturalWidth, naturalHeight);
6265
+ const ctx = maskCanvas.getContext("2d");
6266
+ if (!ctx) return maskCanvas;
6267
+ ctx.fillStyle = "#000000";
6268
+ ctx.fillRect(0, 0, naturalWidth, naturalHeight);
6269
+ const useCrop = options?.useCrop ?? true;
6270
+ const useAnnotations = options?.useAnnotations ?? true;
6271
+ if (useCrop && state.crop) {
6272
+ ctx.fillStyle = "#ffffff";
6273
+ ctx.fillRect(state.crop.x, state.crop.y, state.crop.width, state.crop.height);
6274
+ }
6275
+ if (useAnnotations && state.annotations.length > 0) {
6276
+ for (const ann of state.annotations) {
6277
+ ctx.save();
6278
+ if (ann.rotation) {
6279
+ ctx.translate(ann.x, ann.y);
6280
+ ctx.rotate(ann.rotation * Math.PI / 180);
6281
+ ctx.translate(-ann.x, -ann.y);
6282
+ }
6283
+ if (ann.type === "pen") {
6284
+ const pen = ann;
6285
+ if (pen.points.length > 0) {
6286
+ ctx.beginPath();
6287
+ ctx.strokeStyle = "#ffffff";
6288
+ ctx.lineWidth = Math.max(pen.strokeWidth, 8);
6289
+ ctx.lineCap = "round";
6290
+ ctx.lineJoin = "round";
6291
+ ctx.moveTo(pen.points[0].x, pen.points[0].y);
6292
+ for (let i = 1; i < pen.points.length; i++) {
6293
+ ctx.lineTo(pen.points[i].x, pen.points[i].y);
6294
+ }
6295
+ ctx.stroke();
6296
+ }
6297
+ } else if (ann.type === "rect") {
6298
+ const rect = ann;
6299
+ ctx.fillStyle = "#ffffff";
6300
+ ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
6301
+ } else if (ann.type === "circle") {
6302
+ const circ = ann;
6303
+ ctx.fillStyle = "#ffffff";
6304
+ ctx.beginPath();
6305
+ ctx.ellipse(circ.x, circ.y, circ.radiusX, circ.radiusY, 0, 0, Math.PI * 2);
6306
+ ctx.fill();
6307
+ } else {
6308
+ ctx.strokeStyle = "#ffffff";
6309
+ ctx.fillStyle = "#ffffff";
6310
+ await this.drawAnnotation(ctx, ann);
6311
+ }
6312
+ ctx.restore();
6313
+ }
6314
+ }
6315
+ return maskCanvas;
6316
+ }
6261
6317
  };
6262
6318
 
6263
6319
  // src/image-editor/controller.ts
@@ -6600,7 +6656,10 @@ var ImageEditorController = class {
6600
6656
  async render(targetCanvas, options) {
6601
6657
  const source = this.imageElement || this.state.sourceUrl;
6602
6658
  if (!source) return;
6603
- await this.renderer.renderToCanvas(targetCanvas, source, this.state, options);
6659
+ try {
6660
+ await this.renderer.renderToCanvas(targetCanvas, source, this.state, options);
6661
+ } catch {
6662
+ }
6604
6663
  }
6605
6664
  async renderExport(options) {
6606
6665
  const source = this.imageElement || this.state.sourceUrl;
@@ -6642,6 +6701,23 @@ var ImageEditorController = class {
6642
6701
  }
6643
6702
  });
6644
6703
  }
6704
+ // --- AI Masking & Transformation Helpers ---
6705
+ async renderMask(options) {
6706
+ return this.renderer.renderMask(this.state, options);
6707
+ }
6708
+ async toMaskDataURL(options) {
6709
+ const canvas = await this.renderMask(options);
6710
+ return canvas.toDataURL("image/png");
6711
+ }
6712
+ async applyInpaintedImage(newDataUrl) {
6713
+ this.recordHistory();
6714
+ await this.loadImage(newDataUrl);
6715
+ this.clearAnnotations();
6716
+ }
6717
+ async applyBackgroundRemovedImage(newDataUrl) {
6718
+ this.recordHistory();
6719
+ await this.loadImage(newDataUrl);
6720
+ }
6645
6721
  // --- Serialization ---
6646
6722
  toJSON() {
6647
6723
  return this.serializeState();
@@ -6933,6 +7009,8 @@ function hitTestAnnotation(imgPt, ann, padding = 10) {
6933
7009
  SmilieReplacer,
6934
7010
  SpreadsheetController,
6935
7011
  StreamingLayoutManager,
7012
+ TEMPLATE_DEPTH_ERROR,
7013
+ TEMPLATE_LOOP_ERROR,
6936
7014
  TEMPLATE_REGEX,
6937
7015
  TemplateSuggestions,
6938
7016
  VARIABLE_REGEX,
@@ -6978,6 +7056,7 @@ function hitTestAnnotation(imgPt, ann, padding = 10) {
6978
7056
  getEditorText,
6979
7057
  getLatestVersion,
6980
7058
  getLayoutedNodes,
7059
+ getTemplateBody,
6981
7060
  getViewportMetrics,
6982
7061
  globalImageCache,
6983
7062
  handleImageInsertion,