///|
fn normal_text(text : String) -> String {
text.replace_all(old="\r\n", new="\n").replace_all(old="\r", new="\n")
}
///|
fn default_font() -> @model.Font {
@model.Font::new(size=number(13.0))
}
///|
fn font_size(font : @model.Font, budget : Budget) -> Double? {
match font.size {
None => Some(13.0)
Some(value) => {
if !budget.charge(value.token.length()) {
return None
}
match coordinate(value) {
Some(n) if n >= 0.0 && n <= 1000000.0 => Some(n)
_ => {
budget.error(
"bpmn/invalid-geometry", "Font size must be nonnegative, finite and bounded",
)
None
}
}
}
}
}
///|
fn measure(
text : String,
font : @model.Font,
budget : Budget,
) -> (Double, Double)? {
guard font_size(font, budget) is Some(size) else { return None }
if !budget.charge(text.length()) {
return None
}
let size_int = Double::max(1.0, size.ceil()).to_int()
if text.length() > 1000000000 / size_int {
budget.error(
"bpmn/layout-limit", "Measured text extent exceeded its safe range",
)
return None
}
let metrics = @metrics.FontMetrics::from_defaults(
profile=Monospace,
font_size=size_int,
font_weight=if font.bold.unwrap_or(false) { 700 } else { 400 },
)
let mut width = 0
for line in normal_text(text).split("\n") {
width = Int32::max(width, metrics.measure(line.to_owned()).width)
}
Some(
(
width.to_double(),
normal_text(text).split("\n").count().to_double() * (size + 4.0),
),
)
}
///|
fn node_label(
node : @model.Node,
box : Rect,
supplied : Bool,
budget : Budget,
) -> Label? {
let label = node.geometry.bind(g => g.label)
let properties = node.properties
let text = normal_text(
node.label +
properties.bind(p => p.sublabel).map(s => "\n" + s).unwrap_or(""),
)
if text.is_empty() {
return None
}
let font = if supplied {
label.bind(l => l.font).unwrap_or_else(default_font)
} else {
default_font()
}
guard measure(text, font, budget) is Some((width, height)) &&
font_size(font, budget) is Some(size) else {
return None
}
let bounds = if supplied { label.bind(l => l.bounds) } else { None }
let horizontal = node.geometry.bind(g => g.horizontal).unwrap_or(true)
let rotation = if (node.kind == Participant || node.kind == Lane) &&
horizontal &&
bounds == None {
-90
} else {
0
}
let fallback : Rect = if rotation == -90 {
{
x: box.x + 6.0,
y: box.y + (box.height - width) / 2.0,
width: height,
height: width,
}
} else if node.kind == Participant || node.kind == Lane {
{ x: box.x + (box.width - width) / 2.0, y: box.y + 8.0, width, height, }
} else if (
node.kind == SubProcess ||
node.kind == AdHocSubProcess ||
node.kind == Transaction ||
node.kind == CallActivity
) &&
node.expanded.unwrap_or(false) {
{ x: box.x + (box.width - width) / 2.0, y: box.y + 8.0, width, height, }
} else if event(node.kind) ||
gateway(node.kind) ||
[
@model.NodeKind::DataObject,
DataObjectReference,
DataStore,
DataStoreReference,
DataInput,
DataOutput,
Message,
].contains(node.kind) {
{
x: box.x + (box.width - width) / 2.0,
y: box.y + box.height + 8.0,
width,
height,
}
} else if node.kind == Group {
{ x: box.x + 12.0, y: box.y - 8.0 - height, width, height, }
} else {
{
x: box.x + (box.width - width) / 2.0,
y: box.y + (box.height - height) / 2.0,
width,
height,
}
}
let actual = match bounds {
Some(r) =>
match rectangle(r) {
Some(r) => r
None => {
budget.error(
"bpmn/invalid-geometry",
"Invalid label bounds on \{node.id}",
)
return None
}
}
None => fallback
}
Some({
text,
font,
bounds,
box: actual,
size,
weight: if font.bold.unwrap_or(false) {
700
} else {
400
},
rotation,
mask: bounds != None || !activity(node.kind),
owner: node.id,
family: Some(family(node.kind)),
})
}
///|
fn flow_label(
flow : @model.Flow,
line : ArrayView[Point],
supplied : Bool,
budget : Budget,
) -> Label? {
let text = normal_text(
flow.label +
flow.properties.bind(p => p.sublabel).map(s => "\n" + s).unwrap_or(""),
)
if text.is_empty() {
return None
}
let label = flow.geometry.bind(g => g.label)
let font = if supplied {
label.bind(l => l.font).unwrap_or_else(default_font)
} else {
default_font()
}
guard measure(text, font, budget) is Some((width, height)) &&
font_size(font, budget) is Some(size) else {
return None
}
let bounds = if supplied { label.bind(l => l.bounds) } else { None }
let center = midpoint(line).unwrap()
let box = match bounds {
Some(r) =>
match rectangle(r) {
Some(r) => r
None => {
budget.error(
"bpmn/invalid-geometry",
"Invalid connection label bounds on \{flow.id}",
)
return None
}
}
None =>
{ x: center.x - width / 2.0, y: center.y - height - 8.0, width, height, }
}
Some({
text,
font,
bounds,
box,
size,
weight: if font.bold.unwrap_or(false) {
700
} else {
400
},
rotation: 0,
mask: true,
owner: flow.id,
family: None,
})
}