use oxipdf_ir::color::Color;
use oxipdf_ir::style::typography::*;
use oxipdf_ir::style::visual::BorderSide;
use oxipdf_ir::style::*;
use oxipdf_ir::units::Pt;
use oxipdf_ir::{Dimension, LengthPercentage};
use super::Declaration;
use super::values::*;
pub(crate) fn apply_declarations(style: &mut ResolvedStyle, decls: &[Declaration]) {
for d in decls {
apply_property(style, &d.property, &d.value);
}
}
fn apply_property(style: &mut ResolvedStyle, prop: &str, val: &str) {
match prop {
"font-size" => {
if let Some(pt) = parse_length(val) {
style.typography.font_size = Pt::new(pt);
}
}
"font-weight" => {
style.typography.font_weight = parse_font_weight(val);
}
"font-style" => {
style.typography.font_style = match val.trim() {
"italic" => FontStyle::Italic,
"oblique" => FontStyle::Oblique,
_ => FontStyle::Normal,
};
}
"font-family" => {
style.typography.font_families = parse_font_family(val);
}
"color" => {
if let Some(c) = parse_color(val) {
style.typography.color = c;
}
}
"line-height" => {
if let Some(lh) = parse_line_height(val) {
style.typography.line_height = lh;
}
}
"text-align" => {
style.typography.text_align = match val.trim() {
"left" => TextAlign::Left,
"right" => TextAlign::Right,
"center" => TextAlign::Center,
"justify" => TextAlign::Justify,
"end" => TextAlign::End,
_ => TextAlign::Start,
};
}
"text-decoration" | "text-decoration-line" => {
style.typography.text_decoration = match val.trim() {
"underline" => TextDecoration::Underline,
"overline" => TextDecoration::Overline,
"line-through" => TextDecoration::LineThrough,
_ => TextDecoration::None,
};
}
"text-transform" => {
style.typography.text_transform = match val.trim() {
"uppercase" => TextTransform::Uppercase,
"lowercase" => TextTransform::Lowercase,
"capitalize" => TextTransform::Capitalize,
_ => TextTransform::None,
};
}
"text-indent" => {
if let Some(pt) = parse_length(val) {
style.typography.text_indent = Pt::new(pt);
}
}
"white-space" => {
style.typography.white_space = match val.trim() {
"pre" => WhiteSpace::Pre,
"nowrap" => WhiteSpace::NoWrap,
"pre-wrap" => WhiteSpace::PreWrap,
"pre-line" => WhiteSpace::PreLine,
_ => WhiteSpace::Normal,
};
}
"letter-spacing" => {
if let Some(pt) = parse_length(val) {
style.typography.letter_spacing = Pt::new(pt);
}
}
"word-spacing" => {
if let Some(pt) = parse_length(val) {
style.typography.word_spacing = Pt::new(pt);
}
}
"vertical-align" => {
style.typography.vertical_align = match val.trim() {
"top" => VerticalAlign::Top,
"middle" => VerticalAlign::Middle,
"bottom" => VerticalAlign::Bottom,
"sub" => VerticalAlign::Sub,
"super" => VerticalAlign::Super,
_ => VerticalAlign::Baseline,
};
}
"background-color" | "background" => {
if let Some(c) = parse_color(val) {
style.visual.background_color = Some(c);
}
}
"opacity" => {
if let Ok(v) = val.trim().parse::<f32>() {
style.visual.opacity = v.clamp(0.0, 1.0);
}
}
"border" => apply_border_shorthand(style, val, BorderSide::all),
"border-top" => apply_border_shorthand(style, val, BorderSide::top),
"border-right" => apply_border_shorthand(style, val, BorderSide::right),
"border-bottom" => apply_border_shorthand(style, val, BorderSide::bottom),
"border-left" => apply_border_shorthand(style, val, BorderSide::left),
"border-color" => {
if let Some(c) = parse_color(val) {
style.visual.border_top.color = c;
style.visual.border_right.color = c;
style.visual.border_bottom.color = c;
style.visual.border_left.color = c;
}
}
"border-width" => {
if let Some(w) = parse_length(val) {
let pt = Pt::new(w);
style.visual.border_top.width = pt;
style.visual.border_right.width = pt;
style.visual.border_bottom.width = pt;
style.visual.border_left.width = pt;
}
}
"border-radius" => {
if let Some(r) = parse_length(val) {
let pt = Pt::new(r);
style.visual.border_radius_top_left = pt;
style.visual.border_radius_top_right = pt;
style.visual.border_radius_bottom_right = pt;
style.visual.border_radius_bottom_left = pt;
}
}
"font-variant-ligatures" => {
style.typography.font_features.ligatures = match val.trim() {
"none" | "no-common-ligatures" => Some(false),
"normal" | "common-ligatures" => Some(true),
_ => None,
};
}
"font-variant-caps" => {
style.typography.font_features.small_caps = val.trim() == "small-caps";
}
"font-variant-numeric" => {
style.typography.font_features.old_style_nums = val.trim().contains("oldstyle-nums");
}
"font-kerning" => {
style.typography.font_features.kerning = match val.trim() {
"none" => Some(false),
"normal" => Some(true),
_ => None, };
}
"hanging-punctuation" => {
style.typography.microtypography.hanging_punctuation = match val.trim() {
"none" => 0.0,
"first" | "last" | "first last" => 0.5,
"allow-end" | "force-end" => 0.7,
_ => val.trim().parse::<f64>().unwrap_or(0.0),
};
}
"optical-margin" | "optical-margin-alignment" => {
style.typography.microtypography.optical_margin = match val.trim() {
"none" => 0.0,
"auto" => 0.5,
_ => val.trim().parse::<f64>().unwrap_or(0.0),
};
}
"max-tracking-adjust" | "tracking-adjust" => {
if let Some(pt) = parse_length(val) {
style.typography.microtypography.max_tracking_adjust = pt;
}
}
"font-feature-settings" => {
for part in val.split(',') {
let part = part.trim();
let tokens: Vec<&str> = part.split_whitespace().collect();
if let Some(tag) = tokens.first() {
let tag = tag.trim_matches('"').trim_matches('\'');
if tag.len() == 4 {
let value = tokens
.get(1)
.and_then(|v| v.parse::<u32>().ok())
.unwrap_or(1);
style
.typography
.font_features
.custom
.push((tag.to_string(), value));
}
}
}
}
"display" => {
style.layout.display = match val.trim() {
"block" => Display::Block,
"inline" => Display::Inline,
"flex" => Display::Flex,
"grid" => Display::Grid,
"none" => Display::None,
_ => Display::Block,
};
}
"overflow" => {
style.layout.overflow = match val.trim() {
"hidden" => Overflow::Hidden,
_ => Overflow::Visible,
};
}
"visibility" => {
style.layout.visibility = match val.trim() {
"hidden" => Visibility::Hidden,
_ => Visibility::Visible,
};
}
"float" => {
style.layout.float = match val.trim() {
"left" => Float::Left,
"right" => Float::Right,
_ => Float::None,
};
}
"clear" => {
style.layout.clear = match val.trim() {
"left" => Clear::Left,
"right" => Clear::Right,
"both" => Clear::Both,
_ => Clear::None,
};
}
"float-placement" => {
style.fragmentation.float_placement = match val.trim() {
"best-fit" => oxipdf_ir::style::fragmentation::FloatPlacement::BestFit,
"top-of-page" | "top" => oxipdf_ir::style::fragmentation::FloatPlacement::TopOfPage,
"next-page" => oxipdf_ir::style::fragmentation::FloatPlacement::NextPage,
_ => oxipdf_ir::style::fragmentation::FloatPlacement::Inline,
};
}
"z-index" => {
let v = val.trim();
if v != "auto" {
if let Ok(z) = v.parse::<i32>() {
style.layout.z_index = z;
}
}
}
"width" => style.layout.width = parse_dimension(val),
"height" => style.layout.height = parse_dimension(val),
"min-width" => style.layout.min_width = parse_dimension(val),
"min-height" => style.layout.min_height = parse_dimension(val),
"max-width" => style.layout.max_width = parse_dimension(val),
"max-height" => style.layout.max_height = parse_dimension(val),
"margin" => apply_margin_shorthand(style, val),
"margin-top" => style.layout.margin_top = parse_dimension(val),
"margin-right" => style.layout.margin_right = parse_dimension(val),
"margin-bottom" => style.layout.margin_bottom = parse_dimension(val),
"margin-left" => style.layout.margin_left = parse_dimension(val),
"padding" => apply_padding_shorthand(style, val),
"padding-top" => {
if let Some(lp) = parse_length_percentage(val) {
style.layout.padding_top = lp;
}
}
"padding-right" => {
if let Some(lp) = parse_length_percentage(val) {
style.layout.padding_right = lp;
}
}
"padding-bottom" => {
if let Some(lp) = parse_length_percentage(val) {
style.layout.padding_bottom = lp;
}
}
"padding-left" => {
if let Some(lp) = parse_length_percentage(val) {
style.layout.padding_left = lp;
}
}
_ => {} }
}
type BorderSetter = fn(&mut ResolvedStyle, BorderSide);
trait BorderSetters {
fn all(style: &mut ResolvedStyle, side: BorderSide);
fn top(style: &mut ResolvedStyle, side: BorderSide);
fn right(style: &mut ResolvedStyle, side: BorderSide);
fn bottom(style: &mut ResolvedStyle, side: BorderSide);
fn left(style: &mut ResolvedStyle, side: BorderSide);
}
impl BorderSetters for BorderSide {
fn all(style: &mut ResolvedStyle, side: BorderSide) {
style.visual.border_top = side;
style.visual.border_right = side;
style.visual.border_bottom = side;
style.visual.border_left = side;
}
fn top(style: &mut ResolvedStyle, side: BorderSide) {
style.visual.border_top = side;
}
fn right(style: &mut ResolvedStyle, side: BorderSide) {
style.visual.border_right = side;
}
fn bottom(style: &mut ResolvedStyle, side: BorderSide) {
style.visual.border_bottom = side;
}
fn left(style: &mut ResolvedStyle, side: BorderSide) {
style.visual.border_left = side;
}
}
fn apply_border_shorthand(style: &mut ResolvedStyle, val: &str, setter: BorderSetter) {
let parts: Vec<&str> = val.split_whitespace().collect();
let mut width = Pt::new(1.0);
let mut border_style = oxipdf_ir::style::visual::BorderStyle::Solid;
let mut color = Color::BLACK;
for part in &parts {
if let Some(w) = parse_length(part) {
width = Pt::new(w);
} else if let Some(c) = parse_color(part) {
color = c;
} else {
border_style = match *part {
"solid" => oxipdf_ir::style::visual::BorderStyle::Solid,
"dashed" => oxipdf_ir::style::visual::BorderStyle::Dashed,
"dotted" => oxipdf_ir::style::visual::BorderStyle::Dotted,
"none" => oxipdf_ir::style::visual::BorderStyle::None,
_ => oxipdf_ir::style::visual::BorderStyle::Solid,
};
}
}
setter(
style,
BorderSide {
width,
style: border_style,
color,
},
);
}
fn apply_margin_shorthand(style: &mut ResolvedStyle, val: &str) {
let parts: Vec<&str> = val.split_whitespace().collect();
let dims: Vec<Dimension> = parts.iter().map(|p| parse_dimension(p)).collect();
match dims.len() {
1 => {
style.layout.margin_top = dims[0];
style.layout.margin_right = dims[0];
style.layout.margin_bottom = dims[0];
style.layout.margin_left = dims[0];
}
2 => {
style.layout.margin_top = dims[0];
style.layout.margin_bottom = dims[0];
style.layout.margin_right = dims[1];
style.layout.margin_left = dims[1];
}
3 => {
style.layout.margin_top = dims[0];
style.layout.margin_right = dims[1];
style.layout.margin_left = dims[1];
style.layout.margin_bottom = dims[2];
}
4 => {
style.layout.margin_top = dims[0];
style.layout.margin_right = dims[1];
style.layout.margin_bottom = dims[2];
style.layout.margin_left = dims[3];
}
_ => {}
}
}
fn apply_padding_shorthand(style: &mut ResolvedStyle, val: &str) {
let parts: Vec<&str> = val.split_whitespace().collect();
let lps: Vec<Option<LengthPercentage>> =
parts.iter().map(|p| parse_length_percentage(p)).collect();
let get = |i: usize| {
lps.get(i)
.copied()
.flatten()
.unwrap_or(LengthPercentage::ZERO)
};
match lps.len() {
1 => {
let v = get(0);
style.layout.padding_top = v;
style.layout.padding_right = v;
style.layout.padding_bottom = v;
style.layout.padding_left = v;
}
2 => {
style.layout.padding_top = get(0);
style.layout.padding_bottom = get(0);
style.layout.padding_right = get(1);
style.layout.padding_left = get(1);
}
3 => {
style.layout.padding_top = get(0);
style.layout.padding_right = get(1);
style.layout.padding_left = get(1);
style.layout.padding_bottom = get(2);
}
4 => {
style.layout.padding_top = get(0);
style.layout.padding_right = get(1);
style.layout.padding_bottom = get(2);
style.layout.padding_left = get(3);
}
_ => {}
}
}