mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
The new version of rust allows us to elide some lifetimes and clippy is now complaining about this. This change elides them where possible and removes the clippy exceptions. Fixes #34804. Signed-off-by: Martin Robinson <mrobinson@igalia.com>
631 lines
26 KiB
Rust
631 lines
26 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||
|
||
//! The generated content assignment phase.
|
||
//!
|
||
//! This phase handles CSS counters, quotes, and ordered lists per CSS § 12.3-12.5. It cannot be
|
||
//! done in parallel and is therefore a sequential pass that runs on as little of the flow tree
|
||
//! as possible.
|
||
|
||
use std::collections::{HashMap, LinkedList};
|
||
use std::sync::LazyLock;
|
||
|
||
use script_layout_interface::wrapper_traits::PseudoElementType;
|
||
use smallvec::SmallVec;
|
||
use style::computed_values::list_style_type::T as ListStyleType;
|
||
use style::properties::ComputedValues;
|
||
use style::selector_parser::RestyleDamage;
|
||
use style::servo::restyle_damage::ServoRestyleDamage;
|
||
use style::values::generics::counters::ContentItem;
|
||
use style::values::specified::list::{QuotePair, Quotes};
|
||
|
||
use crate::context::LayoutContext;
|
||
use crate::display_list::items::OpaqueNode;
|
||
use crate::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils};
|
||
use crate::fragment::{
|
||
Fragment, GeneratedContentInfo, SpecificFragmentInfo, UnscannedTextFragmentInfo,
|
||
};
|
||
use crate::text::TextRunScanner;
|
||
use crate::traversal::InorderFlowTraversal;
|
||
|
||
static INITIAL_QUOTES: LazyLock<style::ArcSlice<QuotePair>> = LazyLock::new(|| {
|
||
style::ArcSlice::from_iter_leaked(
|
||
vec![
|
||
QuotePair {
|
||
opening: "\u{201c}".to_owned().into(),
|
||
closing: "\u{201d}".to_owned().into(),
|
||
},
|
||
QuotePair {
|
||
opening: "\u{2018}".to_owned().into(),
|
||
closing: "\u{2019}".to_owned().into(),
|
||
},
|
||
]
|
||
.into_iter(),
|
||
)
|
||
});
|
||
|
||
// Decimal styles per CSS-COUNTER-STYLES § 6.1:
|
||
static DECIMAL: [char; 10] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||
// TODO(pcwalton): `decimal-leading-zero`
|
||
static ARABIC_INDIC: [char; 10] = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'];
|
||
// TODO(pcwalton): `armenian`, `upper-armenian`, `lower-armenian`
|
||
static BENGALI: [char; 10] = ['০', '১', '২', '৩', '৪', '৫', '৬', '৭', '৮', '৯'];
|
||
static CAMBODIAN: [char; 10] = ['០', '១', '២', '៣', '៤', '៥', '៦', '៧', '៨', '៩'];
|
||
// TODO(pcwalton): Suffix for CJK decimal.
|
||
static CJK_DECIMAL: [char; 10] = ['〇', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
|
||
static DEVANAGARI: [char; 10] = ['०', '१', '२', '३', '४', '५', '६', '७', '८', '९'];
|
||
// TODO(pcwalton): `georgian`
|
||
static GUJARATI: [char; 10] = ['૦', '૧', '૨', '૩', '૪', '૫', '૬', '૭', '૮', '૯'];
|
||
static GURMUKHI: [char; 10] = ['੦', '੧', '੨', '੩', '੪', '੫', '੬', '੭', '੮', '੯'];
|
||
// TODO(pcwalton): `hebrew`
|
||
static KANNADA: [char; 10] = ['೦', '೧', '೨', '೩', '೪', '೫', '೬', '೭', '೮', '೯'];
|
||
static LAO: [char; 10] = ['໐', '໑', '໒', '໓', '໔', '໕', '໖', '໗', '໘', '໙'];
|
||
static MALAYALAM: [char; 10] = ['൦', '൧', '൨', '൩', '൪', '൫', '൬', '൭', '൮', '൯'];
|
||
static MONGOLIAN: [char; 10] = ['᠐', '᠑', '᠒', '᠓', '᠔', '᠕', '᠖', '᠗', '᠘', '᠙'];
|
||
static MYANMAR: [char; 10] = ['၀', '၁', '၂', '၃', '၄', '၅', '၆', '၇', '၈', '၉'];
|
||
static ORIYA: [char; 10] = ['୦', '୧', '୨', '୩', '୪', '୫', '୬', '୭', '୮', '୯'];
|
||
static PERSIAN: [char; 10] = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
|
||
// TODO(pcwalton): `lower-roman`, `upper-roman`
|
||
static TELUGU: [char; 10] = ['౦', '౧', '౨', '౩', '౪', '౫', '౬', '౭', '౮', '౯'];
|
||
static THAI: [char; 10] = ['๐', '๑', '๒', '๓', '๔', '๕', '๖', '๗', '๘', '๙'];
|
||
static TIBETAN: [char; 10] = ['༠', '༡', '༢', '༣', '༤', '༥', '༦', '༧', '༨', '༩'];
|
||
|
||
// Alphabetic styles per CSS-COUNTER-STYLES § 6.2:
|
||
static LOWER_ALPHA: [char; 26] = [
|
||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
|
||
't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||
];
|
||
static UPPER_ALPHA: [char; 26] = [
|
||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
|
||
'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||
];
|
||
static CJK_EARTHLY_BRANCH: [char; 12] = [
|
||
'子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥',
|
||
];
|
||
static CJK_HEAVENLY_STEM: [char; 10] = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'];
|
||
static LOWER_GREEK: [char; 24] = [
|
||
'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', 'π', 'ρ', 'σ', 'τ',
|
||
'υ', 'φ', 'χ', 'ψ', 'ω',
|
||
];
|
||
static HIRAGANA: [char; 48] = [
|
||
'あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ', 'さ', 'し', 'す', 'せ', 'そ', 'た',
|
||
'ち', 'つ', 'て', 'と', 'な', 'に', 'ぬ', 'ね', 'の', 'は', 'ひ', 'ふ', 'へ', 'ほ', 'ま', 'み',
|
||
'む', 'め', 'も', 'や', 'ゆ', 'よ', 'ら', 'り', 'る', 'れ', 'ろ', 'わ', 'ゐ', 'ゑ', 'を', 'ん',
|
||
];
|
||
static HIRAGANA_IROHA: [char; 47] = [
|
||
'い', 'ろ', 'は', 'に', 'ほ', 'へ', 'と', 'ち', 'り', 'ぬ', 'る', 'を', 'わ', 'か', 'よ', 'た',
|
||
'れ', 'そ', 'つ', 'ね', 'な', 'ら', 'む', 'う', 'ゐ', 'の', 'お', 'く', 'や', 'ま', 'け', 'ふ',
|
||
'こ', 'え', 'て', 'あ', 'さ', 'き', 'ゆ', 'め', 'み', 'し', 'ゑ', 'ひ', 'も', 'せ', 'す',
|
||
];
|
||
static KATAKANA: [char; 48] = [
|
||
'ア', 'イ', 'ウ', 'エ', 'オ', 'カ', 'キ', 'ク', 'ケ', 'コ', 'サ', 'シ', 'ス', 'セ', 'ソ', 'タ',
|
||
'チ', 'ツ', 'テ', 'ト', 'ナ', 'ニ', 'ヌ', 'ネ', 'ノ', 'ハ', 'ヒ', 'フ', 'ヘ', 'ホ', 'マ', 'ミ',
|
||
'ム', 'メ', 'モ', 'ヤ', 'ユ', 'ヨ', 'ラ', 'リ', 'ル', 'レ', 'ロ', 'ワ', 'ヰ', 'ヱ', 'ヲ', 'ン',
|
||
];
|
||
static KATAKANA_IROHA: [char; 47] = [
|
||
'イ', 'ロ', 'ハ', 'ニ', 'ホ', 'ヘ', 'ト', 'チ', 'リ', 'ヌ', 'ル', 'ヲ', 'ワ', 'カ', 'ヨ', 'タ',
|
||
'レ', 'ソ', 'ツ', 'ネ', 'ナ', 'ラ', 'ム', 'ウ', 'ヰ', 'ノ', 'オ', 'ク', 'ヤ', 'マ', 'ケ', 'フ',
|
||
'コ', 'エ', 'テ', 'ア', 'サ', 'キ', 'ユ', 'メ', 'ミ', 'シ', 'ヱ', 'ヒ', 'モ', 'セ', 'ス',
|
||
];
|
||
|
||
/// The generated content resolution traversal.
|
||
pub struct ResolveGeneratedContent<'a> {
|
||
/// The layout context.
|
||
layout_context: &'a LayoutContext<'a>,
|
||
/// The counter representing an ordered list item.
|
||
list_item: Counter,
|
||
/// Named CSS counters.
|
||
counters: HashMap<String, Counter>,
|
||
/// The level of quote nesting.
|
||
quote: u32,
|
||
}
|
||
|
||
impl<'a> ResolveGeneratedContent<'a> {
|
||
/// Creates a new generated content resolution traversal.
|
||
pub fn new(layout_context: &'a LayoutContext) -> ResolveGeneratedContent<'a> {
|
||
ResolveGeneratedContent {
|
||
layout_context,
|
||
list_item: Counter::new(),
|
||
counters: HashMap::new(),
|
||
quote: 0,
|
||
}
|
||
}
|
||
}
|
||
|
||
impl InorderFlowTraversal for ResolveGeneratedContent<'_> {
|
||
#[inline]
|
||
fn process(&mut self, flow: &mut dyn Flow, level: u32) {
|
||
let mut mutator = ResolveGeneratedContentFragmentMutator {
|
||
traversal: self,
|
||
level,
|
||
is_block: flow.is_block_like(),
|
||
incremented: false,
|
||
};
|
||
flow.mutate_fragments(&mut |fragment| mutator.mutate_fragment(fragment))
|
||
}
|
||
|
||
#[inline]
|
||
fn should_process_subtree(&mut self, flow: &mut dyn Flow) -> bool {
|
||
flow.base()
|
||
.restyle_damage
|
||
.intersects(ServoRestyleDamage::RESOLVE_GENERATED_CONTENT) ||
|
||
flow.base().flags.intersects(
|
||
FlowFlags::AFFECTS_COUNTERS | FlowFlags::HAS_COUNTER_AFFECTING_CHILDREN,
|
||
)
|
||
}
|
||
}
|
||
|
||
/// The object that mutates the generated content fragments.
|
||
struct ResolveGeneratedContentFragmentMutator<'a, 'b: 'a> {
|
||
/// The traversal.
|
||
traversal: &'a mut ResolveGeneratedContent<'b>,
|
||
/// The level we're at in the flow tree.
|
||
level: u32,
|
||
/// Whether this flow is a block flow.
|
||
is_block: bool,
|
||
/// Whether we've incremented the counter yet.
|
||
incremented: bool,
|
||
}
|
||
|
||
impl ResolveGeneratedContentFragmentMutator<'_, '_> {
|
||
fn mutate_fragment(&mut self, fragment: &mut Fragment) {
|
||
// We only reset and/or increment counters once per flow. This avoids double-incrementing
|
||
// counters on list items (once for the main fragment and once for the marker).
|
||
if !self.incremented {
|
||
self.reset_and_increment_counters_as_necessary(fragment);
|
||
}
|
||
|
||
let mut list_style_type = fragment.style().get_list().list_style_type;
|
||
if !fragment.style().get_box().display.is_list_item() {
|
||
list_style_type = ListStyleType::None
|
||
}
|
||
|
||
let mut new_info = None;
|
||
{
|
||
let info =
|
||
if let SpecificFragmentInfo::GeneratedContent(ref mut info) = fragment.specific {
|
||
info
|
||
} else {
|
||
return;
|
||
};
|
||
|
||
match **info {
|
||
GeneratedContentInfo::ListItem => {
|
||
new_info = self.traversal.list_item.render(
|
||
self.traversal.layout_context,
|
||
fragment.node,
|
||
fragment.pseudo,
|
||
fragment.style.clone(),
|
||
list_style_type,
|
||
RenderingMode::Suffix(".\u{00a0}"),
|
||
)
|
||
},
|
||
GeneratedContentInfo::ContentItem(ContentItem::Counter(
|
||
ref counter_name,
|
||
counter_style,
|
||
)) => {
|
||
let temporary_counter = Counter::new();
|
||
let counter = self
|
||
.traversal
|
||
.counters
|
||
.get(&*counter_name.0)
|
||
.unwrap_or(&temporary_counter);
|
||
new_info = counter.render(
|
||
self.traversal.layout_context,
|
||
fragment.node,
|
||
fragment.pseudo,
|
||
fragment.style.clone(),
|
||
counter_style,
|
||
RenderingMode::Plain,
|
||
)
|
||
},
|
||
GeneratedContentInfo::ContentItem(ContentItem::Counters(
|
||
ref counter_name,
|
||
ref separator,
|
||
counter_style,
|
||
)) => {
|
||
let temporary_counter = Counter::new();
|
||
let counter = self
|
||
.traversal
|
||
.counters
|
||
.get(&*counter_name.0)
|
||
.unwrap_or(&temporary_counter);
|
||
new_info = counter.render(
|
||
self.traversal.layout_context,
|
||
fragment.node,
|
||
fragment.pseudo,
|
||
fragment.style.clone(),
|
||
counter_style,
|
||
RenderingMode::All(separator),
|
||
);
|
||
},
|
||
GeneratedContentInfo::ContentItem(ContentItem::OpenQuote) => {
|
||
new_info = render_text(
|
||
self.traversal.layout_context,
|
||
fragment.node,
|
||
fragment.pseudo,
|
||
fragment.style.clone(),
|
||
self.quote(&fragment.style, false),
|
||
);
|
||
self.traversal.quote += 1
|
||
},
|
||
GeneratedContentInfo::ContentItem(ContentItem::CloseQuote) => {
|
||
if self.traversal.quote >= 1 {
|
||
self.traversal.quote -= 1
|
||
}
|
||
|
||
new_info = render_text(
|
||
self.traversal.layout_context,
|
||
fragment.node,
|
||
fragment.pseudo,
|
||
fragment.style.clone(),
|
||
self.quote(&fragment.style, true),
|
||
);
|
||
},
|
||
GeneratedContentInfo::ContentItem(ContentItem::NoOpenQuote) => {
|
||
self.traversal.quote += 1
|
||
},
|
||
GeneratedContentInfo::ContentItem(ContentItem::NoCloseQuote) => {
|
||
if self.traversal.quote >= 1 {
|
||
self.traversal.quote -= 1
|
||
}
|
||
},
|
||
GeneratedContentInfo::Empty |
|
||
GeneratedContentInfo::ContentItem(ContentItem::String(_)) |
|
||
GeneratedContentInfo::ContentItem(ContentItem::Attr(_)) |
|
||
GeneratedContentInfo::ContentItem(ContentItem::Image(..)) => {
|
||
// Nothing to do here.
|
||
},
|
||
}
|
||
};
|
||
|
||
fragment.specific = match new_info {
|
||
Some(new_info) => new_info,
|
||
// If the fragment did not generate any content, replace it with a no-op placeholder
|
||
// so that it isn't processed again on the next layout. FIXME (mbrubeck): When
|
||
// processing an inline flow, this traversal should be allowed to insert or remove
|
||
// fragments. Then we can just remove these fragments rather than adding placeholders.
|
||
None => SpecificFragmentInfo::GeneratedContent(Box::new(GeneratedContentInfo::Empty)),
|
||
};
|
||
}
|
||
|
||
fn reset_and_increment_counters_as_necessary(&mut self, fragment: &mut Fragment) {
|
||
let mut list_style_type = fragment.style().get_list().list_style_type;
|
||
if !self.is_block || !fragment.style().get_box().display.is_list_item() {
|
||
list_style_type = ListStyleType::None
|
||
}
|
||
|
||
match list_style_type {
|
||
ListStyleType::Disc |
|
||
ListStyleType::None |
|
||
ListStyleType::Circle |
|
||
ListStyleType::Square |
|
||
ListStyleType::DisclosureOpen |
|
||
ListStyleType::DisclosureClosed => {},
|
||
_ => self.traversal.list_item.increment(self.level, 1),
|
||
}
|
||
|
||
// Truncate down counters.
|
||
for counter in self.traversal.counters.values_mut() {
|
||
counter.truncate_to_level(self.level);
|
||
}
|
||
self.traversal.list_item.truncate_to_level(self.level);
|
||
|
||
for pair in &*fragment.style().get_counters().counter_reset {
|
||
let counter_name = &*pair.name.0;
|
||
if let Some(ref mut counter) = self.traversal.counters.get_mut(counter_name) {
|
||
counter.reset(self.level, pair.value);
|
||
continue;
|
||
}
|
||
|
||
let mut counter = Counter::new();
|
||
counter.reset(self.level, pair.value);
|
||
self.traversal
|
||
.counters
|
||
.insert(counter_name.to_owned(), counter);
|
||
}
|
||
|
||
for pair in &*fragment.style().get_counters().counter_increment {
|
||
let counter_name = &*pair.name.0;
|
||
if let Some(ref mut counter) = self.traversal.counters.get_mut(counter_name) {
|
||
counter.increment(self.level, pair.value);
|
||
continue;
|
||
}
|
||
|
||
let mut counter = Counter::new();
|
||
counter.increment(self.level, pair.value);
|
||
self.traversal
|
||
.counters
|
||
.insert(counter_name.to_owned(), counter);
|
||
}
|
||
|
||
self.incremented = true
|
||
}
|
||
|
||
fn quote(&self, style: &ComputedValues, close: bool) -> String {
|
||
let quotes = match style.get_list().quotes {
|
||
Quotes::Auto => &*INITIAL_QUOTES,
|
||
Quotes::QuoteList(ref list) => &list.0,
|
||
};
|
||
if quotes.is_empty() {
|
||
return String::new();
|
||
}
|
||
let pair = if self.traversal.quote as usize >= quotes.len() {
|
||
quotes.last().unwrap()
|
||
} else {
|
||
"es[self.traversal.quote as usize]
|
||
};
|
||
if close {
|
||
pair.closing.to_string()
|
||
} else {
|
||
pair.opening.to_string()
|
||
}
|
||
}
|
||
}
|
||
|
||
/// A counter per CSS 2.1 § 12.4.
|
||
struct Counter {
|
||
/// The values at each level.
|
||
values: Vec<CounterValue>,
|
||
}
|
||
|
||
impl Counter {
|
||
fn new() -> Counter {
|
||
Counter { values: Vec::new() }
|
||
}
|
||
|
||
fn reset(&mut self, level: u32, value: i32) {
|
||
// Do we have an instance of the counter at this level? If so, just mutate it.
|
||
if let Some(ref mut existing_value) = self.values.last_mut() {
|
||
if level == existing_value.level {
|
||
existing_value.value = value;
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Otherwise, push a new instance of the counter.
|
||
self.values.push(CounterValue { level, value })
|
||
}
|
||
|
||
fn truncate_to_level(&mut self, level: u32) {
|
||
if let Some(position) = self.values.iter().position(|value| value.level > level) {
|
||
self.values.truncate(position)
|
||
}
|
||
}
|
||
|
||
fn increment(&mut self, level: u32, amount: i32) {
|
||
if let Some(ref mut value) = self.values.last_mut() {
|
||
value.value += amount;
|
||
return;
|
||
}
|
||
|
||
self.values.push(CounterValue {
|
||
level,
|
||
value: amount,
|
||
})
|
||
}
|
||
|
||
fn render(
|
||
&self,
|
||
layout_context: &LayoutContext,
|
||
node: OpaqueNode,
|
||
pseudo: PseudoElementType,
|
||
style: crate::ServoArc<ComputedValues>,
|
||
list_style_type: ListStyleType,
|
||
mode: RenderingMode,
|
||
) -> Option<SpecificFragmentInfo> {
|
||
let mut string = String::new();
|
||
match mode {
|
||
RenderingMode::Plain => {
|
||
let value = match self.values.last() {
|
||
Some(value) => value.value,
|
||
None => 0,
|
||
};
|
||
push_representation(value, list_style_type, &mut string)
|
||
},
|
||
RenderingMode::Suffix(suffix) => {
|
||
let value = match self.values.last() {
|
||
Some(value) => value.value,
|
||
None => 0,
|
||
};
|
||
push_representation(value, list_style_type, &mut string);
|
||
string.push_str(suffix)
|
||
},
|
||
RenderingMode::All(separator) => {
|
||
let mut first = true;
|
||
for value in &self.values {
|
||
if !first {
|
||
string.push_str(separator)
|
||
}
|
||
first = false;
|
||
push_representation(value.value, list_style_type, &mut string)
|
||
}
|
||
},
|
||
}
|
||
|
||
if string.is_empty() {
|
||
None
|
||
} else {
|
||
render_text(layout_context, node, pseudo, style, string)
|
||
}
|
||
}
|
||
}
|
||
|
||
/// How a counter value is to be rendered.
|
||
enum RenderingMode<'a> {
|
||
/// The innermost counter value is rendered with no extra decoration.
|
||
Plain,
|
||
/// The innermost counter value is rendered with the given string suffix.
|
||
Suffix(&'a str),
|
||
/// All values of the counter are rendered with the given separator string between them.
|
||
All(&'a str),
|
||
}
|
||
|
||
/// The value of a counter at a given level.
|
||
struct CounterValue {
|
||
/// The level of the flow tree that this corresponds to.
|
||
level: u32,
|
||
/// The value of the counter at this level.
|
||
value: i32,
|
||
}
|
||
|
||
/// Creates fragment info for a literal string.
|
||
fn render_text(
|
||
layout_context: &LayoutContext,
|
||
node: OpaqueNode,
|
||
pseudo: PseudoElementType,
|
||
style: crate::ServoArc<ComputedValues>,
|
||
string: String,
|
||
) -> Option<SpecificFragmentInfo> {
|
||
let mut fragments = LinkedList::new();
|
||
let info = SpecificFragmentInfo::UnscannedText(Box::new(UnscannedTextFragmentInfo::new(
|
||
string.into_boxed_str(),
|
||
None,
|
||
)));
|
||
fragments.push_back(Fragment::from_opaque_node_and_style(
|
||
node,
|
||
pseudo,
|
||
style.clone(),
|
||
style,
|
||
RestyleDamage::rebuild_and_reflow(),
|
||
info,
|
||
));
|
||
// FIXME(pcwalton): This should properly handle multiple marker fragments. This could happen
|
||
// due to text run splitting.
|
||
let fragments = TextRunScanner::new().scan_for_runs(&layout_context.font_context, fragments);
|
||
if fragments.is_empty() {
|
||
None
|
||
} else {
|
||
Some(fragments.fragments.into_iter().next().unwrap().specific)
|
||
}
|
||
}
|
||
|
||
/// Appends string that represents the value rendered using the system appropriate for the given
|
||
/// `list-style-type` onto the given string.
|
||
fn push_representation(value: i32, list_style_type: ListStyleType, accumulator: &mut String) {
|
||
match list_style_type {
|
||
ListStyleType::None => {},
|
||
ListStyleType::Disc |
|
||
ListStyleType::Circle |
|
||
ListStyleType::Square |
|
||
ListStyleType::DisclosureOpen |
|
||
ListStyleType::DisclosureClosed => accumulator.push(static_representation(list_style_type)),
|
||
ListStyleType::Decimal => push_numeric_representation(value, &DECIMAL, accumulator),
|
||
ListStyleType::ArabicIndic => {
|
||
push_numeric_representation(value, &ARABIC_INDIC, accumulator)
|
||
},
|
||
ListStyleType::Bengali => push_numeric_representation(value, &BENGALI, accumulator),
|
||
ListStyleType::Cambodian | ListStyleType::Khmer => {
|
||
push_numeric_representation(value, &CAMBODIAN, accumulator)
|
||
},
|
||
ListStyleType::CjkDecimal => push_numeric_representation(value, &CJK_DECIMAL, accumulator),
|
||
ListStyleType::Devanagari => push_numeric_representation(value, &DEVANAGARI, accumulator),
|
||
ListStyleType::Gujarati => push_numeric_representation(value, &GUJARATI, accumulator),
|
||
ListStyleType::Gurmukhi => push_numeric_representation(value, &GURMUKHI, accumulator),
|
||
ListStyleType::Kannada => push_numeric_representation(value, &KANNADA, accumulator),
|
||
ListStyleType::Lao => push_numeric_representation(value, &LAO, accumulator),
|
||
ListStyleType::Malayalam => push_numeric_representation(value, &MALAYALAM, accumulator),
|
||
ListStyleType::Mongolian => push_numeric_representation(value, &MONGOLIAN, accumulator),
|
||
ListStyleType::Myanmar => push_numeric_representation(value, &MYANMAR, accumulator),
|
||
ListStyleType::Oriya => push_numeric_representation(value, &ORIYA, accumulator),
|
||
ListStyleType::Persian => push_numeric_representation(value, &PERSIAN, accumulator),
|
||
ListStyleType::Telugu => push_numeric_representation(value, &TELUGU, accumulator),
|
||
ListStyleType::Thai => push_numeric_representation(value, &THAI, accumulator),
|
||
ListStyleType::Tibetan => push_numeric_representation(value, &TIBETAN, accumulator),
|
||
ListStyleType::LowerAlpha => {
|
||
push_alphabetic_representation(value, &LOWER_ALPHA, accumulator)
|
||
},
|
||
ListStyleType::UpperAlpha => {
|
||
push_alphabetic_representation(value, &UPPER_ALPHA, accumulator)
|
||
},
|
||
ListStyleType::CjkEarthlyBranch => {
|
||
push_alphabetic_representation(value, &CJK_EARTHLY_BRANCH, accumulator)
|
||
},
|
||
ListStyleType::CjkHeavenlyStem => {
|
||
push_alphabetic_representation(value, &CJK_HEAVENLY_STEM, accumulator)
|
||
},
|
||
ListStyleType::LowerGreek => {
|
||
push_alphabetic_representation(value, &LOWER_GREEK, accumulator)
|
||
},
|
||
ListStyleType::Hiragana => push_alphabetic_representation(value, &HIRAGANA, accumulator),
|
||
ListStyleType::HiraganaIroha => {
|
||
push_alphabetic_representation(value, &HIRAGANA_IROHA, accumulator)
|
||
},
|
||
ListStyleType::Katakana => push_alphabetic_representation(value, &KATAKANA, accumulator),
|
||
ListStyleType::KatakanaIroha => {
|
||
push_alphabetic_representation(value, &KATAKANA_IROHA, accumulator)
|
||
},
|
||
}
|
||
}
|
||
|
||
/// Returns the static character that represents the value rendered using the given list-style, if
|
||
/// possible.
|
||
pub fn static_representation(list_style_type: ListStyleType) -> char {
|
||
match list_style_type {
|
||
ListStyleType::Disc => '•',
|
||
ListStyleType::Circle => '◦',
|
||
ListStyleType::Square => '▪',
|
||
ListStyleType::DisclosureOpen => '▾',
|
||
ListStyleType::DisclosureClosed => '‣',
|
||
_ => panic!("No static representation for this list-style-type!"),
|
||
}
|
||
}
|
||
|
||
/// Pushes the string that represents the value rendered using the given *alphabetic system* onto
|
||
/// the accumulator per CSS-COUNTER-STYLES § 3.1.4.
|
||
fn push_alphabetic_representation(value: i32, system: &[char], accumulator: &mut String) {
|
||
let mut abs_value = handle_negative_value(value, accumulator);
|
||
|
||
let mut string: SmallVec<[char; 8]> = SmallVec::new();
|
||
while abs_value != 0 {
|
||
// Step 1.
|
||
abs_value -= 1;
|
||
// Step 2.
|
||
string.push(system[abs_value % system.len()]);
|
||
// Step 3.
|
||
abs_value /= system.len();
|
||
}
|
||
|
||
accumulator.extend(string.iter().cloned().rev())
|
||
}
|
||
|
||
/// Pushes the string that represents the value rendered using the given *numeric system* onto the
|
||
/// accumulator per CSS-COUNTER-STYLES § 3.1.5.
|
||
fn push_numeric_representation(value: i32, system: &[char], accumulator: &mut String) {
|
||
let mut abs_value = handle_negative_value(value, accumulator);
|
||
|
||
// Step 1.
|
||
if abs_value == 0 {
|
||
accumulator.push(system[0]);
|
||
return;
|
||
}
|
||
|
||
// Step 2.
|
||
let mut string: SmallVec<[char; 8]> = SmallVec::new();
|
||
while abs_value != 0 {
|
||
// Step 2.1.
|
||
string.push(system[abs_value % system.len()]);
|
||
// Step 2.2.
|
||
abs_value /= system.len();
|
||
}
|
||
|
||
// Step 3.
|
||
accumulator.extend(string.iter().cloned().rev())
|
||
}
|
||
|
||
/// If the system uses a negative sign, handle negative values per CSS-COUNTER-STYLES § 2.
|
||
///
|
||
/// Returns the absolute value of the counter.
|
||
fn handle_negative_value(value: i32, accumulator: &mut String) -> usize {
|
||
// 3. If the counter value is negative and the counter style uses a negative sign, instead
|
||
// generate an initial representation using the absolute value of the counter value.
|
||
if value < 0 {
|
||
// TODO: Support different negative signs using the 'negative' descriptor.
|
||
// https://drafts.csswg.org/date/2015-07-16/css-counter-styles/#counter-style-negative
|
||
accumulator.push('-');
|
||
value.unsigned_abs() as usize
|
||
} else {
|
||
value as usize
|
||
}
|
||
}
|