Compare commits
14 Commits
3bc103b9d7
...
v0.2.0
Author | SHA1 | Date | |
---|---|---|---|
19c60c5ecf | |||
df3a2ddf68
|
|||
42e8e26aa7
|
|||
b33531bef5
|
|||
ab1386e721
|
|||
1f155063a8
|
|||
d986284e4e
|
|||
a037727edf
|
|||
b0950f5e68
|
|||
eb09a23fc1
|
|||
c4f09a0a3e
|
|||
9275169e8c
|
|||
a198708743
|
|||
b66634d44f
|
@ -15,7 +15,7 @@ This package lets you render sequence diagrams directly in Typst. The following
|
||||
<td>
|
||||
|
||||
```typst
|
||||
#import "@preview/chronos:0.1.0"
|
||||
#import "@preview/chronos:0.2.0"
|
||||
#chronos.diagram({
|
||||
import chronos: *
|
||||
_par("Alice")
|
||||
|
2
TODO.md
2
TODO.md
@ -23,6 +23,6 @@
|
||||
- [ ] PlantUML parser
|
||||
- [ ] (Message numbering)
|
||||
- [ ] Mainframes
|
||||
- [ ] Different types of groups (alt/loop/etc.)
|
||||
- [x] Different types of groups (alt/loop/etc.)
|
||||
- [ ] Delays
|
||||
- [ ] Auto-fit in parent
|
@ -58,10 +58,12 @@
|
||||
/// - width (auto, int, float, length): Optional fixed width of the column\ If the column's content (e.g. sequence comments) is larger, it will overflow
|
||||
/// - margin (int, float, length): Additional margin to add to the column\ This margin is not included in `width` and `min-width`, but rather added separately
|
||||
/// - min-width (int, float, length): Minimum width of the column\ If set to a larger value than `width`, the latter will be overriden
|
||||
/// - max-width (int, float, length, none): Maximum width of the column\ If set to a lower value than `width`, the latter will be overriden\ If set to `none`, no restriction is applied
|
||||
#let _col(
|
||||
p1,
|
||||
p2,
|
||||
width: auto,
|
||||
margin: 0,
|
||||
min-width: 0
|
||||
min-width: 0,
|
||||
max-width: none
|
||||
) = {}
|
Binary file not shown.
@ -78,6 +78,15 @@ Alice <-- Bob: Another authentication Response
|
||||
_seq("Bob", "Alice", comment: "another authentication Response", dashed: true)
|
||||
})
|
||||
|
||||
#chronos.diagram({
|
||||
import chronos: *
|
||||
_seq("Alice", "Bob", comment: "Authentication Request")
|
||||
_delay()
|
||||
_seq("Bob", "Alice", comment: "Authentication Response")
|
||||
_delay(name: "5 minutes later")
|
||||
_seq("Bob", "Alice", comment: "Good Bye !")
|
||||
})
|
||||
|
||||
#chronos.diagram({
|
||||
import chronos: *
|
||||
_seq("Alice", "Bob", comment: "message 1")
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Binary file not shown.
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 92 KiB |
Binary file not shown.
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
BIN
manual.pdf
BIN
manual.pdf
Binary file not shown.
@ -30,8 +30,8 @@
|
||||
|
||||
#set page(numbering: "1/1", header: align(right)[chronos #sym.dash.em v#chronos.version])
|
||||
#set page(
|
||||
header: locate(loc => align(left)[chronos #sym.dash.em v#chronos.version]),
|
||||
footer: locate(loc => align(center, counter(page).display("1/1", both: true)))
|
||||
header: align(left)[chronos #sym.dash.em v#chronos.version],
|
||||
footer: context align(center, counter(page).display("1/1", both: true))
|
||||
)
|
||||
|
||||
= Introduction
|
||||
|
@ -1,6 +1,7 @@
|
||||
#import "utils.typ": get-group-span, fit-canvas
|
||||
#import "renderer.typ": render
|
||||
#import "participant.typ" as participant: _par, PAR-SPECIALS
|
||||
#import "sequence.typ": _seq
|
||||
|
||||
#let _gap(size: 20) = {
|
||||
return ((
|
||||
@ -18,14 +19,15 @@
|
||||
),)
|
||||
}
|
||||
|
||||
#let _col(p1, p2, width: auto, margin: 0, min-width: 0) = {
|
||||
#let _col(p1, p2, width: auto, margin: 0, min-width: 0, max-width: none) = {
|
||||
return ((
|
||||
type: "col",
|
||||
p1: p1,
|
||||
p2: p2,
|
||||
width: width,
|
||||
margin: margin,
|
||||
min-width: min-width
|
||||
min-width: min-width,
|
||||
max-width: max-width
|
||||
),)
|
||||
}
|
||||
|
||||
@ -38,7 +40,9 @@
|
||||
let elmts = elements
|
||||
let i = 0
|
||||
|
||||
// Flatten groups
|
||||
let activation-history = ()
|
||||
|
||||
// Flatten groups + convert returns
|
||||
while i < elmts.len() {
|
||||
let elmt = elmts.at(i)
|
||||
if elmt.type == "grp" {
|
||||
@ -62,6 +66,30 @@
|
||||
),) +
|
||||
elmts.slice(i+1)
|
||||
)
|
||||
} else if elmt.type == "seq" {
|
||||
if elmt.enable-dst {
|
||||
activation-history.push(elmt)
|
||||
}
|
||||
} else if elmt.type == "evt" {
|
||||
if elmt.event == "enable" {
|
||||
for elmt2 in elmts.slice(0, i).rev() {
|
||||
if elmt2.type == "seq" {
|
||||
activation-history.push(elmt2)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if elmt.type == "ret" {
|
||||
if activation-history.len() == 0 {
|
||||
panic("Cannot return if no lifeline is activated")
|
||||
}
|
||||
let seq = activation-history.pop()
|
||||
elmts.at(i) = _seq(
|
||||
seq.p2, seq.p1,
|
||||
comment: elmt.comment,
|
||||
disable-src: true,
|
||||
dashed: true
|
||||
).first()
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
@ -130,6 +158,21 @@
|
||||
} else if elmt.side == "right" {
|
||||
linked.push("]")
|
||||
}
|
||||
|
||||
let pars = none
|
||||
if type(elmt.pos) == str {
|
||||
pars = (elmt.pos,)
|
||||
} else if type(elmt.pos) == array {
|
||||
pars = elmt.pos
|
||||
}
|
||||
if pars != none {
|
||||
for par in pars {
|
||||
if not participant._exists(participants, par) {
|
||||
participants.push(_par(par).first())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
last-note = (
|
||||
elmt: elmt,
|
||||
i: i
|
||||
@ -198,7 +241,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
set text(font: "Source Sans 3")
|
||||
let canvas = render(participants, elmts)
|
||||
fit-canvas(canvas, width: width)
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
#import "@preview/cetz:0.2.2": draw
|
||||
#import "@preview/cetz:0.3.1": draw
|
||||
#import "consts.typ": *
|
||||
|
||||
#let _grp(name, desc: none, type: "default", elmts) = {
|
||||
|
@ -1,9 +1,9 @@
|
||||
#let version = version(0, 1, 1)
|
||||
#let version = version(0, 2, 0)
|
||||
#import "diagram.typ": diagram, from-plantuml, _gap, _evt, _col
|
||||
|
||||
#import "sequence.typ": _seq
|
||||
#import "sequence.typ": _seq, _ret
|
||||
#import "group.typ": _grp, _loop, _alt, _opt, _break
|
||||
#import "participant.typ": _par
|
||||
#import "separator.typ": _sep
|
||||
#import "separator.typ": _sep, _delay
|
||||
#import "note.typ": _note
|
||||
#import "sync.typ": _sync
|
@ -1,4 +1,4 @@
|
||||
#import "@preview/cetz:0.2.2": draw
|
||||
#import "@preview/cetz:0.3.1": draw
|
||||
#import "consts.typ": *
|
||||
|
||||
#let SIDES = (
|
||||
@ -25,6 +25,9 @@
|
||||
panic("Aligned notes can only be over a participant (got side '" + side + "')")
|
||||
}
|
||||
}
|
||||
if color == auto {
|
||||
color = COL-NOTE
|
||||
}
|
||||
return ((
|
||||
type: "note",
|
||||
side: side,
|
||||
|
@ -1,4 +1,4 @@
|
||||
#import "@preview/cetz:0.2.2": draw
|
||||
#import "@preview/cetz:0.3.1": draw
|
||||
#import "consts.typ": *
|
||||
|
||||
#let PAR-SPECIALS = "?[]"
|
||||
@ -13,6 +13,7 @@
|
||||
"queue",
|
||||
"custom"
|
||||
)
|
||||
#let DEFAULT-COLOR = rgb("#E2E2F0")
|
||||
|
||||
#let _par(
|
||||
name,
|
||||
@ -20,11 +21,14 @@
|
||||
from-start: true,
|
||||
invisible: false,
|
||||
shape: "participant",
|
||||
color: rgb("#E2E2F0"),
|
||||
color: DEFAULT-COLOR,
|
||||
custom-image: none,
|
||||
show-bottom: true,
|
||||
show-top: true,
|
||||
) = {
|
||||
if color == auto {
|
||||
color = DEFAULT-COLOR
|
||||
}
|
||||
return ((
|
||||
type: "par",
|
||||
name: name,
|
||||
|
@ -1,4 +1,4 @@
|
||||
#import "@preview/cetz:0.2.2": canvas, draw
|
||||
#import "@preview/cetz:0.3.1": canvas, draw
|
||||
#import "utils.typ": get-participants-i, get-style, normalize-units
|
||||
#import "group.typ"
|
||||
#import "participant.typ"
|
||||
@ -218,10 +218,13 @@
|
||||
if elmt.width != auto {
|
||||
widths.at(i) = normalize-units(elmt.width)
|
||||
}
|
||||
widths.at(i) = calc.max(
|
||||
widths.at(i),
|
||||
normalize-units(elmt.min-width)
|
||||
) + normalize-units(elmt.margin)
|
||||
|
||||
let width = widths.at(i)
|
||||
width = calc.max(width, normalize-units(elmt.min-width))
|
||||
if elmt.max-width != none {
|
||||
width = calc.min(width, normalize-units(elmt.max-width))
|
||||
}
|
||||
widths.at(i) = width + normalize-units(elmt.margin)
|
||||
}
|
||||
}
|
||||
|
||||
@ -322,6 +325,26 @@
|
||||
// Gap
|
||||
} else if elmt.type == "gap" {
|
||||
y -= elmt.size
|
||||
|
||||
// Delay
|
||||
} else if elmt.type == "delay" {
|
||||
let y0 = y
|
||||
let y1 = y - elmt.size
|
||||
for (i, line) in lifelines.enumerate() {
|
||||
line.lines.push(("delay-start", y0))
|
||||
line.lines.push(("delay-end", y1))
|
||||
lifelines.at(i) = line
|
||||
}
|
||||
if elmt.name != none {
|
||||
let x0 = x-pos.first()
|
||||
let x1 = x-pos.last()
|
||||
shapes += draw.content(
|
||||
((x0 + x1) / 2, (y0 + y1) / 2),
|
||||
anchor: "center",
|
||||
elmt.name
|
||||
)
|
||||
}
|
||||
y = y1
|
||||
|
||||
// Event
|
||||
} else if elmt.type == "evt" {
|
||||
@ -435,6 +458,28 @@
|
||||
if event == "destroy" {
|
||||
destructions.push((x + lvl * LIFELINE-W / 2, line.at(1)))
|
||||
}
|
||||
} else if event == "delay-start" {
|
||||
draw.line(
|
||||
(x, last-y),
|
||||
(x, line.at(1)),
|
||||
stroke: (
|
||||
dash: "dashed",
|
||||
paint: gray.darken(40%),
|
||||
thickness: .5pt
|
||||
)
|
||||
)
|
||||
last-y = line.at(1)
|
||||
} else if event == "delay-end" {
|
||||
draw.line(
|
||||
(x, last-y),
|
||||
(x, line.at(1)),
|
||||
stroke: (
|
||||
dash: "loosely-dotted",
|
||||
paint: gray.darken(40%),
|
||||
thickness: .8pt
|
||||
)
|
||||
)
|
||||
last-y = line.at(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
#import "@preview/cetz:0.2.2": draw
|
||||
#import "@preview/cetz:0.3.1": draw
|
||||
#import "consts.typ": *
|
||||
|
||||
#let _sep(name) = {
|
||||
@ -8,6 +8,14 @@
|
||||
),)
|
||||
}
|
||||
|
||||
#let _delay(name: none, size: 30) = {
|
||||
return ((
|
||||
type: "delay",
|
||||
name: name,
|
||||
size: size
|
||||
),)
|
||||
}
|
||||
|
||||
#let render(x-pos, elmt, y) = {
|
||||
let shapes = ()
|
||||
y -= Y-SPACE
|
||||
|
@ -1,9 +1,12 @@
|
||||
#import "@preview/cetz:0.2.2": draw, vector
|
||||
#import "@preview/cetz:0.3.1": draw, vector
|
||||
#import "consts.typ": *
|
||||
#import "participant.typ"
|
||||
#import "note.typ"
|
||||
|
||||
#let get-arrow-marks(sym, color) = {
|
||||
if sym == none {
|
||||
return none
|
||||
}
|
||||
if type(sym) == array {
|
||||
return sym.map(s => get-arrow-marks(s, color))
|
||||
}
|
||||
@ -85,24 +88,39 @@
|
||||
),)
|
||||
}
|
||||
|
||||
#let _ret(comment: none) = {
|
||||
return ((
|
||||
type: "ret",
|
||||
comment: comment
|
||||
),)
|
||||
}
|
||||
|
||||
#let render(pars-i, x-pos, participants, elmt, y, lifelines) = {
|
||||
let shapes = ()
|
||||
|
||||
y -= Y-SPACE
|
||||
|
||||
let i1 = pars-i.at(elmt.p1)
|
||||
let i2 = pars-i.at(elmt.p2)
|
||||
let width = calc.abs(x-pos.at(i1) - x-pos.at(i2))
|
||||
|
||||
let h = 0
|
||||
let comment = if elmt.comment == none {none} else {
|
||||
let w = calc.min(width * 1pt, measure(elmt.comment).width)
|
||||
box(
|
||||
width: if i1 == i2 {auto} else {w},
|
||||
elmt.comment
|
||||
)
|
||||
}
|
||||
// Reserve space for comment
|
||||
if elmt.comment != none {
|
||||
h = calc.max(h, measure(box(elmt.comment)).height / 1pt + 6)
|
||||
if comment != none {
|
||||
h = calc.max(h, measure(comment).height / 1pt + 6)
|
||||
}
|
||||
if "linked-note" in elmt {
|
||||
h = calc.max(h, note.get-size(elmt.linked-note).height / 2)
|
||||
}
|
||||
y -= h
|
||||
|
||||
let i1 = pars-i.at(elmt.p1)
|
||||
let i2 = pars-i.at(elmt.p2)
|
||||
|
||||
let start-info = (
|
||||
i: i1,
|
||||
x: x-pos.at(i1),
|
||||
@ -159,7 +177,7 @@
|
||||
let m = measure(box(par.display-name))
|
||||
let f = if i1 > i2 {-1} else {1}
|
||||
end-info.x -= (m.width + PAR-PAD.last() * 2) / 2pt * f
|
||||
shapes += participant.render(x-pos, par, y: end-info.y - CREATE-OFFSET)
|
||||
shapes += participant.render(x-pos, par, y: end-info.y)
|
||||
}
|
||||
|
||||
end-info.ll-lvl = lifelines.at(i2).level * LIFELINE-W / 2
|
||||
@ -234,7 +252,7 @@
|
||||
(x2, end-info.y)
|
||||
)
|
||||
|
||||
if elmt.comment != none {
|
||||
if comment != none {
|
||||
comment-anchor = (
|
||||
start: if x-mid < x1 {"south-east"} else {"south-west"},
|
||||
end: if x-mid < x1 {"south-west"} else {"south-east"},
|
||||
@ -258,7 +276,7 @@
|
||||
(x2, end-info.y)
|
||||
)
|
||||
|
||||
if elmt.comment != none {
|
||||
if comment != none {
|
||||
let start-pt = pts.first()
|
||||
let end-pt = pts.last()
|
||||
if elmt.start-tip != "" {
|
||||
@ -294,7 +312,7 @@
|
||||
|
||||
// Start circle tip
|
||||
if is-circle-tip(elmt.start-tip) {
|
||||
shapes += draw.circle(pts.first(), radius: CIRCLE-TIP-RADIUS, stroke: elmt.color, fill: none, name: "_circle-start-tip")
|
||||
shapes += draw.circle(pts.first(), radius: CIRCLE-TIP-RADIUS, stroke: none, fill: elmt.color, name: "_circle-start-tip")
|
||||
pts.at(0) = "_circle-start-tip"
|
||||
|
||||
// Start cross tip
|
||||
@ -316,7 +334,7 @@
|
||||
|
||||
// End circle tip
|
||||
if is-circle-tip(elmt.end-tip) {
|
||||
shapes += draw.circle(pts.last(), radius: 3, stroke: elmt.color, fill: none, name: "_circle-end-tip")
|
||||
shapes += draw.circle(pts.last(), radius: 3, stroke: none, fill: elmt.color, name: "_circle-end-tip")
|
||||
pts.at(pts.len() - 1) = "_circle-end-tip"
|
||||
|
||||
// End cross tip
|
||||
@ -338,27 +356,26 @@
|
||||
|
||||
shapes += draw.line(..pts, ..style)
|
||||
|
||||
if elmt.comment != none {
|
||||
if comment != none {
|
||||
shapes += draw.content(
|
||||
comment-pt,
|
||||
elmt.comment,
|
||||
comment,
|
||||
anchor: comment-anchor,
|
||||
angle: comment-angle,
|
||||
padding: 3pt
|
||||
)
|
||||
}
|
||||
|
||||
if elmt.create-dst {
|
||||
let dst-line = lifelines.at(i2)
|
||||
dst-line.lines.push(("create", end-info.y))
|
||||
lifelines.at(i2) = dst-line
|
||||
}
|
||||
if elmt.enable-dst {
|
||||
let dst-line = lifelines.at(i2)
|
||||
dst-line.lines.push(("enable", end-info.y, elmt.lifeline-style))
|
||||
lifelines.at(i2) = dst-line
|
||||
}
|
||||
if elmt.create-dst {
|
||||
end-info.y -= CREATE-OFFSET
|
||||
let dst-line = lifelines.at(i2)
|
||||
dst-line.lines.push(("create", end-info.y))
|
||||
lifelines.at(i2) = dst-line
|
||||
}
|
||||
|
||||
if "linked-note" in elmt {
|
||||
let m = note.get-size(elmt.linked-note)
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "chronos"
|
||||
version = "0.1.1"
|
||||
compiler = "0.11.0"
|
||||
version = "0.2.0"
|
||||
compiler = "0.12.0"
|
||||
repository = "https://git.kb28.ch/HEL/chronos"
|
||||
entrypoint = "src/lib.typ"
|
||||
authors = [
|
||||
|
Reference in New Issue
Block a user