mod caseless;
mod fit;
mod fit_text;
mod generics;
mod icon;
mod literal_label;
pub(crate) mod outline;
pub(crate) mod scan;
pub(crate) mod tokens;
#[cfg(test)]
mod tests;
pub(crate) use caseless::word_at;
pub(crate) use fit::{fit_label, plain_label};
pub(crate) use fit_text::{Fitted, fit_text};
pub(crate) use generics::parse_generic_types;
pub(crate) use icon::{has_icon, icon_svg};
pub(crate) use literal_label::{break_label, literal_label};
use crate::js::chars::is_space_char;
use crate::js::math::{max, min, round};
use crate::layout::{Options, Routing};
use crate::scene::style::{Paint, Style};
use crate::scene::text::{Face, Label, Span};
use crate::scene::{Item, Kind, Placed, Point, Scene};
use crate::text::{Setting, layout_label};
const BREAKPOINT: f64 = 640.0;
pub(crate) fn routing_curve(r: Routing) -> Option<&'static str> {
match r {
Routing::Auto => None,
Routing::Orthogonal => Some("step"),
Routing::Straight => Some("linear"),
}
}
pub(crate) fn runs_across(direction: Option<&str>) -> bool {
matches!(direction, Some("LR" | "RL"))
}
pub(crate) fn tighten(o: &Options, mut lay: impl FnMut(bool) -> Scene) -> Scene {
let loose = lay(false);
if !(o.max_width > 0.0) || loose.width <= o.max_width {
return loose;
}
let tight = lay(true);
if tight.width < loose.width {
tight
} else {
loose
}
}
pub(crate) fn turn_to_fit(o: &Options, mut lay: impl FnMut(bool) -> Scene) -> Scene {
let across = lay(false);
if o.keep_direction || !(o.max_width > 0.0) || across.width <= o.max_width {
return across;
}
let breakpoint = if o.direction_breakpoint > 0.0 {
o.direction_breakpoint
} else {
BREAKPOINT
};
if o.max_width >= breakpoint {
return across;
}
let down = lay(true);
if down.width < across.width {
down
} else {
across
}
}
pub(crate) fn fit_width(
o: &Options,
natural: f64,
least: f64,
mut lay: impl FnMut(f64, bool) -> Scene,
) -> Scene {
let mut sc = lay(natural, false);
let available = o.max_width;
if !(available > 0.0) || sc.width <= available {
return sc;
}
let floor = min(least, natural);
let mut plot = natural;
sc = lay(plot, true);
let mut round = 0;
while round < 3 && sc.width > available && plot > floor {
plot = max(floor, plot - (sc.width - available));
sc = lay(plot, true);
round += 1;
}
sc
}
pub(crate) fn title_room(o: &Options) -> f64 {
if o.max_width > 0.0 {
max(80.0, o.max_width - 2.0 * o.pad())
} else {
4000.0
}
}
pub(crate) fn cap_width(o: &Options, natural: f64, least: f64, around: f64) -> f64 {
if !(o.max_width > 0.0) {
return natural;
}
max(min(least, natural), min(natural, o.max_width - around))
}
pub(crate) fn setting<'a>(o: &'a Options<'a>) -> Setting<'a> {
Setting {
measurer: o.measurer,
icons: match &o.icons {
Some(icons) => *icons,
None => &|_| false,
},
}
}
pub(crate) fn title_label(title: Option<&str>, set: &Setting, size: f64, max_width: f64) -> Label {
layout_label(title.unwrap_or(""), false, set, size, max_width, Face::BOLD)
}
pub(crate) fn with_title(
sc: &mut Scene,
title: Option<&str>,
role: &'static str,
set: &Setting,
size: f64,
pad: f64,
) {
let label = title_label(title, set, size, 4000.0);
if label.height == 0.0 {
return;
}
let shift = label.height + 12.0;
let full = max(sc.width, (label.width + 2.0 * pad).ceil());
let dx = (full - sc.width) / 2.0;
for it in &mut sc.items {
crate::items::move_by(it, dx, shift);
}
sc.width = full;
sc.height = (sc.height + shift).ceil();
let at = Point::new(full / 2.0, pad + label.height / 2.0);
let mut it = Item::new(Kind::Text, role);
it.label = Some(Placed::new(label, at));
it.style = Style {
text: Some(Paint::FG),
..Style::default()
};
sc.items.insert(0, it);
}
pub(crate) fn line(s: &str, width: f64, size: f64, face: Face) -> Label {
line_of(s, width, size, round(size * crate::text::LINE_HEIGHT), face)
}
pub(crate) fn line_of(text: &str, width: f64, size: f64, line_height: f64, face: Face) -> Label {
Label {
lines: vec![vec![Span {
text: text.to_string(),
face,
icon: None,
width,
}]],
widths: vec![width],
width,
height: line_height,
size,
line_height,
}
}
pub(crate) fn class_names(raw: &str) -> Vec<String> {
raw.split(|c: char| !(c == '_' || c == '-' || c.is_ascii_alphanumeric()))
.filter(|s| !s.is_empty())
.map(str::to_string)
.collect()
}
pub(crate) fn trim_default(classes: &str) -> &str {
let Some(rest) = classes.strip_prefix("default") else {
return classes;
};
match rest.chars().next() {
Some(c) if is_space_char(c) => &rest[c.len_utf8()..],
_ => rest,
}
}
pub(crate) fn clickable(classes: Vec<String>) -> (Vec<String>, Vec<String>) {
classes.into_iter().partition(|c| c != "clickable")
}