use super::*;
use crate::appearance::ImportedColor;
pub(super) struct StyleTable {
by_item: HashMap<usize, ImportedColor>,
}
impl StyleTable {
pub(super) fn read(entities: &HashMap<usize, Entity>) -> Self {
let mut found: Vec<(usize, usize, ImportedColor, bool)> = Vec::new();
for (id, entity) in entities.iter() {
let over_riding = entity.has("OVER_RIDING_STYLED_ITEM");
let Some(args) = entity
.find("OVER_RIDING_STYLED_ITEM")
.or_else(|| entity.find("STYLED_ITEM"))
else {
continue;
};
let (Some(styles), Some(item)) = (args.get(1), args.get(2)) else {
continue;
};
let Ok(item_ref) = item.as_ref_id() else {
continue;
};
let Some(color) = style_list_color(entities, styles) else {
continue;
};
found.push((item_ref, *id, color, over_riding));
}
found.sort_unstable_by_key(|(item, id, _, over_riding)| {
(*item, !*over_riding, *id)
});
let mut by_item: HashMap<usize, ImportedColor> = HashMap::default();
for (item_ref, _, color, _) in found {
by_item.entry(item_ref).or_insert(color);
}
Self { by_item }
}
pub(super) fn color_for(&self, item_ref: usize) -> Option<ImportedColor> {
self.by_item.get(&item_ref).copied()
}
pub(super) fn is_empty(&self) -> bool {
self.by_item.is_empty()
}
}
fn style_list_color(entities: &HashMap<usize, Entity>, styles: &Value) -> Option<ImportedColor> {
for assignment in styles.as_list().ok()? {
let Ok(assignment_ref) = assignment.as_ref_id() else {
continue;
};
if let Some(color) = presentation_style_color(entities, assignment_ref) {
return Some(color);
}
}
None
}
fn presentation_style_color(
entities: &HashMap<usize, Entity>,
assignment_ref: usize,
) -> Option<ImportedColor> {
let entity = entities.get(&assignment_ref)?;
let args = entity
.find("PRESENTATION_STYLE_ASSIGNMENT")
.or_else(|| entity.find("PRESENTATION_STYLE_BY_CONTEXT"))?;
for style in args.first()?.as_list().ok()? {
let Ok(style_ref) = style.as_ref_id() else {
continue;
};
if let Some(color) = surface_style_color(entities, style_ref) {
return Some(color);
}
}
None
}
fn surface_style_color(
entities: &HashMap<usize, Entity>,
style_ref: usize,
) -> Option<ImportedColor> {
let usage = entities.get(&style_ref)?.find("SURFACE_STYLE_USAGE")?;
let side_style_ref = usage.get(1)?.as_ref_id().ok()?;
let side_style = entities.get(&side_style_ref)?.find("SURFACE_SIDE_STYLE")?;
for element in side_style.get(1)?.as_list().ok()? {
let Ok(element_ref) = element.as_ref_id() else {
continue;
};
if let Some(color) = style_element_color(entities, element_ref) {
return Some(color);
}
}
None
}
fn style_element_color(
entities: &HashMap<usize, Entity>,
element_ref: usize,
) -> Option<ImportedColor> {
let entity = entities.get(&element_ref)?;
if let Some(args) = entity.find("SURFACE_STYLE_FILL_AREA") {
let fill_ref = args.first()?.as_ref_id().ok()?;
return fill_area_style_color(entities, fill_ref);
}
if let Some(args) = entity
.find("SURFACE_STYLE_RENDERING_WITH_PROPERTIES")
.or_else(|| entity.find("SURFACE_STYLE_RENDERING"))
{
let colour_ref = args.get(1)?.as_ref_id().ok()?;
return resolve_colour(entities, colour_ref);
}
None
}
fn fill_area_style_color(
entities: &HashMap<usize, Entity>,
fill_ref: usize,
) -> Option<ImportedColor> {
let args = entities.get(&fill_ref)?.find("FILL_AREA_STYLE")?;
for style in args.get(1)?.as_list().ok()? {
let Ok(style_ref) = style.as_ref_id() else {
continue;
};
let Some(colour_args) = entities
.get(&style_ref)
.and_then(|entity| entity.find("FILL_AREA_STYLE_COLOUR"))
else {
continue;
};
let Some(colour_ref) = colour_args.get(1).and_then(|v| v.as_ref_id().ok()) else {
continue;
};
if let Some(color) = resolve_colour(entities, colour_ref) {
return Some(color);
}
}
None
}
fn resolve_colour(
entities: &HashMap<usize, Entity>,
colour_ref: usize,
) -> Option<ImportedColor> {
let entity = entities.get(&colour_ref)?;
if let Some(args) = entity.find("COLOUR_RGB") {
let r = args.get(1)?.as_real().ok()?;
let g = args.get(2)?.as_real().ok()?;
let b = args.get(3)?.as_real().ok()?;
return Some(ImportedColor::new(r, g, b));
}
if let Some(args) = entity.find("DRAUGHTING_PRE_DEFINED_COLOUR") {
let name = args
.first()
.and_then(string_value)
.or_else(|| entity.find("PRE_DEFINED_ITEM")?.first().and_then(string_value))?;
return pre_defined_colour(&name);
}
None
}
fn string_value(value: &Value) -> Option<String> {
match value {
Value::Str(text) => Some(text.clone()),
_ => None,
}
}
fn pre_defined_colour(name: &str) -> Option<ImportedColor> {
let (r, g, b) = match name.trim().to_ascii_lowercase().as_str() {
"red" => (1.0, 0.0, 0.0),
"green" => (0.0, 1.0, 0.0),
"blue" => (0.0, 0.0, 1.0),
"yellow" => (1.0, 1.0, 0.0),
"magenta" => (1.0, 0.0, 1.0),
"cyan" => (0.0, 1.0, 1.0),
"black" => (0.0, 0.0, 0.0),
"white" => (1.0, 1.0, 1.0),
_ => return None,
};
Some(ImportedColor::new(r, g, b))
}