///|
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=Int::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})
}