diff --git a/src/draw/shapes.typ b/src/draw/shapes.typ index 46adef4a..05012666 100644 --- a/src/draw/shapes.typ +++ b/src/draw/shapes.typ @@ -27,12 +27,16 @@ /// circle((0,0)) /// // Draws an ellipse /// circle((0,-2), radius: (0.75, 0.5)) +/// // Draws a circle at (0, 2) through point (1, 3) +/// circle((0,2), (rel: (1,1))) /// ``` /// -/// - position (coordinate): The position to place the circle on. +/// - ..points-style (coordinate, style): The position to place the circle on. +/// If given two coordinates, the distance between them is used as radius. +/// If given a single coordinate, the radius can be set via the `radius` (style) +/// argument. /// - name (none,str): /// - anchor (none, str): -/// - ..style (style): /// /// ### Styling /// *Root*: `circle` @@ -42,20 +46,33 @@ /// ### Anchors /// Supports border and path anchors. The `"center"` anchor is the default. /// -#let circle(position, name: none, anchor: none, ..style) = { - // No extra positional arguments from the style sink - assert.eq( - style.pos(), - (), - message: "Unexpected positional arguments: " + repr(style.pos()), - ) - let style = style.named() +#let circle(..points-style, name: none, anchor: none) = { + let style = points-style.named() + let points = points-style.pos() + assert(points.len() in (1, 2), + message: "circle expects one or two points, got " + repr(points)) + assert(points.len() != 2 or "radius" not in style, + message: "unexpected radius for circle constructed by two points") (ctx => { - let (ctx, pos) = coordinate.resolve(ctx, position) + let (center, outer) = if points.len() == 1 { + (points.at(0), none) + } else { + points + } + + let (ctx, center) = coordinate.resolve(ctx, center) let style = styles.resolve(ctx.style, merge: style, root: "circle") - let (rx, ry) = util.resolve-radius(style.radius).map(util.resolve-number.with(ctx)) - let (cx, cy, cz) = pos + + // If we got two points, use the second one to calculate + // the radius. + let (rx, ry) = if outer != none { + (ctx, outer) = coordinate.resolve(ctx, outer, update: false) + (vector.dist(center, outer),) * 2 + } else { + util.resolve-radius(style.radius).map(util.resolve-number.with(ctx)) + } + let (cx, cy, cz) = center let drawables = drawable.ellipse( cx, cy, cz, @@ -65,7 +82,7 @@ ) let (transform, anchors) = anchor_.setup( - (_) => pos, + (_) => center, ("center",), default: "center", name: name, diff --git a/tests/circle/ref/1.png b/tests/circle/ref/1.png index 5a4a1fc2..25a4ca8b 100644 Binary files a/tests/circle/ref/1.png and b/tests/circle/ref/1.png differ diff --git a/tests/circle/test.typ b/tests/circle/test.typ index f19e23b3..73cf06cf 100644 --- a/tests/circle/test.typ +++ b/tests/circle/test.typ @@ -1,31 +1,28 @@ #set page(width: auto, height: auto) #import "/src/lib.typ": * +#import "/tests/helper.typ": * -#box(stroke: 2pt + red, canvas(length: .5cm, { - import draw: * +#test-case(radius => { + import draw: * - set-style(radius: (4, .5), stroke: none) - for r in range(0, 6) { - group({ - translate((4.5, 4.5)) - rotate(r * 30deg) + circle((0, 0), radius: radius, name: "c") + point("c.center", "M") +}, args: (1, 1cm, (1, .5), (1cm, .5), (.5, 1), (.5, 1cm))) - circle((0,0), fill: (red, green, blue, yellow).at(calc.rem(r, 4))) - }) - } +#test-case(outer => { + import draw: * - set-style(radius: 0.45, stroke: black, fill: none) - for x in range(0, 10) { - for y in range(0, 10) { - circle((x, y)) - } - } -})) + let center = (1, 1) + circle(center, outer) + move-to(center) + point(outer, "O") + point(center, "M") +}, args: ((2, 1), (rel: (1, 0)), (rel: (1, 1)))) -#box(stroke: 2pt + red, canvas(length: .5cm, { +#test-case({ import draw: * - for z in range(-2, 2) { + for z in range(-1, 2) { circle((0,0,z)) } -})) +}) diff --git a/tests/helper.typ b/tests/helper.typ index bf81d612..1133fe7b 100644 --- a/tests/helper.typ +++ b/tests/helper.typ @@ -1,5 +1,11 @@ #import "/src/lib.typ" as cetz +/// Draw a point + label +#let point(pt, name) = { + cetz.draw.circle(pt, radius: .05cm, fill: black, stroke: none, name: "pt") + cetz.draw.content((rel: (.2cm, 0), to: "pt"), [#name], anchor: "west") +} + /// Draw a cross at position pt #let cross(pt, size: .25, ..style) = { import cetz.draw: *