12 Commits

29 changed files with 2918 additions and 24 deletions

View File

@ -22,6 +22,7 @@
- [ ] Add args verification to catch user errors + pretty error messages
- [ ] PlantUML parser
- [ ] (Message numbering)
- [ ] Different types of groups (alt/loop/etc.)
- [ ] Mainframes
- [x] Different types of groups (alt/loop/etc.)
- [ ] Delays
- [ ] Auto-fit in parent

39
docs/example.typ Normal file
View File

@ -0,0 +1,39 @@
#import "../src/lib.typ" as chronos
#let example-preamble = "import \"../src/lib.typ\": *;"
#let example-scope = (
chronos: chronos
)
#let example(src, show-src: true, vertical: false, fill: true, wrap: true) = {
src = src.text
let full-src = example-preamble + src
let body = eval(full-src, scope: example-scope)
let img = if wrap { chronos.diagram(body) } else { body }
block(width: 100%,
align(center,
box(
stroke: black + 1pt,
radius: .5em,
fill: if fill {color.white.darken(5%)} else {none},
if show-src {
let src-block = align(left, raw(src, lang: "typc"))
table(
columns: if vertical {1} else {2},
inset: 1em,
align: horizon + center,
stroke: none,
img,
if vertical {table.hline()} else {table.vline()}, src-block
)
} else {
table(
inset: 1em,
img
)
}
)
)
)
}

155
docs/examples.typ Normal file
View File

@ -0,0 +1,155 @@
#import "example.typ": example
#let seq-comm-align = example(```
_par("p1", display-name: "Start participant")
_par("p2", display-name: "End participant")
let alignments = (
"start", "end",
"left", "right",
"center"
)
for a in alignments {
_seq(
"p2", "p1",
comment: raw(a),
comment-align: a
)
}
```)
#let seq-tips = example(```
let _seq = _seq.with(comment-align: "center")
_par("a", display-name: "Alice")
_par("b", display-name: "Bob")
_seq("a", "b", comment: "Various tips", end-tip: "")
_seq("a", "b", end-tip: ">", comment: `->`)
_seq("a", "b", end-tip: ">>", comment: `->>`)
_seq("a", "b", end-tip: "\\", comment: `-\`)
_seq("a", "b", end-tip: "\\\\", comment: `-\\`)
_seq("a", "b", end-tip: "/", comment: `-/`)
_seq("a", "b", end-tip: "//", comment: `-//`)
_seq("a", "b", end-tip: "x", comment: `->x`)
_seq("a", "b", start-tip: "x", comment: `x->`)
_seq("a", "b", start-tip: "o", comment: `o->`)
_seq("a", "b", end-tip: ("o", ">"), comment: `->o`)
_seq("a", "b", start-tip: "o",
end-tip: ("o", ">"), comment: `o->o`)
_seq("a", "b", start-tip: ">",
end-tip: ">", comment: `<->`)
_seq("a", "b", start-tip: ("o", ">"),
end-tip: ("o", ">"), comment: `o<->o`)
_seq("a", "b", start-tip: "x",
end-tip: "x", comment: `x<->x`)
_seq("a", "b", end-tip: ("o", ">>"), comment: `->>o`)
_seq("a", "b", end-tip: ("o", "\\"), comment: `-\o`)
_seq("a", "b", end-tip: ("o", "\\\\"), comment: `-\\o`)
_seq("a", "b", end-tip: ("o", "/"), comment: `-/o`)
_seq("a", "b", end-tip: ("o", "//"), comment: `-//o`)
_seq("a", "b", start-tip: "x",
end-tip: ("o", ">"), comment: `x->o`)
```)
#let grp = example(```
_par("a", display-name: "Alice")
_par("b", display-name: "Bob")
_grp("Group 1", desc: "Description", {
_seq("a", "b", comment: "Authentication")
_grp("loop", desc: "1000 times", {
_seq("a", "b", comment: "DoS Attack")
})
_seq("a", "b", end-tip: "x")
})
```)
#let alt = example(```
_par("a", display-name: "Alice")
_par("b", display-name: "Bob")
_alt(
"first encounter", {
_seq("a", "b", comment: "Who are you ?")
_seq("b", "a", comment: "I'm Bob")
},
"know eachother", {
_seq("a", "b", comment: "Hello Bob")
_seq("b", "a", comment: "Hello Alice")
},
"best friends", {
_seq("a", "b", comment: "Hi !")
_seq("b", "a", comment: "Hi !")
}
)
```)
#let loop = example(```
_par("a", display-name: "Alice")
_par("b", display-name: "Bob")
_loop("default loop", {
_seq("a", "b", comment: "Are you here ?")
})
_gap()
_loop("min loop", min: 1, {
_seq("a", "b", comment: "Are you here ?")
})
_gap()
_loop("min-max loop", min: 1, max: 5, {
_seq("a", "b", comment: "Are you still here ?")
})
```)
#let sync = example(```
_par("alice", display-name: "Alice")
_par("bob", display-name: "Bob")
_par("craig", display-name: "Craig")
_seq("bob", "alice") // Unsynchronized
_seq("bob", "craig") // "
_sync({
_seq("bob", "alice") // Synchronized
_seq("bob", "craig") // "
})
_seq("alice", "bob") // Unsynchronized
_seq("craig", "bob") // "
_sync({
_seq("alice", "bob") // Synchronized
_seq("craig", "bob") // "
})
```)
#let gaps-seps = example(```
_par("alice", display-name: "Alice")
_par("bob", display-name: "Bob")
_seq("alice", "bob", comment: "Hello")
_gap(size: 10)
_seq("bob", "alice", comment: "Hi")
_sep("Another day")
_seq("alice", "bob", comment: "Hello again")
```)
#let notes-shapes = example(```
_par("alice", display-name: "Alice")
_par("bob", display-name: "Bob")
_note("over", `default`, pos: "alice")
_note("over", `rect`, pos: "bob", shape: "rect")
_note("over", `hex`, pos: ("alice", "bob"), shape: "hex")
```)
#let notes-sides = example(```
_par("alice", display-name: "Alice")
_par("bob", display-name: "Bob")
_par("charlie", display-name: "Charlie")
_note("left", [`left` of Alice], pos: "alice")
_note("right", [`right` of Charlie], pos: "charlie")
_note("over", [`over` Alice and Bob], pos: ("alice", "bob"))
_note("across", [`across` all participants])
_seq("alice", "bob")
_note("left", [linked with sequence])
_note("over", [A note], pos: "alice")
_note("over", [Aligned note], pos: "charlie", aligned: true)
```, vertical: true)

8
docs/gaps_seps.typ Normal file
View File

@ -0,0 +1,8 @@
/// Creates a gap before the next element
/// - size (int): Size of the gap
#let _gap(size: 20) = {}
/// Creates a separator before the next element
/// #examples.gaps-seps
/// - name (content): Name to display in the middle of the separator
#let _sep(name) = {}

58
docs/groups.typ Normal file
View File

@ -0,0 +1,58 @@
/// Creates a group of sequences
/// #examples.grp
/// - name (content): The group's name
/// - desc (none, content): Optional description
/// - type (str): The groups's type (should only be set through other functions like @@_alt() or @@_loop() )
/// - elmts (array): Elements inside the group (can be sequences, other groups, notes, etc.)
#let _grp(
name,
desc: none,
type: "default",
elmts
) = {}
/// Creates an alt-else group of sequences
///
/// It contains at least one section but can have as many as needed
/// #examples.alt
/// - desc (content): The alt's label
/// - elmts (array): Elements inside the alt's first section
/// - ..args (content, array): Complementary "else" sections.\ You can add as many else sections as you need by passing a content (else section label) followed by an array of elements (see example)
#let _alt(
desc,
elmts,
..args
)
/// Creates a looped group of sequences
/// #examples.loop
/// - desc (content): Loop description
/// - min (none, number): Optional lower bound of the loop
/// - max (auto, number): Upper bound of the loop. If left as `auto` and `min` is set, it will be infinity (`'*'`)
/// - elmts (array): Elements inside the group
#let _loop(
desc,
min: none,
max: auto,
elmts
) = {}
/// Synchronizes multiple sequences\
/// All elements inside a synchronized group will start at the same time
/// #examples.sync
/// - elmts (array): Synchronized elements (generally sequences or notes)
#let _sync(
elmts
)
/// Creates an optional group\
/// This is a simple wrapper around @@_grp()
/// - desc (content): Group description
/// - elmts (array): Elements inside the group
#let _opt(desc, elmts) = {}
/// Creates a break group\
/// This is a simple wrapper around @@_grp()
/// - desc (content): Group description
/// - elmts (array): Elements inside the group
#let _break(desc, elmts) = {}

23
docs/notes.typ Normal file
View File

@ -0,0 +1,23 @@
/// Creates a note
/// - side (str): The side on which to place the note (see @@SIDES for accepted values)
/// - content (content): The note's content
/// - pos (none, str, array): Optional participant(s) on which to draw next to / over. If `side` is "left" or "right", sets next to which participant the note is placed. If `side` is "over", sets over which participant(s) it is placed
/// - color (color): The note's color
/// - shape (str): The note's shape (see @@SHAPES for accepted values)
/// - aligned (bool): True if the note is aligned with another note, in which case `side` must be `"over"`, false otherwise
#let _note(
side,
content,
pos: none,
color: rgb("#FEFFDD"),
shape: "default",
aligned: false
) = {}
/// Accepted values for `shape` argument of @@_note()
/// #examples.notes-shapes
#let SHAPES = ("default", "rect", "hex")
/// Accepted values for `side` argument of @@_note()
/// #examples.notes-sides
#let SIDES = ("left", "right", "over", "across")

67
docs/participants.typ Normal file
View File

@ -0,0 +1,67 @@
/// Possible participant shapes
/// #box(width: 100%, align(center)[
/// #chronos.diagram({
/// import chronos: *
/// let _par = _par.with(show-bottom: false)
/// _par("Foo", display-name: "participant", shape: "participant")
/// _par("Foo1", display-name: "actor", shape: "actor")
/// _par("Foo2", display-name: "boundary", shape: "boundary")
/// _par("Foo3", display-name: "control", shape: "control")
/// _par("Foo4", display-name: "entity", shape: "entity")
/// _par("Foo5", display-name: "database", shape: "database")
/// _par("Foo6", display-name: "collections", shape: "collections")
/// _par("Foo7", display-name: "queue", shape: "queue")
/// _par("Foo8", display-name: "custom", shape: "custom", custom-image: TYPST)
/// _gap()
/// })
/// ])
#let SHAPES = (
"participant",
"actor",
"boundary",
"control",
"entity",
"database",
"collections",
"queue",
"custom"
)
/// Creates a new participant
/// - name (str): Unique participant name used as reference in other functions
/// - display-name (auto, content): Name to display in the diagram. If set to `auto`, `name` is used
/// - from-start (bool): If set to true, the participant is created at the top of the diagram. Otherwise, it is created at the first reference
/// - invisible (bool): If set to true, the participant will not be shown
/// - shape (str): The shape of the participant. Possible values in @@SHAPES
/// - color (color): The participant's color
/// - custom-image (none, image): If shape is 'custom', sets the custom image to display
/// - show-bottom (bool): Whether to display the bottom shape
/// - show-top (bool): Whether to display the top shape
/// -> array
#let _par(
name,
display-name: auto,
from-start: true,
invisible: false,
shape: "participant",
color: rgb("#E2E2F0"),
custom-image: none,
show-bottom: true,
show-top: true,
) = {}
/// Sets some options for columns between participants
///
/// Parameters `p1` and `p2` MUST be consecutive participants (also counting found/lost messages), but they do not need to be in the left to right order
/// - p1 (str): The first neighbouring participant
/// - p2 (str): The second neighbouring participant
/// - 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
#let _col(
p1,
p2,
width: auto,
margin: 0,
min-width: 0
) = {}

60
docs/sequences.typ Normal file
View File

@ -0,0 +1,60 @@
/// Manually adds an event to the given participant
/// - participant (str): The participant concerned by the event
/// - event (str): The event type (see @@EVENTS for ccepted values)
#let _evt(participant, event) = {}
/// Creates a sequence / message between two participants
/// - p1 (str): Start participant
/// - p2 (str): End participant
/// - comment (none, content): Optional comment to display along the arrow
/// - comment-align (str): Where to align the comment with respect to the arrow (see @@comment-align for accepted values)
/// - dashed (bool): Whether the arrow's stroke is dashed or not
/// - start-tip (str): Start arrow tip (see @@tips for accepted values)
/// - end-tip (str): End arrow tip (see @@tips for accepted values)
/// - color (color): Arrow's color
/// - flip (bool): If true, the arrow is flipped (goes from end to start). This is particularly useful for self calls, to change the side on which the arrow appears
/// - enable-dst (bool): If true, enables the destination lifeline
/// - create-dst (bool): If true, creates the destination lifeline and participant
/// - disable-dst (bool): If true, disables the destination lifeline
/// - destroy-dst (bool): If true, destroys the destination lifeline and participant
/// - disable-src (bool): If true, disables the source lifeline
/// - destroy-src (bool): If true, destroy the source lifeline and participant
/// - lifeline-style (auto, dict): Optional styling options for lifeline rectangles (see CeTZ documentation for more information on all possible values)
/// - slant (none, int): Optional slant of the arrow
/// -> array
#let _seq(
p1,
p2,
comment: none,
comment-align: "left",
dashed: false,
start-tip: "",
end-tip: ">",
color: black,
flip: false,
enable-dst: false,
create-dst: false,
disable-dst: false,
destroy-dst: false,
disable-src: false,
destroy-src: false,
lifeline-style: auto,
slant: none
) = {}
/// Accepted values for `event` argument of @@_evt()
///
/// `EVENTS = ("create", "destroy", "enable", "disable")`
#let EVENTS = ("create", "destroy", "enable", "disable")
/// Accepted values for `start-tip` and `end-tip` arguments of @@_seq()
/// #examples.seq-tips
#let tips = (
"", ">", ">>", "\\", "\\\\", "/", "//", "x", "o",
)
/// Accepted values for `comment-align` argument of @@_seq()
/// #examples.seq-comm-align
#let comment-align = (
"start", "end", "left", "center", "right"
)

BIN
gallery/doc_examples.pdf Normal file

Binary file not shown.

689
gallery/doc_examples.typ Normal file
View File

@ -0,0 +1,689 @@
#import "../src/lib.typ": from-plantuml
#set page(width: auto, height: auto)
#let examples = (
(
[Basic Examples],
```
@startuml
Alice -> Bob: Authentication Request
Bob --> Alice: Authentication Response
Alice -> Bob: Another authentication Request
Alice <-- Bob: Another authentication Response
@enduml
```
),
(
[Declaring participant],
```
@startuml
participant Participant as Foo
actor Actor as Foo1
boundary Boundary as Foo2
control Control as Foo3
entity Entity as Foo4
database Database as Foo5
collections Collections as Foo6
queue Queue as Foo7
Foo -> Foo1 : To actor
Foo -> Foo2 : To boundary
Foo -> Foo3 : To control
Foo -> Foo4 : To entity
Foo -> Foo5 : To database
Foo -> Foo6 : To collections
Foo -> Foo7: To queue
@enduml
```
),
(
[Declaring participant (2)],
```
@startuml
actor Bob #red
' The only difference between actor
'and participant is the drawing
participant Alice
participant "I have a really\nlong name" as L #99FF99
/' You can also declare:
participant L as "I have a really\nlong name" #99FF99
'/
Alice->Bob: Authentication Request
Bob->Alice: Authentication Response
Bob->L: Log transaction
@enduml
```
),
(
[Use non-letters in participants],
```
@startuml
Alice -> "Bob()" : Hello
"Bob()" -> "This is very\nlong" as Long
' You can also declare:
' "Bob()" -> Long as "This is very\nlong"
Long --> "Bob()" : ok
@enduml
```
),
(
[Message to Self],
```
@startuml
Alice -> Alice: This is a signal to self.\nIt also demonstrates\nmultiline \ntext
@enduml
```
),
(
[Message to Self (2)],
```
@startuml
Alice <- Alice: This is a signal to self.\nIt also demonstrates\nmultiline \ntext
@enduml
```
),
(
[Change arrow style],
```
@startuml
Bob ->x Alice
Bob -> Alice
Bob ->> Alice
Bob -\ Alice
Bob \\- Alice
Bob //-- Alice
Bob ->o Alice
Bob o\\-- Alice
Bob <-> Alice
Bob <->o Alice
@enduml
```
),
(
[Grouping message],
```
@startuml
Alice -> Bob: Authentication Request
alt successful case
Bob -> Alice: Authentication Accepted
else some kind of failure
Bob -> Alice: Authentication Failure
group My own label
Alice -> Log : Log attack start
loop 1000 times
Alice -> Bob: DNS Attack
end
Alice -> Log : Log attack end
end
else Another type of failure
Bob -> Alice: Please repeat
end
@enduml
```
),
(
[Secondary group label],
```
@startuml
Alice -> Bob: Authentication Request
Bob -> Alice: Authentication Failure
group My own label [My own label 2]
Alice -> Log : Log attack start
loop 1000 times
Alice -> Bob: DNS Attack
end
Alice -> Log : Log attack end
end
@enduml
```
),
(
[Notes on messages],
```
@startuml
Alice->Bob : hello
note left: this is a first note
Bob->Alice : ok
note right: this is another note
Bob->Bob : I am thinking
note left
a note
can also be defined
on several lines
end note
@enduml
```
),
(
[Some other notes],
```
@startuml
participant Alice
participant Bob
note left of Alice #aqua
This is displayed
left of Alice.
end note
note right of Alice: This is displayed right of Alice.
note over Alice: This is displayed over Alice.
note over Alice, Bob #FFAAAA: This is displayed\n over Bob and Alice.
note over Bob, Alice
This is yet another
example of
a long note.
end note
@enduml
```
),
(
[Changing notes shape \[hnote, rnote\]],
```
@startuml
caller -> server : conReq
hnote over caller : idle
caller <- server : conConf
rnote over server
"r" as rectangle
"h" as hexagon
endrnote
rnote over server
this is
on several
lines
endrnote
hnote over caller
this is
on several
lines
endhnote
@enduml
```
),
(
[Note over all participants \[across\]],
```
@startuml
Alice->Bob:m1
Bob->Charlie:m2
note over Alice, Charlie: Old method for note over all part. with:\n ""note over //FirstPart, LastPart//"".
note across: New method with:\n""note across""
Bob->Alice
hnote across:Note across all part.
@enduml
```
),
(
[Several notes aligned at the same level \[/\]],
```
@startuml
note over Alice : initial state of Alice
note over Bob : initial state of Bob
Bob -> Alice : hello
@enduml
```
),
(
[Several notes aligned at the same level \[/\] (2)],
```
@startuml
note over Alice : initial state of Alice
/ note over Bob : initial state of Bob
Bob -> Alice : hello
@enduml
```
),
(
[Divider or separator],
```
@startuml
== Initialization ==
Alice -> Bob: Authentication Request
Bob --> Alice: Authentication Response
== Repetition ==
Alice -> Bob: Another authentication Request
Alice <-- Bob: another authentication Response
@enduml
```
),
(
[Delay],
```
@startuml
Alice -> Bob: Authentication Request
...
Bob --> Alice: Authentication Response
...5 minutes later...
Bob --> Alice: Good Bye !
@enduml
```
),
(
[Space],
```
@startuml
Alice -> Bob: message 1
Bob --> Alice: ok
|||
Alice -> Bob: message 2
Bob --> Alice: ok
||45||
Alice -> Bob: message 3
Bob --> Alice: ok
@enduml
```
),
(
[Lifeline Activation and Destruction],
```
@startuml
participant User
User -> A: DoWork
activate A
A -> B: << createRequest >>
activate B
B -> C: DoWork
activate C
C --> B: WorkDone
destroy C
B --> A: RequestCreated
deactivate B
A -> User: Done
deactivate A
@enduml
```
),
(
[Lifeline Activation and Destruction (2)],
```
@startuml
participant User
User -> A: DoWork
activate A #FFBBBB
A -> A: Internal call
activate A #DarkSalmon
A -> B: << createRequest >>
activate B
B --> A: RequestCreated
deactivate B
deactivate A
A -> User: Done
deactivate A
@enduml
```
),
/*(
[Lifeline Activation and Destruction (3)],
```
@startuml
'autoactivate on
alice -> bob : hello
bob -> bob : self call
bill -> bob /'#005500'/ : hello from thread 2
bob -> george ** : create
return done in thread 2
return rc
bob -> george !! : delete
return success
@enduml
```
),*/
(
[Return],
```
@startuml
Bob -> Alice : hello
activate Alice
Alice -> Alice : some action
return bye
@enduml
```
),
(
[Participant creation],
```
@startuml
Bob -> Alice : hello
create Other
Alice -> Other : new
create /'control'/ String
Alice -> String
note right : You can also put notes!
Alice --> Bob : ok
@enduml
```
),
(
[Shortcut syntax for activation, deactivation, creation],
```
@startuml
alice -> bob ++ : hello
bob -> bob ++ : self call
bob -> bib ++ /' #005500'/ : hello
bob -> george ** : create
return done
return rc
bob -> george !! : delete
return success
@enduml
```
),
(
[Shortcut syntax for activation, deactivation, creation (2)],
```
@startuml
alice -> bob ++ : hello1
bob -> charlie --++ : hello2
charlie --> alice -- : ok
@enduml
```
),
(
[Shortcut syntax for activation, deactivation, creation (3)],
```
@startuml
alice -> bob ++ /'#gold'/: hello
bob -> alice --++ /'#gold'/: you too
alice -> bob --: step1
alice -> bob : step2
@enduml
@enduml
```
),
(
[Incoming and outgoing messages],
```
@startuml
[-> A: DoWork
activate A
A -> A: Internal call
activate A
A ->] : << createRequest >>
A<--] : RequestCreated
deactivate A
[<- A: Done
deactivate A
@enduml
```
),
(
[Incoming and outgoing messages (2)],
```
@startuml
participant Alice
participant Bob #lightblue
Alice -> Bob
Bob -> Carol
...
[-> Bob
[o-> Bob
[o->o Bob
[x-> Bob
...
[<- Bob
[x<- Bob
...
Bob ->]
Bob ->o]
Bob o->o]
Bob ->x]
...
Bob <-]
Bob x<-]
@enduml
```
),
(
[Short arrows for incoming and outgoing messages],
```
@startuml
?-> Alice : ""?->""\n**short** to actor1
[-> Alice : ""[->""\n**from start** to actor1
[-> Bob : ""[->""\n**from start** to actor2
?-> Bob : ""?->""\n**short** to actor2
Alice ->] : ""->]""\nfrom actor1 **to end**
Alice ->? : ""->?""\n**short** from actor1
Alice -> Bob : ""->"" \nfrom actor1 to actor2
@enduml
```
),
(
[Normal arrow],
```
@startuml
participant Alice as a
participant Bob as b
a -> b : ""-> ""
a ->> b : ""->> ""
a -\ b : ""-\ ""
a -\\ b : ""-\\\\""
a -/ b : ""-/ ""
a -// b : ""-// ""
a ->x b : ""->x ""
a x-> b : ""x-> ""
a o-> b : ""o-> ""
a ->o b : ""->o ""
a o->o b : ""o->o ""
a <-> b : ""<-> ""
a o<->o b : ""o<->o""
a x<->x b : ""x<->x""
a ->>o b : ""->>o ""
a -\o b : ""-\o ""
a -\\o b : ""-\\\\o""
a -/o b : ""-/o ""
a -//o b : ""-//o ""
a x->o b : ""x->o ""
@enduml
```
),
(
[Itself arrow],
```
@startuml
participant Alice as a
participant Bob as b
a -> a : ""-> ""
a ->> a : ""->> ""
a -\ a : ""-\ ""
a -\\ a : ""-\\\\""
a -/ a : ""-/ ""
a -// a : ""-// ""
a ->x a : ""->x ""
a x-> a : ""x-> ""
a o-> a : ""o-> ""
a ->o a : ""->o ""
a o->o a : ""o->o ""
a <-> a : ""<-> ""
a o<->o a : ""o<->o""
a x<->x a : ""x<->x""
a ->>o a : ""->>o ""
a -\o a : ""-\o ""
a -\\o a : ""-\\\\o""
a -/o a : ""-/o ""
a -//o a : ""-//o ""
a x->o a : ""x->o ""
@enduml
```
),
(
[Incoming messages (with '|')],
```
@startuml
participant Alice as a
participant Bob as b
[-> b : ""[-> ""
[->> b : ""[->> ""
[-\ b : ""[-\ ""
[-\\ b : ""[-\\\\""
[-/ b : ""[-/ ""
[-// b : ""[-// ""
[->x b : ""[->x ""
[x-> b : ""[x-> ""
[o-> b : ""[o-> ""
[->o b : ""[->o ""
[o->o b : ""[o->o ""
[<-> b : ""[<-> ""
[o<->o b : ""[o<->o""
[x<->x b : ""[x<->x""
[->>o b : ""[->>o ""
[-\o b : ""[-\o ""
[-\\o b : ""[-\\\\o""
[-/o b : ""[-/o ""
[-//o b : ""[-//o ""
[x->o b : ""[x->o ""
@enduml
```
),
(
[Outgoing messages (with '|')],
```
@startuml
participant Alice as a
participant Bob as b
a ->] : ""->] ""
a ->>] : ""->>] ""
a -\] : ""-\] ""
a -\\] : ""-\\\\]""
a -/] : ""-/] ""
a -//] : ""-//] ""
a ->x] : ""->x] ""
a x->] : ""x->] ""
a o->] : ""o->] ""
a ->o] : ""->o] ""
a o->o] : ""o->o] ""
a <->] : ""<->] ""
a o<->o] : ""o<->o]""
a x<->x] : ""x<->x]""
a ->>o] : ""->>o] ""
a -\o] : ""-\o] ""
a -\\o] : ""-\\\\o]""
a -/o] : ""-/o] ""
a -//o] : ""-//o] ""
a x->o] : ""x->o] ""
@enduml
```
),
(
[Short incoming (with '?')],
```
@startuml
participant Alice as a
participant Bob as b
a -> b : //Long long label//
?-> b : ""?-> ""
?->> b : ""?->> ""
?-\ b : ""?-\ ""
?-\\ b : ""?-\\\\""
?-/ b : ""?-/ ""
?-// b : ""?-// ""
?->x b : ""?->x ""
?x-> b : ""?x-> ""
?o-> b : ""?o-> ""
?->o b : ""?->o ""
?o->o b : ""?o->o ""
?<-> b : ""?<-> ""
?o<->o b : ""?o<->o""
?x<->x b : ""?x<->x""
?->>o b : ""?->>o ""
?-\o b : ""?-\o ""
?-\\o b : ""?-\\\\o ""
?-/o b : ""?-/o ""
?-//o b : ""?-//o ""
?x->o b : ""?x->o ""
@enduml
```
),
(
[Short outgoing (with '?')],
```
@startuml
participant Alice as a
participant Bob as b
a -> b : //Long long label//
a ->? : ""->? ""
a ->>? : ""->>? ""
a -\? : ""-\? ""
a -\\? : ""-\\\\?""
a -/? : ""-/? ""
a -//? : ""-//? ""
a ->x? : ""->x? ""
a x->? : ""x->? ""
a o->? : ""o->? ""
a ->o? : ""->o? ""
a o->o? : ""o->o? ""
a <->? : ""<->? ""
a o<->o? : ""o<->o?""
a x<->x? : ""x<->x?""
a ->>o? : ""->>o? ""
a -\o? : ""-\o? ""
a -\\o? : ""-\\\\o?""
a -/o? : ""-/o? ""
a -//o? : ""-//o? ""
a x->o? : ""x->o? ""
@enduml
```
)
)
#{
for (title, uml) in examples {
heading(title)
box(
stroke: gray,
inset: 1em,
stack(
dir: ltr,
spacing: 1em,
raw(uml.text, block: true, lang: "plantuml"),
from-plantuml(uml)
)
)
pagebreak(weak: true)
}
}

Binary file not shown.

View File

@ -33,15 +33,38 @@ Alice <-- Bob: Another authentication Response
#chronos.diagram({
import chronos: *
_seq("Alice", "Bob", comment: "Authentication Request")
_seq("Bob", "Alice", comment: "Authentication Failure")
_grp("My own label", desc: "My own label2", {
_seq("Alice", "Log", comment: "Log attack start")
_grp("loop", desc: "1000 times", {
_seq("Alice", "Bob", comment: "DNS Attack")
})
_seq("Alice", "Bob", comment: "Log attack end")
_alt(
"successful case", {
_seq("Bob", "Alice", comment: "Authentication Accepted")
},
"some kind of failure", {
_seq("Bob", "Alice", comment: "Authentication Failure")
_grp("My own label", desc: "My own label2", {
_seq("Alice", "Log", comment: "Log attack start")
_loop("1000 times", {
_seq("Alice", "Bob", comment: "DNS Attack")
})
_seq("Alice", "Log", comment: "Log attack end")
})
},
"Another type of failure", {
_seq("Bob", "Alice", comment: "Please repeat")
}
)
})
#chronos.diagram({
import chronos: *
_par("a", display-name: box(width: 1.5em, height: .5em), show-bottom: false)
_par("b", display-name: box(width: 1.5em, height: .5em), show-bottom: false)
_col("a", "b", width: 2cm)
_loop("a<1", min: 1, {
_seq("a", "b", end-tip: ">>")
_seq("b", "a", end-tip: ">>")
})
_seq("a", "b", end-tip: ">>")
})
#chronos.diagram({
@ -55,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.

View File

@ -144,4 +144,23 @@ chronos.diagram({
_par("d", display-name: "Danny", show-bottom: false, show-top: false)
_gap()
})
#chronos.diagram({
import chronos: *
_par("a", display-name: "Alice")
_par("b", display-name: "Bob")
_par("c", display-name: "Caleb")
_par("d", display-name: "Danny")
_par("e", display-name: "Erika")
_col("a", "b")
_col("b", "c", width: 2cm)
_col("c", "d", margin: .5cm)
_col("d", "e", min-width: 2cm)
//_seq("b", "c", comment: [Hello World !])
//_seq("c", "d", comment: [Hello World])
//_seq("d", "e", comment: [Hello World])
})

BIN
gallery/plantuml_test.pdf Normal file

Binary file not shown.

271
gallery/plantuml_test.typ Normal file
View File

@ -0,0 +1,271 @@
#import "../src/lib.typ": from-plantuml
#set page(width: auto, height: auto)
/*
#from-plantuml(```
@startuml
actor User as usr
participant can_message as can
control kartculator as kc
queue XF as xf
entity Drive as drive
entity Steering as steering
usr -\ xf : set message "move"
xf -> can : new value on joystick
== If X axis change value ==
can -> kc : calculate new position
kc -> can : build message
can -> steering : set new position
== If Y axis change value ==
can -> kc : calculate new torque
kc -> can : build message
can -> xf : set message "torque"
xf -> drive : set new torque
@enduml
```)
#pagebreak(weak: true)
#from-plantuml(```
@startuml
actor CAN_BUS as bus
participant interrupt as ISR
queue XF as xf
participant ecan as ecan
participant canInterface as can
control canMessageController as msg
bus -\\ ISR ++ : can message
ISR -> can : newMsg
can -> ecan : read
ecan --> can : message
can -> xf : POST XF
destroy ISR
group TICK XF
xf o-> can : receiveCan()
can -> msg : processIncoming()
msg -> can : create message
can -> xf : POST XF
end
group TICK XF
xf o-> can : sendCan()
can -> ecan : write
ecan -\\ bus : can message
end
@enduml
```)
#pagebreak(weak: true)
*/
/*
#from-plantuml(```
@startuml
participant "Behavior::StateMachine" as sm
participant Dispatcher as d
participant TimeoutManager as tm
entity "Event::Timeout" as t
queue "TimeoutManager::timeouts_" as timeouts
autoactivate off
|||
|||
== Schedule timeout ==
|||
sm -> sm++ : scheduleTimeout
sm -> d ++: getDispatcher
d --> sm --: dispatcher
sm -> d --++ : scheduleTimeout
d -> tm ++: getTimeoutManager
tm --> d --: timeoutManager
d -> tm --++ : scheduleTimeout
tm -> t ** : new
t --> tm
tm -> timeouts --++: insert
|||
|||
== Decrement timeout (and dispatch) ==
|||
loop every tickInterval
?->> tm ++: tick
tm -> timeouts : getFront
timeouts -> t ++
t --> timeouts
timeouts --> tm : timeout
tm -> t --: decrement
end
|||
note left t
When timeout is 0,
dispatch event
end note
t -> timeouts : pop
deactivate timeouts
t ->? --: pushEvent
|||
|||
== Unschedule timeout ==
|||
sm -> sm++ : unscheduleTimeout
sm -> d ++: getDispatcher
d --> sm --: dispatcher
sm -> d --++ : unscheduleTimeout
d -> tm ++: getTimeoutManager
tm --> d --: timeoutManager
d -> tm --++ : unscheduleTimeout
tm -> timeouts --: erase
timeouts -> t !!
@enduml
```)
*/
#pagebreak(weak: true)
#from-plantuml(```
@startuml
participant Behavior as b
participant Dispatcher as d
entity Event as e
participant EventQueue as eq
queue "EventQueue::queue_" as q
== Create an Event ==
|||
?->> b ++ : GEN
b -> e ** : new
b -> b --++ : pushEvent
e -> b : getBehavior
b --> e ++: setBehavior
e --> b --
b -> d ++ : getDispatcher
d --> b
b -> d -- : pushEvent
d ->? -- : push
|||
|||
== Push Event ==
|||
?->> d ++: pushEvent
d -> eq--++: push
eq -> q ++
q --> eq
eq -> q -- : pushEndQueue
|||
|||
== Dispatch ==
|||
?->> d ++: executeOnce
d -> q : getFront
q -> e ++
e --> q
q --> d : event
d -> q : pop
deactivate q
d -> d --++ : dispatchEvent
d -> b ++ : getBehavior
b --> d
d -> b -- : process
b -> b--: processEvent
destroy e
@enduml
```)
#pagebreak(weak: true)
#from-plantuml(```
@startuml
'https://plantuml.com/sequence-diagram
actor User as usr
participant "Pb L" as pbL
participant "Pb R" as pbR
participant "LED L" as ledL
participant "LED R" as ledR
== Single click ==
group Single click left
usr -\ pbL ++: pressButton
usr -\ pbL : releaseButton
pbL -> ledL --++ : blink
usr -\ pbL ++: pressButton
usr -\ pbL : releaseButton
pbL -> ledL -- : endBlink
deactivate ledL
end
group Single click right
usr -\ pbR ++: pressButton
usr -\ pbR : releaseButton
pbR -> ledR --++ : blink
usr -\ pbR ++: pressButton
usr -\ pbR : releaseButton
pbR -> ledR -- : endBlink
deactivate ledR
end
== Double click ==
group Double click left
usr -\ pbL ++: pressButton
usr -\ pbL : releaseButton
usr -\ pbL : pressButton
pbL -> ledL --++ : blink
note right ledL: blink 3x
ledL ->x ledL -- : finished
end
group Double click right
usr -\ pbR ++: pressButton
usr -\ pbR : releaseButton
usr -\ pbR : pressButton
pbR -> ledR --++ : blink
note right ledR: blink 3x
ledR ->x ledR -- : finished
end
== Long click ==
group Long click left
usr -\ pbL ++: pressButton
pbL -> ledR--: blink
activate ledL
activate ledR
usr -\ pbL ++: pressButton
pbL -> ledR -- : endBlink
deactivate ledL
deactivate ledR
end
group Long click right
usr -\ pbR ++: pressButton
pbR -> ledR--: blink
activate ledL
activate ledR
usr -\ pbL ++: pressButton
pbL -> ledR -- : endBlink
deactivate ledL
deactivate ledR
end
@enduml
```)

BIN
manual.pdf Normal file

Binary file not shown.

214
manual.typ Normal file
View File

@ -0,0 +1,214 @@
#import "@preview/tidy:0.3.0"
#import "src/lib.typ" as chronos
#import "src/participant.typ" as mod-par
#import "docs/examples.typ"
#import "docs/example.typ": example
#let TYPST = image("gallery/typst.png", width: 1.5cm, height: 1.5cm, fit: "contain")
#let doc-ref(target, full: false, var: false) = {
let (module, func) = target.split(".")
let label-name = module + func
let display-name = func
if full {
display-name = target
}
if not var {
label-name += "()"
display-name += "()"
}
link(label(label-name))[#display-name]
}
#set heading(numbering: (..num) => if num.pos().len() < 4 {
numbering("1.1", ..num)
})
#{
outline(indent: true, depth: 3)
}
#show link: set text(fill: blue)
#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)))
)
= Introduction
This package lets you create nice sequence diagrams using the CeTZ package.
= Usage
Simply import #link("https://typst.app/universe/package/chronos/")[chronos] and call the `diagram` function:
#pad(left: 1em)[```typ
#import "@preview/chronos:0.1.0"
#chronos.diagram({
import chronos: *
...
})
```]
= Examples
You can find the following examples and more in the #link("https://git.kb28.ch/HEL/circuiteria/src/branch/main/gallery")[gallery] directory
== Some groups and sequences
#example(```
chronos.diagram({
import chronos: *
_seq("Alice", "Bob", comment: "Authentication Request")
_seq("Bob", "Alice", comment: "Authentication Failure")
_grp("My own label", desc: "My own label2", {
_seq("Alice", "Log", comment: "Log attack start")
_grp("loop", desc: "1000 times", {
_seq("Alice", "Bob", comment: "DNS Attack")
})
_seq("Alice", "Bob", comment: "Log attack end")
})
})
```, wrap: false, vertical: true)
#pagebreak(weak: true)
== Lifelines
#example(```
chronos.diagram({
import chronos: *
_seq("alice", "bob", comment: "hello", enable-dst: true)
_seq("bob", "bob", comment: "self call", enable-dst: true)
_seq(
"bill", "bob",
comment: "hello from thread 2",
enable-dst: true,
lifeline-style: (fill: rgb("#005500"))
)
_seq("bob", "george", comment: "create", create-dst: true)
_seq(
"bob", "bill",
comment: "done in thread 2",
disable-src: true,
dashed: true
)
_seq("bob", "bob", comment: "rc", disable-src: true, dashed: true)
_seq("bob", "george", comment: "delete", destroy-dst: true)
_seq("bob", "alice", comment: "success", disable-src: true, dashed: true)
})
```, wrap: false, vertical: true)
#pagebreak(weak: true)
== Found and lost messages
#example(```
chronos.diagram({
import chronos: *
_seq("?", "Alice", comment: [?->\ *short* to actor1])
_seq("[", "Alice", comment: [\[->\ *from start* to actor1])
_seq("[", "Bob", comment: [\[->\ *from start* to actor2])
_seq("?", "Bob", comment: [?->\ *short* to actor2])
_seq("Alice", "]", comment: [->\]\ from actor1 *to end*])
_seq("Alice", "?", comment: [->?\ *short* from actor1])
_seq("Alice", "Bob", comment: [->\ from actor1 to actor2])
})
```, wrap: false, vertical: true)
#pagebreak(weak: true)
== Custom images
#example(```
let load-img(path) = image(path, width: 1.5cm, height: 1.5cm, fit:"contain")
let TYPST = load-img("../gallery/typst.png")
let FERRIS = load-img("../gallery/ferris.png")
let ME = load-img("../gallery/me.jpg")
chronos.diagram({
import chronos: *
_par("me", display-name: "Me", shape: "custom", custom-image: ME)
_par("typst", display-name: "Typst", shape: "custom", custom-image: TYPST)
_par("rust", display-name: "Rust", shape: "custom", custom-image: FERRIS)
_seq("me", "typst", comment: "opens document", enable-dst: true)
_seq("me", "typst", comment: "types document")
_seq("typst", "rust", comment: "compiles content", enable-dst: true)
_seq("rust", "typst", comment: "renders document", disable-src: true)
_seq("typst", "me", comment: "displays document", disable-src: true)
})
```, wrap: false, vertical: true)
#pagebreak(weak: true)
= Reference
#let par-docs = tidy.parse-module(
read("docs/participants.typ"),
name: "Participants",
require-all-parameters: true,
scope: (
chronos: chronos,
mod-par: mod-par,
TYPST: TYPST,
doc-ref: doc-ref
)
)
#tidy.show-module(par-docs, show-outline: false, sort-functions: none)
#pagebreak(weak: true)
#let seq-docs = tidy.parse-module(
read("docs/sequences.typ"),
name: "Sequences",
require-all-parameters: true,
scope: (
chronos: chronos,
doc-ref: doc-ref,
examples: examples
)
)
#tidy.show-module(seq-docs, show-outline: false, sort-functions: none)
#pagebreak(weak: true)
#let grp-docs = tidy.parse-module(
read("docs/groups.typ"),
name: "Groups",
require-all-parameters: true,
scope: (
chronos: chronos,
doc-ref: doc-ref,
examples: examples
)
)
#tidy.show-module(grp-docs, show-outline: false, sort-functions: none)
#pagebreak(weak: true)
#let gap-sep-docs = tidy.parse-module(
read("docs/gaps_seps.typ"),
name: "Gaps and separators",
require-all-parameters: true,
scope: (
chronos: chronos,
doc-ref: doc-ref,
examples: examples
)
)
#tidy.show-module(gap-sep-docs, show-outline: false)
#pagebreak(weak: true)
#let notes-docs = tidy.parse-module(
read("docs/notes.typ"),
name: "Notes",
require-all-parameters: true,
scope: (
chronos: chronos,
doc-ref: doc-ref,
examples: examples
)
)
#tidy.show-module(notes-docs, show-outline: false)

View File

@ -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,6 +19,17 @@
),)
}
#let _col(p1, p2, width: auto, margin: 0, min-width: 0) = {
return ((
type: "col",
p1: p1,
p2: p2,
width: width,
margin: margin,
min-width: min-width
),)
}
#let diagram(elements, width: auto) = {
if elements == none {
return
@ -27,7 +39,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" {
@ -51,6 +65,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
}
@ -119,6 +157,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
@ -174,7 +227,7 @@
// Compute groups spans (horizontal)
for (i, elmt) in elmts.enumerate() {
if elmt.type == "grp" {
if elmt.type == "grp" or elmt.type == "alt" {
let (min-i, max-i) = get-group-span(participants, elmt)
elmts.at(i).insert("min-i", min-i)
elmts.at(i).insert("max-i", max-i)
@ -190,8 +243,4 @@
set text(font: "Source Sans 3")
let canvas = render(participants, elmts)
fit-canvas(canvas, width: width)
}
#let from-plantuml(code) = {
let code = code.text
}

View File

@ -11,6 +11,36 @@
),)
}
#let _alt(desc, elmts, ..args) = {
let all-elmts = ()
all-elmts += elmts
let args = args.pos()
for i in range(0, args.len(), step: 2) {
let else-desc = args.at(i)
let else-elmts = args.at(i + 1, default: ())
all-elmts.push((
type: "else",
desc: else-desc
))
all-elmts += else-elmts
}
return _grp("alt", desc: desc, type: "alt", all-elmts)
}
#let _loop(desc, min: none, max: auto, elmts) = {
let name = "loop"
if min != none {
if max == auto {
max = "*"
}
name += "(" + str(min) + "," + str(max) + ")"
}
_grp(name, desc: desc, type: "loop", elmts)
}
#let _opt(desc, elmts) = grp("opt", desc: desc, type: "opt", elmts)
#let _break(desc, elmts) = grp("break", desc: desc, type: "break", elmts)
#let render(x0, x1, y0, y1, group) = {
let shapes = ()
let name = text(group.name, weight: "bold")
@ -50,5 +80,20 @@
)
}
return shapes
}
#let render-else(x0, x1, y, elmt) = {
let shapes = draw.line(
(x0, y),
(x1, y),
stroke: (dash: (2pt, 1pt), thickness: .5pt)
)
shapes += draw.content(
(x0, y),
text([\[#elmt.desc\]], weight: "bold", size: .8em),
anchor: "north-west",
padding: 3pt
)
return shapes
}

View File

@ -1,8 +1,10 @@
#import "diagram.typ": diagram, from-plantuml, _gap, _evt
#let version = version(0, 1, 1)
#import "diagram.typ": diagram, _gap, _evt, _col
#import "parser.typ": from-plantuml
#import "sequence.typ": _seq
#import "group.typ": _grp
#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

View File

@ -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,

1036
src/parser.typ Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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,

View File

@ -1,5 +1,5 @@
#import "@preview/cetz:0.2.2": canvas, draw
#import "utils.typ": get-participants-i, get-style
#import "utils.typ": get-participants-i, get-style, normalize-units
#import "group.typ"
#import "participant.typ"
#import participant: PAR-SPECIALS
@ -194,6 +194,37 @@
}
widths.at(i) = w
}
for elmt in elements {
if elmt.type == "col" {
let i1 = pars-i.at(elmt.p1)
let i2 = pars-i.at(elmt.p2)
if calc.abs(i1 - i2) != 1 {
let i-min = calc.min(i1, i2)
let i-max = calc.max(i1, i2)
let others = pars-i.pairs()
.sorted(key: p => p.last())
.slice(i-min + 1, i-max)
.map(p => "'" + p.first() + "'")
.join(", ")
panic(
"Column participants must be consecutive (participants (" +
others +
") are in between)"
)
}
let i = calc.min(i1, i2)
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)
}
}
return widths
}
@ -211,6 +242,7 @@
let draw-seq = sequence.render.with(pars-i, x-pos, participants)
let draw-group = group.render.with()
let draw-else = group.render-else.with()
let draw-sep = separator.render.with(x-pos)
let draw-par = participant.render.with(x-pos)
let draw-note = note.render.with(pars-i, x-pos)
@ -252,6 +284,9 @@
if g.at(1).max-i == elmt.max-i { g.at(3) += 1 }
g
})
if elmt.grp-type == "alt" {
elmt.insert("elses", ())
}
groups.push((y, elmt, 0, 0))
y -= m.height / 1pt
@ -263,6 +298,21 @@
let x1 = x-pos.at(group.max-i) + end-lvl * 10 + 20
shapes += draw-group(x0, x1, start-y, y, group)
if group.grp-type == "alt" {
for (else-y, else-elmt) in group.elses {
shapes += draw-else(x0, x1, else-y, else-elmt)
}
}
// Alt's elses -> reserve space for label + store position
} else if elmt.type == "else" {
y -= Y-SPACE
let m = measure(text([\[#elmt.desc\]], weight: "bold", size: .8em))
groups.last().at(1).elses.push((
y, elmt
))
y -= m.height / 1pt
// Separator
} else if elmt.type == "sep" {
let shps
@ -272,6 +322,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" {
@ -385,6 +455,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)
}
}

View File

@ -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

View File

@ -4,6 +4,9 @@
#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,6 +88,13 @@
),)
}
#let _ret(comment: none) = {
return ((
type: "ret",
comment: comment
),)
}
#let render(pars-i, x-pos, participants, elmt, y, lifelines) = {
let shapes = ()
@ -294,7 +304,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 +326,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

View File

@ -1,3 +1,12 @@
#let normalize-units(value) = {
if type(value) == int or type(value) == float {
return value
}
if type(value) == length {
return value / 1pt
}
panic("Unsupported type '" + str(type(value)) + "'")
}
#let get-participants-i(participants) = {
let pars-i = (:)
for (i, p) in participants.enumerate() {

View File

@ -1,6 +1,6 @@
[package]
name = "chronos"
version = "0.1.0"
version = "0.1.1"
compiler = "0.11.0"
repository = "https://git.kb28.ch/HEL/chronos"
entrypoint = "src/lib.typ"
@ -11,4 +11,4 @@ categories = ["visualization"]
license = "Apache-2.0"
description = "A package to draw sequence diagrams with CeTZ"
keywords = ["sequence", "diagram", "plantuml"]
exclude = [ "gallery", "gallery.bash" ]
exclude = [ "gallery", "gallery.bash", "docs" ]