diff --git a/gallery/example1.pdf b/gallery/example1.pdf index 63f94d3..a3c2da1 100644 Binary files a/gallery/example1.pdf and b/gallery/example1.pdf differ diff --git a/gallery/example1.typ b/gallery/example1.typ index a259669..902d151 100644 --- a/gallery/example1.typ +++ b/gallery/example1.typ @@ -28,4 +28,18 @@ Alice <-- Bob: Another authentication Response import "/src/diagram.typ": * _seq("Alice", "Bob", comment: "This is a test") _seq("Alice", "Callum", comment: "This is another test with a long text") +}) + +#chronos.diagram({ + import "/src/diagram.typ": * + _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") + }) }) \ No newline at end of file diff --git a/src/diagram.typ b/src/diagram.typ index d152ac2..bbc9ea2 100644 --- a/src/diagram.typ +++ b/src/diagram.typ @@ -1,3 +1,4 @@ +#import "utils.typ": get-group-span #import "renderer.typ": render #let _seq( @@ -37,9 +38,40 @@ return false } +#let _grp(name, desc: none, type: "default", elmts) = { + return (( + type: "grp", + name: name, + desc: desc, + grp-type: type, + elmts: elmts + ),) +} + #let diagram(elements) = { let participants = () - for elmt in elements { + let elmts = elements + let i = 0 + let a = elmts.len() + while i < elmts.len() { + let elmt = elmts.at(i) + if elmt.type == "grp" { + elmts = ( + elmts.slice(0, i + 1) + + elmt.elmts + + (( + type: "grp-end" + ),) + + elmts.slice(i+1) + ) + } + i += 1 + } + let b = elmts.len() + [#a / #b] + [#elmts.map(e => e.type)] + + for elmt in elmts { if elmt.type == "par" { participants.push(elmt) } else if elmt.type == "seq" { @@ -52,7 +84,15 @@ } } - render(participants, elements) + for (i, elmt) in elmts.enumerate() { + if elmt.type == "grp" { + 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) + } + } + + render(participants, elmts) } #let from-plantuml(code) = { diff --git a/src/renderer.typ b/src/renderer.typ index 32cd8f1..ab67726 100644 --- a/src/renderer.typ +++ b/src/renderer.typ @@ -1,16 +1,10 @@ #import "@preview/cetz:0.2.2": canvas, draw +#import "utils.typ": get-participants-i -#let Y-SPACE = 20 +#let Y-SPACE = 10 #let PAR-PAD = (5pt, 3pt) #let PAR-SPACE = 10 -#let get-participants-i(participants) = { - let pars-i = (:) - for (i, p) in participants.enumerate() { - pars-i.insert(p.name, i) - } - return pars-i -} #let get-columns-width(participants, elements) = { let pars-i = get-participants-i(participants) @@ -62,6 +56,44 @@ return widths } +#let draw-group(x0, x1, y0, y1, group) = { + let m = measure(box(group.name)) + let w = m.width / 1pt + 15 + let h = m.height / 1pt + 6 + draw.rect( + (x0, y0), + (x1, y1) + ) + draw.merge-path( + fill: gray.lighten(20%), + close: true, + { + draw.line( + (x0, y0), + (x0 + w, y0), + (x0 + w, y0 - h / 2), + (x0 + w - 5, y0 - h), + (x0, y0 - h) + ) + } + ) + draw.content( + (x0, y0), + group.name, + anchor: "north-west", + padding: (left: 5pt, right: 10pt, top: 3pt, bottom: 3pt) + ) + + if group.desc != none { + draw.content( + (x0 + w, y0), + text([\[#group.desc\]], weight: "bold"), + anchor: "north-west", + padding: 3pt + ) + } +} + #let render(participants, elements) = context canvas(length: 1pt, { let pars-i = get-participants-i(participants) @@ -85,6 +117,8 @@ } let y = -Y-SPACE + let groups = () + // Draw sequences for elmt in elements { if elmt.type == "seq" { @@ -98,12 +132,8 @@ ) ) - draw.line( - (x1, y), - (x2, y), - ..style - ) if elmt.comment != none { + y -= measure(box(elmt.comment)).height / 1pt + 6 draw.content( (calc.min(x1, x2), y), elmt.comment, @@ -111,6 +141,33 @@ padding: 3pt ) } + + draw.line( + (x1, y), + (x2, y), + ..style + ) + y -= Y-SPACE + } else if elmt.type == "grp" { + let m = measure( + box( + inset: (left: 5pt, right: 5pt, top: 3pt, bottom: 3pt), + ) + ) + groups = groups.map(g => { + if g.at(1).min-i == elmt.min-i { g.at(2) += 1 } + if g.at(1).max-i == elmt.max-i { g.at(3) += 1 } + g + }) + groups.push((y, elmt, 0, 0)) + y -= m.height / 1pt + Y-SPACE + + } else if elmt.type == "grp-end" { + let (start-y, group, start-lvl, end-lvl) = groups.pop() + let x0 = x-pos.at(group.min-i) - start-lvl * 10 - 20 + let x1 = x-pos.at(group.max-i) + end-lvl * 10 + 20 + draw-group(x0, x1, start-y, y, group) + y -= Y-SPACE } } diff --git a/src/utils.typ b/src/utils.typ new file mode 100644 index 0000000..bbe073f --- /dev/null +++ b/src/utils.typ @@ -0,0 +1,27 @@ +#let get-participants-i(participants) = { + let pars-i = (:) + for (i, p) in participants.enumerate() { + pars-i.insert(p.name, i) + } + return pars-i +} + +#let get-group-span(participants, group) = { + let min-i = participants.len() - 1 + let max-i = 0 + let pars-i = get-participants-i(participants) + + for elmt in group.elmts { + if elmt.type == "seq" { + let i1 = pars-i.at(elmt.p1) + let i2 = pars-i.at(elmt.p2) + min-i = calc.min(min-i, i1, i2) + max-i = calc.max(max-i, i1, i2) + } else if elmt.type == "grp" { + let (i0, i1) = get-group-span(participants, elmt) + min-i = calc.min(min-i, i0) + max-i = calc.max(max-i, i1) + } + } + return (min-i, max-i) +} \ No newline at end of file