@ngroznykh/papirus 0.3.7 → 0.3.9

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
@@ -8635,12 +8635,12 @@ class SvgExporter {
8635
8635
  parts.push(this.renderGroup(group));
8636
8636
  }
8637
8637
  }
8638
- parts.push(...renderedEdges);
8639
8638
  for (const node of this.renderer.nodes.values()) {
8640
8639
  if (node.visible) {
8641
8640
  parts.push(this.renderNode(node));
8642
8641
  }
8643
8642
  }
8643
+ parts.push(...renderedEdges);
8644
8644
  parts.push(`</g></svg>`);
8645
8645
  return parts.join("");
8646
8646
  }
@@ -8711,7 +8711,7 @@ class SvgExporter {
8711
8711
  shape = `<rect x="${bounds.x}" y="${bounds.y}" width="${bounds.width}" height="${bounds.height}"`;
8712
8712
  }
8713
8713
  }
8714
- const label = node.label ? this.renderTextLabel(node.label.text, node.getLabelPosition(), node.label.style) : "";
8714
+ const label = this.renderNodeLabel(node, bounds);
8715
8715
  const icon = this.renderNodeIcon(node, bounds);
8716
8716
  return [
8717
8717
  `${shape} fill="${fill}" fill-opacity="${fillOpacity}" stroke="${stroke}" stroke-width="${strokeWidth}" stroke-opacity="${strokeOpacity}"${dash}${dashOffset}/>`,
@@ -8727,14 +8727,72 @@ class SvgExporter {
8727
8727
  const style = edge.style;
8728
8728
  const stroke = style.strokeColor ?? "#666666";
8729
8729
  const strokeWidth = style.strokeWidth ?? 2;
8730
- const opacity = style.opacity ?? 1;
8730
+ const strokeOpacity = (style.strokeOpacity ?? 1) * (style.opacity ?? 1);
8731
+ const lineCap = style.lineCap ? ` stroke-linecap="${style.lineCap}"` : "";
8732
+ const lineJoin = style.lineJoin ? ` stroke-linejoin="${style.lineJoin}"` : "";
8731
8733
  const dashValues = style.flowDash ?? style.lineDash;
8732
8734
  const dash = dashValues ? ` stroke-dasharray="${dashValues.join(" ")}"` : "";
8733
8735
  const dashOffset = style.lineDashOffset !== void 0 ? ` stroke-dashoffset="${style.lineDashOffset}"` : "";
8734
8736
  const d = this.buildPath(edge);
8735
8737
  const markerShapes = this.renderEdgeMarkers(edge, stroke);
8736
- const label = edge.label ? this.renderTextLabel(edge.label.text, this.getEdgeLabelPoint(edge, edgeLabelOffset), edge.label.style) : "";
8737
- return `<path d="${d}" fill="none" stroke="${stroke}" stroke-width="${strokeWidth}" opacity="${opacity}" color="${stroke}"${dash}${dashOffset}/>${markerShapes}${label}`;
8738
+ const label = this.renderEdgeLabel(edge, edgeLabelOffset);
8739
+ return `<path d="${d}" fill="none" stroke="${stroke}" stroke-width="${strokeWidth}" stroke-opacity="${strokeOpacity}" color="${stroke}"${lineCap}${lineJoin}${dash}${dashOffset}/>${markerShapes}${label}`;
8740
+ }
8741
+ renderEdgeLabel(edge, edgeLabelOffset) {
8742
+ if (!edge.label) {
8743
+ return "";
8744
+ }
8745
+ const labelPoint = this.getEdgeLabelPoint(edge, edgeLabelOffset);
8746
+ const text = this.renderTextLabel(edge.label.text, labelPoint, edge.label.style);
8747
+ const bg = this.renderEdgeLabelBackground(edge, labelPoint);
8748
+ return `${bg}${text}`;
8749
+ }
8750
+ renderEdgeLabelBackground(edge, point) {
8751
+ if (!edge.label) {
8752
+ return "";
8753
+ }
8754
+ const metrics = this.measureTextLabel(edge.label.text, edge.label.style, edge.label.padding, edge.label.margin);
8755
+ const bgPadding = edge.labelBackground?.padding ?? EDGE_LABEL_BACKGROUND_PADDING;
8756
+ const bgColor = edge.labelBackground?.color ?? "#ffffff";
8757
+ const bgOpacity = edge.labelBackground?.opacity ?? 1;
8758
+ const bgRadius = edge.labelBackground?.borderRadius ?? EDGE_LABEL_BACKGROUND_RADIUS;
8759
+ const x = point.x - metrics.width / 2 - bgPadding;
8760
+ const y = point.y - metrics.height / 2 - bgPadding;
8761
+ const width = metrics.width + bgPadding * 2;
8762
+ const height = metrics.height + bgPadding * 2;
8763
+ const radius = Math.max(0, Math.min(bgRadius, width / 2, height / 2));
8764
+ if (radius <= 0) {
8765
+ return `<rect x="${x}" y="${y}" width="${width}" height="${height}" fill="${bgColor}" fill-opacity="${bgOpacity}"/>`;
8766
+ }
8767
+ return `<rect x="${x}" y="${y}" width="${width}" height="${height}" rx="${radius}" ry="${radius}" fill="${bgColor}" fill-opacity="${bgOpacity}"/>`;
8768
+ }
8769
+ measureTextLabel(text, style = {}, padding = 8, margin = 0) {
8770
+ const fontSize = style.fontSize ?? 14;
8771
+ const fontFamily = style.fontFamily ?? "sans-serif";
8772
+ const fontWeight = style.fontWeight ?? "normal";
8773
+ const lineHeight = fontSize * 1.2;
8774
+ const lines = text.split("\n");
8775
+ let maxWidth = 0;
8776
+ if (typeof document !== "undefined") {
8777
+ const canvas = document.createElement("canvas");
8778
+ const ctx = canvas.getContext("2d");
8779
+ if (ctx) {
8780
+ ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`;
8781
+ for (const line of lines) {
8782
+ maxWidth = Math.max(maxWidth, ctx.measureText(line).width);
8783
+ }
8784
+ }
8785
+ }
8786
+ if (maxWidth === 0) {
8787
+ const longestLine = lines.reduce((max, line) => line.length > max.length ? line : max, "");
8788
+ maxWidth = longestLine.length * fontSize * 0.6;
8789
+ }
8790
+ const resolvedPadding = Math.max(0, padding);
8791
+ const resolvedMargin = Math.max(0, margin);
8792
+ return {
8793
+ width: maxWidth + resolvedPadding * 2 + resolvedMargin * 2,
8794
+ height: lines.length * lineHeight + resolvedPadding * 2 + resolvedMargin * 2
8795
+ };
8738
8796
  }
8739
8797
  resolveMarkerConfig(edge, side) {
8740
8798
  const marker = side === "start" ? edge.startMarker : edge.endMarker;
@@ -8905,9 +8963,55 @@ class SvgExporter {
8905
8963
  const opacity = style.opacity ?? 1;
8906
8964
  const anchor = style.align === "left" ? "start" : style.align === "right" ? "end" : "middle";
8907
8965
  const baseline = style.baseline === "top" ? "text-before-edge" : style.baseline === "bottom" ? "text-after-edge" : "middle";
8908
- return `<text x="${point.x}" y="${point.y}" fill="${fill}" fill-opacity="${opacity}" font-size="${fontSize}" font-family="${fontFamily}" font-weight="${fontWeight}" text-anchor="${anchor}" dominant-baseline="${baseline}">${this.escapeText(
8909
- text
8910
- )}</text>`;
8966
+ const lines = text.split("\n");
8967
+ if (lines.length <= 1) {
8968
+ return `<text x="${point.x}" y="${point.y}" fill="${fill}" fill-opacity="${opacity}" font-size="${fontSize}" font-family="${fontFamily}" font-weight="${fontWeight}" text-anchor="${anchor}" dominant-baseline="${baseline}">${this.escapeText(
8969
+ text
8970
+ )}</text>`;
8971
+ }
8972
+ const lineHeight = fontSize * 1.2;
8973
+ const startY = point.y - (lines.length - 1) * lineHeight / 2;
8974
+ const tspans = lines.map((line, index) => `<tspan x="${point.x}" y="${startY + index * lineHeight}">${this.escapeText(line)}</tspan>`).join("");
8975
+ return `<text x="${point.x}" y="${point.y}" fill="${fill}" fill-opacity="${opacity}" font-size="${fontSize}" font-family="${fontFamily}" font-weight="${fontWeight}" text-anchor="${anchor}" dominant-baseline="${baseline}">${tspans}</text>`;
8976
+ }
8977
+ renderNodeLabel(node, bounds) {
8978
+ const label = node.label;
8979
+ if (!label) {
8980
+ return "";
8981
+ }
8982
+ const style = label.style;
8983
+ const fontSize = style.fontSize ?? 14;
8984
+ const lineHeight = fontSize * 1.2;
8985
+ const lines = label.text.split("\n");
8986
+ const textHeight = Math.max(lineHeight, lines.length * lineHeight);
8987
+ const padding = Math.max(0, label.padding);
8988
+ const margin = Math.max(0, label.margin);
8989
+ const inset = padding + margin;
8990
+ const align = style.align ?? "center";
8991
+ const placement = node.labelPlacement === "auto" ? "center" : node.labelPlacement;
8992
+ const inner = {
8993
+ x: bounds.x + margin,
8994
+ y: bounds.y + margin,
8995
+ width: Math.max(0, bounds.width - margin * 2),
8996
+ height: Math.max(0, bounds.height - margin * 2)
8997
+ };
8998
+ let x = inner.x + inner.width / 2;
8999
+ if (align === "left") {
9000
+ x = inner.x + inset;
9001
+ } else if (align === "right") {
9002
+ x = inner.x + inner.width - inset;
9003
+ }
9004
+ let y = inner.y + inner.height / 2;
9005
+ if (placement === "top") {
9006
+ y = inner.y + inset + textHeight / 2;
9007
+ } else if (placement === "bottom") {
9008
+ y = inner.y + inner.height - inset - textHeight / 2;
9009
+ } else if (placement === "left") {
9010
+ x = inner.x + inset;
9011
+ } else if (placement === "right") {
9012
+ x = inner.x + inner.width - inset;
9013
+ }
9014
+ return this.renderTextLabel(label.text, { x, y }, style);
8911
9015
  }
8912
9016
  getNodeCornerRadius(node, bounds) {
8913
9017
  const rectangleRadius = "cornerRadius" in node && typeof node.cornerRadius === "number" ? node.cornerRadius ?? 0 : node.style.cornerRadius ?? 0;