From 57a3a371eb17ccdce6871f535baecc731479b9ff Mon Sep 17 00:00:00 2001 From: LordBaryhobal Date: Sun, 13 Jul 2025 16:06:02 +0200 Subject: [PATCH] refactored pre-rendering --- src/{ => core}/renderer.typ | 368 ++++++++++++++++++++++-------------- src/diagram.typ | 2 +- 2 files changed, 232 insertions(+), 138 deletions(-) rename src/{ => core}/renderer.typ (59%) diff --git a/src/renderer.typ b/src/core/renderer.typ similarity index 59% rename from src/renderer.typ rename to src/core/renderer.typ index 205ca58..9a137c6 100644 --- a/src/renderer.typ +++ b/src/core/renderer.typ @@ -1,119 +1,157 @@ -#import "/src/cetz.typ": canvas, draw -#import "utils.typ": get-participants-i, get-style, normalize-units -#import "group.typ" -#import "participant.typ" +#import "/src/cetz.typ" as cetz: canvas, draw +#import "utils.typ": get-participants-i, get-style, normalize-units, is-elmt +#import "../group.typ" +#import "../participant.typ" #import participant: PAR-SPECIALS -#import "sequence.typ" -#import "separator.typ" -#import "sync.typ" -#import "consts.typ": * -#import "note.typ" as note: get-note-box +#import "../sequence.typ" +#import "../separator.typ" +#import "../sync.typ" +#import "../consts.typ": * +#import "../note.typ" as note: get-note-box +#import "../styles.typ" #let DEBUG-INVISIBLE = false -#let get-columns-width(participants, elements) = { - participants = participants.map(p => { +#let init-lifelines(participants) = { + return participants.map(p => { p.insert("lifeline-lvl", 0) p.insert("max-lifelines", 0) p }) - let pars-i = get-participants-i(participants) - let cells = () +} - // Unwrap syncs +#let unwrap-syncs(elements) = { let i = 0 while i < elements.len() { let elmt = elements.at(i) if elmt.type == "sync" { - elements = elements.slice(0, i + 1) + elmt.elmts + elements.slice(i + 1) + elements = ( + elements.slice(0, i + 1) + + elmt.elmts + + elements.slice(i + 1) + ) } i += 1 } + return elements +} - // Compute max lifeline levels +#let seq-update-lifelines(participants, pars-i, seq) = { + let participants = participants + let com = if seq.comment == none {""} else {seq.comment} + let i1 = pars-i.at(seq.p1) + let i2 = pars-i.at(seq.p2) + let cell = ( + elmt: seq, + i1: calc.min(i1, i2), + i2: calc.max(i1, i2), + cell: box(com, inset: 3pt) + ) + + if seq.disable-src or seq.destroy-src { + let p = participants.at(i1) + p.lifeline-lvl -= 1 + participants.at(i1) = p + } + if seq.disable-dst { + let p = participants.at(i2) + p.lifeline-lvl -= 1 + participants.at(i2) = p + } + if seq.enable-dst { + let p = participants.at(i2) + p.lifeline-lvl += 1 + p.max-lifelines = calc.max(p.max-lifelines, p.lifeline-lvl) + participants.at(i2) = p + } + + return (participants, cell) +} + +#let evt-update-lifelines(participants, pars-i, evt) = { + let par-name = evt.participant + let i = pars-i.at(par-name) + let par = participants.at(i) + if evt.event == "disable" or evt.event == "destroy" { + par.lifeline-lvl -= 1 + + } else if evt.event == "enable" { + par.lifeline-lvl += 1 + par.max-lifelines = calc.max(par.max-lifelines, par.lifeline-lvl) + } + participants.at(i) = par + return participants +} + +#let note-get-cell(note) = { + let (p1, p2) = (none, none) + let cell = none + if note.side == "left" { + p1 = "[" + p2 = note.pos + cell = get-note-box(note) + } else if note.side == "right" { + p1 = note.pos + p2 = "]" + cell = get-note-box(note) + } else if note.side == "over" and note.aligned-with != none { + let box1 = get-note-box(note) + let box2 = get-note-box(note.aligned-with) + let m1 = measure(box1) + let m2 = measure(box2) + cell = box( + width: (m1.width + m2.width) / 2, + height: calc.max(m1.height, m2.height) + ) + p1 = note.pos + p2 = note.aligned-with.pos + } else { + return none + } + + let i1 = pars-i.at(p1) + let i2 = pars-i.at(p2) + cell = ( + elmt: note, + i1: calc.min(i1, i2), + i2: calc.max(i1, i2), + cell: cell + ) + + return cell +} + +#let compute-max-lifeline-levels(participants, elements, pars-i) = { + let cells = () for elmt in elements { if elmt.type == "seq" { - let com = if elmt.comment == none {""} else {elmt.comment} - let i1 = pars-i.at(elmt.p1) - let i2 = pars-i.at(elmt.p2) - cells.push( - ( - elmt: elmt, - i1: calc.min(i1, i2), - i2: calc.max(i1, i2), - cell: box(com, inset: 3pt) - ) + let cell + (participants, cell) = seq-update-lifelines( + participants, + pars-i, + elmt ) - - if elmt.disable-src or elmt.destroy-src { - let p = participants.at(i1) - p.lifeline-lvl -= 1 - participants.at(i1) = p - } - if elmt.disable-dst { - let p = participants.at(i2) - p.lifeline-lvl -= 1 - participants.at(i2) = p - } - if elmt.enable-dst { - let p = participants.at(i2) - p.lifeline-lvl += 1 - p.max-lifelines = calc.max(p.max-lifelines, p.lifeline-lvl) - participants.at(i2) = p - } + cells.push(cell) } else if elmt.type == "evt" { - let par-name = elmt.participant - let i = pars-i.at(par-name) - let par = participants.at(i) - if elmt.event == "disable" or elmt.event == "destroy" { - par.lifeline-lvl -= 1 - - } else if elmt.event == "enable" { - par.lifeline-lvl += 1 - par.max-lifelines = calc.max(par.max-lifelines, par.lifeline-lvl) - } - participants.at(i) = par + participants = evt-update-lifelines( + participants, + pars-i, + elmt + ) } else if elmt.type == "note" { - let (p1, p2) = (none, none) - let cell = none - if elmt.side == "left" { - p1 = "[" - p2 = elmt.pos - cell = get-note-box(elmt) - } else if elmt.side == "right" { - p1 = elmt.pos - p2 = "]" - cell = get-note-box(elmt) - } else if elmt.side == "over" { - if elmt.aligned-with != none { - let box1 = get-note-box(elmt) - let box2 = get-note-box(elmt.aligned-with) - let m1 = measure(box1) - let m2 = measure(box2) - cell = box(width: (m1.width + m2.width) / 2, height: calc.max(m1.height, m2.height)) - p1 = elmt.pos - p2 = elmt.aligned-with.pos - } - } - - if p1 != none and p2 != none and cell != none { - let i1 = pars-i.at(p1) - let i2 = pars-i.at(p2) - cells.push( - ( - elmt: elmt, - i1: calc.min(i1, i2), - i2: calc.max(i1, i2), - cell: cell - ) - ) + let cell = note-get-cell(elmt) + if cell != none { + cells.push(cell) } } } - // Compute column widths - // Compute minimum widths for participant names and shapes + return (participants, elements, cells) +} + +/// Compute minimum widths for participant names and shapes +#let participants-min-col-widths(participants) = { let widths = () for i in range(participants.len() - 1) { let p1 = participants.at(i) @@ -124,11 +162,15 @@ let w2 = m2.width widths.push(w1 / 2pt + w2 / 2pt + PAR-SPACE) } + return widths +} - // Compute minimum width for over notes - for n in elements.filter(e => (e.type == "note" and - e.side == "over" and - type(e.pos) == str)) { +/// Compute minimum width for over notes +#let notes-min-col-widths(elements, widths, pars-i) = { + let widths = widths + let notes = elements.filter(e => e.type == "note") + for n in notes.filter(e => (e.side == "over" and + type(e.pos) == str)) { let m = note.get-size(n) let i = pars-i.at(n.pos) @@ -146,8 +188,12 @@ ) } } + return widths +} - // Compute minimum width for simple sequences (spanning 1 column) +/// Compute minimum width for simple sequences (spanning 1 column) +#let simple-seq-min-col-widths(cells, widths) = { + let widths = widths for cell in cells.filter(c => c.i2 - c.i1 == 1) { let m = measure(cell.cell) widths.at(cell.i1) = calc.max( @@ -155,9 +201,14 @@ m.width / 1pt + COMMENT-PAD ) } + return widths +} - // Compute minimum width for self sequences - for cell in cells.filter(c => c.elmt.type == "seq" and c.i1 == c.i2) { +/// Compute minimum width for self sequences +#let self-seq-min-col-widths(cells, widths) = { + let widths = widths + for cell in cells.filter(c => (c.elmt.type == "seq" and + c.i1 == c.i2)) { let m = measure(cell.cell) let i = cell.i1 if cell.elmt.flip { @@ -170,72 +221,112 @@ ) } } + return widths +} - // Compute remaining widths for longer sequences (spanning multiple columns) +/// Compute remaining widths for longer sequences (spanning multiple columns) +#let long-seq-min-col-widths(cells, widths) = { + let widths = widths let multicol-cells = cells.filter(c => c.i2 - c.i1 > 1) multicol-cells = multicol-cells.sorted(key: c => { c.i1 * 1000 + c.i2 }) for cell in multicol-cells { let m = measure(cell.cell) + let width = ( + m.width / 1pt + + COMMENT-PAD - + widths.slice(cell.i1, cell.i2 - 1).sum() + ) widths.at(cell.i2 - 1) = calc.max( - widths.at(cell.i2 - 1), - m.width / 1pt + COMMENT-PAD - widths.slice(cell.i1, cell.i2 - 1).sum() + widths.at(cell.i2 - 1), width ) } + return widths +} - // Add lifeline widths - for (i, w) in widths.enumerate() { +/// Add lifeline widths +#let col-widths-add-lifelines(participants, widths) = { + return widths.enumerate().map(((i, w)) => { let p1 = participants.at(i) let p2 = participants.at(i + 1) - let w = w + p1.max-lifelines * LIFELINE-W / 2 + w += p1.max-lifelines * LIFELINE-W / 2 if p2.max-lifelines != 0 { w += LIFELINE-W / 2 } - widths.at(i) = w - } + return 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) - } - - 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) +#let process-col-elements(elements, widths, pars-i) = { + let widths = widths + let cols = elements.filter(e => e.type == "col") + for col in cols { + let i1 = pars-i.at(col.p1) + let i2 = pars-i.at(col.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) + let width = widths.at(i) + + if col.width != auto { + width = normalize-units(col.width) + } + + width = calc.max( + width, + normalize-units(col.min-width) + ) + if col.max-width != none { + width = calc.min( + width, + normalize-units(col.max-width) + ) + } + widths.at(i) = width + normalize-units(col.margin) + } + return widths +} + +#let get-columns-width(participants, elements, pars-i) = { + elements = elements.filter(is-elmt) + elements = unwrap-syncs(elements) + + let cells + (participants, elements, cells) = compute-max-lifeline-levels(participants, elements, pars-i) + + let widths = participants-min-col-widths(participants) + widths = notes-min-col-widths(elements, widths, pars-i) + widths = simple-seq-min-col-widths(cells, widths) + widths = self-seq-min-col-widths(cells, widths) + widths = long-seq-min-col-widths(cells, widths) + widths = col-widths-add-lifelines(participants, widths) + widths = process-col-elements(elements, widths, pars-i) return widths } #let render(participants, elements) = context canvas(length: 1pt, { + let participants = participants + let elements = elements + let shapes = () + participants = init-lifelines(participants) let pars-i = get-participants-i(participants) - let widths = get-columns-width(participants, elements) + let widths = get-columns-width(participants, elements, pars-i) // Compute each column's X position let x-pos = (0,) @@ -267,8 +358,11 @@ // Draw elemnts for elmt in elements { + if not is-elmt(elmt) { + shapes.push(elmt) + // Sequences - if elmt.type == "seq" { + } else if elmt.type == "seq" { let shps (y, lifelines, shps) = draw-seq(elmt, y, lifelines) shapes += shps diff --git a/src/diagram.typ b/src/diagram.typ index 0dc89aa..6156ad1 100644 --- a/src/diagram.typ +++ b/src/diagram.typ @@ -1,5 +1,5 @@ #import "core/utils.typ": fit-canvas -#import "renderer.typ": render +#import "core/renderer.typ": render #import "participant.typ" as participant: _par, PAR-SPECIALS #import "sequence.typ": _seq