|
14 | 14 | #import "/src/anchor.typ" as anchor_
|
15 | 15 | #import "/src/mark.typ" as mark_
|
16 | 16 | #import "/src/mark-shapes.typ" as mark-shapes_
|
| 17 | +#import "/src/polygon.typ" |
17 | 18 | #import "/src/aabb.typ"
|
18 | 19 |
|
19 | 20 | #import "transformations.typ": *
|
|
37 | 38 | /// *Root*: `circle`
|
38 | 39 | ///
|
39 | 40 | /// - radius (number, array) = 1: A number that defines the size of the circle's radius. Can also be set to a tuple of two numbers to define the radii of an ellipse, the first number is the `x` radius and the second is the `y` radius.
|
40 |
| -/// |
| 41 | +/// |
41 | 42 | /// ### Anchors
|
42 | 43 | /// Supports border and path anchors. The "center" anchor is the default.
|
43 | 44 | ///
|
44 |
| -#let circle(position, name: none, anchor: none, ..style) = { |
| 45 | +#let circle(position, name: none, anchor: none, ..style) = { |
45 | 46 | // No extra positional arguments from the style sink
|
46 | 47 | assert.eq(
|
47 | 48 | style.pos(),
|
48 | 49 | (),
|
49 | 50 | message: "Unexpected positional arguments: " + repr(style.pos()),
|
50 | 51 | )
|
51 | 52 | let style = style.named()
|
52 |
| - |
| 53 | + |
53 | 54 | (ctx => {
|
54 | 55 | let (ctx, pos) = coordinate.resolve(ctx, position)
|
55 | 56 | let style = styles.resolve(ctx.style, merge: style, root: "circle")
|
|
163 | 164 | },)
|
164 | 165 | }
|
165 | 166 |
|
166 |
| -/// Draws a circular segment. |
| 167 | +/// Draws a circular segment. |
167 | 168 | ///
|
168 | 169 | /// ```typc example
|
169 | 170 | /// arc((0,0), start: 45deg, stop: 135deg)
|
|
190 | 191 | ///
|
191 | 192 | /// ## Anchors
|
192 | 193 | /// Supports border and path anchors.
|
193 |
| -/// - **arc-start** The position at which the arc's curve starts, this is the default. |
194 |
| -/// - **arc-end** The position of the arc's curve end. |
195 |
| -/// - **arc-center** The midpoint of the arc's curve. |
196 |
| -/// - **center** The center of the arc, this position changes depending on if the arc is closed or not. |
197 |
| -/// - **chord-center** Center of chord of the arc drawn between the start and end point. |
198 |
| -/// - **origin** The origin of the arc's circle. |
| 194 | +/// - **arc-start**: The position at which the arc's curve starts, this is the default. |
| 195 | +/// - **arc-end**: The position of the arc's curve end. |
| 196 | +/// - **arc-center**: The midpoint of the arc's curve. |
| 197 | +/// - **center**: The center of the arc, this position changes depending on if the arc is closed or not. |
| 198 | +/// - **chord-center**: Center of chord of the arc drawn between the start and end point. |
| 199 | +/// - **origin**: The origin of the arc's circle. |
199 | 200 | #let arc(
|
200 | 201 | position,
|
201 | 202 | start: auto,
|
|
210 | 211 | (start, stop, delta).filter(it => { it == auto }).len() == 1,
|
211 | 212 | message: "Exactly two of three options start, stop and delta should be defined.",
|
212 | 213 | )
|
213 |
| - |
| 214 | + |
214 | 215 | // No extra positional arguments from the style sink
|
215 | 216 | assert.eq(
|
216 | 217 | style.pos(),
|
217 | 218 | (),
|
218 | 219 | message: "Unexpected positional arguments: " + repr(style.pos()),
|
219 | 220 | )
|
220 | 221 | let style = style.named()
|
221 |
| - |
| 222 | + |
222 | 223 | // Coordinate check
|
223 | 224 | let t = coordinate.resolve-system(position)
|
224 |
| - |
| 225 | + |
225 | 226 | let start-angle = if start == auto { stop - delta } else { start }
|
226 | 227 | let stop-angle = if stop == auto { start + delta } else { stop }
|
227 | 228 | // Border angles can break if the angle is 0.
|
|
272 | 273 | let center = if style.mode != "CLOSE" {
|
273 | 274 | // A circular sector's center anchor is placed half way between the sector-center and arc-center when the angle is 180deg. At 60deg it is placed 1/3 of the way between, this is mirrored at 300deg.
|
274 | 275 | vector.lerp(
|
275 |
| - arc-center, |
| 276 | + arc-center, |
276 | 277 | sector-center,
|
277 | 278 | if (stop-angle + start-angle) > 180deg { (stop-angle + start-angle) } else { (stop-angle + start-angle) + 180deg } / 720deg
|
278 | 279 | )
|
|
335 | 336 | /// - name (none, str):
|
336 | 337 | /// - ..style (style):
|
337 | 338 | ///
|
338 |
| -/// ### Styling |
| 339 | +/// ### Styling |
339 | 340 | /// *Root*: `arc`
|
340 |
| -/// |
| 341 | +/// |
341 | 342 | /// Uses the same styling as @@arc()
|
342 | 343 | ///
|
343 | 344 | /// ### Anchors
|
|
435 | 436 | (),
|
436 | 437 | message: "Unexpected positional arguments: " + repr(style.pos()),
|
437 | 438 | )
|
438 |
| - |
| 439 | + |
439 | 440 | let style = style.named()
|
440 | 441 |
|
441 | 442 | if type(to) == angle {
|
|
445 | 446 | }
|
446 | 447 |
|
447 | 448 | (from, to).map(coordinate.resolve-system)
|
448 |
| - |
| 449 | + |
449 | 450 | return (ctx => {
|
450 | 451 | let (ctx, ..pts) = coordinate.resolve(ctx, from, to)
|
451 | 452 | let style = styles.resolve(ctx.style, merge: style, root: "mark")
|
|
469 | 470 | }
|
470 | 471 |
|
471 | 472 | /// Draws a line, more than two points can be given to create a line-strip.
|
472 |
| -/// |
| 473 | +/// |
473 | 474 | /// ```typc example
|
474 | 475 | /// line((-1.5, 0), (1.5, 0))
|
475 | 476 | /// line((0, -1.5), (0, 1.5))
|
|
490 | 491 | /// - close (bool): If true, the line-strip gets closed to form a polygon
|
491 | 492 | /// - name (none,str):
|
492 | 493 | ///
|
493 |
| -/// ## Styling |
| 494 | +/// ## Styling |
494 | 495 | /// *Root:* `line`
|
495 | 496 | ///
|
496 | 497 | /// Supports mark styling.
|
497 |
| -/// |
| 498 | +/// |
498 | 499 | /// ## Anchors
|
499 |
| -/// Supports path anchors. |
| 500 | +/// Supports path anchors. |
| 501 | +/// - **centroid**: The centroid anchor is calculated for _closed non self-intersecting_ polygons if all vertices share the same z value. |
500 | 502 | #let line(..pts-style, close: false, name: none) = {
|
501 | 503 | // Extra positional arguments from the pts-style sink are interpreted as coordinates.
|
502 | 504 | let pts = pts-style.pos()
|
503 | 505 | let style = pts-style.named()
|
504 |
| - |
| 506 | + |
505 | 507 | assert(pts.len() >= 2, message: "Line must have a minimum of two points")
|
506 |
| - |
| 508 | + |
507 | 509 | // Coordinate check
|
508 | 510 | let pts-system = pts.map(coordinate.resolve-system)
|
509 | 511 |
|
|
528 | 530 | return util.revert-transform(ctx.transform, pt)
|
529 | 531 | }
|
530 | 532 | }
|
531 |
| - |
| 533 | + |
532 | 534 | return (ctx => {
|
533 | 535 | let first-elem = pts.first()
|
534 | 536 | let last-elem = pts.last()
|
|
557 | 559 |
|
558 | 560 | // Get bounds
|
559 | 561 | let (transform, anchors) = anchor_.setup(
|
560 |
| - auto, |
561 |
| - (), |
| 562 | + name => { |
| 563 | + if name == "centroid" { |
| 564 | + return polygon.simple-centroid(pts) |
| 565 | + } |
| 566 | + }, |
| 567 | + if close != none { ("centroid",) } else { () }, |
562 | 568 | name: name,
|
563 | 569 | transform: ctx.transform,
|
564 | 570 | path-anchors: true,
|
|
586 | 592 | /// ```typc example
|
587 | 593 | /// // Draw a grid
|
588 | 594 | /// grid((0,0), (2,2))
|
589 |
| -/// |
| 595 | +/// |
590 | 596 | /// // Draw a smaller blue grid
|
591 | 597 | /// grid((1,1), (2,2), stroke: blue, step: .25)
|
592 | 598 | /// ```
|
|
700 | 706 | /// ```typc example
|
701 | 707 | /// content((0,0), [Hello World!])
|
702 | 708 | /// ```
|
703 |
| -/// To put text on a line you can let the function calculate the angle between its position and a second coordinate by passing it to `angle`: |
| 709 | +/// To put text on a line you can let the function calculate the angle between its position and a second coordinate by passing it to `angle`: |
704 | 710 | ///
|
705 | 711 | /// ```typc example
|
706 | 712 | /// line((0, 0), (3, 1), name: "line")
|
707 | 713 | /// content(
|
708 | 714 | /// ("line.start", 50%, "line.end"),
|
709 | 715 | /// angle: "line.end",
|
710 | 716 | /// padding: .1,
|
711 |
| -/// anchor: "south", |
| 717 | +/// anchor: "south", |
712 | 718 | /// [Text on a line]
|
713 | 719 | /// )
|
714 | 720 | /// ```
|
|
727 | 733 | /// *Root*: `content`
|
728 | 734 | /// - padding (number, dictionary) = 0: Sets the spacing around content. Can be a single number to set padding on all sides or a dictionary to specify each side specifically. The dictionary follows Typst's `pad` function: https://typst.app/docs/reference/layout/pad/
|
729 | 735 | /// - frame (str, none) = none: Sets the frame style. Can be `none`, "rect" or "circle" and inherits the `stroke` and `fill` style.
|
730 |
| -/// |
| 736 | +/// |
731 | 737 | /// ## Anchors
|
732 | 738 | /// Supports border anchors.
|
733 | 739 | #let content(
|
734 | 740 | ..args-style,
|
735 | 741 | angle: 0deg,
|
736 |
| - anchor: none, |
737 |
| - name: none, |
| 742 | + anchor: none, |
| 743 | + name: none, |
738 | 744 | ) = {
|
739 | 745 | let (args, style) = (args-style.pos(), args-style.named())
|
740 | 746 |
|
741 | 747 | let (a, b, body) = if args.len() == 2 {
|
742 |
| - args.insert(1, auto) |
| 748 | + args.insert(1, auto) |
743 | 749 | args
|
744 | 750 | } else if args.len() == 3 {
|
745 | 751 | args
|
|
961 | 967 | message: "Unexpected positional arguments: " + repr(style.pos()),
|
962 | 968 | )
|
963 | 969 | let style = style.named()
|
964 |
| - |
| 970 | + |
965 | 971 | return (
|
966 | 972 | ctx => {
|
967 | 973 | let ctx = ctx
|
|
1111 | 1117 | /// let (a, b, c) = ((0, 0), (2, 0), (1, 1))
|
1112 | 1118 | /// line(a, c, b, stroke: gray)
|
1113 | 1119 | /// bezier(a, b, c)
|
1114 |
| -/// |
| 1120 | +/// |
1115 | 1121 | /// let (a, b, c, d) = ((0, -1), (2, -1), (.5, -2), (1.5, 0))
|
1116 | 1122 | /// line(a, c, d, b, stroke: gray)
|
1117 | 1123 | /// bezier(a, b, c, d)
|
|
1121 | 1127 | /// - end (coordinate): End position (last coordinate)
|
1122 | 1128 | /// - name (none,str):
|
1123 | 1129 | /// - ..ctrl-style (coordinate,style): The first two positional arguments are taken as cubic bezier control points, where the first is the start control point and the second is the end control point. One control point can be given for a quadratic bezier curve instead. Named arguments are for styling.
|
1124 |
| -/// |
1125 |
| -/// ## Styling |
| 1130 | +/// |
| 1131 | +/// ## Styling |
1126 | 1132 | /// *Root* `bezier`
|
1127 |
| -/// |
| 1133 | +/// |
1128 | 1134 | /// Supports marks.
|
1129 |
| -/// |
| 1135 | +/// |
1130 | 1136 | /// ## Anchors
|
1131 | 1137 | /// Supports path anchors.
|
1132 | 1138 | /// - **ctrl-n**: nth control point where n is an integer starting at 0
|
1133 | 1139 | ///
|
1134 | 1140 | #let bezier(start, end, ..ctrl-style, name: none) = {
|
1135 | 1141 | // Extra positional arguments are treated like control points.
|
1136 | 1142 | let (ctrl, style) = (ctrl-style.pos(), ctrl-style.named())
|
1137 |
| - |
| 1143 | + |
1138 | 1144 | // Control point check
|
1139 | 1145 | let len = ctrl.len()
|
1140 | 1146 | assert(
|
1141 | 1147 | len in (1, 2),
|
1142 | 1148 | message: "Bezier curve expects 1 or 2 control points. Got " + str(len),
|
1143 | 1149 | )
|
1144 | 1150 | let coordinates = (start, ..ctrl, end)
|
1145 |
| - |
| 1151 | + |
1146 | 1152 | // Coordinates check
|
1147 | 1153 | let t = coordinates.map(coordinate.resolve-system)
|
1148 | 1154 |
|
|
1181 | 1187 | }
|
1182 | 1188 |
|
1183 | 1189 | return (
|
1184 |
| - ctx: ctx, |
| 1190 | + ctx: ctx, |
1185 | 1191 | name: name,
|
1186 | 1192 | anchors: anchors,
|
1187 | 1193 | drawables: drawables,
|
|
1230 | 1236 | /// - close (bool): Closes the curve with a straight line between the start and end of the curve.
|
1231 | 1237 | /// - name (none,str):
|
1232 | 1238 | ///
|
1233 |
| -/// ## Styling |
| 1239 | +/// ## Styling |
1234 | 1240 | /// *Root*: `catmull`
|
1235 | 1241 | ///
|
1236 | 1242 | /// Supports marks.
|
1237 |
| -/// |
| 1243 | +/// |
1238 | 1244 | /// - tension (float) = 0.5: How tight the curve should fit to the points. The higher the tension the less curvy the curve.
|
1239 | 1245 | ///
|
1240 | 1246 | /// ## Anchors
|
|
1305 | 1311 | /// - ta (auto, array): Outgoing tension at `pts.at(n)` from `pts.at(n)` to `pts.at(n+1)`. The number given must be one less than the number of points.
|
1306 | 1312 | /// - close (bool): Closes the curve with a proper smooth curve between the start and end of the curve.
|
1307 | 1313 | /// - name (none,str):
|
1308 |
| -/// |
| 1314 | +/// |
1309 | 1315 | /// ## Styling
|
1310 | 1316 | /// *Root* `hobby`
|
1311 | 1317 | ///
|
1312 |
| -/// Supports marks. |
| 1318 | +/// Supports marks. |
1313 | 1319 | /// - omega (array) = (1, 1): A tuple of floats that describe how curly the curve should be at each endpoint. When the curl is close to zero, the spline approaches a straight line near the endpoints. When the curl is close to one, it approaches a circular arc.
|
1314 | 1320 | ///
|
1315 | 1321 | /// ## Anchors
|
|
1385 | 1391 | ///
|
1386 | 1392 | /// Elements hidden via @@hide() are ignored.
|
1387 | 1393 | ///
|
| 1394 | +/// ## Anchors |
| 1395 | +/// **centroid**: Centroid of the _closed and non self-intersecting_ shape. Only exists if `close` is true. |
| 1396 | +/// Supports path anchors and shapes where all vertices share the same z-value. |
| 1397 | +/// |
1388 | 1398 | /// - body (elements): Elements with paths to be merged together.
|
1389 | 1399 | /// - close (bool): Close the path with a straight line from the start of the path to its end.
|
1390 | 1400 | /// - name (none,str):
|
1391 | 1401 | /// - ..style (style):
|
1392 |
| -/// |
1393 |
| -/// ## Anchors |
1394 |
| -/// Supports path anchors. |
1395 | 1402 | #let merge-path(body, close: false, name: none, ..style) = {
|
1396 | 1403 | // No extra positional arguments from the style sink
|
1397 | 1404 | assert.eq(
|
|
1400 | 1407 | message: "Unexpected positional arguments: " + repr(style.pos()),
|
1401 | 1408 | )
|
1402 | 1409 | let style = style.named()
|
1403 |
| - |
| 1410 | + |
1404 | 1411 | return (
|
1405 | 1412 | ctx => {
|
1406 | 1413 | let ctx = ctx
|
|
1429 | 1436 | let drawables = drawable.path(fill: style.fill, stroke: style.stroke, close: close, segments)
|
1430 | 1437 |
|
1431 | 1438 | let (transform, anchors) = anchor_.setup(
|
1432 |
| - auto, |
1433 |
| - (), |
| 1439 | + name => { |
| 1440 | + if name == "centroid" { |
| 1441 | + // Try finding a closed shapes center by |
| 1442 | + // Sampling it to a polygon. |
| 1443 | + return polygon.simple-centroid(polygon.from-segments(drawables.segments)) |
| 1444 | + } |
| 1445 | + }, |
| 1446 | + if close != none { ("centroid",) } else { () }, |
1434 | 1447 | name: name,
|
1435 | 1448 | transform: none,
|
1436 | 1449 | path-anchors: true,
|
|
0 commit comments