@overtone-art/canvas-editor-core 0.2.3 → 0.2.6

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.mjs CHANGED
@@ -576,6 +576,7 @@ var PatternManager = class {
576
576
  config,
577
577
  originalSrc: elementToDataURL(image) ?? image.getSrc(),
578
578
  originalClip: clip ? clip.toObject() : null,
579
+ originalLocks: captureLocks(image),
579
580
  original: {
580
581
  left: image.left ?? 0,
581
582
  top: image.top ?? 0,
@@ -592,7 +593,7 @@ var PatternManager = class {
592
593
  layer.meta.pattern.config = config;
593
594
  }
594
595
  try {
595
- await this.renderLayer(layer, firstEnable);
596
+ await this.renderLayer(layer);
596
597
  } catch (err) {
597
598
  if (firstEnable) delete layer.meta.pattern;
598
599
  throw err;
@@ -600,6 +601,34 @@ var PatternManager = class {
600
601
  this.history.save();
601
602
  });
602
603
  }
604
+ /**
605
+ * Stretch every restored pattern layer back over the full print area and
606
+ * re-freeze it, without re-rasterising: the baked bitmap keeps whatever size
607
+ * it was saved at, so it is scaled (not re-tiled) to the current canvas. Used
608
+ * after a state restore, where the canvas may be a different display size
609
+ * than when the pattern was baked — and to repair states saved while a
610
+ * pattern could still be dragged out of the print area.
611
+ */
612
+ repinAll() {
613
+ const cw = this.canvas.getWidth();
614
+ const ch = this.canvas.getHeight();
615
+ let changed = false;
616
+ for (const layer of this.layers.getAll()) {
617
+ if (!layer.meta.pattern || layer.type !== "image") continue;
618
+ const image = layer.fabricObject;
619
+ image.set({
620
+ left: 0,
621
+ top: 0,
622
+ angle: 0,
623
+ scaleX: cw / (image.width || cw),
624
+ scaleY: ch / (image.height || ch)
625
+ });
626
+ applyPatternLocks(image);
627
+ image.setCoords();
628
+ changed = true;
629
+ }
630
+ if (changed) this.canvas.requestRenderAll();
631
+ }
603
632
  /** Restore the original image and drop the pattern. */
604
633
  disable(layerId) {
605
634
  return this.enqueue(layerId, async () => {
@@ -620,6 +649,7 @@ var PatternManager = class {
620
649
  angle: state.original.angle
621
650
  });
622
651
  image.clipPath = state.originalClip ? (await util.enlivenObjects([state.originalClip]))[0] : void 0;
652
+ restoreLocks(image, state.originalLocks);
623
653
  image.setCoords();
624
654
  delete layer.meta.pattern;
625
655
  this.canvas.requestRenderAll();
@@ -636,13 +666,12 @@ var PatternManager = class {
636
666
  );
637
667
  return next;
638
668
  }
639
- async renderLayer(layer, pin) {
669
+ async renderLayer(layer) {
640
670
  const state = layer.meta.pattern;
641
671
  if (!state) return;
642
672
  const image = layer.fabricObject;
643
673
  const cw = this.canvas.getWidth();
644
674
  const ch = this.canvas.getHeight();
645
- const { left, top, angle } = resolvePatternPlacement(pin, image);
646
675
  const scale = Math.max(1, state.config.scale ?? 100) / 100;
647
676
  const diag = Math.sqrt(cw * cw + ch * ch);
648
677
  const minTile = Math.max(2, diag / MAX_TILES_PER_AXIS);
@@ -658,29 +687,54 @@ var PatternManager = class {
658
687
  );
659
688
  await image.setSrc(dataUrl);
660
689
  image.set({
661
- left,
662
- top,
690
+ left: 0,
691
+ top: 0,
663
692
  scaleX: 1,
664
693
  scaleY: 1,
665
694
  width: cw,
666
695
  height: ch,
667
696
  cropX: 0,
668
697
  cropY: 0,
669
- angle
698
+ angle: 0
670
699
  });
671
700
  image.clipPath = void 0;
701
+ applyPatternLocks(image);
672
702
  image.setCoords();
673
703
  this.canvas.requestRenderAll();
674
704
  }
675
705
  };
676
- function resolvePatternPlacement(pin, current) {
677
- if (pin) return { left: 0, top: 0, angle: 0 };
706
+ function captureLocks(obj) {
678
707
  return {
679
- left: current.left ?? 0,
680
- top: current.top ?? 0,
681
- angle: current.angle ?? 0
708
+ lockMovementX: obj.lockMovementX ?? false,
709
+ lockMovementY: obj.lockMovementY ?? false,
710
+ lockScalingX: obj.lockScalingX ?? false,
711
+ lockScalingY: obj.lockScalingY ?? false,
712
+ lockRotation: obj.lockRotation ?? false,
713
+ hasControls: obj.hasControls ?? true
682
714
  };
683
715
  }
716
+ function applyPatternLocks(obj) {
717
+ obj.set({
718
+ lockMovementX: true,
719
+ lockMovementY: true,
720
+ lockScalingX: true,
721
+ lockScalingY: true,
722
+ lockRotation: true,
723
+ hasControls: false
724
+ });
725
+ }
726
+ function restoreLocks(obj, locks) {
727
+ obj.set(
728
+ locks ?? {
729
+ lockMovementX: false,
730
+ lockMovementY: false,
731
+ lockScalingX: false,
732
+ lockScalingY: false,
733
+ lockRotation: false,
734
+ hasControls: true
735
+ }
736
+ );
737
+ }
684
738
  function elementToDataURL(image) {
685
739
  try {
686
740
  const el = image.getElement();
@@ -749,11 +803,13 @@ function computeTilePositions(config, targetW, targetH, baseW, baseH) {
749
803
  const rows = Math.ceil(diag / tileH) + 2;
750
804
  const halfCols = Math.ceil(cols / 2);
751
805
  const halfRows = Math.ceil(rows / 2);
806
+ const shiftX = tileW * (clampOffset(config.offsetX) / 100);
807
+ const shiftY = tileH * (clampOffset(config.offsetY) / 100);
752
808
  const placements = [];
753
809
  for (let j = -halfRows; j <= halfRows; j++) {
754
810
  for (let i = -halfCols; i <= halfCols; i++) {
755
- let x = i * tileW;
756
- let y = j * tileH;
811
+ let x = i * tileW + shiftX;
812
+ let y = j * tileH + shiftY;
757
813
  if (config.mode === "brick-horizontal" && mod2(j) === 1) {
758
814
  x += tileW * (config.horizontalOffset / 100);
759
815
  } else if (config.mode === "brick-vertical" && mod2(i) === 1) {
@@ -778,6 +834,10 @@ function drawTiles(ctx, img, config, targetW, targetH, baseW, baseH) {
778
834
  }
779
835
  ctx.restore();
780
836
  }
837
+ function clampOffset(value) {
838
+ if (typeof value !== "number" || !Number.isFinite(value)) return 0;
839
+ return Math.max(-100, Math.min(100, value));
840
+ }
781
841
  function mod2(n) {
782
842
  return (n % 2 + 2) % 2;
783
843
  }
@@ -1039,6 +1099,7 @@ var CanvasEditor = class {
1039
1099
  }
1040
1100
  async fromJSON(state) {
1041
1101
  await deserializeEditor(this, state);
1102
+ this.patterns.repinAll();
1042
1103
  }
1043
1104
  // ─── Export ──────────────────────────────────────────
1044
1105
  async toPNG(options) {
@@ -1233,6 +1294,8 @@ var DEFAULT_PATTERN_CONFIG = {
1233
1294
  verticalSpacing: 0,
1234
1295
  angle: 0,
1235
1296
  horizontalOffset: 0,
1297
+ offsetX: 0,
1298
+ offsetY: 0,
1236
1299
  rotationStepH: 0,
1237
1300
  rotationStepV: 0
1238
1301
  };
@@ -1247,7 +1310,9 @@ export {
1247
1310
  PatternManager,
1248
1311
  SnapManager,
1249
1312
  UnitConverter,
1313
+ applyPatternLocks,
1250
1314
  buildPatternDataURL,
1315
+ captureLocks,
1251
1316
  clamp,
1252
1317
  clearPatternImageCache,
1253
1318
  computeTilePositions,
@@ -1257,7 +1322,7 @@ export {
1257
1322
  exportPNG,
1258
1323
  exportSVG,
1259
1324
  generateId,
1260
- resolvePatternPlacement,
1325
+ restoreLocks,
1261
1326
  round2,
1262
1327
  serializeEditor
1263
1328
  };