Skip to content

Commit a5da2d6

Browse files
committed
Handle text decoration shorthand and vendor prefixes
1 parent 5a3ea31 commit a5da2d6

File tree

4 files changed

+280
-3
lines changed

4 files changed

+280
-3
lines changed

src/declaration.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ use crate::properties::{
1414
animation::AnimationHandler,
1515
prefix_handler::PrefixHandler,
1616
display::DisplayHandler,
17-
transform::TransformHandler
17+
transform::TransformHandler,
18+
text::TextDecorationHandler
1819
};
1920
use crate::properties::prefixes::Browsers;
2021

@@ -54,6 +55,7 @@ pub struct DeclarationHandler {
5455
scroll_margin: ScrollMarginHandler,
5556
scroll_padding: ScrollPaddingHandler,
5657
font: FontHandler,
58+
text: TextDecorationHandler,
5759
transition: TransitionHandler,
5860
animation: AnimationHandler,
5961
display: DisplayHandler,
@@ -73,6 +75,7 @@ impl DeclarationHandler {
7375
animation: AnimationHandler::new(targets),
7476
display: DisplayHandler::new(targets),
7577
transform: TransformHandler::new(targets),
78+
text: TextDecorationHandler::new(targets),
7679
prefix: PrefixHandler::new(targets),
7780
..DeclarationHandler::default()
7881
}
@@ -84,6 +87,7 @@ impl DeclarationHandler {
8487
self.border.handle_property(property) ||
8588
self.outline.handle_property(property) ||
8689
self.flex.handle_property(property) ||
90+
self.text.handle_property(property) ||
8791
self.align.handle_property(property) ||
8892
self.margin.handle_property(property) ||
8993
self.padding.handle_property(property) ||
@@ -109,13 +113,14 @@ impl DeclarationHandler {
109113
let mut scroll_margin = self.scroll_margin.finalize();
110114
let mut scroll_padding = self.scroll_padding.finalize();
111115
let mut font = self.font.finalize();
116+
let mut text = self.text.finalize();
112117
let mut transition = self.transition.finalize();
113118
let mut animation = self.animation.finalize();
114119
let mut display = self.display.finalize();
115120
let mut transform = self.transform.finalize();
116121
let mut prefixed = self.prefix.finalize();
117122

118-
let mut decls = Vec::with_capacity(display.len() + background.len() + border.len() + outline.len() + flex.len() + align.len() + margin.len() + padding.len() + scroll_margin.len() + scroll_padding.len() + font.len() + transition.len() + animation.len() + prefixed.len());
123+
let mut decls = Vec::with_capacity(display.len() + background.len() + border.len() + outline.len() + flex.len() + text.len() + align.len() + margin.len() + padding.len() + scroll_margin.len() + scroll_padding.len() + font.len() + transition.len() + animation.len() + prefixed.len());
119124
decls.extend(display.drain(..).map(|property| Declaration { property, important }));
120125
decls.extend(background.drain(..).map(|property| Declaration { property, important }));
121126
decls.extend(border.drain(..).map(|property| Declaration { property, important }));
@@ -127,6 +132,7 @@ impl DeclarationHandler {
127132
decls.extend(scroll_margin.drain(..).map(|property| Declaration { property, important }));
128133
decls.extend(scroll_padding.drain(..).map(|property| Declaration { property, important }));
129134
decls.extend(font.drain(..).map(|property| Declaration { property, important }));
135+
decls.extend(text.drain(..).map(|property| Declaration { property, important }));
130136
decls.extend(transition.drain(..).map(|property| Declaration { property, important }));
131137
decls.extend(animation.drain(..).map(|property| Declaration { property, important }));
132138
decls.extend(transform.drain(..).map(|property| Declaration { property, important }));

src/lib.rs

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4207,5 +4207,140 @@ mod tests {
42074207
minify_test(".foo { text-decoration: underline overline dotted yellow }", ".foo{text-decoration:underline overline dotted #ff0}");
42084208
minify_test(".foo { -webkit-text-decoration: yellow dotted underline }", ".foo{-webkit-text-decoration:underline dotted #ff0}");
42094209
minify_test(".foo { -moz-text-decoration: yellow dotted underline }", ".foo{-moz-text-decoration:underline dotted #ff0}");
4210+
4211+
test(r#"
4212+
.foo {
4213+
text-decoration-line: underline;
4214+
text-decoration-style: dotted;
4215+
text-decoration-color: yellow;
4216+
text-decoration-thickness: 2px;
4217+
}
4218+
"#, indoc! {r#"
4219+
.foo {
4220+
text-decoration: underline 2px dotted #ff0;
4221+
}
4222+
"#});
4223+
4224+
test(r#"
4225+
.foo {
4226+
text-decoration: underline;
4227+
text-decoration-style: dotted;
4228+
}
4229+
"#, indoc! {r#"
4230+
.foo {
4231+
text-decoration: underline dotted;
4232+
}
4233+
"#});
4234+
4235+
test(r#"
4236+
.foo {
4237+
-webkit-text-decoration: underline;
4238+
-webkit-text-decoration-style: dotted;
4239+
}
4240+
"#, indoc! {r#"
4241+
.foo {
4242+
-webkit-text-decoration: underline dotted;
4243+
}
4244+
"#});
4245+
4246+
prefix_test(r#"
4247+
.foo {
4248+
text-decoration: underline dotted;
4249+
}
4250+
"#, indoc! {r#"
4251+
.foo {
4252+
-webkit-text-decoration: underline dotted;
4253+
-moz-text-decoration: underline dotted;
4254+
text-decoration: underline dotted;
4255+
}
4256+
"#},
4257+
Browsers {
4258+
safari: Some(8 << 16),
4259+
firefox: Some(30 << 16),
4260+
..Browsers::default()
4261+
});
4262+
4263+
prefix_test(r#"
4264+
.foo {
4265+
text-decoration-line: underline;
4266+
}
4267+
"#, indoc! {r#"
4268+
.foo {
4269+
-webkit-text-decoration-line: underline;
4270+
-moz-text-decoration-line: underline;
4271+
text-decoration-line: underline;
4272+
}
4273+
"#},
4274+
Browsers {
4275+
safari: Some(8 << 16),
4276+
firefox: Some(30 << 16),
4277+
..Browsers::default()
4278+
});
4279+
4280+
prefix_test(r#"
4281+
.foo {
4282+
text-decoration-style: dotted;
4283+
}
4284+
"#, indoc! {r#"
4285+
.foo {
4286+
-webkit-text-decoration-style: dotted;
4287+
-moz-text-decoration-style: dotted;
4288+
text-decoration-style: dotted;
4289+
}
4290+
"#},
4291+
Browsers {
4292+
safari: Some(8 << 16),
4293+
firefox: Some(30 << 16),
4294+
..Browsers::default()
4295+
});
4296+
4297+
prefix_test(r#"
4298+
.foo {
4299+
text-decoration-color: yellow;
4300+
}
4301+
"#, indoc! {r#"
4302+
.foo {
4303+
-webkit-text-decoration-color: #ff0;
4304+
-moz-text-decoration-color: #ff0;
4305+
text-decoration-color: #ff0;
4306+
}
4307+
"#},
4308+
Browsers {
4309+
safari: Some(8 << 16),
4310+
firefox: Some(30 << 16),
4311+
..Browsers::default()
4312+
});
4313+
4314+
prefix_test(r#"
4315+
.foo {
4316+
text-decoration: underline;
4317+
}
4318+
"#, indoc! {r#"
4319+
.foo {
4320+
text-decoration: underline;
4321+
}
4322+
"#},
4323+
Browsers {
4324+
safari: Some(8 << 16),
4325+
firefox: Some(30 << 16),
4326+
..Browsers::default()
4327+
});
4328+
4329+
prefix_test(r#"
4330+
.foo {
4331+
-webkit-text-decoration: underline dotted;
4332+
-moz-text-decoration: underline dotted;
4333+
text-decoration: underline dotted;
4334+
}
4335+
"#, indoc! {r#"
4336+
.foo {
4337+
text-decoration: underline dotted;
4338+
}
4339+
"#},
4340+
Browsers {
4341+
safari: Some(14 << 16),
4342+
firefox: Some(45 << 16),
4343+
..Browsers::default()
4344+
});
42104345
}
42114346
}

src/properties/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,7 @@ define_properties! {
454454
"letter-spacing": LetterSpacing(Spacing),
455455
"text-indent": TextIndent(TextIndent),
456456

457+
// https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506
457458
"text-decoration-line": TextDecorationLine(TextDecorationLine, VendorPrefix) / "webkit" / "moz",
458459
"text-decoration-style": TextDecorationStyle(TextDecorationStyle, VendorPrefix) / "webkit" / "moz",
459460
"text-decoration-color": TextDecorationColor(CssColor, VendorPrefix) / "webkit" / "moz",

src/properties/text.rs

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use cssparser::*;
2-
use crate::traits::{Parse, ToCss};
2+
use crate::traits::{Parse, ToCss, PropertyHandler};
3+
use super::{Property, VendorPrefix};
4+
use super::prefixes::{Browsers, Feature};
35
use crate::macros::enum_property;
46
use crate::values::length::{Length, LengthPercentage};
57
use crate::values::color::CssColor;
@@ -501,3 +503,136 @@ impl ToCss for TextDecoration {
501503
Ok(())
502504
}
503505
}
506+
507+
#[derive(Default)]
508+
pub struct TextDecorationHandler {
509+
targets: Option<Browsers>,
510+
line: Option<(TextDecorationLine, VendorPrefix)>,
511+
thickness: Option<TextDecorationThickness>,
512+
style: Option<(TextDecorationStyle, VendorPrefix)>,
513+
color: Option<(CssColor, VendorPrefix)>,
514+
decls: Vec<Property>
515+
}
516+
517+
impl TextDecorationHandler {
518+
pub fn new(targets: Option<Browsers>) -> TextDecorationHandler {
519+
TextDecorationHandler {
520+
targets,
521+
..TextDecorationHandler::default()
522+
}
523+
}
524+
}
525+
526+
impl PropertyHandler for TextDecorationHandler {
527+
fn handle_property(&mut self, property: &Property) -> bool {
528+
use Property::*;
529+
530+
macro_rules! maybe_flush {
531+
($prop: ident, $val: expr, $vp: expr) => {{
532+
// If two vendor prefixes for the same property have different
533+
// values, we need to flush what we have immediately to preserve order.
534+
if let Some((val, prefixes)) = &self.$prop {
535+
if val != $val && !prefixes.contains(*$vp) {
536+
self.flush();
537+
}
538+
}
539+
}};
540+
}
541+
542+
macro_rules! property {
543+
($prop: ident, $val: expr, $vp: expr) => {{
544+
maybe_flush!($prop, $val, $vp);
545+
546+
// Otherwise, update the value and add the prefix.
547+
if let Some((val, prefixes)) = &mut self.$prop {
548+
*val = $val.clone();
549+
*prefixes |= *$vp;
550+
} else {
551+
self.$prop = Some(($val.clone(), *$vp))
552+
}
553+
}};
554+
}
555+
556+
match property {
557+
TextDecorationLine(val, vp) => property!(line, val, vp),
558+
TextDecorationThickness(val) => self.thickness = Some(val.clone()),
559+
TextDecorationStyle(val, vp) => property!(style, val, vp),
560+
TextDecorationColor(val, vp) => property!(color, val, vp),
561+
TextDecoration(val, vp) => {
562+
maybe_flush!(line, &val.line, vp);
563+
maybe_flush!(style, &val.style, vp);
564+
maybe_flush!(color, &val.color, vp);
565+
property!(line, &val.line, vp);
566+
self.thickness = Some(val.thickness.clone());
567+
property!(style, &val.style, vp);
568+
property!(color, &val.color, vp);
569+
}
570+
_ => return false
571+
}
572+
573+
true
574+
}
575+
576+
fn finalize(&mut self) -> Vec<Property> {
577+
self.flush();
578+
std::mem::take(&mut self.decls)
579+
}
580+
}
581+
582+
impl TextDecorationHandler {
583+
fn flush(&mut self) {
584+
let mut line = std::mem::take(&mut self.line);
585+
let mut thickness = std::mem::take(&mut self.thickness);
586+
let mut style = std::mem::take(&mut self.style);
587+
let mut color = std::mem::take(&mut self.color);
588+
589+
if let (Some((line, line_vp)), Some(thickness_val), Some((style, style_vp)), Some((color, color_vp))) = (&mut line, &mut thickness, &mut style, &mut color) {
590+
let intersection = *line_vp | *style_vp | *color_vp;
591+
if !intersection.is_empty() {
592+
let mut prefix = intersection;
593+
594+
// Only add prefixes if one of the new sub-properties was used
595+
if prefix.contains(VendorPrefix::None) && (*style != TextDecorationStyle::default() || *color != CssColor::current_color()) {
596+
if let Some(targets) = self.targets {
597+
prefix = Feature::TextDecoration.prefixes_for(targets)
598+
}
599+
}
600+
601+
self.decls.push(Property::TextDecoration(TextDecoration {
602+
line: line.clone(),
603+
thickness: thickness_val.clone(),
604+
style: style.clone(),
605+
color: color.clone()
606+
}, prefix));
607+
line_vp.remove(intersection);
608+
style_vp.remove(intersection);
609+
color_vp.remove(intersection);
610+
thickness = None;
611+
}
612+
}
613+
614+
macro_rules! single_property {
615+
($key: ident, $prop: ident) => {
616+
if let Some((val, vp)) = $key {
617+
if !vp.is_empty() {
618+
let mut prefix = vp;
619+
if prefix.contains(VendorPrefix::None) {
620+
if let Some(targets) = self.targets {
621+
prefix = Feature::$prop.prefixes_for(targets);
622+
}
623+
}
624+
self.decls.push(Property::$prop(val, prefix))
625+
}
626+
}
627+
};
628+
}
629+
630+
single_property!(line, TextDecorationLine);
631+
single_property!(style, TextDecorationStyle);
632+
single_property!(color, TextDecorationColor);
633+
634+
if let Some(thickness) = thickness {
635+
self.decls.push(Property::TextDecorationThickness(thickness))
636+
}
637+
}
638+
}

0 commit comments

Comments
 (0)