cirCeTZ icon indicating copy to clipboard operation
cirCeTZ copied to clipboard

add some other mantainer

Open Kreijstal opened this issue 8 months ago • 10 comments

You should probably nominate someone else to mantain this, I'm not talking about me but about someone who wrote a PR if they decide they want the role

Kreijstal avatar Mar 30 '25 11:03 Kreijstal

Hi @Kreijstal,

I'm currently taking over and expanding on the original work by @fenjalien to publish it soon on Typst Universe. The updated repository is available here:

https://github.com/l0uisgrange/zap

The original repository hasn't been updated for about two years, before Typst Universe even launched. I initially wanted to contribute directly, but given the inactivity, I decided to start from there.

Of course, you are very welcome to join and contribute if interested — it would be a pleasure to collaborate!

l0uisgrange avatar Apr 26 '25 09:04 l0uisgrange

tbh I found circetz not to my liking im using a custom one.

`components.typ`

#import "@preview/cetz:0.3.4"
#set page(width: auto, height: auto, margin: 8pt)

#let connect-orthogonal(start_anchor, end_anchor, style: "hv", ..styling) = {
  assert(style in ("hv", "vh"), message: "Style must be 'hv' or 'vh'.")
  let corner = if style == "hv" { (start_anchor, "|-", end_anchor) } else { (start_anchor, "-|", end_anchor) }
  cetz.draw.line(start_anchor, corner, end_anchor, ..styling)
}

#let _draw_label(label, label_pos, label_offset, label_anchor, label_size, text_fill: black) = {
  if label != none {
    cetz.draw.content(
      (rel: label_offset, to: label_pos),
      text(size: label_size, fill: text_fill, label),
      anchor: label_anchor,
    )
  }
}

#let _define_anchors(anchors) = {
  for (name, pos) in anchors { cetz.draw.anchor(name, pos) }
}

#let _base_component(
  position,
  name,
  scale: 1.0,
  rotate: 0deg,
  draw_content_func,
  anchor_definitions,
  label: none,
  label_pos: "center",
  label_anchor: "center",
  label_offset: (0, 0),
  label_size: 8pt,
  text_fill: black,
  ..styling,
) = {
  cetz.draw.group(
    name: name,
    ..styling,
    {
      cetz.draw.set-origin(position)
      cetz.draw.scale(scale)
      cetz.draw.rotate(rotate)
      draw_content_func(..styling)
      _define_anchors(anchor_definitions)
      _draw_label(label, label_pos, label_offset, label_anchor, label_size, text_fill: text_fill)
    },
  )
}

#let _transistor(
  is_pmos: false,
  position,
  name,
  label: none,
  label_pos: auto,
  label_anchor: auto,
  label_offset: (0.05, 0.05),
  label_size: 8pt,
  show_pin_labels: false,
  pin_label_size: 7pt,
  show_gate_bubble: auto,
  bubble_radius_factor: 0.08,
  scale: 1.0,
  rotate: 0deg,
  width: 0.9,
  height: 1.2,
  gate_lead_factor: 0.3,
  bulk_lead_factor: 0.3,
  gate_pos_factor: 0.3,
  channel_pos_factor: 0.4,
  gate_v_extent_factor: 0.35,
  channel_v_extent_factor: 0.35,
  thick_factor: 2.0,
  arrow_scale: 0.8,
  arrow_fill: black,
  ..styling,
) = {
  let final_label_pos = if label_pos == auto { if is_pmos { "S" } else { "D" } } else { label_pos }
  let final_label_anchor = if label_anchor == auto { if is_pmos { "south-west" } else { "north-west" } } else {
    label_anchor
  }
  let final_show_gate_bubble = if show_gate_bubble == auto { is_pmos } else { show_gate_bubble }

  cetz.draw.group(
    name: name,
    ..styling,
    {
      cetz.draw.set-origin(position)
      cetz.draw.scale(scale)
      cetz.draw.rotate(rotate)

      let center_y = height / 2
      let gate_line_x = gate_pos_factor * width
      let channel_line_x = channel_pos_factor * width
      let gate_v_extent = height * gate_v_extent_factor
      let channel_v_extent = height * channel_v_extent_factor
      let drain_term_rel = (width, if is_pmos { 0 } else { height })
      let source_term_rel = (width, if is_pmos { height } else { 0 })
      let gate_term_rel = (-gate_lead_factor * width, center_y)
      let bulk_term_rel = (width + bulk_lead_factor * width, center_y)
      let gate_line_top = (gate_line_x, center_y + gate_v_extent)
      let gate_line_bottom = (gate_line_x, center_y - gate_v_extent)
      let gate_conn_pt = (gate_line_x, center_y)
      let channel_top = (channel_line_x, center_y + channel_v_extent)
      let channel_bottom = (channel_line_x, center_y - channel_v_extent)
      let bulk_conn_pt = (channel_line_x, center_y)
      let horiz_top = (width, center_y + channel_v_extent)
      let horiz_bottom = (width, center_y - channel_v_extent)
      let line_thickness = 0.6pt * thick_factor

      _define_anchors((
        ("G", gate_term_rel),
        ("D", drain_term_rel),
        ("S", source_term_rel),
        ("B", bulk_term_rel),
        ("center", (width / 2, height / 2)),
        ("bulk_conn", bulk_conn_pt),
        ("gate_conn", gate_conn_pt),
        ("north", (width / 2, height)),
        ("south", (width / 2, 0)),
        ("east", (width, height / 2)),
        ("west", (0, height / 2)),
        ("north-east", (width, height)),
        ("south-west", (0, 0)),
        ("south-east", (width, 0)),
        ("default", gate_term_rel),
      ))

      cetz.draw.line(channel_bottom, channel_top, ..styling, thickness: line_thickness)
      cetz.draw.line(gate_line_bottom, gate_line_top, ..styling, thickness: line_thickness)
      cetz.draw.line(gate_term_rel, gate_conn_pt, ..styling)
      cetz.draw.line(bulk_conn_pt, bulk_term_rel, ..styling)

      if is_pmos {
        cetz.draw.line(drain_term_rel, horiz_bottom, ..styling)
        cetz.draw.line(horiz_bottom, channel_bottom, ..styling)
        cetz.draw.line(source_term_rel, horiz_top, ..styling)
        cetz.draw.line(horiz_top, channel_top, ..styling, mark: (end: "stealth", fill: arrow_fill, scale: arrow_scale))
        if final_show_gate_bubble {
          let bubble_radius = height * bubble_radius_factor
          cetz.draw.circle((rel: (-bubble_radius*width/calc.abs(width), 0), to: "gate_conn"), radius: bubble_radius, ..styling, fill: white)
        }
      } else {
        cetz.draw.line(drain_term_rel, horiz_top, ..styling)
        cetz.draw.line(horiz_top, channel_top, ..styling)
        cetz.draw.line(horiz_bottom, source_term_rel, ..styling)
        cetz.draw.line(
          channel_bottom,
          horiz_bottom,
          ..styling,
          mark: (end: "stealth", fill: arrow_fill, scale: arrow_scale),
        )
      }

      if show_pin_labels {
        cetz.draw.content((rel: (-0.05, 0), to: "G"), text(size: pin_label_size, $G$), anchor: "east")
        cetz.draw.content(
          (rel: (0.05, 0), to: "S"),
          text(size: pin_label_size, $S$),
          anchor: if is_pmos { "west" } else { "south" },
        )
        cetz.draw.content(
          (rel: (0.05, 0), to: "D"),
          text(size: pin_label_size, $D$),
          anchor: if is_pmos { "west" } else { "north" },
        )
        cetz.draw.content((rel: (0.05, 0), to: "B"), text(size: pin_label_size, $B$), anchor: "west")
      }
      _draw_label(label, final_label_pos, label_offset, final_label_anchor, label_size)
    },
  )
}

#let nmos_transistor(..args) = _transistor(is_pmos: false, ..args)
#let pmos_transistor(..args) = _transistor(is_pmos: true, ..args)

#let gnd_symbol(
  position,
  name,
  label: none,
  label_pos: "north",
  label_anchor: "south",
  label_offset: (0, 0.1),
  label_size: 8pt,
  scale: 1.0,
  rotate: 0deg,
  lead_length: 0.3,
  bar_width: 0.5,
  bar_spacing: 0.05,
  bar_width_factors: (1.0, 0.7, 0.4),
  ..styling,
) = {
  assert(bar_width_factors.len() == 3, message: "bar_width_factors must have 3 elements.")
  let draw_func(..styling) = {
    let y_coords = (-lead_length, -lead_length - bar_spacing, -lead_length - 2 * bar_spacing)
    cetz.draw.line((0, 0), (0, -lead_length), ..styling)
    for (idx, y) in y_coords.enumerate() {
      let half_w = bar_width * bar_width_factors.at(idx) / 2
      cetz.draw.line((-half_w, y), (half_w, y), ..styling)
    }
  }
  let south_y = -lead_length - 2 * bar_spacing
  let anchors = (
    ("T", (0, 0)),
    ("north", (0, 0)),
    ("south", (0, south_y)),
    ("west", (-bar_width * bar_width_factors.at(0) / 2, -lead_length)),
    ("east", (bar_width * bar_width_factors.at(0) / 2, -lead_length)),
    ("center", (0, (-lead_length + south_y) / 2)),
    ("default", (0, 0)),
  )
  _base_component(
    position,
    name,
    scale: scale,
    rotate: rotate,
    draw_func,
    anchors,
    label: label,
    label_pos: label_pos,
    label_anchor: label_anchor,
    label_offset: label_offset,
    label_size: label_size,
    ..styling,
  )
}

#let resistor(
  position,
  name,
  label: none,
  label_pos: "south",
  label_anchor: "north",
  label_offset: (0, -0.1),
  label_size: 8pt,
  scale: 1.0,
  rotate: 0deg,
  width: 0.8,
  height: 0.3,
  zigs: 3,
  lead_extension: 0.3,
  ..styling,
) = {
  let draw_func(..styling) = {
    let hw = width / 2
    let hh = height / 2
    let lead_start_x = -hw - lead_extension
    let lead_end_x = hw + lead_extension
    let zig_start_x = -hw
    let num_segments = zigs * 2
    let seg_h = width / num_segments
    let sgn = 1
    cetz.draw.line(
      (lead_start_x, 0),
      (zig_start_x, 0),
      (rel: (seg_h / 2, hh * sgn)),
      ..for _ in range(num_segments - 1) {
        sgn *= -1
        ((rel: (seg_h, hh * 2 * sgn)),)
      },
      (rel: (seg_h / 2, hh)),
      (lead_end_x, 0),
      ..styling,
    )
  }
  let hw = width / 2
  let hh = height / 2
  let lead_start_x = -hw - lead_extension
  let lead_end_x = hw + lead_extension
  let anchors = (
    ("L", (lead_start_x, 0)),
    ("R", (lead_end_x, 0)),
    ("center", (0, 0)),
    ("north", (0, hh)),
    ("south", (0, -hh)),
    ("east", (lead_end_x, 0)),
    ("west", (lead_start_x, 0)),
    ("T", (0, hh)),
    ("B", (0, -hh)),
    ("default", (lead_start_x, 0)),
  )
  _base_component(
    position,
    name,
    scale: scale,
    rotate: rotate,
    draw_func,
    anchors,
    label: label,
    label_pos: label_pos,
    label_anchor: label_anchor,
    label_offset: label_offset,
    label_size: label_size,
    ..styling,
  )
}

#let capacitor(
  position,
  name,
  label: none,
  label_pos: "south",
  label_anchor: "north",
  label_offset: (0, -0.1),
  label_size: 8pt,
  scale: 1.0,
  rotate: 0deg,
  plate_height: 0.6,
  plate_gap: 0.2,
  lead_extension: 0.55,
  ..styling,
) = {
  let draw_func(..styling) = {
    let hg = plate_gap / 2
    let hh = plate_height / 2
    let lead_start_x = -hg - lead_extension
    let lead_end_x = hg + lead_extension
    let plate_left_x = -hg
    let plate_right_x = hg
    cetz.draw.line((lead_start_x, 0), (plate_left_x, 0), ..styling)
    cetz.draw.line((plate_left_x, -hh), (plate_left_x, hh), ..styling)
    cetz.draw.line((plate_right_x, hh), (plate_right_x, -hh), ..styling)
    cetz.draw.line((plate_right_x, 0), (lead_end_x, 0), ..styling)
  }
  let hg = plate_gap / 2
  let hh = plate_height / 2
  let lead_start_x = -hg - lead_extension
  let lead_end_x = hg + lead_extension
  let anchors = (
    ("L", (lead_start_x, 0)),
    ("R", (lead_end_x, 0)),
    ("center", (0, 0)),
    ("north", (0, hh)),
    ("south", (0, -hh)),
    ("east", (lead_end_x, 0)),
    ("west", (lead_start_x, 0)),
    ("T", (0, hh)),
    ("B", (0, -hh)),
    ("default", (lead_start_x, 0)),
  )
  _base_component(
    position,
    name,
    scale: scale,
    rotate: rotate,
    draw_func,
    anchors,
    label: label,
    label_pos: label_pos,
    label_anchor: label_anchor,
    label_offset: label_offset,
    label_size: label_size,
    ..styling,
  )
}

#let voltage_source(
  position,
  name,
  label: none,
  annotation_label_pos: "left",
  annotation_label_anchor: auto,
  annotation_label_offset: auto,
  annotation_label_size: 8pt,
  show_voltage_annotation: true,
  voltage_arrow_pos: "left",
  voltage_arrow_dir: "down",
  voltage_arrow_length_factor: 2,
  voltage_arrow_offset_factor: 0.7,
  arrow_scale: 1.0,
  arrow_fill: black,
  stroke_thickness: 0.6pt,
  scale: 1.0,
  rotate: 0deg,
  radius: 0.3,
  lead_length: 0.3,
  ..styling,
) = {
  let draw_func(..styling) = {
    let top_y = radius
    let bottom_y = -radius
    let top_lead_y = top_y + lead_length
    let bottom_lead_y = bottom_y - lead_length
    cetz.draw.circle((0, 0), radius: radius, ..styling)
    cetz.draw.line((0, top_y), (0, top_lead_y), ..styling)
    cetz.draw.line((0, bottom_y), (0, bottom_lead_y), ..styling)
    if show_voltage_annotation {
      let arrow_x = if voltage_arrow_pos == "left" { -radius * (1 + voltage_arrow_offset_factor) } else {
        radius * (1 + voltage_arrow_offset_factor)
      }
      let arrow_len = radius * voltage_arrow_length_factor
      let arrow_half_len = arrow_len / 2
      let (arrow_start_y, arrow_end_y) = if voltage_arrow_dir == "down" { (arrow_half_len, -arrow_half_len) } else {
        (-arrow_half_len, arrow_half_len)
      }
      cetz.draw.line(
        (arrow_x, arrow_start_y),
        (arrow_x, arrow_end_y),
        ..styling,
        mark: (
          end: "stealth",
          scale: arrow_scale * 0.4,
          fill: arrow_fill,
          stroke: (paint: black, thickness: stroke_thickness),
        ),
      )
      if label != none {
        let (default_anchor, default_offset) = if annotation_label_pos == "left" { ("east", (-0.05, 0)) } else {
          ("west", (0.05, 0))
        }
        let final_anchor = if annotation_label_anchor == auto { default_anchor } else { annotation_label_anchor }
        let final_offset = if annotation_label_offset == auto { default_offset } else { annotation_label_offset }
        cetz.draw.content(
          (rel: (0.1 * scale * arrow_x / calc.abs(arrow_x), 0), to: (arrow_x, 0)),
          text(size: annotation_label_size, fill: arrow_fill, label),
          anchor: final_anchor,
          offset: final_offset,
        )
      }
    }
  }
  let top_lead_y = radius + lead_length
  let bottom_lead_y = -radius - lead_length
  let anchors = (
    ("T", (0, top_lead_y)),
    ("B", (0, bottom_lead_y)),
    ("center", (0, 0)),
    ("north", (0, top_lead_y)),
    ("south", (0, bottom_lead_y)),
    ("east", (radius, 0)),
    ("west", (-radius, 0)),
    ("default", (0, top_lead_y)),
  )
  _base_component(
    position,
    name,
    scale: scale,
    rotate: rotate,
    draw_func,
    anchors,
    label: none,
    ..styling,
  )
}

#let node(
  position,
  name,
  label: none,
  radius: 0.05,
  label_size: 8pt,
  label_offset: (0, 0),
  label_anchor: "center",
  fill: white,
  text_fill: black,
  scale: 1.0,
  rotate: 0deg,
  ..styling,
) = {
  let draw_func(..styling) = {
    cetz.draw.circle((0, 0), radius: radius, ..styling, fill: fill)
  }
  let diag_offset = radius * calc.cos(45deg)
  let anchors = (
    ("center", (0, 0)),
    ("default", (0, 0)),
    ("north", (0, radius)),
    ("south", (0, -radius)),
    ("east", (radius, 0)),
    ("west", (-radius, 0)),
    ("north-east", (diag_offset, diag_offset)),
    ("north-west", (-diag_offset, diag_offset)),
    ("south-east", (diag_offset, -diag_offset)),
    ("south-west", (-diag_offset, -diag_offset)),
  )
  _base_component(
    position,
    name,
    scale: scale,
    rotate: rotate,
    draw_func,
    anchors,
    label: label,
    label_pos: "center",
    label_anchor: label_anchor,
    label_offset: label_offset,
    label_size: label_size,
    text_fill: text_fill,
    ..styling,
  )
}

#let vdd_symbol(
  position,
  name,
  label: none,
  label_pos: "north",
  label_anchor: "south",
  label_offset: (0, 0.1),
  label_size: 8pt,
  scale: 1.0,
  rotate: 0deg,
  stem_length: 0.3,
  bar_width: 0.5,
  text_fill: black,
  ..styling,
) = {
  let draw_func(..styling) = {
    let stem_top_y = stem_length
    let bar_half_width = bar_width / 2
    cetz.draw.line((0, 0), (0, stem_top_y), ..styling)
    cetz.draw.line((-bar_half_width, stem_top_y), (bar_half_width, stem_top_y), ..styling)
  }
  let stem_top_y = stem_length
  let bar_half_width = bar_width / 2
  let anchors = (
    ("B", (0, 0)),
    ("south", (0, 0)),
    ("default", (0, 0)),
    ("T", (0, stem_top_y)),
    ("north", (0, stem_top_y)),
    ("TL", (-bar_half_width, stem_top_y)),
    ("TR", (bar_half_width, stem_top_y)),
    ("west", (-bar_half_width, stem_top_y)),
    ("east", (bar_half_width, stem_top_y)),
    ("center", (0, stem_top_y / 2)),
  )
  _base_component(
    position,
    name,
    scale: scale,
    rotate: rotate,
    draw_func,
    anchors,
    label: label,
    label_pos: label_pos,
    label_anchor: label_anchor,
    label_offset: label_offset,
    label_size: label_size,
    text_fill: text_fill,
    ..styling,
  )
}

#let current_source(
  position,
  name,
  label: none,
  label_pos: "west",
  label_anchor: "west",
  label_offset: (0.1, 0),
  label_size: 8pt,
  scale: 1.0,
  rotate: 0deg,
  radius: 0.3,
  lead_length: 0.3,
  arrow_dir: "up",
  arrow_scale: 1.0,
  arrow_fill: black,
  ..styling,
) = {
  let draw_func(..styling) = {
    let top_y = radius
    let bottom_y = -radius
    let top_lead_y = top_y + lead_length
    let bottom_lead_y = bottom_y - lead_length
    cetz.draw.circle((0, 0), radius: radius, ..styling)
    cetz.draw.line((0, top_y), (0, top_lead_y), ..styling)
    cetz.draw.line((0, bottom_y), (0, bottom_lead_y), ..styling)
    let arrow_v_extent = radius * 0.7
    assert(arrow_dir in ("up", "down"), message: "Arrow direction must be 'up' or 'down'.")
    let (arrow_start_y, arrow_end_y) = if arrow_dir == "up" { (-arrow_v_extent, arrow_v_extent) } else {
      (arrow_v_extent, -arrow_v_extent)
    }
    cetz.draw.line(
      (0, arrow_start_y),
      (0, arrow_end_y),
      ..styling,
      mark: (end: "stealth", scale: arrow_scale * 0.4, fill: arrow_fill),
    )
  }
  let top_lead_y = radius + lead_length
  let bottom_lead_y = -radius - lead_length
  let anchors = (
    ("T", (0, top_lead_y)),
    ("B", (0, bottom_lead_y)),
    ("center", (0, 0)),
    ("north", (0, top_lead_y)),
    ("south", (0, bottom_lead_y)),
    ("east", (radius, 0)),
    ("west", (-radius, 0)),
    ("default", (0, top_lead_y)),
  )
  _base_component(
    position,
    name,
    scale: scale,
    rotate: rotate,
    draw_func,
    anchors,
    label: label,
    label_pos: label_pos,
    label_anchor: label_anchor,
    label_offset: label_offset,
    label_size: label_size,
    ..styling,
  )
}

#let voltage_points(
  position,
  name,
  label: none,
  annotation_label_pos: "left",
  annotation_label_anchor: auto,
  annotation_label_offset: auto,
  annotation_label_size: 8pt,
  annotation_text_fill: black,
  show_voltage_annotation: true,
  voltage_arrow_pos: "left",
  voltage_arrow_dir: "down",
  arrow_length_factor: 1.0,
  arrow_offset: 0.3,
  arrow_scale: 1.0,
  arrow_fill: black,
  arrow_stroke: black,
  arrow_stroke_thickness: 0.6pt,
  point_separation: 0.6,
  point_radius: 0.05,
  point_fill: black,
  point_stroke: none,
  show_point_labels: false,
  top_label: $[+]$,
  bottom_label: $[-]$,
  point_label_size: 7pt,
  point_text_fill: black,
  top_label_offset: (0, 0.05),
  top_label_anchor: "south",
  bottom_label_offset: (0, -0.05),
  bottom_label_anchor: "north",
  scale: 1.0,
  rotate: 0deg,
  ..styling,
) = {
  let draw_func(..styling) = {
    let half_sep = point_separation / 2
    let top_pos = (0, half_sep)
    let bottom_pos = (0, -half_sep)

    cetz.draw.circle(top_pos, radius: point_radius, fill: point_fill, stroke: point_stroke, ..styling)
    cetz.draw.circle(bottom_pos, radius: point_radius, fill: point_fill, stroke: point_stroke, ..styling)

    if show_voltage_annotation {
      let arrow_x = if voltage_arrow_pos == "left" { -arrow_offset } else { arrow_offset }
      let arrow_len = point_separation * arrow_length_factor
      let arrow_half_len = arrow_len / 2
      let (arrow_start_y, arrow_end_y) = if voltage_arrow_dir == "down" {
        (arrow_half_len, -arrow_half_len)
      } else {
        (-arrow_half_len, arrow_half_len)
      }
      cetz.draw.line(
        (arrow_x, arrow_start_y),
        (arrow_x, arrow_end_y),
        stroke: (paint: arrow_stroke, thickness: arrow_stroke_thickness),
        ..styling,
        mark: (
          end: "stealth",
          scale: arrow_scale * 0.4,
          fill: arrow_fill,
          stroke: (paint: arrow_stroke, thickness: arrow_stroke_thickness),
        ),
      )
      if label != none {
        let (default_anchor, default_offset) = if annotation_label_pos == "left" { ("east", (-0.05, 0)) } else {
          ("west", (0.05, 0))
        }
        let final_anchor = if annotation_label_anchor == auto { default_anchor } else { annotation_label_anchor }
        let final_offset = if annotation_label_offset == auto { default_offset } else { annotation_label_offset }
        cetz.draw.content(
          (arrow_x, 0),
          text(size: annotation_label_size, fill: annotation_text_fill, label),
          anchor: final_anchor,
          offset: final_offset,
        )
      }
    }
    
    if show_point_labels {
      _draw_label(top_label, (0, half_sep), top_label_offset, top_label_anchor, point_label_size, text_fill: point_text_fill)
      _draw_label(bottom_label, (0, -half_sep), bottom_label_offset, bottom_label_anchor, point_label_size, text_fill: point_text_fill)
    }
  }

  let half_sep = point_separation / 2
  let anchors = (
    ("T", (0, half_sep)),
    ("B", (0, -half_sep)),
    ("center", (0, 0)),
    ("north", (0, half_sep)),
    ("south", (0, -half_sep)),
    ("east", (point_radius, 0)),
    ("west", (-point_radius, 0)),
    ("default", (0, half_sep)),
  )

  _base_component(
    position,
    name,
    scale: scale,
    rotate: rotate,
    draw_func,
    anchors,
    label: none,
    ..styling,
  )
}



#let wire_hop(
  wire1_start, wire1_end,
  wire2_start, wire2_end,
  name: none,
  hopping_wire: 1,
  hop_radius: 0.15,
  hop_direction: 1, 
  ..styling
) = {
  assert(hopping_wire in (1, 2), message: "hopping_wire must be 1 or 2.")
  assert(hop_direction in (1, -1), message: "hop_direction must be 1 or -1.")
  
  cetz.draw.group(name: name, {
    cetz.draw.get-ctx(ctx=>{
      let a1=cetz.coordinate.resolve(ctx,wire1_start).at(1)
      let a2=cetz.coordinate.resolve(ctx,wire1_end).at(1)
      let b1=cetz.coordinate.resolve(ctx,wire2_start).at(1)
      let b2=cetz.coordinate.resolve(ctx,wire2_end).at(1)
      let intersection = cetz.intersection.line-line(
        a1, a2, 
        b1, b2, 
      )
      assert(intersection != none, message: "Wires do not intersect, cannot hop.")

      let (straight_start, straight_end, hopping_start, hopping_end) = if hopping_wire == 1 {
        (b1, b2, a1, a2)
      } else {
        (a1, a2, b1, b2)
      }

      cetz.draw.line(straight_start, straight_end, ..styling)

      let hop_vec = cetz.vector.sub(hopping_end, hopping_start)
      let wire_angle = calc.atan2(hop_vec.at(0), hop_vec.at(1))
      let hop_unit_vec = cetz.vector.norm(hop_vec)
      assert(hop_unit_vec != none, message: "Cannot get unit vector for zero-length hopping wire.")

      let offset_vec_neg = cetz.vector.scale(hop_unit_vec, -hop_radius)
      let offset_vec_pos = cetz.vector.scale(hop_unit_vec, hop_radius)

      let arc_start_point = (rel: offset_vec_neg, to: intersection)
      let arc_end_point = (rel: offset_vec_pos, to: intersection)

      let arc_start_angle = wire_angle
      let arc_stop_angle = wire_angle + (180deg * hop_direction)

      cetz.draw.line(hopping_start, arc_start_point, ..styling)

      cetz.draw.arc(
        (rel: cetz.vector.scale((calc.cos(arc_start_angle), calc.sin(arc_start_angle)), hop_radius*2), to: arc_start_point),
        start: arc_start_angle,
        stop: arc_stop_angle,
        radius: hop_radius,
        ..styling
      )

      cetz.draw.line(arc_end_point, hopping_end, ..styling)

      // Define anchors
      let diag_offset = hop_radius * calc.cos(45deg)
      let anchors = (
        ("wire1_start", a1),
        ("wire1_end", a2),
        ("wire2_start", b1),
        ("wire2_end", b2),
        ("intersection", intersection),
        ("center", intersection),
        ("north", (intersection.at(0), intersection.at(1) + hop_radius)),
        ("south", (intersection.at(0), intersection.at(1) - hop_radius)),
        ("east", (intersection.at(0) + hop_radius, intersection.at(1))),
        ("west", (intersection.at(0) - hop_radius, intersection.at(1))),
        ("north-east", (intersection.at(0) + diag_offset, intersection.at(1) + diag_offset)),
        ("north-west", (intersection.at(0) - diag_offset, intersection.at(1) + diag_offset)),
        ("south-east", (intersection.at(0) + diag_offset, intersection.at(1) - diag_offset)),
        ("south-west", (intersection.at(0) - diag_offset, intersection.at(1) - diag_offset)),
        ("default", intersection),
      )
      _define_anchors(anchors)
    })
  })
}




#let capacitor_between(
  start_point, 
  end_point,   
  name,        
  scale: 1.0, 
  plate_height: 0.6, 
  plate_gap: 0.2, 
  ..capparams,
) = {

  cetz.draw.get-ctx(ctx => {

    let start_coord = cetz.coordinate.resolve(ctx, start_point).at(1)
    let end_coord = cetz.coordinate.resolve(ctx, end_point).at(1)

    let dx = end_coord.at(0) - start_coord.at(0)
    let dy = end_coord.at(1) - start_coord.at(1)
    let distance = calc.sqrt(dx*dx + dy*dy)

    let angle = calc.atan2(dx, dy)

    let scaled_plate_gap = plate_gap * scale
    let min_distance = scaled_plate_gap

    assert(
      distance > min_distance,
      message: "Distance between start and end points (" + repr(distance) + ") must be greater than the scaled plate gap (" + repr(min_distance) + "). Reduce scale or increase distance.",
    )

    let calculated_lead_extension = (distance / scale - plate_gap) / 2

    assert(   calculated_lead_extension >= 0,
        message: "Internal error: Calculated lead extension is negative. Check scale and distance (" + repr(calculated_lead_extension) +").",
    )

    let mid_x = (start_coord.at(0) + end_coord.at(0)) / 2
    let mid_y = (start_coord.at(1) + end_coord.at(1)) / 2
    let calculated_position = (mid_x, mid_y)

    capacitor(
      calculated_position, 
      name,                

      scale: scale, 
      rotate: angle, 
      plate_height: plate_height, 
      plate_gap: plate_gap,       
      lead_extension: calculated_lead_extension, 
      ..capparams 
    )
  })
}

//*
// Example Usage: (Requires the necessary base functions to be defined)
#set page(width: auto, height: auto, margin: 10pt)
#cetz.canvas({
  import cetz.draw: *

  // Define two points
  let p1 = (0, 0)
  let p2 = (3, 1)
  let p3 = (5, 1)
  let p4 = (5, -1)

  // Draw points for reference
  circle(p1, radius: 0.05, fill: blue)
  circle(p2, radius: 0.05, fill: blue)
  circle(p3, radius: 0.05, fill: red)
  circle(p4, radius: 0.05, fill: red)

  // Use the wrapper function
  capacitor_between(p1, p2, "C1", label: $C_1$, scale: 1.0)

  // Use the wrapper function with different scale and parameters
  capacitor_between(
    p3, p4, // start, end
    "C2",   // name
    label: $C_2$,
    scale: 1, // Makes plates larger relative to leads
    plate_gap: 0.15,
    plate_height: 0.8,
    stroke: red,
    label_offset: (0.1, 0.1), // Adjust offset if needed
    label_anchor: "south-west",
  )
})
//*/


#let current_arrow(
  start_point,
  end_point,
  name,
  label: none,
  label_anchor: "south", // Default anchor point ON the label text box
  label_offset: (0, 0.1), // Default offset FROM the midpoint TO the anchor point
  label_size: 8pt,
  text_fill: black,
  arrow_position:60%,
  arrow_scale: 1.0,    // Scale for the arrowhead size
  arrow_fill: black,
  arrow_stroke: black, // Stroke color for the arrowhead outline
  // Use stroke argument directly for line styling:
  // Use ..styling for any *other* Cetz styling options if needed
  ..styling
) = {
  // Determine the final stroke for the main line, prioritizing the direct argument
  // Ensure the arrow mark stroke thickness matches the line stroke
  // Define the midpoint specification (used multiple times)
  let mid_point_spec = (start_point, arrow_position, end_point)


  cetz.draw.group(name: name, {// Pass styling to the group
    // No get-ctx or resolve needed here anymore

    // --- Draw func logic implementation ---

    // 1. Draw the full line segment using direct points
    cetz.draw.line(start_point, end_point, ..styling)

    // 2. Draw the "invisible" line carrying the mark from start to midpoint
    cetz.draw.line(
      start_point,
      mid_point_spec, // Use the relative midpoint specification
      mark: (
        end: "stealth",
        scale: arrow_scale *0.7, // Use consistent scaling factor
        fill: arrow_fill
      ),
      ..styling
    )
    // --- End of draw func logic ---


    // --- Label Placement ---
    if label != none {
      cetz.draw.content(
        (rel:label_offset,to:mid_point_spec), // Place relative to the midpoint specification
        text(size: label_size, fill: text_fill, label),
        anchor: label_anchor, 
      )
    }

    // --- Define Anchors Directly using specifications ---
    cetz.draw.anchor("start", start_point)
    cetz.draw.anchor("end", end_point)
    cetz.draw.anchor("center", mid_point_spec) // Anchor at the relative midpoint
    cetz.draw.anchor("default", mid_point_spec)
  })
}

`example.typ`

#import "./components.typ" as comp
#import "@preview/cetz:0.3.4" as cetz 

/*
; figure 1
; RC Low-Pass Filter (from Figure 1)
; Node (Vin) is the input voltage node (relative to GND).
; Node (Vout) is the output voltage node (relative to GND).

(Vin) -- R -- (Vout) -- C -- (GND)
*/

#figure(
cetz.canvas({
  let default_stroke = (stroke: (thickness:0.6pt))
  let node_styling = (radius: 0.06, fill: black, ..default_stroke)

  let x_in = 0
  let x_R = 1.5
  let x_C = 3.0
  let x_out = 4.0
  let y_top = 1.0
  let y_bottom = -1.0
  let y_gnd = -1.5

  // Input terminals and voltage label
  comp.node((x_in, y_top), "in_T", ..node_styling)
  comp.node((x_in, y_bottom), "in_B", ..node_styling)
 /* comp.voltage_points(
    (x_in, y_top),
    (x_in, y_bottom),
    "Vin",
    label: $V_"in"$,
    voltage_arrow_pos: "left",
    voltage_arrow_dir: "down",
    arrow_offset: 0.3,
    show_points: false,
    show_point_labels: false
  )
*/
comp.voltage_points(
    (x_in, (y_top +y_bottom)/2),
    "Vin",
    label: $V_"in"$,          
    point_separation: 2,    
    point_radius: 0.05,       
    point_fill: white,        
    point_stroke: black,      
    show_point_labels: true,  
    top_label: $$,           
    bottom_label: none,        
    point_text_fill: gray,    
    point_label_size: 9pt,

    top_label_offset: (0, 0.1),
    top_label_anchor: "south",
    bottom_label_offset: (0, -0.1),
    bottom_label_anchor: "north",

    show_voltage_annotation: true,
    voltage_arrow_pos: "left",
    voltage_arrow_dir: "down",
    arrow_offset: 0.35,        
    arrow_length_factor: 0.9,  
    arrow_stroke_thickness: 0.8pt,
    annotation_label_size: 10pt
  )

  comp.voltage_points(
    (x_out, (y_top +y_bottom)/2),
    "Vout",
    label: $V_"out"$,          
    point_separation: 2,    
    point_radius: 0.05,       
    point_fill: white,        
    point_stroke: black,      
    show_point_labels: true,  
    top_label: $$,           
    bottom_label: none,        
    point_text_fill: gray,    
    point_label_size: 9pt,

    top_label_offset: (0, 0.1),
    top_label_anchor: "south",
    bottom_label_offset: (0, -0.1),
    bottom_label_anchor: "north",

    show_voltage_annotation: true,
    voltage_arrow_pos: "left",
    voltage_arrow_dir: "down",
    arrow_offset: -0.35,        
    arrow_length_factor: 0.9,  
    arrow_stroke_thickness: 0.8pt,
    annotation_label_size: 10pt
  )
  // Resistor
  comp.resistor((x_R, y_top), "R", label: $R$, width: 1.2, ..default_stroke)

  // Capacitor
  comp.capacitor((x_C, (y_top+ y_bottom)/2), "C", label: $C$, rotate: 90deg, lead_extension: 0.9, ..default_stroke)

  // Output terminals and voltage label
  comp.node((x_out, y_top), "out_T", ..node_styling)
  comp.node((x_out, y_bottom), "out_B", ..node_styling)
  // Ground symbol
  comp.gnd_symbol((x_C -1, y_gnd), "GND", ..default_stroke)

  // Connections
  cetz.draw.line("in_T", "R.L", ..default_stroke)
  cetz.draw.line("R.R", "C.R", ..default_stroke)
  cetz.draw.line("C.R", "out_T", ..default_stroke)
  cetz.draw.line("in_B", (x_C, y_bottom), ..default_stroke) // Connect bottom input to ground line
  cetz.draw.line("C.L", (x_C, y_bottom), ..default_stroke) // Connect capacitor bottom to ground line
  cetz.draw.line("out_B", (x_C, y_bottom), ..default_stroke) // Connect bottom output to ground line
  cetz.draw.line((x_C -1, y_bottom), "GND.T", ..default_stroke) // Connect ground line to GND symbol


}),
     caption: [RC filter.]
   )

/*
VoltageSource VinSrc 
Resistor R1
Nmos M1
Resistor R2
Capacitor CL
(VinSrc.T): (Vin)
(VinSrc.B): (GND)   
(Vin) -- R1 -- (M1.G)
(M1.D):(VDD)
(M1.B):(GND)
(Vout):(M1.S)
(Vout) -- [ R2 || CL ] -- (GND)
*/
#cetz.canvas({
   let default_stroke = (stroke: (thickness:.6pt))

  let x_vin = 0
  let x_r1 = 1.
  let x_m1 = 1.9
  let x_out_comps = x_m1 + 0.9 
  let x_cl = x_out_comps + 2.0 
  let x_vout = x_cl + 1.0 

  let y_gate = 1.6 
  let y_m1_base = 1.0 
  let y_m1_s = y_m1_base 
  let y_m1_d = y_m1_base + 1.2 
  let y_m1_b = y_m1_base + 0.6 
  let y_vdd = y_m1_d + 1.5 
  let y_gnd = -1 
  let y_cl_center = y_m1_s - 0.95 

  comp.voltage_source((x_vin, y_gate -1), "Vin",
    label: $V_"in"$,
    voltage_arrow_pos: "left",
    voltage_arrow_dir: "down",
    annotation_label_pos: "left",
    radius: 0.4,
    lead_length: 0.2,
    ..default_stroke
  )

  comp.resistor((x_r1, y_gate ), "R1", label: $R_1$, label_pos: "north", label_offset: (0, 0.4), width: 1.0, ..default_stroke)

  comp.nmos_transistor((x_m1, y_m1_base), "M1", label: $M_1$, label_pos: "east", label_anchor: "west", label_offset: (0.3, 0.3),..default_stroke)

  comp.vdd_symbol((x_out_comps, y_vdd), "Vdd", label: $V_"DD"$, ..default_stroke)

  comp.resistor((x_out_comps, y_m1_s -1), "R2", rotate: 90deg, label: $R_2$, label_pos: "west", label_offset: (0.5, 0.5), ..default_stroke)

  comp.capacitor((x_cl, y_cl_center), "CL", rotate: 90deg, label: $C_L$, label_pos: "east", label_offset: (-0.2, -0.5), ..default_stroke)

  comp.gnd_symbol((x_vin, y_gnd), "GND_Vin", ..default_stroke)
  comp.gnd_symbol((x_m1 + 1.27, y_m1_b), "GND_M1B", ..default_stroke)
  comp.gnd_symbol((x_out_comps, y_gnd), "GND_R2", ..default_stroke)
  comp.gnd_symbol((x_cl, y_gnd), "GND_CL", ..default_stroke)

  comp.node((x_vout, y_m1_b), "VoutNode", label:$V_"out"$,  label_offset: (0.15, 0), label_anchor: "west", ..default_stroke)

  comp.connect-orthogonal("Vin.T", "R1.L", style: "hv", ..default_stroke) 
  comp.connect-orthogonal("Vin.B", "GND_Vin.T", style: "hv", ..default_stroke)
  comp.connect-orthogonal("R1.R", "M1.G", style: "hv", ..default_stroke) 
  comp.connect-orthogonal("M1.D", "Vdd.B", style: "hv", ..default_stroke) 
  comp.connect-orthogonal("M1.B", "GND_M1B.T", style: "hv", ..default_stroke)
  comp.connect-orthogonal("M1.S", "R2.R", style: "hv", ..default_stroke)
   comp.connect-orthogonal("GND_R2.T", "R2.L", style: "hv", ..default_stroke) 
   comp.connect-orthogonal("GND_CL.T", "CL.L", style: "hv", ..default_stroke) 
  comp.connect-orthogonal("CL.R", "M1.S", style: "hv", ..default_stroke) 
  comp.connect-orthogonal("VoutNode", "M1.S", style: "hv", ..default_stroke) 
})
 // /*

#figure(
  cetz.canvas({

    let default_stroke = (stroke: (thickness: 0.6pt))
    let node_styling = (radius: 0.06, fill: white, ..default_stroke)
    let label_styling(clr) = (text_fill: clr, label_size: 9pt)
    let term_styling = label_styling(gray) + node_styling + (fill: none,)
    let voltage_label_styling = label_styling(blue)

    let xG = -2.5
    let xS = 0
    let xGm1 = 0
    let xGmB1 = 1.8
    let xRds = 3.6
    let xD = xRds+1

    let yTop = 1.0 
    let yBottom = 0.0 
    let yB = -1.0
    let yGnd = yB - 0.5

    let vgs_x = xG - 0.4
    let vbs_x = xS - 0.5

    comp.voltage_points(
    (xG, yTop/2), 
    "Vgs",
    label: $V_"GS"$,          
    point_separation: 1,    
    point_radius: 0.05,       
    point_fill: white,        
    point_stroke: black,      
    show_point_labels: true,  
    top_label: $G$,           
    bottom_label: none,        
    point_text_fill: gray,    
    point_label_size: 9pt,

    top_label_offset: (0, 0.1),
    top_label_anchor: "south",
    bottom_label_offset: (0, -0.1),
    bottom_label_anchor: "north",

    show_voltage_annotation: true,
    voltage_arrow_pos: "left",
    voltage_arrow_dir: "down",
    arrow_offset: 0.35,        
    arrow_length_factor: 0.9,  
    arrow_stroke_thickness: 0.8pt,
    annotation_label_size: 10pt
  )

    comp.node((xD, yTop), "D", label:$D$, ..term_styling, label_anchor: "south", label_offset: (0, 0.15))

comp.voltage_points(
    ((xS, yBottom),50%, (xS, yB)), 
    "Vbs",
    label: $V_"BS"$,          
    point_separation: 1,    
    point_radius: 0.05,       
    point_fill: white,        
    point_stroke: black,      
    show_point_labels: true,  
    top_label: $S$,           
    bottom_label: $B$,        
    point_text_fill: gray,    
    point_label_size: 9pt,

    top_label_offset: (0.2, -0.25),
    top_label_anchor: "south",
    bottom_label_offset: (0.2, 0.25),
    bottom_label_anchor: "north",

    show_voltage_annotation: true,
    voltage_arrow_pos: "left",
    voltage_arrow_dir: "up",
    arrow_offset: 0.35,        
    arrow_length_factor: 0.9,  
    arrow_stroke_thickness: 0.8pt,
    annotation_label_size: 10pt
  )
    comp.current_source(
      (xGm1, yTop/2), "gm1",
      label: $g_"m1" V_"GS"$, arrow_dir: "down",
      radius: 0.35, lead_length: (yTop/2 - 0.35), 
      label_offset: (0.75, 0), label_anchor: "west",
      ..default_stroke
    )

    comp.current_source(
      (xGmB1, yTop/2), "gmB1",
      label: $g_"mB1" V_"BS"$, arrow_dir: "down",
      radius: 0.35, lead_length: (yTop/2 - 0.35),
      label_offset: (0.75, 0), label_anchor: "west",
      ..default_stroke
    )

    comp.resistor(
      (xRds, yTop/2), "rds1",
      label: $r_"DS1"$, rotate: 90deg,
      height: 0.4, width: 0.7, 
      lead_extension: (yTop - 0.7)/2, 
      label_offset: (0., -0.10), label_anchor: "west",
      ..default_stroke
    )

    comp.gnd_symbol((xS, yGnd), "GndB", ..default_stroke)

    cetz.draw.line("gm1.T", "gmB1.T", ..default_stroke)
    cetz.draw.line("gmB1.T", "rds1.R", ..default_stroke)
    cetz.draw.line("rds1.R", "D", ..default_stroke) 

    cetz.draw.line("Vgs.B", (xS, yBottom), ..default_stroke) 

    cetz.draw.line("gm1.B", "gmB1.B", ..default_stroke)
    cetz.draw.line("gmB1.B", "rds1.L", ..default_stroke)

    cetz.draw.line("Vbs.B", "GndB.T", ..default_stroke)

  }),
  caption: [(b) Transistor small signal model]
)
//*
#cetz.canvas({
   let default_stroke = (stroke: (thickness:.6pt))

  let x_vin = 0
  let x_r1 = 1.
  let x_m1 = 1.9
  let x_out_comps = x_m1 + 0.9 
  let x_cl = x_out_comps + 4.0 
  let x_vout = x_cl + 1.5 

  let y_gate = 1.6 
  let y_m1_base = 1.0 
  let y_m1_s = y_m1_base 
  let y_m1_d = y_m1_base + 1.2 
  let y_m1_b = y_m1_base + 0.6 
  let y_vdd = y_m1_d + 1.5 
  let y_gnd = -1 
  let y_cl_center = y_m1_s - 0.95 

  comp.voltage_source((x_vin, y_gate -1), "Vin",
    label: $V_"in"$,
    voltage_arrow_pos: "left",
    voltage_arrow_dir: "down",
    annotation_label_pos: "left",
    radius: 0.4,
    lead_length: 0.2,
    ..default_stroke
  )

  comp.resistor((x_r1, y_gate ), "R1", label: $R_1$, label_pos: "north", label_offset: (0, 0.4), width: 1.0, ..default_stroke)

  comp.gnd_symbol((x_out_comps, y_vdd), "Vdd", label: $\"V_"DD"\"$, label_offset:(0, -0.6),rotate:180deg,..default_stroke)

  comp.resistor((x_out_comps, y_m1_s -1), "R2", rotate: 90deg, label: $R_2$, label_pos: "west", label_offset: (0.5, 0.5), ..default_stroke)

  comp.voltage_points(
     ("R1.R",50%,"R2.R"), 
    "Vgs",
    label: $V_"GS"$,          
    point_separation: 1,    
    point_radius: 0.05,       
    point_fill: white,        
    point_stroke: black,      
    show_point_labels: true,  
    top_label: $G$,           
    bottom_label: none,        
    point_text_fill: gray,    
    point_label_size: 9pt,

    top_label_offset: (0, 0.1),
    top_label_anchor: "south",
    bottom_label_offset: (0, -0.1),
    bottom_label_anchor: "north",

    show_voltage_annotation: true,
    voltage_arrow_pos: "left",
    voltage_arrow_dir: "down",
    arrow_offset: 0.35,        
    arrow_length_factor: 0.9,  
    arrow_stroke_thickness: 0.8pt,
    annotation_label_size: 10pt
  )

  comp.current_source(
    (rel:(1,0),to:"Vgs.east"), "gm1",
      label: $g_"m1" V_"GS"$, arrow_dir: "down",
      radius: 0.35,  
      label_offset: (0.75, 0), 
      lead_length: 0.15,
      label_anchor: "west",
      ..default_stroke
    )

     comp.current_source(
    (rel:(1.7,0),to:"gm1.east"), "gmB1",
      label: $g_"mB1" V_"BS"$, arrow_dir: "down",
      radius: 0.35,  
      lead_length: 0.15,
      label_offset: (0.75, 0), label_anchor: "west",
      ..default_stroke
    )

       comp.resistor(
      (rel:(1.7,0),to:"gmB1.east"), "rds1",
      label: $r_"DS1"$, rotate: 90deg,
      height: 0.4, width: 0.7, 
      lead_extension: (0.1), 
      label_offset: (0., -0.10), label_anchor: "west",
      ..default_stroke
    )

  comp.capacitor((x_cl, y_cl_center), "CL", rotate: 90deg, label: $C_L$, label_pos: "east", label_offset: (-0.2, -0.5), ..default_stroke)
  comp.voltage_points(
    (to:"R2.center",rel:(2,0)), 
    "Vbs",
    label: $V_"BS"$,          
    point_separation: 1.3,    
    point_radius: 0.05,       
    point_fill: white,        
    point_stroke: black,      
    show_point_labels: true,  
    top_label: $S$,           
    bottom_label: $B$,        
    point_text_fill: gray,    
    point_label_size: 9pt,

    top_label_offset: (0.2, -0.25),
    top_label_anchor: "south",
    bottom_label_offset: (0.2, 0.25),
    bottom_label_anchor: "north",

    show_voltage_annotation: true,
    voltage_arrow_pos: "left",
    voltage_arrow_dir: "up",
    arrow_offset: 0.35,        
    arrow_length_factor: 0.9,  
    arrow_stroke_thickness: 0.8pt,
    annotation_label_size: 10pt
  )
comp.gnd_symbol(("Vbs.B"), "GND_CL", ..default_stroke)
  comp.gnd_symbol((x_vin, y_gnd), "GND_Vin", ..default_stroke)
  comp.gnd_symbol((x_out_comps, y_gnd), "GND_R2", ..default_stroke)
  comp.gnd_symbol((x_cl, y_gnd), "GND_CL", ..default_stroke)

  comp.node((x_vout, y_m1_b), "VoutNode", label:$V_"out"$,  label_offset: (0.15, 0), label_anchor: "west", ..default_stroke)
comp.connect-orthogonal("Vin.T", "R1.L", style: "hv", ..default_stroke) 
  comp.connect-orthogonal("Vin.B", "GND_Vin.T", style: "hv", ..default_stroke)

   comp.connect-orthogonal("GND_R2.T", "R2.L", style: "hv", ..default_stroke) 
   comp.connect-orthogonal("GND_CL.T", "CL.L", style: "hv", ..default_stroke) 
    comp.connect-orthogonal("R1.R", "Vgs.T", style: "vh", ..default_stroke) 
    comp.connect-orthogonal("rds1.L", "Vgs.B", style: "hv", ..default_stroke) 
    comp.connect-orthogonal("rds1.R", "gm1.T", style: "hv", ..default_stroke) 
    comp.connect-orthogonal("Vdd.T", "gm1.T", style: "hv", ..default_stroke) 
    comp.connect-orthogonal("Vgs.B", "CL.R", style: "vh", ..default_stroke) 
 comp.connect-orthogonal("Vgs.B", "R2.R", style: "vh", ..default_stroke) 
     comp.connect-orthogonal("VoutNode", "rds1.L", style: "hv", ..default_stroke) 

})

/*
Resistor R2
Capacitor CL
(M1_S):(Vout)
(M1_D):(GND)
(Vout) -- [ gm1*Vin (<-) || gmB1*Vout (<-) || rds1 || R2 || CL ] -- (GND)
*/
#cetz.canvas({
   let default_stroke = (stroke: (thickness:.6pt))

   comp.voltage_points(
    (rel:(0,0)), 
    "Vgs",
    label: $V_"out"$,          
    point_separation: 1.2,    
    point_radius: 0.05,       
    point_fill: white,        
    point_stroke: black,      
    show_point_labels: true,  
    top_label: $S$,           
    bottom_label: $D$,        
    point_text_fill: gray,    
    point_label_size: 9pt,

    top_label_offset: (-0.2, -0.25),
    top_label_anchor: "south",
    bottom_label_offset: (-0.2, 0.25),
    bottom_label_anchor: "north",

    show_voltage_annotation: true,
    voltage_arrow_pos: "left",
    voltage_arrow_dir: "down",
    arrow_offset: 0.65,        
    arrow_length_factor: 0.9,  
    arrow_stroke_thickness: 0.8pt,
    annotation_label_size: 10pt
  )

  comp.current_source(
     (rel:(1,0),to:"Vgs.center"), "gm1",
      label: $g_"m1" (V_"in"-V_"out")$, arrow_dir: "up",
      radius: 0.35,  
      label_offset: (0.75, 0), 
      label_anchor: "west",
      ..default_stroke
    )

    comp.current_source(
      (rel:(2.4,0),to:("gm1.east")), "gmB1",
      label: $-g_"mV1" V_"out"$, arrow_dir: "up",
      radius: 0.35,  
      label_offset: (0.75, 0), 
      label_anchor: "west",
      ..default_stroke
    )

     comp.resistor(
      (rel:(1.7,0),to:"gmB1.east"), "rds1",
      label: $r_"DS1"$, rotate: 90deg,
      height: 0.4, width: 0.7, 

      label_offset: (0., -0.10), label_anchor: "west",
      ..default_stroke
    )

     comp.resistor(
      (rel:(1.7,0),to:"rds1.B"),
      "R2",
      label: $R_2$, rotate: 90deg,
      height: 0.4, width: 0.7, 
      label_offset: (0., -0.10), label_anchor: "west",
      ..default_stroke
    )

     comp.capacitor((rel:(1.7,0),to:"R2.B"), "CL", rotate: 90deg, label: $C_L$, label_pos: "east", label_offset: (-0.2, -0.5), ..default_stroke)

    comp.gnd_symbol(
      (rel:(5, -0.2), to: "Vgs.B"), 
      "gnd",
      ..default_stroke
    )

  comp.connect-orthogonal("Vgs.T", "gm1.T", style: "hv", ..default_stroke)
    cetz.draw.line("gm1.T", "gmB1.T", ..default_stroke) 
    cetz.draw.line("gmB1.T", "rds1.R", ..default_stroke) 
    cetz.draw.line("rds1.R", "R2.R", ..default_stroke)   
    cetz.draw.line("R2.R", "CL.R", ..default_stroke)     

 comp.connect-orthogonal("Vgs.B", "gm1.B", style: "hv", ..default_stroke)
    cetz.draw.line("gm1.B", "gmB1.B", ..default_stroke) 
    cetz.draw.line("gmB1.B", "rds1.L", ..default_stroke) 
    cetz.draw.line("rds1.L", "R2.L", ..default_stroke)   
    cetz.draw.line("R2.L", "CL.L", ..default_stroke)     

     comp.connect-orthogonal("gm1.B", "gnd.T", style: "vh", ..default_stroke)
 })
 //*/


#cetz.canvas({

  let default_stroke = (stroke: (thickness: 0.6pt))
  let node_style = (fill: black, radius: 0.05pt)

  let x_in_labels = -1
  let x_transistors = 0
  let x_out_label = 2

  let y_vdd = 4.4
  let y_m3_base = 3.0  
  let y_m2_base = 1.0  
  let y_m1_base = -0.5 
  let y_gnd = -1.5

  let pt_vin_label = (x_in_labels, 0)    
  let pt_vbias_label = (x_in_labels, 1.6)  
  let pt_vout_label = (x_out_label+1, 2.2) 
  let pt_vdd_connect = (x_transistors, y_vdd)
  let pt_gnd_connect = (x_transistors, y_gnd)

  comp.vdd_symbol(pt_vdd_connect, "VddSym", label: $V_"DD"$, label_offset:(0, 0.3), ..default_stroke)
  comp.gnd_symbol(pt_gnd_connect, "GndSym", ..default_stroke)

  comp.nmos_transistor((x_transistors, y_m1_base), "M1", label: $M_1$, label_pos: "west", label_offset: (0.5, 0.3), ..default_stroke,bulk_lead_factor:-0.6)

  comp.nmos_transistor((x_transistors, y_m2_base), "M2", label: $M_2$, label_pos: "west", label_offset: (0.5, 0.3), ..default_stroke,bulk_lead_factor:-0.6)

  comp.pmos_transistor((x_transistors+1.8, y_m3_base), "M3", label: $M_3$, label_pos: "west", label_offset: (-1.0, -0.0), ..default_stroke,width:-0.9,bulk_lead_factor:-0.6)

  comp.node(pt_vin_label, "VinNode", label: $V_"in"$, label_anchor: "east", label_offset: (-0.2, 0))
  comp.node(pt_vbias_label, "VbiasNode", label: $V_"bias"$, label_anchor: "east", label_offset: (-0.2, 0))
  comp.node(pt_vout_label, "VoutNode", label: $V_"out"$, label_anchor: "west", label_offset: (0.2, 0))

  comp.connect-orthogonal("VinNode", "M1.G", style: "hv", ..default_stroke)

  comp.connect-orthogonal("VbiasNode", "M2.G", style: "hv", ..default_stroke)

  comp.connect-orthogonal("VddSym.B", "M3.S", style: "vh", ..default_stroke)

  comp.connect-orthogonal("M1.S", "GndSym.T", style: "hv", ..default_stroke)

  comp.connect-orthogonal("M1.D", "M2.S", style: "vh", ..default_stroke)

  comp.connect-orthogonal("M2.D", "M3.D", style: "vh", ..default_stroke)

  comp.wire_hop(
    (rel:(0,0),to:"M3.G"), (rel:(1.16,0),to:"M1.D"),     
    "M2.D", "VoutNode", 
    hopping_wire: 1,    
    hop_radius: 0.1,    
    hop_direction: 1,   
    ..default_stroke
  )
  comp.connect-orthogonal("M1.D", (rel:(1.16,0),to:"M1.D"), style: "vh", ..default_stroke)

})

#figure(
  cetz.canvas({

    let default_stroke = (stroke: (thickness: 0.6pt))

    let x_in = -2.0
    let x_m1 = 0.
    let x_cl = 1.5
    let x_vout = 0.7 

    let y_gnd = -1.0
    let y_src = 0.0
    let y_gate = 1.0
    let y_drain = 2.5
    let y_ib_mid = 3.25
    let y_vdd = 4.0
    let y_cl_mid = (y_drain + y_src) / 2

    comp.nmos_transistor(
      (x_m1, y_src), "M1",
      anchor: "S", 
      label: $M_1$,
      label_pos: "west",
      label_offset: (-0.2, 0.5),
      bulk_lead_factor:-0.6,
      ..default_stroke
    )

comp.vdd_symbol(
  (x_m1 -0.27, y_vdd),
  "Vdd",
  label: $V_"dd"$,
  label_pos: "north",
  label_anchor: "south",
  label_offset: (0, 0.1),

 ..default_stroke
)

    comp.current_source(
      (x_m1 -0.27, y_ib_mid), "Ib",
      label: $I_b$,
      arrow_dir:"down",
       label_offset: (0.6, 0),
      ..default_stroke
    )

    comp.capacitor(
      (x_cl, y_cl_mid), "CL",
      label: $C_L$,
      label_pos: "center",
      label_offset: (0.1, -0.6),
      rotate: 90deg, 
      lead_extension: 0.9, 
      color: blue, 
      ..default_stroke
    )

    comp.gnd_symbol((x_m1 -0.27, y_gnd), "GND_M1", ..default_stroke)
    comp.gnd_symbol((x_cl, y_gnd), "GND_CL", ..default_stroke)
comp.node((rel:(0.9,0),to:"CL.R"),"Vout",label:$V_"out"$,label_offset:(0.4,0))

comp.node((rel:(-0.9,0),to:"M1.G"),"Vin")

     cetz.draw.arc((rel:(0.,-0.2),to:"Vin"),start:180deg+10deg,stop:270deg,radius:2,stroke:black,mark: (end: ">"))
     cetz.draw.content(
        (rel:(0.3,-1.5),to:"Vin"), 
        [$V_"in"$]
      )

    comp.connect-orthogonal("Vin.east", "M1.G", ..default_stroke)

    comp.connect-orthogonal("M1.S", "GND_M1.T", ..default_stroke)
    comp.connect-orthogonal("CL.R", "Vout.west", ..default_stroke)

comp.connect-orthogonal("Vdd.B", "Ib.T", ..default_stroke)

    comp.connect-orthogonal("M1.D", "Ib.B", ..default_stroke)

    comp.connect-orthogonal("M1.D", "CL.R", ..default_stroke)

    comp.connect-orthogonal("CL.L", "GND_CL.T", ..default_stroke)

cetz.draw.content(
        (rel:(5,0),to:"Vout"), 
        [#rect([
          
        desired amplifier specs:
#set list(indent: 1em)
- $|A_v| > 7$
- $omega_"3dB" >= 2pi dot "250 MHz at " C_L = "500 fF"$
- Assume for now: $V^\* = "200 mV"$

        
        ])]
      )

  }),
  caption: [Common source amplifier and the desired specifications.]
)


/*
; Simple circuit to characterize NMOS (Figure 2)
; Components: M1 (NMOS), VGS (VSource), VDS (VSource)
; Current: ID1 (Drain Current)

(GND) -- VGS (-+) -- (M1.G)          ; VGS source: negative terminal to GND, positive to M1.G
(GND) -- VDS (-+) -- ->ID1 -- (M1.D)  ; VDS source: negative terminal to GND, positive to M1.D.
                                     ; Current ID1 flows from VDS(+) towards M1.D.
(M1.S):(GND)                          ; M1 Source terminal connected directly to GND
(M1.B):(GND)                          ; M1 Bulk terminal connected directly to GND (assumed standard connection)
*/
#figure(
cetz.canvas({
    let default_stroke = (stroke: (thickness:0.6pt))
    let node_styling = (radius: 0.06, fill: black, ..default_stroke)

    let x_vgs = -2.0
    let x_m1 = -0
    let x_vds = 2.0
    let y_gate = 1.0 
    let y_drain=3.0
    let y_source_base = -0.5 
    let y_gnd = -1.0 
    let y_v_center = (y_gate + y_source_base) / 2 

    comp.nmos_transistor((x_m1 -0.9, 0.3), "M1",
      label: $M_1$,
      label_pos: "east",
      label_offset: (0.2, 0.3), 
      ..default_stroke
    )

    comp.voltage_source((x_vgs, y_v_center), "VGS",
      label: $V_"GS"$,
      voltage_arrow_pos: "left",
      voltage_arrow_dir: "down",
      annotation_label_pos: "left",

      lead_length: (y_gate - y_source_base)/2 - 0.4, 
      ..default_stroke
    )

    comp.voltage_source((x_vds, y_v_center), "VDS",
      label: $V_"DS"$,
      voltage_arrow_pos: "right",
      voltage_arrow_dir: "down",
      annotation_label_pos: "right",

      lead_length: (y_gate - y_source_base)/2 -0.4, 
      ..default_stroke
    )

    comp.gnd_symbol((x_m1, y_gnd), "GND", ..default_stroke)

    let arrow_start = (x_m1, y_drain + 0.4) 
    let arrow_end = (x_m1, y_drain + 0.05)

    comp.connect-orthogonal("VGS.T", "M1.G", ..default_stroke)
    comp.connect-orthogonal("VGS.B", (x_vgs, y_source_base), ..default_stroke) 
    comp.connect-orthogonal((x_vgs, y_source_base), (x_m1, y_source_base), ..default_stroke) 

    comp.connect-orthogonal("VDS.T", (x_m1, y_drain), ..default_stroke) 
    comp.connect-orthogonal("VDS.B", (x_vds, y_source_base), ..default_stroke) 
    comp.connect-orthogonal((x_vds, y_source_base), (x_m1, y_source_base), ..default_stroke) 

    comp.connect-orthogonal("M1.S", (x_m1, y_source_base), ..default_stroke) 
    comp.connect-orthogonal("M1.B", (x_m1, y_source_base+0.5), ..default_stroke) 
    comp.connect-orthogonal((x_m1, y_source_base), "GND.T", ..default_stroke) 
 comp.current_arrow(
    (0,y_drain),
    "M1.D",
    "I1",
    label: $I_1$,
    label_offset: (0.2, 0.15),
    label_anchor: "north",
    ..default_stroke
  )
  }),
  caption: [Simple circuit to characterize NMOS.]
)

feel free to take this, the code was also inspired out of circetz so not all credit is mine, and some part was modified by @janosh here https://github.com/janosh/diagrams/issues/44

Kreijstal avatar Apr 26 '25 10:04 Kreijstal

Thanks a lot for the code you shared!

I think @fenjalien’s project is a really good starting point, but it’s not exactly the direction I want to follow — I totally agree with you.

For me, the best way to call components would be something like this:

#import "@preview/circuitor:0.1.0"

#canvas({
    resistor("componentID", (x,y), label: $R_1$, label-position: "west", rotate: 0deg, scale: 1, ...parameters)
    wire("firstComponentID", "secondComponentID", stroke: 1pt, ...parameters)
    vsource("componentID", (x,y), label: $V1$, rotate: 0deg, scale: 1, color: blue, ...parameters)
    
    //or wires using points
    wire((x1,y1), "someComponentID", stroke: 1pt, ...parameters)
})

What do you think about this way of declaring components?

By the way, I want to implement something to allow users to customize stroke, color and scale globally for a project.

l0uisgrange avatar Apr 26 '25 11:04 l0uisgrange

Hi all, I do apologise for the extended silence but I have been on a project hiatus, and still am until the end of May. It's coincidental that I've checked my notifications today to find this.

cirCeTZ is intended to be a port of circuitikz, which includes all the options and the settings, which requires reading whatever tex flavour its been written in because its not otherwise easily available. A lot of my previous active development time has also been used on improving CeTZ to make this even possible (and I still don't think its there yet). So yes even though nothing has happened in this repository for a long time, I would very much like to keep working on it, but there have been so many hurdles I've been stumbling over.

@l0uisgrange its fair if you want to make your own circuit drawing package, but you can't just take my code from here without attribution and use it as your own. See T&C 4 of the Apache 2.0 license. I'm also not sure you can change the license to MIT. For the name of circetz, I don't think I have any particular claiming rights, its whoever gets there first for Typst Universe submission, However, I've been using the name for a while now and grown fond of it, what am I supposed to do when I continue my work on this? Its probably also going to confuse people.

Please reach out to me on discord DMs through the Typst server, I use the same username.

fenjalien avatar Apr 26 '25 11:04 fenjalien

I will also add for the sake of revelvance to this issue, I am open to adding another maintainer but no one has yet volunteered themselves.

fenjalien avatar Apr 26 '25 11:04 fenjalien

Thanks a lot for the code you shared!

I think @fenjalien’s project is a really good starting point, but it’s not exactly the direction I want to follow — I totally agree with you.

For me, the best way to call components would be something like this:

#import "@preview/circetz:0.1.0"

#canvas({ resistor("componentID", (x,y), label: $R_1$, label-position: "west", rotate: 0deg, scale: 1, ...parameters) wire("firstComponentID", "secondComponentID", stroke: 1pt, ...parameters) vsource("componentID", (x,y), label: $V1$, rotate: 0deg, scale: 1, color: blue, ...parameters)

//or wires using points
wire((x1,y1), "someComponentID", stroke: 1pt, ...parameters)

})

What do you think about this way of declaring components?

By the way, I want to implement something to allow users to customize stroke, color and scale globally for a project.

you know at the beginning I thought that was good tho, but after drawing circuits with myself I wish the api was different, it should've been like this

-> (starting_point,ending_point) this automagically implies rotation position (you don't have to think much). and how big the wires are. this obviously will never apply to objects with more than 2 nodes, aka transistors gates, but for resistors,vdc,caps, I am pretty sure this is the correct thing to do even if a bit unconventional. I made a wrapper you see it's called capacitor_between I truly love it.

Kreijstal avatar Apr 26 '25 11:04 Kreijstal

Hi @fenjalien, thanks for your quick reply.

I will remove your code from my repository and start fresh. I'm sorry for the confusion — I copied the code without thinking it through properly.

About the name: I had the idea for circetz before finding your repository. I’m open to changing it, but only if you want to continue working on this project (which seems inactive for now). I’m not interested in rushing to claim the name circetz on Typst Universe — I just want to release a solid package for version 0.1.0.

l0uisgrange avatar Apr 26 '25 11:04 l0uisgrange

@Kreijstal I hadn't thought of that — thanks a lot!

Quick note: I think it would be better to continue this conversation in the dedicated discussion here: https://github.com/l0uisgrange/zap/discussions/14. I just posted a new idea there as well!

l0uisgrange avatar Apr 26 '25 11:04 l0uisgrange

Hi @fenjalien, thanks for your quick reply.

I will remove your code from my repository and start fresh. I'm sorry for the confusion — I copied the code without thinking it through properly.

About the name: I had the idea for circetz before finding your repository. I’m open to changing it, but only if you want to continue working on this project (which seems inactive for now). I’m not interested in rushing to claim the name circetz on Typst Universe — I just want to release a solid package for version 0.1.0.

Thanks!

As said, I would like to continue at somepoint, I don't know when that is exactly.

fenjalien avatar Apr 26 '25 11:04 fenjalien

@fenjalien, the code has been removed from my repository, and I apologize once again.

After some thought, I’ve decided to use a different name for the package instead of circetz.

Great news on your project — I’m looking forward to it! I’d gladly welcome your expertise, contributions and suggestions on my project!

l0uisgrange avatar Apr 26 '25 11:04 l0uisgrange