Skip to content

Commit 5a3ea31

Browse files
committed
Add text-decoration properties
1 parent 739cbcd commit 5a3ea31

File tree

3 files changed

+260
-0
lines changed

3 files changed

+260
-0
lines changed

src/lib.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4177,4 +4177,35 @@ mod tests {
41774177
minify_test(".foo { text-indent: each-line 3em hanging }", ".foo{text-indent:3em hanging each-line}");
41784178
minify_test(".foo { text-indent: each-line hanging 3em }", ".foo{text-indent:3em hanging each-line}");
41794179
}
4180+
4181+
#[test]
4182+
fn test_text_decoration() {
4183+
minify_test(".foo { text-decoration-line: none }", ".foo{text-decoration-line:none}");
4184+
minify_test(".foo { text-decoration-line: underline }", ".foo{text-decoration-line:underline}");
4185+
minify_test(".foo { text-decoration-line: overline }", ".foo{text-decoration-line:overline}");
4186+
minify_test(".foo { text-decoration-line: line-through }", ".foo{text-decoration-line:line-through}");
4187+
minify_test(".foo { text-decoration-line: blink }", ".foo{text-decoration-line:blink}");
4188+
minify_test(".foo { text-decoration-line: underline overline }", ".foo{text-decoration-line:underline overline}");
4189+
minify_test(".foo { text-decoration-line: overline underline }", ".foo{text-decoration-line:underline overline}");
4190+
minify_test(".foo { text-decoration-line: overline line-through underline }", ".foo{text-decoration-line:underline overline line-through}");
4191+
minify_test(".foo { text-decoration-line: spelling-error }", ".foo{text-decoration-line:spelling-error}");
4192+
minify_test(".foo { text-decoration-line: grammar-error }", ".foo{text-decoration-line:grammar-error}");
4193+
minify_test(".foo { -webkit-text-decoration-line: overline underline }", ".foo{-webkit-text-decoration-line:underline overline}");
4194+
minify_test(".foo { -moz-text-decoration-line: overline underline }", ".foo{-moz-text-decoration-line:underline overline}");
4195+
4196+
minify_test(".foo { text-decoration-style: solid }", ".foo{text-decoration-style:solid}");
4197+
minify_test(".foo { text-decoration-style: dotted }", ".foo{text-decoration-style:dotted}");
4198+
minify_test(".foo { -webkit-text-decoration-style: solid }", ".foo{-webkit-text-decoration-style:solid}");
4199+
4200+
minify_test(".foo { text-decoration-color: yellow }", ".foo{text-decoration-color:#ff0}");
4201+
minify_test(".foo { -webkit-text-decoration-color: yellow }", ".foo{-webkit-text-decoration-color:#ff0}");
4202+
4203+
minify_test(".foo { text-decoration: none }", ".foo{text-decoration:none}");
4204+
minify_test(".foo { text-decoration: underline dotted }", ".foo{text-decoration:underline dotted}");
4205+
minify_test(".foo { text-decoration: underline dotted yellow }", ".foo{text-decoration:underline dotted #ff0}");
4206+
minify_test(".foo { text-decoration: yellow dotted underline }", ".foo{text-decoration:underline dotted #ff0}");
4207+
minify_test(".foo { text-decoration: underline overline dotted yellow }", ".foo{text-decoration:underline overline dotted #ff0}");
4208+
minify_test(".foo { -webkit-text-decoration: yellow dotted underline }", ".foo{-webkit-text-decoration:underline dotted #ff0}");
4209+
minify_test(".foo { -moz-text-decoration: yellow dotted underline }", ".foo{-moz-text-decoration:underline dotted #ff0}");
4210+
}
41804211
}

src/properties/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,12 @@ define_properties! {
453453
"word-spacing": WordSpacing(Spacing),
454454
"letter-spacing": LetterSpacing(Spacing),
455455
"text-indent": TextIndent(TextIndent),
456+
457+
"text-decoration-line": TextDecorationLine(TextDecorationLine, VendorPrefix) / "webkit" / "moz",
458+
"text-decoration-style": TextDecorationStyle(TextDecorationStyle, VendorPrefix) / "webkit" / "moz",
459+
"text-decoration-color": TextDecorationColor(CssColor, VendorPrefix) / "webkit" / "moz",
460+
"text-decoration-thickness": TextDecorationThickness(TextDecorationThickness),
461+
"text-decoration": TextDecoration(TextDecoration, VendorPrefix) / "webkit" / "moz",
456462
}
457463

458464
impl<T: smallvec::Array<Item = V>, V: Parse> Parse for SmallVec<T> {

src/properties/text.rs

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use cssparser::*;
22
use crate::traits::{Parse, ToCss};
33
use crate::macros::enum_property;
44
use crate::values::length::{Length, LengthPercentage};
5+
use crate::values::color::CssColor;
56
use crate::printer::Printer;
67
use bitflags::bitflags;
78
use std::fmt::Write;
@@ -278,3 +279,225 @@ impl ToCss for TextIndent {
278279
Ok(())
279280
}
280281
}
282+
283+
// https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-decoration-line-property
284+
bitflags! {
285+
pub struct TextDecorationLine: u8 {
286+
const Underline = 0b00000001;
287+
const Overline = 0b00000010;
288+
const LineThrough = 0b00000100;
289+
const Blink = 0b00001000;
290+
const SpellingError = 0b00010000;
291+
const GrammarError = 0b00100000;
292+
}
293+
}
294+
295+
impl Default for TextDecorationLine {
296+
fn default() -> TextDecorationLine {
297+
TextDecorationLine::empty()
298+
}
299+
}
300+
301+
impl Parse for TextDecorationLine {
302+
fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ()>> {
303+
let mut value = TextDecorationLine::empty();
304+
let mut any = false;
305+
306+
loop {
307+
let flag: Result<_, ParseError<'i, ()>> = input.try_parse(|input| {
308+
let location = input.current_source_location();
309+
let ident = input.expect_ident()?;
310+
Ok(match_ignore_ascii_case! { &ident,
311+
"none" if value.is_empty() => TextDecorationLine::empty(),
312+
"underline" => TextDecorationLine::Underline,
313+
"overline" => TextDecorationLine::Overline,
314+
"line-through" => TextDecorationLine::LineThrough,
315+
"blink" =>TextDecorationLine::Blink,
316+
"spelling-error" if value.is_empty() => TextDecorationLine::SpellingError,
317+
"grammar-error" if value.is_empty() => TextDecorationLine::GrammarError,
318+
_ => return Err(location.new_unexpected_token_error(
319+
cssparser::Token::Ident(ident.clone())
320+
))
321+
})
322+
});
323+
324+
if let Ok(flag) = flag {
325+
value |= flag;
326+
any = true;
327+
} else {
328+
break
329+
}
330+
}
331+
332+
if !any {
333+
return Err(input.new_error(BasicParseErrorKind::QualifiedRuleInvalid))
334+
}
335+
336+
Ok(value)
337+
}
338+
}
339+
340+
impl ToCss for TextDecorationLine {
341+
fn to_css<W>(&self, dest: &mut Printer<W>) -> std::fmt::Result where W: std::fmt::Write {
342+
if self.is_empty() {
343+
return dest.write_str("none")
344+
}
345+
346+
if self.contains(TextDecorationLine::SpellingError) {
347+
return dest.write_str("spelling-error")
348+
}
349+
350+
if self.contains(TextDecorationLine::GrammarError) {
351+
return dest.write_str("grammar-error")
352+
}
353+
354+
let mut needs_space = false;
355+
macro_rules! val {
356+
($val: ident, $str: expr) => {
357+
if self.contains(TextDecorationLine::$val) {
358+
if needs_space {
359+
dest.write_char(' ')?;
360+
}
361+
dest.write_str($str)?;
362+
needs_space = true;
363+
}
364+
};
365+
}
366+
367+
val!(Underline, "underline");
368+
val!(Overline, "overline");
369+
val!(LineThrough, "line-through");
370+
val!(Blink, "blink");
371+
Ok(())
372+
}
373+
}
374+
375+
// https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-decoration-style-property
376+
enum_property!(TextDecorationStyle,
377+
Solid,
378+
Double,
379+
Dotted,
380+
Dashed,
381+
Wavy
382+
);
383+
384+
impl Default for TextDecorationStyle {
385+
fn default() -> TextDecorationStyle {
386+
TextDecorationStyle::Solid
387+
}
388+
}
389+
390+
/// https://www.w3.org/TR/2020/WD-css-text-decor-4-20200506/#text-decoration-width-property
391+
#[derive(Debug, Clone, PartialEq)]
392+
pub enum TextDecorationThickness {
393+
Auto,
394+
FromFont,
395+
LengthPercentage(LengthPercentage)
396+
}
397+
398+
impl Default for TextDecorationThickness {
399+
fn default() -> TextDecorationThickness {
400+
TextDecorationThickness::Auto
401+
}
402+
}
403+
404+
impl Parse for TextDecorationThickness {
405+
fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ()>> {
406+
if input.try_parse(|input| input.expect_ident_matching("auto")).is_ok() {
407+
return Ok(TextDecorationThickness::Auto)
408+
}
409+
410+
if input.try_parse(|input| input.expect_ident_matching("from-font")).is_ok() {
411+
return Ok(TextDecorationThickness::FromFont)
412+
}
413+
414+
let lp = LengthPercentage::parse(input)?;
415+
Ok(TextDecorationThickness::LengthPercentage(lp))
416+
}
417+
}
418+
419+
impl ToCss for TextDecorationThickness {
420+
fn to_css<W>(&self, dest: &mut Printer<W>) -> std::fmt::Result where W: std::fmt::Write {
421+
match self {
422+
TextDecorationThickness::Auto => dest.write_str("auto"),
423+
TextDecorationThickness::FromFont => dest.write_str("from-font"),
424+
TextDecorationThickness::LengthPercentage(lp) => lp.to_css(dest)
425+
}
426+
}
427+
}
428+
429+
#[derive(Debug, Clone, PartialEq)]
430+
pub struct TextDecoration {
431+
line: TextDecorationLine,
432+
thickness: TextDecorationThickness,
433+
style: TextDecorationStyle,
434+
color: CssColor
435+
}
436+
437+
impl Parse for TextDecoration {
438+
fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ()>> {
439+
let mut line = None;
440+
let mut thickness = None;
441+
let mut style = None;
442+
let mut color = None;
443+
444+
loop {
445+
macro_rules! prop {
446+
($key: ident, $type: ident) => {
447+
if $key.is_none() {
448+
if let Ok(val) = input.try_parse($type::parse) {
449+
$key = Some(val);
450+
continue
451+
}
452+
}
453+
};
454+
}
455+
456+
prop!(line, TextDecorationLine);
457+
prop!(thickness, TextDecorationThickness);
458+
prop!(style, TextDecorationStyle);
459+
prop!(color, CssColor);
460+
break
461+
}
462+
463+
Ok(TextDecoration {
464+
line: line.unwrap_or_default(),
465+
thickness: thickness.unwrap_or_default(),
466+
style: style.unwrap_or_default(),
467+
color: color.unwrap_or(CssColor::current_color())
468+
})
469+
}
470+
}
471+
472+
impl ToCss for TextDecoration {
473+
fn to_css<W>(&self, dest: &mut Printer<W>) -> std::fmt::Result where W: std::fmt::Write {
474+
self.line.to_css(dest)?;
475+
if self.line.is_empty() {
476+
return Ok(())
477+
}
478+
479+
let mut needs_space = true;
480+
if self.thickness != TextDecorationThickness::default() {
481+
dest.write_char(' ')?;
482+
self.thickness.to_css(dest)?;
483+
needs_space = true;
484+
}
485+
486+
if self.style != TextDecorationStyle::default() {
487+
if needs_space {
488+
dest.write_char(' ')?;
489+
}
490+
self.style.to_css(dest)?;
491+
needs_space = true;
492+
}
493+
494+
if self.color != CssColor::current_color() {
495+
if needs_space {
496+
dest.write_char(' ')?;
497+
}
498+
self.color.to_css(dest)?;
499+
}
500+
501+
Ok(())
502+
}
503+
}

0 commit comments

Comments
 (0)