@ngroznykh/papirus 0.3.1 → 0.3.3

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/papirus.js CHANGED
@@ -8104,7 +8104,7 @@ class SvgExporter {
8104
8104
  const padding = options.padding ?? 20;
8105
8105
  const includeBackground = options.includeBackground ?? true;
8106
8106
  const backgroundColor = options.backgroundColor ?? "#ffffff";
8107
- const edgeLabelOffset = options.edgeLabelOffset ?? 0;
8107
+ const edgeLabelOffset = options.edgeLabelOffset;
8108
8108
  const bounds = getContentBounds({
8109
8109
  nodes: this.renderer.nodes.values(),
8110
8110
  edges: this.renderer.edges.values(),
@@ -8124,17 +8124,15 @@ class SvgExporter {
8124
8124
  this.renderer.nodes.values()
8125
8125
  );
8126
8126
  const parts = [];
8127
- const markerDefs = /* @__PURE__ */ new Map();
8128
8127
  const renderedEdges = [];
8129
8128
  parts.push(
8130
8129
  `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">`
8131
8130
  );
8132
8131
  for (const edge of this.renderer.edges.values()) {
8133
8132
  if (edge.visible) {
8134
- renderedEdges.push(this.renderEdge(edge, markerDefs, edgeLabelOffset));
8133
+ renderedEdges.push(this.renderEdge(edge, edgeLabelOffset));
8135
8134
  }
8136
8135
  }
8137
- parts.push(this.buildDefs(markerDefs));
8138
8136
  if (includeBackground) {
8139
8137
  parts.push(`<rect width="100%" height="100%" fill="${backgroundColor}"/>`);
8140
8138
  }
@@ -8222,7 +8220,7 @@ class SvgExporter {
8222
8220
  label
8223
8221
  ].join("");
8224
8222
  }
8225
- renderEdge(edge, markerDefs, edgeLabelOffset) {
8223
+ renderEdge(edge, edgeLabelOffset) {
8226
8224
  const path = edge.path;
8227
8225
  if (path.length < 2) {
8228
8226
  return "";
@@ -8235,12 +8233,9 @@ class SvgExporter {
8235
8233
  const dash = dashValues ? ` stroke-dasharray="${dashValues.join(" ")}"` : "";
8236
8234
  const dashOffset = style.lineDashOffset !== void 0 ? ` stroke-dashoffset="${style.lineDashOffset}"` : "";
8237
8235
  const d = this.buildPath(edge);
8238
- const startMarkerConfig = this.resolveMarkerConfig(edge, "start");
8239
- const endMarkerConfig = this.resolveMarkerConfig(edge, "end");
8240
- const markerStart = startMarkerConfig ? ` marker-start="url(#${this.ensureMarkerDef(markerDefs, startMarkerConfig, "start", stroke)})"` : "";
8241
- const markerEnd = endMarkerConfig ? ` marker-end="url(#${this.ensureMarkerDef(markerDefs, endMarkerConfig, "end", stroke)})"` : "";
8236
+ const markerShapes = this.renderEdgeMarkers(edge, stroke);
8242
8237
  const label = edge.label ? this.renderTextLabel(edge.label.text, this.getEdgeLabelPoint(edge, edgeLabelOffset), edge.label.style) : "";
8243
- return `<path d="${d}" fill="none" stroke="${stroke}" stroke-width="${strokeWidth}" opacity="${opacity}" color="${stroke}"${dash}${dashOffset}${markerStart}${markerEnd}/>${label}`;
8238
+ return `<path d="${d}" fill="none" stroke="${stroke}" stroke-width="${strokeWidth}" opacity="${opacity}" color="${stroke}"${dash}${dashOffset}/>${markerShapes}${label}`;
8244
8239
  }
8245
8240
  resolveMarkerConfig(edge, side) {
8246
8241
  const marker = side === "start" ? edge.startMarker : edge.endMarker;
@@ -8252,40 +8247,99 @@ class SvgExporter {
8252
8247
  }
8253
8248
  return edge.arrowType === "double" ? { type: "arrow" } : null;
8254
8249
  }
8255
- ensureMarkerDef(markerDefs, marker, side, stroke) {
8256
- const size = marker.size ?? 12;
8257
- const markerStroke = marker.strokeColor ?? stroke;
8258
- const markerFill = marker.fillColor ?? markerStroke;
8259
- const fillOpacity = marker.fillOpacity ?? 1;
8260
- const markerId = this.buildMarkerId(side, marker.type, size, markerFill, markerStroke, fillOpacity);
8261
- if (!markerDefs.has(markerId)) {
8262
- const refX = marker.type === "circle" ? 5 : 10;
8263
- markerDefs.set(
8264
- markerId,
8265
- [
8266
- `<marker id="${markerId}" viewBox="0 0 10 10" refX="${refX}" refY="5" markerWidth="${size}" markerHeight="${size}" orient="auto-start-reverse" markerUnits="userSpaceOnUse">`,
8267
- this.renderMarkerShape(marker, markerFill, markerStroke, fillOpacity),
8268
- `</marker>`
8269
- ].join("")
8270
- );
8250
+ renderEdgeMarkers(edge, edgeStroke) {
8251
+ const startMarker = this.resolveMarkerConfig(edge, "start");
8252
+ const endMarker = this.resolveMarkerConfig(edge, "end");
8253
+ const parts = [];
8254
+ if (endMarker) {
8255
+ const points = this.getMarkerPoints(edge, "end");
8256
+ if (points) {
8257
+ parts.push(this.renderMarkerShape(endMarker, points.from, points.to, edgeStroke));
8258
+ }
8259
+ }
8260
+ if (startMarker) {
8261
+ const points = this.getMarkerPoints(edge, "start");
8262
+ if (points) {
8263
+ parts.push(this.renderMarkerShape(startMarker, points.from, points.to, edgeStroke));
8264
+ }
8271
8265
  }
8272
- return markerId;
8266
+ return parts.join("");
8273
8267
  }
8274
- buildMarkerId(side, type, size, fill, stroke, fillOpacity) {
8275
- const token = `${side}-${type}-${size}-${fill}-${stroke}-${fillOpacity}`.toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
8276
- return `marker-${token}`;
8268
+ getMarkerPoints(edge, position) {
8269
+ const path = edge.path;
8270
+ if (path.length < 2) {
8271
+ return null;
8272
+ }
8273
+ if (edge.type === "bezier" && path.length >= 4) {
8274
+ const epsilon = 1e-3;
8275
+ const isSame = (a, b) => Math.abs(a.x - b.x) < epsilon && Math.abs(a.y - b.y) < epsilon;
8276
+ if (position === "end") {
8277
+ const endIndex = path.length - 1;
8278
+ const endPoint = path[endIndex];
8279
+ let from = path[endIndex - 1];
8280
+ if (isSame(from, endPoint)) {
8281
+ from = path[endIndex - 2];
8282
+ if (isSame(from, endPoint)) {
8283
+ from = path[0];
8284
+ }
8285
+ }
8286
+ return { from, to: endPoint };
8287
+ }
8288
+ const start = path[0];
8289
+ let next = path[1];
8290
+ if (isSame(next, start)) {
8291
+ next = path[2];
8292
+ if (isSame(next, start)) {
8293
+ next = path[path.length - 1];
8294
+ }
8295
+ }
8296
+ return { from: next, to: start };
8297
+ }
8298
+ if (position === "end") {
8299
+ return { from: path[path.length - 2], to: path[path.length - 1] };
8300
+ }
8301
+ return { from: path[1], to: path[0] };
8277
8302
  }
8278
- renderMarkerShape(marker, fill, stroke, fillOpacity) {
8303
+ renderMarkerShape(marker, from, to, edgeStroke) {
8304
+ const angle2 = Math.atan2(to.y - from.y, to.x - from.x);
8305
+ const size = marker.size ?? 12;
8306
+ const stroke = marker.strokeColor ?? edgeStroke;
8307
+ const fill = marker.fillColor ?? stroke;
8308
+ const fillOpacity = marker.fillOpacity ?? 1;
8279
8309
  switch (marker.type) {
8280
- case "open":
8281
- return `<path d="M 10 0 L 0 5 L 10 10" fill="none" stroke="${stroke}" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>`;
8282
- case "diamond":
8283
- return `<path d="M 10 5 L 5 0 L 0 5 L 5 10 z" fill="${fill}" fill-opacity="${fillOpacity}" stroke="${stroke}" stroke-width="1"/>`;
8284
- case "circle":
8285
- return `<circle cx="5" cy="5" r="4" fill="${fill}" fill-opacity="${fillOpacity}" stroke="${stroke}" stroke-width="1"/>`;
8310
+ case "open": {
8311
+ const x1 = to.x - size * Math.cos(angle2 - ARROW_ANGLE);
8312
+ const y1 = to.y - size * Math.sin(angle2 - ARROW_ANGLE);
8313
+ const x2 = to.x - size * Math.cos(angle2 + ARROW_ANGLE);
8314
+ const y2 = to.y - size * Math.sin(angle2 + ARROW_ANGLE);
8315
+ return `<path d="M ${to.x} ${to.y} L ${x1} ${y1} M ${to.x} ${to.y} L ${x2} ${y2}" fill="none" stroke="${stroke}" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>`;
8316
+ }
8317
+ case "diamond": {
8318
+ const halfLength = size / 2;
8319
+ const halfWidth = size * 0.3;
8320
+ const cos = Math.cos(angle2);
8321
+ const sin = Math.sin(angle2);
8322
+ const p1x = to.x - halfLength * cos + halfWidth * sin;
8323
+ const p1y = to.y - halfLength * sin - halfWidth * cos;
8324
+ const backX = to.x - size * cos;
8325
+ const backY = to.y - size * sin;
8326
+ const p2x = to.x - halfLength * cos - halfWidth * sin;
8327
+ const p2y = to.y - halfLength * sin + halfWidth * cos;
8328
+ return `<path d="M ${to.x} ${to.y} L ${p1x} ${p1y} L ${backX} ${backY} L ${p2x} ${p2y} Z" fill="${fill}" fill-opacity="${fillOpacity}" stroke="${stroke}" stroke-width="1"/>`;
8329
+ }
8330
+ case "circle": {
8331
+ const cx = to.x - size * Math.cos(angle2);
8332
+ const cy = to.y - size * Math.sin(angle2);
8333
+ return `<circle cx="${cx}" cy="${cy}" r="${size}" fill="${fill}" fill-opacity="${fillOpacity}" stroke="${stroke}" stroke-width="1"/>`;
8334
+ }
8286
8335
  case "arrow":
8287
- default:
8288
- return `<path d="M 0 0 L 10 5 L 0 10 z" fill="${fill}" fill-opacity="${fillOpacity}" stroke="${stroke}" stroke-width="1"/>`;
8336
+ default: {
8337
+ const x1 = to.x - size * Math.cos(angle2 - ARROW_ANGLE);
8338
+ const y1 = to.y - size * Math.sin(angle2 - ARROW_ANGLE);
8339
+ const x2 = to.x - size * Math.cos(angle2 + ARROW_ANGLE);
8340
+ const y2 = to.y - size * Math.sin(angle2 + ARROW_ANGLE);
8341
+ return `<path d="M ${to.x} ${to.y} L ${x1} ${y1} L ${x2} ${y2} Z" fill="${fill}" fill-opacity="${fillOpacity}" stroke="${stroke}" stroke-width="1"/>`;
8342
+ }
8289
8343
  }
8290
8344
  }
8291
8345
  buildPath(edge) {
@@ -8338,9 +8392,10 @@ class SvgExporter {
8338
8392
  }
8339
8393
  getEdgeLabelPoint(edge, edgeLabelOffset) {
8340
8394
  const midpoint = this.getPathMidpoint(edge);
8395
+ const effectiveOffset = edgeLabelOffset ?? edge.labelOffset ?? 0;
8341
8396
  return {
8342
8397
  x: midpoint.x,
8343
- y: midpoint.y + edgeLabelOffset
8398
+ y: midpoint.y + effectiveOffset
8344
8399
  };
8345
8400
  }
8346
8401
  renderTextLabel(text, point, style = {}) {
@@ -8354,12 +8409,6 @@ class SvgExporter {
8354
8409
  text
8355
8410
  )}</text>`;
8356
8411
  }
8357
- buildDefs(markerDefs) {
8358
- if (markerDefs.size === 0) {
8359
- return "<defs></defs>";
8360
- }
8361
- return `<defs>${Array.from(markerDefs.values()).join("")}</defs>`;
8362
- }
8363
8412
  createEmptySvg(width, height, backgroundColor, includeBackground) {
8364
8413
  return [
8365
8414
  `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">`,