use crate::parser::{Parse, ParserContext};
use crate::properties::longhands::writing_mode::computed_value::T as SpecifiedWritingMode;
use crate::values::computed;
use crate::values::computed::text::TextEmphasisStyle as ComputedTextEmphasisStyle;
use crate::values::computed::{Context, ToComputedValue};
use crate::values::generics::NumberOrAuto;
use crate::values::generics::text::{
GenericHyphenateLimitChars, GenericInitialLetter, GenericTextDecorationLength, GenericTextIndent,
};
use crate::values::specified::length::LengthPercentage;
use crate::values::specified::{AllowQuirks, Integer, Number};
use crate::Zero;
use cssparser::Parser;
use icu_segmenter::GraphemeClusterSegmenter;
use std::fmt::{self, Write};
use style_traits::values::SequenceWriter;
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
use style_traits::{KeywordsCollectFn, SpecifiedValueInfo};
pub type InitialLetter = GenericInitialLetter<Number, Integer>;
#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
pub enum Spacing {
Normal,
Value(LengthPercentage),
}
impl Parse for Spacing {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
if input
.try_parse(|i| i.expect_ident_matching("normal"))
.is_ok()
{
return Ok(Spacing::Normal);
}
LengthPercentage::parse_quirky(context, input, AllowQuirks::Yes).map(Spacing::Value)
}
}
#[derive(Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
pub struct LetterSpacing(pub Spacing);
impl ToComputedValue for LetterSpacing {
type ComputedValue = computed::LetterSpacing;
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
use computed::text::GenericLetterSpacing;
match self.0 {
Spacing::Normal => GenericLetterSpacing(computed::LengthPercentage::zero()),
Spacing::Value(ref v) => GenericLetterSpacing(v.to_computed_value(context)),
}
}
fn from_computed_value(computed: &Self::ComputedValue) -> Self {
if computed.0.is_zero() {
return LetterSpacing(Spacing::Normal);
}
LetterSpacing(Spacing::Value(ToComputedValue::from_computed_value(&computed.0)))
}
}
#[derive(Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
pub struct WordSpacing(pub Spacing);
impl ToComputedValue for WordSpacing {
type ComputedValue = computed::WordSpacing;
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
match self.0 {
Spacing::Normal => computed::LengthPercentage::zero(),
Spacing::Value(ref v) => v.to_computed_value(context),
}
}
fn from_computed_value(computed: &Self::ComputedValue) -> Self {
WordSpacing(Spacing::Value(ToComputedValue::from_computed_value(computed)))
}
}
#[derive(
Clone,
Debug,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(C, u8)]
pub enum HyphenateCharacter {
Auto,
String(crate::OwnedStr),
}
pub type HyphenateLimitChars = GenericHyphenateLimitChars<Integer>;
impl Parse for HyphenateLimitChars {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
type IntegerOrAuto = NumberOrAuto<Integer>;
let total_word_length = IntegerOrAuto::parse(context, input)?;
let pre_hyphen_length = input.try_parse(|i| IntegerOrAuto::parse(context, i)).unwrap_or(IntegerOrAuto::Auto);
let post_hyphen_length = input.try_parse(|i| IntegerOrAuto::parse(context, i)).unwrap_or(pre_hyphen_length);
Ok(Self {
total_word_length,
pre_hyphen_length,
post_hyphen_length,
})
}
}
impl Parse for InitialLetter {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
if input
.try_parse(|i| i.expect_ident_matching("normal"))
.is_ok()
{
return Ok(Self::normal());
}
let size = Number::parse_at_least_one(context, input)?;
let sink = input
.try_parse(|i| Integer::parse_positive(context, i))
.unwrap_or_else(|_| crate::Zero::zero());
Ok(Self { size, sink })
}
}
#[derive(
Clone,
Debug,
Eq,
MallocSizeOf,
PartialEq,
Parse,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(C, u8)]
pub enum TextOverflowSide {
Clip,
Ellipsis,
String(crate::values::AtomString),
}
#[derive(
Clone,
Debug,
Eq,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToResolvedValue,
ToShmem,
)]
#[repr(C)]
pub struct TextOverflow {
pub first: TextOverflowSide,
pub second: TextOverflowSide,
pub sides_are_logical: bool,
}
impl Parse for TextOverflow {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<TextOverflow, ParseError<'i>> {
let first = TextOverflowSide::parse(context, input)?;
Ok(
if let Ok(second) = input.try_parse(|input| TextOverflowSide::parse(context, input)) {
Self {
first,
second,
sides_are_logical: false,
}
} else {
Self {
first: TextOverflowSide::Clip,
second: first,
sides_are_logical: true,
}
},
)
}
}
impl TextOverflow {
pub fn get_initial_value() -> TextOverflow {
TextOverflow {
first: TextOverflowSide::Clip,
second: TextOverflowSide::Clip,
sides_are_logical: true,
}
}
}
impl ToCss for TextOverflow {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
if self.sides_are_logical {
debug_assert_eq!(self.first, TextOverflowSide::Clip);
self.second.to_css(dest)?;
} else {
self.first.to_css(dest)?;
dest.write_char(' ')?;
self.second.to_css(dest)?;
}
Ok(())
}
}
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
PartialEq,
Parse,
Serialize,
SpecifiedValueInfo,
ToCss,
ToComputedValue,
ToResolvedValue,
ToShmem,
)]
#[cfg_attr(feature = "gecko", css(bitflags(
single = "none,spelling-error,grammar-error",
mixed = "underline,overline,line-through,blink",
)))]
#[cfg_attr(not(feature = "gecko"), css(bitflags(
single = "none",
mixed = "underline,overline,line-through,blink",
)))]
#[repr(C)]
pub struct TextDecorationLine(u8);
bitflags! {
impl TextDecorationLine: u8 {
const NONE = 0;
const UNDERLINE = 1 << 0;
const OVERLINE = 1 << 1;
const LINE_THROUGH = 1 << 2;
const BLINK = 1 << 3;
const SPELLING_ERROR = 1 << 4;
const GRAMMAR_ERROR = 1 << 5;
#[cfg(feature = "gecko")]
const COLOR_OVERRIDE = 1 << 7;
}
}
impl Default for TextDecorationLine {
fn default() -> Self {
TextDecorationLine::NONE
}
}
impl TextDecorationLine {
#[inline]
pub fn none() -> Self {
TextDecorationLine::NONE
}
}
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(C)]
pub enum TextTransformCase {
None,
Uppercase,
Lowercase,
Capitalize,
#[cfg(feature = "gecko")]
MathAuto,
}
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
PartialEq,
Parse,
Serialize,
SpecifiedValueInfo,
ToCss,
ToComputedValue,
ToResolvedValue,
ToShmem,
)]
#[cfg_attr(feature = "gecko", css(bitflags(
single = "none,math-auto",
mixed = "uppercase,lowercase,capitalize,full-width,full-size-kana",
validate_mixed = "Self::validate_mixed_flags",
)))]
#[cfg_attr(not(feature = "gecko"), css(bitflags(
single = "none",
mixed = "uppercase,lowercase,capitalize,full-width,full-size-kana",
validate_mixed = "Self::validate_mixed_flags",
)))]
#[repr(C)]
pub struct TextTransform(u8);
bitflags! {
impl TextTransform: u8 {
const NONE = 0;
const UPPERCASE = 1 << 0;
const LOWERCASE = 1 << 1;
const CAPITALIZE = 1 << 2;
#[cfg(feature = "gecko")]
const MATH_AUTO = 1 << 3;
#[cfg(feature = "gecko")]
const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0 | Self::MATH_AUTO.0;
#[cfg(feature = "servo")]
const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0;
const FULL_WIDTH = 1 << 4;
const FULL_SIZE_KANA = 1 << 5;
}
}
impl TextTransform {
#[inline]
pub fn none() -> Self {
Self::NONE
}
#[inline]
pub fn is_none(self) -> bool {
self == Self::NONE
}
fn validate_mixed_flags(&self) -> bool {
let case = self.intersection(Self::CASE_TRANSFORMS);
case.is_empty() || case.bits().is_power_of_two()
}
pub fn case(&self) -> TextTransformCase {
match *self & Self::CASE_TRANSFORMS {
Self::NONE => TextTransformCase::None,
Self::UPPERCASE => TextTransformCase::Uppercase,
Self::LOWERCASE => TextTransformCase::Lowercase,
Self::CAPITALIZE => TextTransformCase::Capitalize,
#[cfg(feature = "gecko")]
Self::MATH_AUTO => TextTransformCase::MathAuto,
_ => unreachable!("Case bits are exclusive with each other"),
}
}
}
#[derive(
Clone,
Copy,
Debug,
Eq,
FromPrimitive,
Hash,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[allow(missing_docs)]
#[repr(u8)]
pub enum TextAlignLast {
Auto,
Start,
End,
Left,
Right,
Center,
Justify,
}
#[derive(
Clone,
Copy,
Debug,
Eq,
FromPrimitive,
Hash,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[allow(missing_docs)]
#[repr(u8)]
pub enum TextAlignKeyword {
Start,
Left,
Right,
Center,
Justify,
End,
#[parse(aliases = "-webkit-center")]
MozCenter,
#[parse(aliases = "-webkit-left")]
MozLeft,
#[parse(aliases = "-webkit-right")]
MozRight,
}
#[derive(
Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
)]
pub enum TextAlign {
Keyword(TextAlignKeyword),
#[cfg(feature = "gecko")]
MatchParent,
#[parse(condition = "ParserContext::chrome_rules_enabled")]
MozCenterOrInherit,
}
impl ToComputedValue for TextAlign {
type ComputedValue = TextAlignKeyword;
#[inline]
fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue {
match *self {
TextAlign::Keyword(key) => key,
#[cfg(feature = "gecko")]
TextAlign::MatchParent => {
if _context.builder.is_root_element {
return TextAlignKeyword::Start;
}
let parent = _context
.builder
.get_parent_inherited_text()
.clone_text_align();
let ltr = _context.builder.inherited_writing_mode().is_bidi_ltr();
match (parent, ltr) {
(TextAlignKeyword::Start, true) => TextAlignKeyword::Left,
(TextAlignKeyword::Start, false) => TextAlignKeyword::Right,
(TextAlignKeyword::End, true) => TextAlignKeyword::Right,
(TextAlignKeyword::End, false) => TextAlignKeyword::Left,
_ => parent,
}
},
TextAlign::MozCenterOrInherit => {
let parent = _context
.builder
.get_parent_inherited_text()
.clone_text_align();
if parent == TextAlignKeyword::Start {
TextAlignKeyword::Center
} else {
parent
}
},
}
}
#[inline]
fn from_computed_value(computed: &Self::ComputedValue) -> Self {
TextAlign::Keyword(*computed)
}
}
fn fill_mode_is_default_and_shape_exists(
fill: &TextEmphasisFillMode,
shape: &Option<TextEmphasisShapeKeyword>,
) -> bool {
shape.is_some() && fill.is_filled()
}
#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
#[allow(missing_docs)]
pub enum TextEmphasisStyle {
Keyword {
#[css(contextual_skip_if = "fill_mode_is_default_and_shape_exists")]
fill: TextEmphasisFillMode,
shape: Option<TextEmphasisShapeKeyword>,
},
None,
String(crate::OwnedStr),
}
#[derive(
Clone,
Copy,
Debug,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToCss,
ToComputedValue,
ToResolvedValue,
ToShmem,
)]
#[repr(u8)]
pub enum TextEmphasisFillMode {
Filled,
Open,
}
impl TextEmphasisFillMode {
#[inline]
pub fn is_filled(&self) -> bool {
matches!(*self, TextEmphasisFillMode::Filled)
}
}
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToCss,
ToComputedValue,
ToResolvedValue,
ToShmem,
)]
#[repr(u8)]
pub enum TextEmphasisShapeKeyword {
Dot,
Circle,
DoubleCircle,
Triangle,
Sesame,
}
impl ToComputedValue for TextEmphasisStyle {
type ComputedValue = ComputedTextEmphasisStyle;
#[inline]
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
match *self {
TextEmphasisStyle::Keyword { fill, shape } => {
let shape = shape.unwrap_or_else(|| {
if context.style().get_inherited_box().clone_writing_mode() ==
SpecifiedWritingMode::HorizontalTb
{
TextEmphasisShapeKeyword::Circle
} else {
TextEmphasisShapeKeyword::Sesame
}
});
ComputedTextEmphasisStyle::Keyword { fill, shape }
},
TextEmphasisStyle::None => ComputedTextEmphasisStyle::None,
TextEmphasisStyle::String(ref s) => {
let first_grapheme_end = GraphemeClusterSegmenter::new()
.segment_str(s)
.nth(1)
.unwrap_or(0);
ComputedTextEmphasisStyle::String(s[0..first_grapheme_end].to_string().into())
},
}
}
#[inline]
fn from_computed_value(computed: &Self::ComputedValue) -> Self {
match *computed {
ComputedTextEmphasisStyle::Keyword { fill, shape } => TextEmphasisStyle::Keyword {
fill,
shape: Some(shape),
},
ComputedTextEmphasisStyle::None => TextEmphasisStyle::None,
ComputedTextEmphasisStyle::String(ref string) => {
TextEmphasisStyle::String(string.clone())
},
}
}
}
impl Parse for TextEmphasisStyle {
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
if input
.try_parse(|input| input.expect_ident_matching("none"))
.is_ok()
{
return Ok(TextEmphasisStyle::None);
}
if let Ok(s) = input.try_parse(|i| i.expect_string().map(|s| s.as_ref().to_owned())) {
return Ok(TextEmphasisStyle::String(s.into()));
}
let mut shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
let fill = input.try_parse(TextEmphasisFillMode::parse).ok();
if shape.is_none() {
shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
}
if shape.is_none() && fill.is_none() {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
let fill = fill.unwrap_or(TextEmphasisFillMode::Filled);
Ok(TextEmphasisStyle::Keyword { fill, shape })
}
}
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
PartialEq,
Parse,
Serialize,
SpecifiedValueInfo,
ToCss,
ToComputedValue,
ToResolvedValue,
ToShmem,
)]
#[repr(C)]
#[css(bitflags(
single = "auto",
mixed = "over,under,left,right",
validate_mixed = "Self::validate_and_simplify"
))]
pub struct TextEmphasisPosition(u8);
bitflags! {
impl TextEmphasisPosition: u8 {
const AUTO = 1 << 0;
const OVER = 1 << 1;
const UNDER = 1 << 2;
const LEFT = 1 << 3;
const RIGHT = 1 << 4;
}
}
impl TextEmphasisPosition {
fn validate_and_simplify(&mut self) -> bool {
if self.intersects(Self::OVER) == self.intersects(Self::UNDER) {
return false;
}
if self.intersects(Self::LEFT) {
return !self.intersects(Self::RIGHT);
}
self.remove(Self::RIGHT); true
}
}
#[repr(u8)]
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[allow(missing_docs)]
pub enum WordBreak {
Normal,
BreakAll,
KeepAll,
#[cfg(feature = "gecko")]
BreakWord,
}
#[repr(u8)]
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[allow(missing_docs)]
pub enum TextJustify {
Auto,
None,
InterWord,
#[parse(aliases = "distribute")]
InterCharacter,
}
#[repr(u8)]
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[allow(missing_docs)]
pub enum MozControlCharacterVisibility {
Hidden,
Visible,
}
#[cfg(feature = "gecko")]
impl Default for MozControlCharacterVisibility {
fn default() -> Self {
if static_prefs::pref!("layout.css.control-characters.visible") {
Self::Visible
} else {
Self::Hidden
}
}
}
#[repr(u8)]
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[allow(missing_docs)]
pub enum LineBreak {
Auto,
Loose,
Normal,
Strict,
Anywhere,
}
#[repr(u8)]
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[allow(missing_docs)]
pub enum OverflowWrap {
Normal,
BreakWord,
Anywhere,
}
pub type TextIndent = GenericTextIndent<LengthPercentage>;
impl Parse for TextIndent {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let mut length = None;
let mut hanging = false;
let mut each_line = false;
while !input.is_exhausted() {
if length.is_none() {
if let Ok(len) = input
.try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::Yes))
{
length = Some(len);
continue;
}
}
if static_prefs::pref!("layout.css.text-indent-keywords.enabled") {
try_match_ident_ignore_ascii_case! { input,
"hanging" if !hanging => hanging = true,
"each-line" if !each_line => each_line = true,
}
continue;
}
break;
}
if let Some(length) = length {
Ok(Self {
length,
hanging,
each_line,
})
} else {
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
}
}
#[repr(u8)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[allow(missing_docs)]
pub enum TextDecorationSkipInk {
Auto,
None,
All,
}
pub type TextDecorationLength = GenericTextDecorationLength<LengthPercentage>;
impl TextDecorationLength {
#[inline]
pub fn auto() -> Self {
GenericTextDecorationLength::Auto
}
#[inline]
pub fn is_auto(&self) -> bool {
matches!(*self, GenericTextDecorationLength::Auto)
}
}
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToResolvedValue,
ToShmem,
)]
#[css(bitflags(
single = "auto",
mixed = "from-font,under,left,right",
validate_mixed = "Self::validate_mixed_flags",
))]
#[repr(C)]
pub struct TextUnderlinePosition(u8);
bitflags! {
impl TextUnderlinePosition: u8 {
const AUTO = 0;
const FROM_FONT = 1 << 0;
const UNDER = 1 << 1;
const LEFT = 1 << 2;
const RIGHT = 1 << 3;
}
}
impl TextUnderlinePosition {
fn validate_mixed_flags(&self) -> bool {
if self.contains(Self::LEFT | Self::RIGHT) {
return false;
}
if self.contains(Self::FROM_FONT | Self::UNDER) {
return false;
}
true
}
}
impl ToCss for TextUnderlinePosition {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
if self.is_empty() {
return dest.write_str("auto");
}
let mut writer = SequenceWriter::new(dest, " ");
let mut any = false;
macro_rules! maybe_write {
($ident:ident => $str:expr) => {
if self.contains(TextUnderlinePosition::$ident) {
any = true;
writer.raw_item($str)?;
}
};
}
maybe_write!(FROM_FONT => "from-font");
maybe_write!(UNDER => "under");
maybe_write!(LEFT => "left");
maybe_write!(RIGHT => "right");
debug_assert!(any);
Ok(())
}
}
#[repr(u8)]
#[derive(
Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
)]
#[allow(missing_docs)]
pub enum RubyPosition {
AlternateOver,
AlternateUnder,
Over,
Under,
}
impl Parse for RubyPosition {
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<RubyPosition, ParseError<'i>> {
let alternate = input
.try_parse(|i| i.expect_ident_matching("alternate"))
.is_ok();
if alternate && input.is_exhausted() {
return Ok(RubyPosition::AlternateOver);
}
let over = try_match_ident_ignore_ascii_case! { input,
"over" => true,
"under" => false,
};
let alternate = alternate ||
input
.try_parse(|i| i.expect_ident_matching("alternate"))
.is_ok();
Ok(match (over, alternate) {
(true, true) => RubyPosition::AlternateOver,
(false, true) => RubyPosition::AlternateUnder,
(true, false) => RubyPosition::Over,
(false, false) => RubyPosition::Under,
})
}
}
impl ToCss for RubyPosition {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
dest.write_str(match self {
RubyPosition::AlternateOver => "alternate",
RubyPosition::AlternateUnder => "alternate under",
RubyPosition::Over => "over",
RubyPosition::Under => "under",
})
}
}
impl SpecifiedValueInfo for RubyPosition {
fn collect_completion_keywords(f: KeywordsCollectFn) {
f(&["alternate", "over", "under"])
}
}