///|
pub struct Limits {
nodes : Int32
edges : Int32
work : Int32
output_units : Int32
minimum_approach : Double
} derive(Eq, Debug)
///|
pub fn Limits::new(
nodes~ : Int32 = 10000,
edges~ : Int32 = 50000,
work~ : Int32 = 1000000,
output_units~ : Int32 = 16777216,
minimum_approach~ : Double = 24.0,
) -> Limits {
{ minimum_approach, nodes, edges, work, output_units, }
}
///|
pub(all) struct Point {
x : Double
y : Double
} derive(Eq, Debug)
///|
pub(all) struct Rect {
x : Double
y : Double
width : Double
height : Double
} derive(Eq, Debug)
///|
pub fn Rect::contains(
self : Rect,
other : Rect,
tolerance~ : Double = 0.0,
) -> Bool {
other.x >= self.x - tolerance &&
other.y >= self.y - tolerance &&
other.x + other.width <= self.x + self.width + tolerance &&
other.y + other.height <= self.y + self.height + tolerance
}
///|
pub fn Rect::overlaps(self : Rect, other : Rect) -> Bool {
self.x < other.x + other.width &&
other.x < self.x + self.width &&
self.y < other.y + other.height &&
other.y < self.y + self.height
}
///|
pub struct Label {
text : String
font : @model.Font
bounds : @model.ExactBounds?
box : Rect
size : Double
weight : Int32
rotation : Int32
mask : Bool
owner : String
family : @theme.Family?
} derive(Eq, Debug)
///|
pub struct Shape {
node : @model.Node
index : Int32
bounds : @model.ExactBounds
box : Rect
label : Label?
} derive(Eq, Debug)
///|
pub struct Connection {
flow : @model.Flow
index : Int32
source : String
target : String
points : ReadOnlyArray[@model.ExactPoint]
line : ReadOnlyArray[Point]
label : Label?
} derive(Eq, Debug)
///|
pub(all) enum Source {
Supplied
Automatic
} derive(Eq, Debug)
///|
pub struct Geometry {
view : @model.Bpmn
shapes : ReadOnlyArray[Shape]
connections : ReadOnlyArray[Connection]
source : Source
translate_x : Int64
translate_y : Int64
width : Int64
height : Int64
} derive(Eq, Debug)
///|
pub struct Result {
geometry : Geometry?
diagnostics : ReadOnlyArray[@diagrams.Diagnostic]
} derive(Eq, Debug)
///|
priv struct Budget {
limits : Limits
diagnostics : Array[@diagrams.Diagnostic]
mut work : Int32
mut failed : Bool
}
///|
fn Budget::error(self : Budget, code : String, message : String) -> Unit {
if !self.failed {
self.diagnostics.push(@diagrams.Diagnostic::error(code, message))
}
self.failed = true
}
///|
fn Budget::charge(self : Budget, amount : Int32) -> Bool {
if amount < 0 || amount > self.limits.work - self.work {
self.error("bpmn/layout-limit", "BPMN geometry work limit exceeded")
return false
}
self.work += amount
true
}
///|
/// Canonical derived SVG numbers have at most three fractional digits. Authored
/// supplied coordinates bypass this formatter and keep their exact tokens.
pub fn svg_number(value : Double) -> String? {
if value != value || value.abs() > 1000000000000.0 {
return None
}
let magnitude = (value.abs() * 1000.0 + 0.5).floor().to_int64()
let scaled = if value < 0.0 { -magnitude } else { magnitude }
if scaled == 0L {
return Some("0")
}
let negative = scaled < 0L
let magnitude = if negative { -scaled } else { scaled }
let whole = magnitude / 1000L
let fraction = (magnitude % 1000L).to_int()
let prefix = if negative { "-" } else { "" }
if fraction == 0 {
return Some("\{prefix}\{whole}")
}
let digits = "\{if fraction < 100 { "0" } else { "" }}\{if fraction < 10 { "0" } else { "" }}\{fraction}"
Some("\{prefix}\{whole}." + digits.trim_end(chars="0").to_owned())
}
///|
fn number(value : Double) -> @model.Number {
@model.Number::parse(svg_number(value).unwrap()).unwrap()
}
///|
fn exact_bounds(r : Rect) -> @model.ExactBounds {
@model.ExactBounds::new(
number(r.x),
number(r.y),
number(r.width),
number(r.height),
)
}
///|
fn exact_point(p : Point) -> @model.ExactPoint {
@model.ExactPoint::new(number(p.x), number(p.y))
}
///|
fn coordinate(n : @model.Number) -> Double? {
let value = @string.parse_double(n.token) catch { _ => return None }
if value == value && value.abs() <= 1000000000.0 {
Some(value)
} else {
None
}
}
///|
fn rectangle(r : @model.ExactBounds) -> Rect? {
guard coordinate(r.x) is Some(x) &&
coordinate(r.y) is Some(y) &&
coordinate(r.width) is Some(width) &&
coordinate(r.height) is Some(height) &&
width > 0.0 &&
height > 0.0 else {
return None
}
Some({ x, y, width, height, })
}
///|
fn point(p : @model.ExactPoint) -> Point? {
match (coordinate(p.x), coordinate(p.y)) {
(Some(x), Some(y)) => Some({ x, y, })
_ => None
}
}
///|
pub fn family(kind : @model.NodeKind) -> @theme.Family {
match kind {
UserTask | ManualTask => Frontend
DataObject
| DataObjectReference
| DataInput
| DataOutput
| DataStore
| DataStoreReference => Database
ExclusiveGateway
| InclusiveGateway
| ParallelGateway
| ComplexGateway
| EventBasedGateway => Cloud
StartEvent
| EndEvent
| IntermediateCatchEvent
| IntermediateThrowEvent
| BoundaryEvent => MessageBus
SendTask | ReceiveTask | Message => MessageBus
Participant | Lane | Group | TextAnnotation => External
_ => Backend
}
}
///|
pub extend Limits with Eq::{equal, not_equal}
///|
pub extend Limits with @debug.Debug::{to_repr}
///|
pub extend Point with Eq::{equal, not_equal}
///|
pub extend Point with @debug.Debug::{to_repr}
///|
pub extend Rect with Eq::{equal, not_equal}
///|
pub extend Rect with @debug.Debug::{to_repr}
///|
pub extend Label with Eq::{equal, not_equal}
///|
pub extend Label with @debug.Debug::{to_repr}
///|
pub extend Shape with Eq::{equal, not_equal}
///|
pub extend Shape with @debug.Debug::{to_repr}
///|
pub extend Connection with Eq::{equal, not_equal}
///|
pub extend Connection with @debug.Debug::{to_repr}
///|
pub extend Source with Eq::{equal, not_equal}
///|
pub extend Source with @debug.Debug::{to_repr}
///|
pub extend Geometry with Eq::{equal, not_equal}
///|
pub extend Geometry with @debug.Debug::{to_repr}
///|
pub extend Result with Eq::{equal, not_equal}
///|
pub extend Result with @debug.Debug::{to_repr}
///|
/// Preserve family hues while checking text against opaque canvas and the
/// actual coloured body. Sequence palette values are unchanged.
pub fn bpmn_palette(p : @theme.Palette) -> @theme.Palette {
fn shaded(colors : @theme.FamilyColors) -> @theme.FamilyColors {
{
..colors,
ink: @theme.text_shade(colors.ink, [p.bg, @theme.over(colors.fill, p.bg)]),
}
}
{
..p,
frontend: shaded(p.frontend),
backend: shaded(p.backend),
database: shaded(p.database),
cloud: shaded(p.cloud),
security: shaded(p.security),
messagebus: shaded(p.messagebus),
external: shaded(p.external),
}
}
///|
/// Half the polyline's measured length, rather than its middle vertex.
pub fn midpoint(line : ArrayView[Point]) -> Point? {
if line.is_empty() {
return None
}
fn distance(a : Point, b : Point) -> Double {
let dx = (b.x - a.x).abs()
let dy = (b.y - a.y).abs()
let hi = Double::max(dx, dy)
let lo = Double::min(dx, dy)
if hi == 0.0 {
0.0
} else {
hi * (1.0 + lo / hi * (lo / hi)).sqrt()
}
}
let mut total = 0.0
for i in 1.. 0.0 && remaining <= length {
let t = remaining / length
return Some({
x: line[i - 1].x + (line[i].x - line[i - 1].x) * t,
y: line[i - 1].y + (line[i].y - line[i - 1].y) * t,
})
}
remaining -= length
}
line.get(0)
}
///|
/// Retain resource limits while selecting the marked terminal reservation.
pub fn Limits::with_minimum_approach(
self : Limits,
minimum_approach : Double,
) -> Limits {
{ ..self, minimum_approach, }
}