@overtone-art/canvas-editor-core 0.5.0 → 0.5.1

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
@@ -886,8 +886,10 @@ function computeTilePositions(config, targetW, targetH, baseW, baseH, origin) {
886
886
  }
887
887
  return placements;
888
888
  }
889
- function drawTiles(ctx, img, config, targetW, targetH, baseW, baseH, origin) {
889
+ function drawTiles(ctx, img, config, targetW, targetH, baseW, baseH, origin, draw) {
890
890
  const anchor = origin ?? { x: targetW / 2, y: targetH / 2 };
891
+ const drawW = draw && draw.width > 0 ? draw.width : baseW;
892
+ const drawH = draw && draw.height > 0 ? draw.height : baseH;
891
893
  ctx.save();
892
894
  ctx.translate(anchor.x, anchor.y);
893
895
  ctx.rotate(config.angle * Math.PI / 180);
@@ -895,7 +897,7 @@ function drawTiles(ctx, img, config, targetW, targetH, baseW, baseH, origin) {
895
897
  ctx.save();
896
898
  ctx.translate(tile.x, tile.y);
897
899
  ctx.rotate(tile.rotation);
898
- ctx.drawImage(img, -baseW / 2, -baseH / 2, baseW, baseH);
900
+ ctx.drawImage(img, -drawW / 2, -drawH / 2, drawW, drawH);
899
901
  ctx.restore();
900
902
  }
901
903
  ctx.restore();
@@ -917,6 +919,7 @@ function mod2(n) {
917
919
  var MAX_SNAPSHOT_PIXELS = 16e6;
918
920
  var MAX_SNAPSHOT_SCALE = 8;
919
921
  var SNAPSHOT_SHRINK_FACTOR = 2;
922
+ var TILE_BLEED_DEVICE_PX = 2;
920
923
  var TiledPatternObject = class _TiledPatternObject extends FabricObject {
921
924
  static type = "TiledPattern";
922
925
  /** The layer's real object. Never rendered directly (it is kept at opacity 0). */
@@ -1006,7 +1009,8 @@ var TiledPatternObject = class _TiledPatternObject extends FabricObject {
1006
1009
  const { width, height } = this.area;
1007
1010
  if (width <= 0 || height <= 0) return;
1008
1011
  const { tileW, tileH } = this.liveTile();
1009
- const snapshot = this.ensureSnapshot(contextScale(ctx) * (tileW / Math.max(1, this.baseW())));
1012
+ const scale = contextScale(ctx);
1013
+ const snapshot = this.ensureSnapshot(scale * (tileW / Math.max(1, this.baseW())));
1010
1014
  if (!snapshot) return;
1011
1015
  const centre = this.getCenterPoint();
1012
1016
  const angle = this.liveAngle();
@@ -1017,7 +1021,17 @@ var TiledPatternObject = class _TiledPatternObject extends FabricObject {
1017
1021
  const shift = this.shiftVector(tileW, tileH, angle);
1018
1022
  const origin = { x: centre.x - shift.x, y: centre.y - shift.y };
1019
1023
  const config = { ...this.config, angle, offsetX: 0, offsetY: 0 };
1020
- drawTiles(ctx, snapshot, config, width, height, tileW, tileH, origin);
1024
+ drawTiles(
1025
+ ctx,
1026
+ snapshot,
1027
+ config,
1028
+ width,
1029
+ height,
1030
+ tileW,
1031
+ tileH,
1032
+ origin,
1033
+ this.drawSize(tileW, tileH, TILE_BLEED_DEVICE_PX / scale)
1034
+ );
1021
1035
  ctx.restore();
1022
1036
  }
1023
1037
  /** Raster fallback for SVG export — one `<image>` covering the print area. */
@@ -1043,7 +1057,9 @@ var TiledPatternObject = class _TiledPatternObject extends FabricObject {
1043
1057
  height,
1044
1058
  tileW,
1045
1059
  tileH,
1046
- { x: centre.x - shift.x, y: centre.y - shift.y }
1060
+ { x: centre.x - shift.x, y: centre.y - shift.y },
1061
+ // The fallback raster is built at 1:1, so a device pixel is a canvas unit.
1062
+ this.drawSize(tileW, tileH, TILE_BLEED_DEVICE_PX)
1047
1063
  );
1048
1064
  return [
1049
1065
  `<g transform="rotate(${-angle}) translate(${-centre.x} ${-centre.y})">`,
@@ -1083,18 +1099,64 @@ var TiledPatternObject = class _TiledPatternObject extends FabricObject {
1083
1099
  });
1084
1100
  return Promise.resolve(copy);
1085
1101
  }
1102
+ /**
1103
+ * The source's tile footprint: its bounding box, minus a stroke it reserves
1104
+ * room for but never paints.
1105
+ *
1106
+ * fabric keeps `strokeWidth` inside an object's box whether or not a `stroke`
1107
+ * colour paints it, and the built-in shapes arrive with `strokeWidth: 1` and no
1108
+ * stroke. Stepping the grid by that box puts a transparent seam between every
1109
+ * pair of neighbours at zero spacing: the artwork is a pixel narrower than the
1110
+ * box it is stepped by.
1111
+ */
1112
+ sourceBox() {
1113
+ const rect = this.source.getBoundingRect();
1114
+ if (paintsStroke(this.source)) return { width: rect.width, height: rect.height };
1115
+ const dims = this.source._getTransformedDimensions({ strokeWidth: 0 });
1116
+ const radians = (this.source.angle ?? 0) * Math.PI / 180;
1117
+ const cos = Math.abs(Math.cos(radians));
1118
+ const sin = Math.abs(Math.sin(radians));
1119
+ return {
1120
+ width: dims.x * cos + dims.y * sin,
1121
+ height: dims.x * sin + dims.y * cos
1122
+ };
1123
+ }
1086
1124
  /** The source's on-canvas width, before the tile scale. */
1087
1125
  baseW() {
1088
- return Math.max(1, this.source.getBoundingRect().width);
1126
+ return Math.max(1, this.sourceBox().width);
1127
+ }
1128
+ /** The source's on-canvas height, before the tile scale. */
1129
+ baseH() {
1130
+ return Math.max(1, this.sourceBox().height);
1131
+ }
1132
+ /**
1133
+ * Size to paint the snapshot at, for a tile of `tileW × tileH`.
1134
+ *
1135
+ * The snapshot is NOT exactly the source's box: `toCanvasElement` rounds the
1136
+ * raster up to whole pixels and pads it further for a shadow. Painting it into
1137
+ * the tile step would squeeze the artwork inside that padding — every tile
1138
+ * shrinks by up to a pixel and the grid shows transparent seams at zero
1139
+ * spacing. So the bitmap is painted at its own footprint, scaled by the same
1140
+ * factor the tile is, and the step stays the source's box.
1141
+ */
1142
+ drawSize(tileW, tileH, bleed = 0) {
1143
+ const snapshot = this.snapshotEl;
1144
+ if (!snapshot || this.snapshotScale <= 0) {
1145
+ return { width: tileW + bleed, height: tileH + bleed };
1146
+ }
1147
+ return {
1148
+ width: snapshot.width / this.snapshotScale * (tileW / this.baseW()) + bleed,
1149
+ height: snapshot.height / this.snapshotScale * (tileH / this.baseH()) + bleed
1150
+ };
1089
1151
  }
1090
1152
  /** Tile size from the config alone, ignoring any in-flight gesture. */
1091
1153
  baseTile() {
1092
- const rect = this.source.getBoundingRect();
1154
+ const box = this.sourceBox();
1093
1155
  const scale = Math.max(1, this.config.scale ?? 100) / 100;
1094
1156
  const floor = this.tileFloor();
1095
1157
  return {
1096
- tileW: Math.max(floor, rect.width * scale),
1097
- tileH: Math.max(floor, rect.height * scale)
1158
+ tileW: Math.max(floor, box.width * scale),
1159
+ tileH: Math.max(floor, box.height * scale)
1098
1160
  };
1099
1161
  }
1100
1162
  /** Tile size as drawn right now, including a live scale gesture. */
@@ -1157,6 +1219,12 @@ var TiledPatternObject = class _TiledPatternObject extends FabricObject {
1157
1219
  return Math.max(0.05, Math.min(requested, budgeted));
1158
1220
  }
1159
1221
  };
1222
+ function paintsStroke(object) {
1223
+ if (!object.strokeWidth || object.strokeWidth <= 0) return false;
1224
+ const { stroke } = object;
1225
+ if (typeof stroke === "string") return stroke !== "" && stroke !== "transparent";
1226
+ return stroke !== null && stroke !== void 0;
1227
+ }
1160
1228
  function clampPercent(value) {
1161
1229
  if (typeof value !== "number" || !Number.isFinite(value)) return 0;
1162
1230
  return Math.max(-100, Math.min(100, value));