circuit-to-svg 0.0.210 → 0.0.211

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.js CHANGED
@@ -1783,7 +1783,7 @@ function getSoftwareUsedString(circuitJson) {
1783
1783
  var package_default = {
1784
1784
  name: "circuit-to-svg",
1785
1785
  type: "module",
1786
- version: "0.0.209",
1786
+ version: "0.0.210",
1787
1787
  description: "Convert Circuit JSON to SVG",
1788
1788
  main: "dist/index.js",
1789
1789
  files: [
@@ -3211,7 +3211,7 @@ function createSvgObjectsFromPinoutComponent(elm, ctx) {
3211
3211
  const [x, y] = applyToPoint29(transform, [center.x, center.y]);
3212
3212
  const scaledWidth = width * Math.abs(transform.a);
3213
3213
  const scaledHeight = height * Math.abs(transform.d);
3214
- const transformStr = `translate(${x}, ${y}) rotate(${-rotation})`;
3214
+ const transformStr = `translate(${x}, ${y})`;
3215
3215
  const children = [
3216
3216
  {
3217
3217
  name: "rect",
@@ -3222,14 +3222,14 @@ function createSvgObjectsFromPinoutComponent(elm, ctx) {
3222
3222
  y: (-scaledHeight / 2).toString(),
3223
3223
  width: scaledWidth.toString(),
3224
3224
  height: scaledHeight.toString(),
3225
- fill: COMPONENT_FILL_COLOR
3225
+ fill: COMPONENT_FILL_COLOR,
3226
+ transform: `rotate(${rotation}deg)`
3226
3227
  },
3227
3228
  value: "",
3228
3229
  children: []
3229
3230
  }
3230
3231
  ];
3231
3232
  if (sourceComponent?.name) {
3232
- const isTall = scaledHeight > scaledWidth * 1.5;
3233
3233
  const labelFontSize = Math.min(scaledWidth, scaledHeight) * 0.4;
3234
3234
  children.push({
3235
3235
  name: "text",
@@ -3241,8 +3241,7 @@ function createSvgObjectsFromPinoutComponent(elm, ctx) {
3241
3241
  "font-size": `${labelFontSize}px`,
3242
3242
  "font-family": "sans-serif",
3243
3243
  "text-anchor": "middle",
3244
- "dominant-baseline": "middle",
3245
- transform: isTall ? "rotate(90)" : ""
3244
+ "dominant-baseline": "middle"
3246
3245
  },
3247
3246
  children: [
3248
3247
  {
@@ -3694,9 +3693,77 @@ function createSvgObjectsFromPinoutSmtPad(pad, ctx) {
3694
3693
  // lib/pinout/svg-object-fns/create-svg-objects-from-pinout-port.ts
3695
3694
  import { applyToPoint as applyToPoint33 } from "transformation-matrix";
3696
3695
  import { calculateElbow } from "calculate-elbow";
3696
+
3697
+ // lib/pinout/svg-object-fns/pinout-label-box.ts
3698
+ function createPinoutLabelBox(params) {
3699
+ const {
3700
+ rectX,
3701
+ rectY,
3702
+ rectWidth,
3703
+ rectHeight,
3704
+ textX,
3705
+ textY,
3706
+ text,
3707
+ fontSize,
3708
+ labelBackground,
3709
+ labelColor,
3710
+ rx = 4,
3711
+ ry = 4,
3712
+ fontFamily = "Arial, sans-serif",
3713
+ fontWeight = "bold",
3714
+ textAnchor = "middle",
3715
+ dominantBaseline = "middle"
3716
+ } = params;
3717
+ return [
3718
+ {
3719
+ name: "rect",
3720
+ type: "element",
3721
+ attributes: {
3722
+ x: rectX.toString(),
3723
+ y: rectY.toString(),
3724
+ width: rectWidth.toString(),
3725
+ height: rectHeight.toString(),
3726
+ fill: labelBackground,
3727
+ rx: typeof rx === "number" ? rx.toString() : rx,
3728
+ ry: typeof ry === "number" ? ry.toString() : ry,
3729
+ stroke: "none"
3730
+ },
3731
+ children: [],
3732
+ value: ""
3733
+ },
3734
+ {
3735
+ name: "text",
3736
+ type: "element",
3737
+ attributes: {
3738
+ x: textX.toString(),
3739
+ y: textY.toString(),
3740
+ fill: labelColor,
3741
+ "font-size": `${fontSize}px`,
3742
+ "font-family": fontFamily,
3743
+ "font-weight": fontWeight,
3744
+ "text-anchor": textAnchor,
3745
+ "dominant-baseline": dominantBaseline
3746
+ },
3747
+ children: [
3748
+ {
3749
+ type: "text",
3750
+ value: text,
3751
+ name: "",
3752
+ attributes: {},
3753
+ children: []
3754
+ }
3755
+ ],
3756
+ value: ""
3757
+ }
3758
+ ];
3759
+ }
3760
+
3761
+ // lib/pinout/svg-object-fns/create-svg-objects-from-pinout-port.ts
3697
3762
  var LABEL_COLOR = "rgb(255, 255, 255)";
3698
3763
  var LABEL_BACKGROUND = "rgb(0, 0, 0)";
3699
3764
  var LINE_COLOR = "rgba(0, 0, 0, 0.6)";
3765
+ var PIN_NUMBER_BACKGROUND = "rgb(200, 200, 200)";
3766
+ var PIN_NUMBER_COLOR = "rgb(0, 0, 0)";
3700
3767
  function createSvgObjectsFromPinoutPort(pcb_port, ctx) {
3701
3768
  const label_info = ctx.label_positions.get(pcb_port.pcb_port_id);
3702
3769
  if (!label_info) return [];
@@ -3718,26 +3785,33 @@ function createSvgObjectsFromPinoutPort(pcb_port, ctx) {
3718
3785
  {}
3719
3786
  );
3720
3787
  const line_points = [...elbow_path, label_pos].map((p) => `${p.x},${p.y}`).join(" ");
3721
- const full_label = [label, ...aliases].join(" | ");
3722
- const fontSize = 11;
3723
- const textWidth = full_label.length * fontSize * 0.6;
3724
- const bgPadding = 5;
3725
- const rectHeight = fontSize + 2 * bgPadding;
3726
- const rectWidth = textWidth + 2 * bgPadding;
3788
+ const numberMatch = /^pin(\d+)$/i.exec(label);
3789
+ const tokensWithStyle = [
3790
+ {
3791
+ text: numberMatch ? numberMatch[1] : label,
3792
+ bg: numberMatch ? PIN_NUMBER_BACKGROUND : LABEL_BACKGROUND,
3793
+ color: numberMatch ? PIN_NUMBER_COLOR : LABEL_COLOR
3794
+ },
3795
+ ...aliases.map((t) => ({
3796
+ text: t,
3797
+ bg: LABEL_BACKGROUND,
3798
+ color: LABEL_COLOR
3799
+ }))
3800
+ ];
3801
+ const scale10 = Math.abs(ctx.transform.a);
3802
+ const LABEL_RECT_HEIGHT_MM2 = 2.2;
3803
+ const rectHeight = LABEL_RECT_HEIGHT_MM2 * scale10;
3804
+ const fontSize = rectHeight * (11 / 21);
3805
+ const bgPadding = (rectHeight - fontSize) / 2;
3806
+ const gap = bgPadding;
3807
+ const tokenRects = tokensWithStyle.map(({ text, bg, color }) => {
3808
+ const safeText = text ?? "";
3809
+ const textWidth = safeText.length * fontSize * 0.6;
3810
+ const rectWidth = textWidth + 2 * bgPadding;
3811
+ return { text: safeText, rectWidth, bg, color };
3812
+ });
3727
3813
  const text_y = label_pos.y;
3728
- let rectX;
3729
- let text_x;
3730
- if (edge === "left") {
3731
- rectX = label_pos.x - rectWidth;
3732
- text_x = label_pos.x - rectWidth / 2;
3733
- } else if (edge === "right") {
3734
- rectX = label_pos.x;
3735
- text_x = label_pos.x + rectWidth / 2;
3736
- } else {
3737
- rectX = label_pos.x - rectWidth / 2;
3738
- text_x = label_pos.x;
3739
- }
3740
- return [
3814
+ const objects = [
3741
3815
  {
3742
3816
  name: "polyline",
3743
3817
  type: "element",
@@ -3749,202 +3823,239 @@ function createSvgObjectsFromPinoutPort(pcb_port, ctx) {
3749
3823
  },
3750
3824
  children: [],
3751
3825
  value: ""
3752
- },
3753
- {
3754
- name: "rect",
3755
- type: "element",
3756
- attributes: {
3757
- x: rectX.toString(),
3758
- y: (text_y - rectHeight / 2).toString(),
3759
- width: rectWidth.toString(),
3760
- height: rectHeight.toString(),
3761
- fill: LABEL_BACKGROUND,
3762
- rx: "8",
3763
- // More rounded corners
3764
- ry: "8",
3765
- stroke: "none"
3766
- },
3767
- children: [],
3768
- value: ""
3769
- },
3770
- {
3771
- name: "text",
3772
- type: "element",
3773
- attributes: {
3774
- x: text_x.toString(),
3775
- y: text_y.toString(),
3776
- fill: LABEL_COLOR,
3777
- "font-size": `${fontSize}px`,
3778
- "font-family": "Arial, sans-serif",
3779
- "font-weight": "bold",
3780
- "text-anchor": "middle",
3781
- "dominant-baseline": "middle"
3782
- },
3783
- children: [
3784
- {
3785
- type: "text",
3786
- value: full_label,
3787
- name: "",
3788
- attributes: {},
3789
- children: []
3790
- }
3791
- ],
3792
- value: ""
3793
3826
  }
3794
3827
  ];
3795
- }
3796
-
3797
- // lib/pinout/calculate-label-positions.ts
3798
- import { applyToPoint as applyToPoint34 } from "transformation-matrix";
3799
-
3800
- // lib/pinout/pinout-utils.ts
3801
- import { su as su6 } from "@tscircuit/circuit-json-util";
3802
- function getPortLabelInfo(port, soup) {
3803
- const source_port = su6(soup).source_port.get(port.source_port_id);
3804
- if (!source_port) return null;
3805
- const eligible_hints = source_port.port_hints?.filter(
3806
- (h) => !/^\d+$/.test(h) && !["left", "right", "top", "bottom"].includes(h)
3807
- ) ?? [];
3808
- let label = eligible_hints[0];
3809
- if (!label) label = source_port.name;
3810
- if (!label) return null;
3811
- const aliases = eligible_hints.filter((h) => h !== label);
3812
- return { text: label, aliases };
3813
- }
3814
- function getClosestEdge(port_pos_real, board_bounds) {
3815
- const dists = {
3816
- left: port_pos_real.x - board_bounds.minX,
3817
- right: board_bounds.maxX - port_pos_real.x,
3818
- top: board_bounds.maxY - port_pos_real.y,
3819
- bottom: port_pos_real.y - board_bounds.minY
3820
- };
3821
- let closest_edge = "left";
3822
- let min_dist = dists.left;
3823
- if (dists.right < min_dist) {
3824
- min_dist = dists.right;
3825
- closest_edge = "right";
3826
- }
3827
- if (dists.top < min_dist) {
3828
- min_dist = dists.top;
3829
- closest_edge = "top";
3830
- }
3831
- if (dists.bottom < min_dist) {
3832
- min_dist = dists.bottom;
3833
- closest_edge = "bottom";
3828
+ if (edge === "left") {
3829
+ let currentX = label_pos.x;
3830
+ for (const { text, rectWidth, bg, color } of tokenRects) {
3831
+ const rectX = currentX - rectWidth;
3832
+ const text_x = rectX + rectWidth / 2;
3833
+ objects.push(
3834
+ ...createPinoutLabelBox({
3835
+ rectX,
3836
+ rectY: text_y - rectHeight / 2,
3837
+ rectWidth,
3838
+ rectHeight,
3839
+ textX: text_x,
3840
+ textY: text_y,
3841
+ text,
3842
+ fontSize,
3843
+ labelBackground: bg,
3844
+ labelColor: color
3845
+ })
3846
+ );
3847
+ currentX = rectX - gap;
3848
+ }
3849
+ } else if (edge === "right") {
3850
+ let currentX = label_pos.x;
3851
+ for (const { text, rectWidth, bg, color } of tokenRects) {
3852
+ const rectX = currentX;
3853
+ const text_x = rectX + rectWidth / 2;
3854
+ objects.push(
3855
+ ...createPinoutLabelBox({
3856
+ rectX,
3857
+ rectY: text_y - rectHeight / 2,
3858
+ rectWidth,
3859
+ rectHeight,
3860
+ textX: text_x,
3861
+ textY: text_y,
3862
+ text,
3863
+ fontSize,
3864
+ labelBackground: bg,
3865
+ labelColor: color
3866
+ })
3867
+ );
3868
+ currentX = rectX + rectWidth + gap;
3869
+ }
3870
+ } else {
3871
+ const totalWidth = tokenRects.reduce((acc, t) => acc + t.rectWidth, 0) + gap * Math.max(0, tokenRects.length - 1);
3872
+ let currentX = label_pos.x - totalWidth / 2;
3873
+ for (const { text, rectWidth, bg, color } of tokenRects) {
3874
+ const rectX = currentX;
3875
+ const text_x = rectX + rectWidth / 2;
3876
+ objects.push(
3877
+ ...createPinoutLabelBox({
3878
+ rectX,
3879
+ rectY: text_y - rectHeight / 2,
3880
+ rectWidth,
3881
+ rectHeight,
3882
+ textX: text_x,
3883
+ textY: text_y,
3884
+ text,
3885
+ fontSize,
3886
+ labelBackground: bg,
3887
+ labelColor: color
3888
+ })
3889
+ );
3890
+ currentX = rectX + rectWidth + gap;
3891
+ }
3834
3892
  }
3835
- return closest_edge;
3893
+ return objects;
3836
3894
  }
3837
3895
 
3838
3896
  // lib/pinout/calculate-label-positions.ts
3897
+ import "@tscircuit/circuit-json-util";
3898
+ import { applyToPoint as applyToPoint34 } from "transformation-matrix";
3839
3899
  var STAGGER_OFFSET_MIN = 20;
3840
- var STAGGER_OFFSET_PER_PIN = 2;
3841
- var STAGGER_OFFSET_STEP = 15;
3900
+ var STAGGER_OFFSET_PER_PIN = 3;
3901
+ var STAGGER_OFFSET_STEP = 18;
3842
3902
  var ALIGNED_OFFSET_MARGIN = 10;
3843
- var FONT_SIZE = 11;
3844
- var BG_PADDING = 5;
3845
- var LABEL_RECT_HEIGHT = FONT_SIZE + 2 * BG_PADDING;
3846
- var LABEL_MARGIN = 5;
3847
- function calculateVerticalEdgeLabels(edge, ports, {
3903
+ var LABEL_RECT_HEIGHT_MM = 1.6;
3904
+ var LABEL_PITCH_MM = 2.54;
3905
+ function calculateVerticalEdgeLabels(edge, pinout_labels, {
3848
3906
  transform,
3849
3907
  soup,
3850
3908
  board_bounds,
3851
3909
  svgHeight
3852
3910
  }, label_positions) {
3853
- const edge_ports = ports.map((port) => ({
3854
- port,
3855
- y: applyToPoint34(transform, [port.x, port.y])[1],
3856
- label_info: getPortLabelInfo(port, soup)
3857
- })).filter((p) => p.label_info).sort((a, b) => a.y - b.y);
3911
+ const x_coords = pinout_labels.map((l) => l.pcb_port.x);
3912
+ const counts = {};
3913
+ for (const x of x_coords) {
3914
+ const rounded = x.toFixed(1);
3915
+ counts[rounded] = (counts[rounded] || 0) + 1;
3916
+ }
3917
+ let edge_ports;
3918
+ if (Object.keys(counts).length > 1 && pinout_labels.length > 2) {
3919
+ const sorted_x_groups = Object.entries(counts).sort((a, b) => b[1] - a[1]);
3920
+ const primary_x = parseFloat(sorted_x_groups[0][0]);
3921
+ const primary_pins = pinout_labels.filter(
3922
+ (l) => Math.abs(l.pcb_port.x - primary_x) < 0.2
3923
+ );
3924
+ const other_pins = pinout_labels.filter(
3925
+ (l) => Math.abs(l.pcb_port.x - primary_x) >= 0.2
3926
+ );
3927
+ const mapToEdgePort = (pinout_label) => ({
3928
+ pcb_port: pinout_label.pcb_port,
3929
+ y: applyToPoint34(transform, [
3930
+ pinout_label.pcb_port.x,
3931
+ pinout_label.pcb_port.y
3932
+ ])[1],
3933
+ aliases: pinout_label.aliases
3934
+ });
3935
+ primary_pins.sort((a, b) => b.pcb_port.y - a.pcb_port.y);
3936
+ other_pins.sort((a, b) => b.pcb_port.y - a.pcb_port.y);
3937
+ const max_y_primary = primary_pins.length > 0 ? Math.max(...primary_pins.map((p) => p.pcb_port.y)) : -Infinity;
3938
+ const max_y_other = other_pins.length > 0 ? Math.max(...other_pins.map((p) => p.pcb_port.y)) : -Infinity;
3939
+ const combined_pins = max_y_other > max_y_primary ? [...other_pins, ...primary_pins] : [...primary_pins, ...other_pins];
3940
+ edge_ports = combined_pins.map(mapToEdgePort);
3941
+ } else {
3942
+ edge_ports = pinout_labels.map((pinout_label) => ({
3943
+ pcb_port: pinout_label.pcb_port,
3944
+ y: applyToPoint34(transform, [
3945
+ pinout_label.pcb_port.x,
3946
+ pinout_label.pcb_port.y
3947
+ ])[1],
3948
+ aliases: pinout_label.aliases
3949
+ })).sort((a, b) => a.y - b.y);
3950
+ }
3858
3951
  if (edge_ports.length === 0) return;
3859
3952
  const board_edge_x = applyToPoint34(transform, [
3860
3953
  edge === "left" ? board_bounds.minX : board_bounds.maxX,
3861
3954
  0
3862
3955
  ])[0];
3863
3956
  const num_labels = edge_ports.length;
3864
- const middle_index = (num_labels - 1) / 2;
3957
+ const x_coords_counts = {};
3958
+ for (const pl of pinout_labels) {
3959
+ const rounded = pl.pcb_port.x.toFixed(1);
3960
+ x_coords_counts[rounded] = (x_coords_counts[rounded] || 0) + 1;
3961
+ }
3962
+ let main_group_pin_port_ids = /* @__PURE__ */ new Set();
3963
+ if (Object.keys(x_coords_counts).length > 1 && pinout_labels.length > 2) {
3964
+ const sorted_x_groups = Object.entries(x_coords_counts).sort(
3965
+ (a, b) => b[1] - a[1]
3966
+ );
3967
+ const primary_x = parseFloat(sorted_x_groups[0][0]);
3968
+ const primary_pins = pinout_labels.filter(
3969
+ (l) => Math.abs(l.pcb_port.x - primary_x) < 0.2
3970
+ );
3971
+ main_group_pin_port_ids = new Set(
3972
+ primary_pins.map((p) => p.pcb_port.pcb_port_id)
3973
+ );
3974
+ }
3975
+ const main_group_indices = edge_ports.map((ep, i) => {
3976
+ if (main_group_pin_port_ids.has(ep.pcb_port.pcb_port_id)) {
3977
+ return i;
3978
+ }
3979
+ return -1;
3980
+ }).filter((i) => i !== -1);
3981
+ const geometric_middle_index = (num_labels - 1) / 2;
3982
+ const scale10 = Math.abs(transform.a);
3983
+ const label_rect_height = LABEL_RECT_HEIGHT_MM * scale10;
3984
+ const label_pitch = LABEL_PITCH_MM * scale10;
3985
+ const label_margin = Math.max(0, label_pitch - label_rect_height);
3865
3986
  const stagger_offset_base = STAGGER_OFFSET_MIN + num_labels * STAGGER_OFFSET_PER_PIN;
3866
- const max_stagger_offset = stagger_offset_base + middle_index * STAGGER_OFFSET_STEP;
3987
+ const max_stagger_offset = stagger_offset_base + geometric_middle_index * STAGGER_OFFSET_STEP;
3867
3988
  const aligned_label_offset = max_stagger_offset + ALIGNED_OFFSET_MARGIN;
3868
- const total_labels_height = num_labels * LABEL_RECT_HEIGHT + Math.max(0, num_labels - 1) * LABEL_MARGIN;
3869
- let current_y = (svgHeight - total_labels_height) / 2 + LABEL_RECT_HEIGHT / 2;
3870
- edge_ports.forEach(({ port, label_info }, i) => {
3871
- const dist_from_middle = Math.abs(i - middle_index);
3872
- const stagger_rank = middle_index - dist_from_middle;
3989
+ const num_other_pins = num_labels - main_group_indices.length;
3990
+ const num_pins_to_stack = main_group_indices.length === 0 ? num_labels : num_other_pins;
3991
+ const stack_total_height = num_pins_to_stack * label_rect_height + Math.max(0, num_pins_to_stack - 1) * label_margin;
3992
+ let current_y;
3993
+ if (main_group_indices.length > 0 && num_other_pins > 0) {
3994
+ const main_group_y_coords = main_group_indices.map((i) => edge_ports[i].y);
3995
+ const min_main_group_y = Math.min(...main_group_y_coords);
3996
+ const max_main_group_y = Math.max(...main_group_y_coords);
3997
+ const main_group_top_extent = min_main_group_y - label_rect_height / 2;
3998
+ const main_group_bottom_extent = max_main_group_y + label_rect_height / 2;
3999
+ const other_pin_indices = edge_ports.map((_, index) => index).filter((index) => !main_group_indices.includes(index));
4000
+ const others_are_above = other_pin_indices[0] < main_group_indices[0];
4001
+ if (others_are_above) {
4002
+ const stack_bottom_edge = main_group_top_extent - label_margin;
4003
+ current_y = stack_bottom_edge - stack_total_height + label_rect_height / 2;
4004
+ } else {
4005
+ const stack_top_edge = main_group_bottom_extent + label_margin;
4006
+ current_y = stack_top_edge + label_rect_height / 2;
4007
+ }
4008
+ } else {
4009
+ current_y = (svgHeight - stack_total_height) / 2 + label_rect_height / 2;
4010
+ }
4011
+ const is_all_main_group = main_group_indices.length === num_labels;
4012
+ edge_ports.forEach(({ pcb_port, aliases }, i) => {
4013
+ let stagger_rank;
4014
+ if (main_group_indices.length > 0) {
4015
+ if (main_group_indices.includes(i)) {
4016
+ stagger_rank = geometric_middle_index;
4017
+ } else {
4018
+ const min_lg_idx = Math.min(...main_group_indices);
4019
+ const max_lg_idx = Math.max(...main_group_indices);
4020
+ let dist_from_main_group;
4021
+ if (i < min_lg_idx) {
4022
+ dist_from_main_group = min_lg_idx - i;
4023
+ } else {
4024
+ dist_from_main_group = i - max_lg_idx;
4025
+ }
4026
+ stagger_rank = geometric_middle_index - dist_from_main_group;
4027
+ }
4028
+ } else {
4029
+ const dist_from_middle = Math.abs(i - geometric_middle_index);
4030
+ stagger_rank = geometric_middle_index - dist_from_middle;
4031
+ }
3873
4032
  const stagger_offset = stagger_offset_base + stagger_rank * STAGGER_OFFSET_STEP;
3874
4033
  const sign = edge === "left" ? -1 : 1;
4034
+ const is_main_group_pin = main_group_indices.includes(i);
4035
+ const y_pos = is_all_main_group ? edge_ports[i].y : main_group_indices.length > 0 && is_main_group_pin ? edge_ports[i].y : current_y;
3875
4036
  const elbow_end = {
3876
4037
  x: board_edge_x + sign * stagger_offset,
3877
- y: current_y
4038
+ y: y_pos
3878
4039
  };
3879
4040
  const label_pos = {
3880
4041
  x: board_edge_x + sign * aligned_label_offset,
3881
- y: current_y
4042
+ y: y_pos
3882
4043
  };
3883
- label_positions.set(port.pcb_port_id, {
3884
- text: label_info.text,
3885
- aliases: label_info.aliases,
4044
+ label_positions.set(pcb_port.pcb_port_id, {
4045
+ text: aliases[0],
4046
+ aliases: aliases.slice(1),
3886
4047
  elbow_end,
3887
4048
  label_pos,
3888
4049
  edge
3889
4050
  });
3890
- current_y += LABEL_RECT_HEIGHT + LABEL_MARGIN;
3891
- });
3892
- }
3893
- function calculateHorizontalEdgeLabels(edge, ports, {
3894
- transform,
3895
- soup,
3896
- board_bounds,
3897
- svgWidth
3898
- }, label_positions) {
3899
- const edge_ports = ports.map((port) => ({
3900
- port,
3901
- x: applyToPoint34(transform, [port.x, port.y])[0],
3902
- label_info: getPortLabelInfo(port, soup)
3903
- })).filter((p) => p.label_info).sort((a, b) => a.x - b.x);
3904
- if (edge_ports.length === 0) return;
3905
- const board_edge_y = applyToPoint34(transform, [
3906
- 0,
3907
- edge === "top" ? board_bounds.maxY : board_bounds.minY
3908
- ])[1];
3909
- const labels_with_widths = edge_ports.map((p) => {
3910
- const label = [p.label_info.text, ...p.label_info.aliases].join(" | ");
3911
- const textWidth = label.length * FONT_SIZE * 0.6;
3912
- const rectWidth = textWidth + 2 * BG_PADDING;
3913
- return { ...p, rectWidth };
3914
- });
3915
- const num_labels = labels_with_widths.length;
3916
- const middle_index = (num_labels - 1) / 2;
3917
- const stagger_offset_base = STAGGER_OFFSET_MIN + num_labels * STAGGER_OFFSET_PER_PIN;
3918
- const max_stagger_offset = stagger_offset_base + middle_index * STAGGER_OFFSET_STEP;
3919
- const aligned_label_offset = max_stagger_offset + ALIGNED_OFFSET_MARGIN;
3920
- const total_labels_width = labels_with_widths.reduce((sum, l) => sum + l.rectWidth, 0) + Math.max(0, num_labels - 1) * LABEL_MARGIN;
3921
- let current_x = (svgWidth - total_labels_width) / 2;
3922
- labels_with_widths.forEach(({ port, label_info, rectWidth }, i) => {
3923
- const dist_from_middle = Math.abs(i - middle_index);
3924
- const stagger_rank = middle_index - dist_from_middle;
3925
- const stagger_offset = stagger_offset_base + stagger_rank * STAGGER_OFFSET_STEP;
3926
- const sign = edge === "top" ? -1 : 1;
3927
- const label_center_x = current_x + rectWidth / 2;
3928
- const elbow_end = {
3929
- x: label_center_x,
3930
- y: board_edge_y + sign * stagger_offset
3931
- };
3932
- const label_pos = {
3933
- x: label_center_x,
3934
- y: board_edge_y + sign * aligned_label_offset
3935
- };
3936
- label_positions.set(port.pcb_port_id, {
3937
- text: label_info.text,
3938
- aliases: label_info.aliases,
3939
- elbow_end,
3940
- label_pos,
3941
- edge
3942
- });
3943
- current_x += rectWidth + LABEL_MARGIN;
4051
+ if (!(main_group_indices.length > 0 && is_main_group_pin)) {
4052
+ current_y += label_rect_height + label_margin;
4053
+ }
3944
4054
  });
3945
4055
  }
3946
4056
  var calculateLabelPositions = ({
3947
- ports_by_edge,
4057
+ left_labels,
4058
+ right_labels,
3948
4059
  transform,
3949
4060
  soup,
3950
4061
  board_bounds,
@@ -3955,7 +4066,7 @@ var calculateLabelPositions = ({
3955
4066
  const shared_params = { transform, soup, board_bounds };
3956
4067
  calculateVerticalEdgeLabels(
3957
4068
  "left",
3958
- ports_by_edge.left,
4069
+ left_labels,
3959
4070
  {
3960
4071
  ...shared_params,
3961
4072
  svgHeight
@@ -3964,34 +4075,54 @@ var calculateLabelPositions = ({
3964
4075
  );
3965
4076
  calculateVerticalEdgeLabels(
3966
4077
  "right",
3967
- ports_by_edge.right,
4078
+ right_labels,
3968
4079
  {
3969
4080
  ...shared_params,
3970
4081
  svgHeight
3971
4082
  },
3972
4083
  label_positions
3973
4084
  );
3974
- calculateHorizontalEdgeLabels(
3975
- "top",
3976
- ports_by_edge.top,
3977
- {
3978
- ...shared_params,
3979
- svgWidth
3980
- },
3981
- label_positions
3982
- );
3983
- calculateHorizontalEdgeLabels(
3984
- "bottom",
3985
- ports_by_edge.bottom,
3986
- {
3987
- ...shared_params,
3988
- svgWidth
3989
- },
3990
- label_positions
3991
- );
3992
4085
  return label_positions;
3993
4086
  };
3994
4087
 
4088
+ // lib/pinout/pinout-utils.ts
4089
+ import { su as su7 } from "@tscircuit/circuit-json-util";
4090
+ function getPortLabelInfo(port, soup) {
4091
+ const source_port = su7(soup).source_port.get(port.source_port_id);
4092
+ if (!source_port) return null;
4093
+ const eligible_hints = source_port.port_hints?.filter(
4094
+ (h) => !/^\d+$/.test(h) && !["left", "right", "top", "bottom"].includes(h)
4095
+ ) ?? [];
4096
+ let label = eligible_hints[0];
4097
+ if (!label) label = source_port.name;
4098
+ if (!label) return null;
4099
+ const aliases = eligible_hints.filter((h) => h !== label);
4100
+ return { text: label, aliases };
4101
+ }
4102
+ function getClosestEdge(port_pos_real, board_bounds) {
4103
+ const dists = {
4104
+ left: port_pos_real.x - board_bounds.minX,
4105
+ right: board_bounds.maxX - port_pos_real.x,
4106
+ top: board_bounds.maxY - port_pos_real.y,
4107
+ bottom: port_pos_real.y - board_bounds.minY
4108
+ };
4109
+ let closest_edge = "left";
4110
+ let min_dist = dists.left;
4111
+ if (dists.right < min_dist) {
4112
+ min_dist = dists.right;
4113
+ closest_edge = "right";
4114
+ }
4115
+ if (dists.top < min_dist) {
4116
+ min_dist = dists.top;
4117
+ closest_edge = "top";
4118
+ }
4119
+ if (dists.bottom < min_dist) {
4120
+ min_dist = dists.bottom;
4121
+ closest_edge = "bottom";
4122
+ }
4123
+ return closest_edge;
4124
+ }
4125
+
3995
4126
  // lib/pinout/convert-circuit-json-to-pinout-svg.ts
3996
4127
  var OBJECT_ORDER3 = [
3997
4128
  "pcb_board",
@@ -4029,8 +4160,8 @@ function convertCircuitJsonToPinoutSvg(soup, options) {
4029
4160
  const padding = 20;
4030
4161
  const circuitWidth = maxX - minX + 2 * padding;
4031
4162
  const circuitHeight = maxY - minY + 2 * padding;
4032
- const svgWidth = options?.width ?? 800;
4033
- const svgHeight = options?.height ?? 600;
4163
+ const svgWidth = options?.width ?? 1200;
4164
+ const svgHeight = options?.height ?? 768;
4034
4165
  const scaleX = svgWidth / circuitWidth;
4035
4166
  const scaleY = svgHeight / circuitHeight;
4036
4167
  const scaleFactor = Math.min(scaleX, scaleY);
@@ -4047,18 +4178,45 @@ function convertCircuitJsonToPinoutSvg(soup, options) {
4047
4178
  const pinout_ports = soup.filter(
4048
4179
  (elm) => elm.type === "pcb_port" && elm.is_board_pinout
4049
4180
  );
4050
- const ports_by_edge = {
4051
- left: [],
4052
- right: [],
4053
- top: [],
4054
- bottom: []
4055
- };
4056
- for (const port of pinout_ports) {
4057
- const edge = getClosestEdge({ x: port.x, y: port.y }, board_bounds);
4058
- ports_by_edge[edge].push(port);
4181
+ const pinout_labels = [];
4182
+ for (const pcb_port of pinout_ports) {
4183
+ const label_info = getPortLabelInfo(pcb_port, soup);
4184
+ if (!label_info) continue;
4185
+ const edge = getClosestEdge({ x: pcb_port.x, y: pcb_port.y }, board_bounds);
4186
+ pinout_labels.push({
4187
+ pcb_port,
4188
+ aliases: [label_info.text, ...label_info.aliases],
4189
+ edge
4190
+ });
4191
+ }
4192
+ const left_labels = pinout_labels.filter((p) => p.edge === "left");
4193
+ const right_labels = pinout_labels.filter((p) => p.edge === "right");
4194
+ const top_labels = pinout_labels.filter((p) => p.edge === "top");
4195
+ const bottom_labels = pinout_labels.filter((p) => p.edge === "bottom");
4196
+ const boardCenterX = (minX + maxX) / 2;
4197
+ if (top_labels.length > 0) {
4198
+ const top_left_count = top_labels.filter(
4199
+ (p) => p.pcb_port.x < boardCenterX
4200
+ ).length;
4201
+ if (top_left_count > top_labels.length / 2) {
4202
+ left_labels.push(...top_labels);
4203
+ } else {
4204
+ right_labels.push(...top_labels);
4205
+ }
4206
+ }
4207
+ if (bottom_labels.length > 0) {
4208
+ const bottom_left_count = bottom_labels.filter(
4209
+ (p) => p.pcb_port.x < boardCenterX
4210
+ ).length;
4211
+ if (bottom_left_count > bottom_labels.length / 2) {
4212
+ left_labels.push(...bottom_labels);
4213
+ } else {
4214
+ right_labels.push(...bottom_labels);
4215
+ }
4059
4216
  }
4060
4217
  const label_positions = calculateLabelPositions({
4061
- ports_by_edge,
4218
+ left_labels,
4219
+ right_labels,
4062
4220
  transform,
4063
4221
  soup,
4064
4222
  board_bounds,
@@ -5576,7 +5734,7 @@ function getSchematicBoundsFromCircuitJson(soup, padding = 0.5) {
5576
5734
  }
5577
5735
 
5578
5736
  // lib/sch/svg-object-fns/create-svg-objects-from-sch-component-with-symbol.ts
5579
- import { su as su8 } from "@tscircuit/circuit-json-util";
5737
+ import { su as su9 } from "@tscircuit/circuit-json-util";
5580
5738
  import { symbols } from "schematic-symbols";
5581
5739
  import "svgson";
5582
5740
  import {
@@ -5737,10 +5895,10 @@ var createSvgObjectsFromSchematicComponentWithSymbol = ({
5737
5895
  })
5738
5896
  ];
5739
5897
  }
5740
- const schPorts = su8(circuitJson).schematic_port.list({
5898
+ const schPorts = su9(circuitJson).schematic_port.list({
5741
5899
  schematic_component_id: schComponent.schematic_component_id
5742
5900
  });
5743
- const srcComponent = su8(circuitJson).source_component.get(
5901
+ const srcComponent = su9(circuitJson).source_component.get(
5744
5902
  schComponent.source_component_id
5745
5903
  );
5746
5904
  const schPortsWithSymbolPorts = matchSchPortsToSymbolPorts({
@@ -5948,7 +6106,7 @@ var createSvgObjectsFromSchematicComponentWithSymbol = ({
5948
6106
  };
5949
6107
 
5950
6108
  // lib/sch/svg-object-fns/create-svg-objects-from-sch-component-with-box.ts
5951
- import { su as su11 } from "@tscircuit/circuit-json-util";
6109
+ import { su as su12 } from "@tscircuit/circuit-json-util";
5952
6110
  import "schematic-symbols";
5953
6111
  import "svgson";
5954
6112
  import { applyToPoint as applyToPoint45 } from "transformation-matrix";
@@ -5959,7 +6117,7 @@ import "@tscircuit/circuit-json-util";
5959
6117
 
5960
6118
  // lib/sch/svg-object-fns/create-svg-objects-for-sch-port-box-line.ts
5961
6119
  import { applyToPoint as applyToPoint40 } from "transformation-matrix";
5962
- import { su as su9 } from "@tscircuit/circuit-json-util";
6120
+ import { su as su10 } from "@tscircuit/circuit-json-util";
5963
6121
  var PIN_CIRCLE_RADIUS_MM = 0.02;
5964
6122
  var createArrow = (tip, angle, size, color, strokeWidth) => {
5965
6123
  const arrowAngle = Math.PI / 6;
@@ -5991,7 +6149,7 @@ var createSvgObjectsForSchPortBoxLine = ({
5991
6149
  circuitJson
5992
6150
  }) => {
5993
6151
  const svgObjects = [];
5994
- const srcPort = su9(circuitJson).source_port.get(schPort.source_port_id);
6152
+ const srcPort = su10(circuitJson).source_port.get(schPort.source_port_id);
5995
6153
  const realEdgePos = {
5996
6154
  x: schPort.center.x,
5997
6155
  y: schPort.center.y
@@ -6392,7 +6550,7 @@ var createSvgObjectsFromSchematicComponentWithBox = ({
6392
6550
  },
6393
6551
  children: []
6394
6552
  });
6395
- const schTexts = su11(circuitJson).schematic_text.list();
6553
+ const schTexts = su12(circuitJson).schematic_text.list();
6396
6554
  for (const schText of schTexts) {
6397
6555
  if (schText.schematic_component_id === schComponent.schematic_component_id) {
6398
6556
  svgObjects.push(
@@ -6404,7 +6562,7 @@ var createSvgObjectsFromSchematicComponentWithBox = ({
6404
6562
  );
6405
6563
  }
6406
6564
  }
6407
- const schematicPorts = su11(circuitJson).schematic_port.list({
6565
+ const schematicPorts = su12(circuitJson).schematic_port.list({
6408
6566
  schematic_component_id: schComponent.schematic_component_id
6409
6567
  });
6410
6568
  for (const schPort of schematicPorts) {
@@ -7417,7 +7575,7 @@ var createSvgObjectsFromSchematicTable = ({
7417
7575
  };
7418
7576
 
7419
7577
  // lib/sch/svg-object-fns/create-svg-objects-for-sch-port-hover.ts
7420
- import { su as su12 } from "@tscircuit/circuit-json-util";
7578
+ import { su as su13 } from "@tscircuit/circuit-json-util";
7421
7579
  import { applyToPoint as applyToPoint53 } from "transformation-matrix";
7422
7580
  var PIN_CIRCLE_RADIUS_MM2 = 0.02;
7423
7581
  var createSvgObjectsForSchPortHover = ({
@@ -7458,7 +7616,7 @@ var createSvgObjectsForSchComponentPortHovers = ({
7458
7616
  transform,
7459
7617
  circuitJson
7460
7618
  }) => {
7461
- const schematicPorts = su12(circuitJson).schematic_port.list({
7619
+ const schematicPorts = su13(circuitJson).schematic_port.list({
7462
7620
  schematic_component_id: component.schematic_component_id
7463
7621
  });
7464
7622
  const svgs = [];