@ngroznykh/papirus 0.3.0 → 0.3.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.
package/dist/papirus.js CHANGED
@@ -8104,6 +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
8108
  const bounds = getContentBounds({
8108
8109
  nodes: this.renderer.nodes.values(),
8109
8110
  edges: this.renderer.edges.values(),
@@ -8123,10 +8124,15 @@ class SvgExporter {
8123
8124
  this.renderer.nodes.values()
8124
8125
  );
8125
8126
  const parts = [];
8127
+ const renderedEdges = [];
8126
8128
  parts.push(
8127
8129
  `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">`
8128
8130
  );
8129
- parts.push(this.arrowDefs());
8131
+ for (const edge of this.renderer.edges.values()) {
8132
+ if (edge.visible) {
8133
+ renderedEdges.push(this.renderEdge(edge, edgeLabelOffset));
8134
+ }
8135
+ }
8130
8136
  if (includeBackground) {
8131
8137
  parts.push(`<rect width="100%" height="100%" fill="${backgroundColor}"/>`);
8132
8138
  }
@@ -8136,11 +8142,7 @@ class SvgExporter {
8136
8142
  parts.push(this.renderGroup(group));
8137
8143
  }
8138
8144
  }
8139
- for (const edge of this.renderer.edges.values()) {
8140
- if (edge.visible) {
8141
- parts.push(this.renderEdge(edge));
8142
- }
8143
- }
8145
+ parts.push(...renderedEdges);
8144
8146
  for (const node of this.renderer.nodes.values()) {
8145
8147
  if (node.visible) {
8146
8148
  parts.push(this.renderNode(node));
@@ -8218,7 +8220,7 @@ class SvgExporter {
8218
8220
  label
8219
8221
  ].join("");
8220
8222
  }
8221
- renderEdge(edge) {
8223
+ renderEdge(edge, edgeLabelOffset) {
8222
8224
  const path = edge.path;
8223
8225
  if (path.length < 2) {
8224
8226
  return "";
@@ -8231,10 +8233,114 @@ class SvgExporter {
8231
8233
  const dash = dashValues ? ` stroke-dasharray="${dashValues.join(" ")}"` : "";
8232
8234
  const dashOffset = style.lineDashOffset !== void 0 ? ` stroke-dashoffset="${style.lineDashOffset}"` : "";
8233
8235
  const d = this.buildPath(edge);
8234
- const markerEnd = edge.arrowType === "none" ? "" : ` marker-end="url(#arrow)"`;
8235
- const markerStart = edge.arrowType === "double" ? ` marker-start="url(#arrow)"` : "";
8236
- const label = edge.label ? this.renderTextLabel(edge.label.text, this.getPathMidpoint(edge), edge.label.style) : "";
8237
- return `<path d="${d}" fill="none" stroke="${stroke}" stroke-width="${strokeWidth}" opacity="${opacity}" color="${stroke}"${dash}${dashOffset}${markerStart}${markerEnd}/>${label}`;
8236
+ const markerShapes = this.renderEdgeMarkers(edge, stroke);
8237
+ const label = edge.label ? this.renderTextLabel(edge.label.text, this.getEdgeLabelPoint(edge, edgeLabelOffset), edge.label.style) : "";
8238
+ return `<path d="${d}" fill="none" stroke="${stroke}" stroke-width="${strokeWidth}" opacity="${opacity}" color="${stroke}"${dash}${dashOffset}/>${markerShapes}${label}`;
8239
+ }
8240
+ resolveMarkerConfig(edge, side) {
8241
+ const marker = side === "start" ? edge.startMarker : edge.endMarker;
8242
+ if (marker && marker.type !== "none") {
8243
+ return marker;
8244
+ }
8245
+ if (side === "end") {
8246
+ return edge.arrowType === "none" ? null : { type: "arrow" };
8247
+ }
8248
+ return edge.arrowType === "double" ? { type: "arrow" } : null;
8249
+ }
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
+ }
8265
+ }
8266
+ return parts.join("");
8267
+ }
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] };
8302
+ }
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;
8309
+ switch (marker.type) {
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
+ }
8335
+ case "arrow":
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
+ }
8343
+ }
8238
8344
  }
8239
8345
  buildPath(edge) {
8240
8346
  const path = edge.path;
@@ -8284,6 +8390,13 @@ class SvgExporter {
8284
8390
  }
8285
8391
  return path[0];
8286
8392
  }
8393
+ getEdgeLabelPoint(edge, edgeLabelOffset) {
8394
+ const midpoint = this.getPathMidpoint(edge);
8395
+ return {
8396
+ x: midpoint.x,
8397
+ y: midpoint.y + edgeLabelOffset
8398
+ };
8399
+ }
8287
8400
  renderTextLabel(text, point, style = {}) {
8288
8401
  const fill = style.color ?? "#000000";
8289
8402
  const fontSize = style.fontSize ?? 14;
@@ -8295,15 +8408,6 @@ class SvgExporter {
8295
8408
  text
8296
8409
  )}</text>`;
8297
8410
  }
8298
- arrowDefs() {
8299
- return [
8300
- "<defs>",
8301
- '<marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">',
8302
- '<path d="M 0 0 L 10 5 L 0 10 z" fill="currentColor"/>',
8303
- "</marker>",
8304
- "</defs>"
8305
- ].join("");
8306
- }
8307
8411
  createEmptySvg(width, height, backgroundColor, includeBackground) {
8308
8412
  return [
8309
8413
  `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">`,