Background: The changes to Servo code to support Stylo began in the `selectors` crate with making pseudo-elements generic, defined be the user, so that different users (such as Servo and Gecko/Stylo) could have a different set of pseudo-elements supported and parsed. Adding a trait makes sense there since `selectors` is in its own repository and has others users (or at least [one](https://github.com/SimonSapin/kuchiki)). Then we kind of kept going with the same pattern and added a bunch of traits in the `style` crate to make everything generic, allowing Servo and Gecko/Stylo to do things differently. But we’ve also added a `gecko` Cargo feature to do conditional compilation, at first to enable or disable some CSS properties and values in the Mako templates. Since we’re doing conditional compilation anyway, it’s often easier and simpler to do it more (with `#[cfg(feature = "gecko")]` and `#[cfg(feature = "servo")]`) that to keep adding traits and making everything generic. When a type is generic, any method that we want to call on it needs to be part of some trait. ---- The first several commits move some code around, mostly from `geckolib` to `style` (with `#[cfg(feature = "gecko")]`) but otherwise don’t change much. The following commits remove some traits and many type parameters through the `style` crate, replacing them with pairs of conditionally-compiled API-compatible items (types, methods, …). Simplifying code is nice to make it more maintainable, but this is motivated by another change described in https://github.com/servo/servo/pull/12391#issuecomment-232183942. (Porting Servo for that change proved difficult because some code in the `style` crate was becoming generic over `String` vs `Atom`, and this PR will help make that concrete. That change, in turn, is motivated by removing geckolib’s `[replace]` override for string-cache, in order to enable using a single Cargo "workspace" in this repository.) r? @bholley --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] These changes fix #__ (github issue number if applicable). <!-- Either: --> - [ ] There are tests for these changes OR - [x] These changes do not require new tests because refactoring <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: 2d01d41a506bcbc7f26a2284b9f42390d6ef96ab
598 lines
26 KiB
Rust
598 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 http://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 context::LayoutContext;
|
||
use flow::InorderFlowTraversal;
|
||
use flow::{self, AFFECTS_COUNTERS, Flow, HAS_COUNTER_AFFECTING_CHILDREN, ImmutableFlowUtils};
|
||
use fragment::{Fragment, GeneratedContentInfo, SpecificFragmentInfo, UnscannedTextFragmentInfo};
|
||
use gfx::display_list::OpaqueNode;
|
||
use script_layout_interface::restyle_damage::{RESOLVE_GENERATED_CONTENT, RestyleDamage};
|
||
use script_layout_interface::wrapper_traits::PseudoElementType;
|
||
use smallvec::SmallVec;
|
||
use std::collections::{HashMap, LinkedList};
|
||
use std::sync::Arc;
|
||
use style::computed_values::content::ContentItem;
|
||
use style::computed_values::{display, list_style_type};
|
||
use style::dom::TRestyleDamage;
|
||
use style::properties::ServoComputedValues;
|
||
use text::TextRunScanner;
|
||
|
||
// 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<'a>) -> ResolveGeneratedContent<'a> {
|
||
ResolveGeneratedContent {
|
||
layout_context: layout_context,
|
||
list_item: Counter::new(),
|
||
counters: HashMap::new(),
|
||
quote: 0,
|
||
}
|
||
}
|
||
}
|
||
|
||
impl<'a> InorderFlowTraversal for ResolveGeneratedContent<'a> {
|
||
#[inline]
|
||
fn process(&mut self, flow: &mut Flow, level: u32) {
|
||
let mut mutator = ResolveGeneratedContentFragmentMutator {
|
||
traversal: self,
|
||
level: level,
|
||
is_block: flow.is_block_like(),
|
||
incremented: false,
|
||
};
|
||
flow.mutate_fragments(&mut |fragment| mutator.mutate_fragment(fragment))
|
||
}
|
||
|
||
#[inline]
|
||
fn should_process(&mut self, flow: &mut Flow) -> bool {
|
||
flow::base(flow).restyle_damage.intersects(RESOLVE_GENERATED_CONTENT) ||
|
||
flow::base(flow).flags.intersects(AFFECTS_COUNTERS | 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<'a,'b> ResolveGeneratedContentFragmentMutator<'a,'b> {
|
||
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 != display::T::list_item {
|
||
list_style_type = list_style_type::T::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.clone(),
|
||
fragment.style.clone(),
|
||
list_style_type,
|
||
RenderingMode::Suffix(".\u{00a0}"))
|
||
}
|
||
GeneratedContentInfo::Empty |
|
||
GeneratedContentInfo::ContentItem(ContentItem::String(_)) => {
|
||
// Nothing to do here.
|
||
}
|
||
GeneratedContentInfo::ContentItem(ContentItem::Counter(ref counter_name,
|
||
counter_style)) => {
|
||
let temporary_counter = Counter::new();
|
||
let counter = self.traversal
|
||
.counters
|
||
.get(&*counter_name)
|
||
.unwrap_or(&temporary_counter);
|
||
new_info = counter.render(self.traversal.layout_context,
|
||
fragment.node,
|
||
fragment.pseudo.clone(),
|
||
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)
|
||
.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
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
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 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 != display::T::list_item {
|
||
list_style_type = list_style_type::T::none
|
||
}
|
||
|
||
match list_style_type {
|
||
list_style_type::T::disc | list_style_type::T::none | list_style_type::T::circle |
|
||
list_style_type::T::square | list_style_type::T::disclosure_open |
|
||
list_style_type::T::disclosure_closed => {}
|
||
_ => self.traversal.list_item.increment(self.level, 1),
|
||
}
|
||
|
||
// Truncate down counters.
|
||
for (_, counter) in &mut self.traversal.counters {
|
||
counter.truncate_to_level(self.level);
|
||
}
|
||
self.traversal.list_item.truncate_to_level(self.level);
|
||
|
||
for &(ref counter_name, value) in &fragment.style().get_counters().counter_reset.0 {
|
||
if let Some(ref mut counter) = self.traversal.counters.get_mut(counter_name) {
|
||
counter.reset(self.level, value);
|
||
continue
|
||
}
|
||
|
||
let mut counter = Counter::new();
|
||
counter.reset(self.level, value);
|
||
self.traversal.counters.insert((*counter_name).clone(), counter);
|
||
}
|
||
|
||
for &(ref counter_name, value) in &fragment.style().get_counters().counter_increment.0 {
|
||
if let Some(ref mut counter) = self.traversal.counters.get_mut(counter_name) {
|
||
counter.increment(self.level, value);
|
||
continue
|
||
}
|
||
|
||
let mut counter = Counter::new();
|
||
counter.increment(self.level, value);
|
||
self.traversal.counters.insert((*counter_name).clone(), counter);
|
||
}
|
||
|
||
self.incremented = true
|
||
}
|
||
|
||
fn quote(&self, style: &ServoComputedValues, close: bool) -> String {
|
||
let quotes = &style.get_list().quotes;
|
||
if quotes.0.is_empty() {
|
||
return String::new()
|
||
}
|
||
let &(ref open_quote, ref close_quote) =
|
||
if self.traversal.quote as usize >= quotes.0.len() {
|
||
quotes.0.last().unwrap()
|
||
} else {
|
||
"es.0[self.traversal.quote as usize]
|
||
};
|
||
if close {
|
||
close_quote.clone()
|
||
} else {
|
||
open_quote.clone()
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 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: level,
|
||
value: 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: level,
|
||
value: amount,
|
||
})
|
||
}
|
||
|
||
fn render(&self,
|
||
layout_context: &LayoutContext,
|
||
node: OpaqueNode,
|
||
pseudo: PseudoElementType<()>,
|
||
style: Arc<ServoComputedValues>,
|
||
list_style_type: list_style_type::T,
|
||
mode: RenderingMode)
|
||
-> Option<SpecificFragmentInfo> {
|
||
let mut string = String::new();
|
||
match mode {
|
||
RenderingMode::Plain => {
|
||
let value = match self.values.last() {
|
||
Some(ref value) => value.value,
|
||
None => 0,
|
||
};
|
||
push_representation(value, list_style_type, &mut string)
|
||
}
|
||
RenderingMode::Suffix(suffix) => {
|
||
let value = match self.values.last() {
|
||
Some(ref 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: Arc<ServoComputedValues>,
|
||
string: String)
|
||
-> Option<SpecificFragmentInfo> {
|
||
let mut fragments = LinkedList::new();
|
||
let info = SpecificFragmentInfo::UnscannedText(
|
||
box UnscannedTextFragmentInfo::new(string, 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(&mut 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: list_style_type::T, accumulator: &mut String) {
|
||
match list_style_type {
|
||
list_style_type::T::none => {}
|
||
list_style_type::T::disc |
|
||
list_style_type::T::circle |
|
||
list_style_type::T::square |
|
||
list_style_type::T::disclosure_open |
|
||
list_style_type::T::disclosure_closed => {
|
||
accumulator.push(static_representation(list_style_type))
|
||
}
|
||
list_style_type::T::decimal => push_numeric_representation(value, &DECIMAL, accumulator),
|
||
list_style_type::T::arabic_indic => {
|
||
push_numeric_representation(value, &ARABIC_INDIC, accumulator)
|
||
}
|
||
list_style_type::T::bengali => push_numeric_representation(value, &BENGALI, accumulator),
|
||
list_style_type::T::cambodian | list_style_type::T::khmer => {
|
||
push_numeric_representation(value, &CAMBODIAN, accumulator)
|
||
}
|
||
list_style_type::T::cjk_decimal => {
|
||
push_numeric_representation(value, &CJK_DECIMAL, accumulator)
|
||
}
|
||
list_style_type::T::devanagari => {
|
||
push_numeric_representation(value, &DEVANAGARI, accumulator)
|
||
}
|
||
list_style_type::T::gujarati => push_numeric_representation(value, &GUJARATI, accumulator),
|
||
list_style_type::T::gurmukhi => push_numeric_representation(value, &GURMUKHI, accumulator),
|
||
list_style_type::T::kannada => push_numeric_representation(value, &KANNADA, accumulator),
|
||
list_style_type::T::lao => push_numeric_representation(value, &LAO, accumulator),
|
||
list_style_type::T::malayalam => {
|
||
push_numeric_representation(value, &MALAYALAM, accumulator)
|
||
}
|
||
list_style_type::T::mongolian => {
|
||
push_numeric_representation(value, &MONGOLIAN, accumulator)
|
||
}
|
||
list_style_type::T::myanmar => push_numeric_representation(value, &MYANMAR, accumulator),
|
||
list_style_type::T::oriya => push_numeric_representation(value, &ORIYA, accumulator),
|
||
list_style_type::T::persian => push_numeric_representation(value, &PERSIAN, accumulator),
|
||
list_style_type::T::telugu => push_numeric_representation(value, &TELUGU, accumulator),
|
||
list_style_type::T::thai => push_numeric_representation(value, &THAI, accumulator),
|
||
list_style_type::T::tibetan => push_numeric_representation(value, &TIBETAN, accumulator),
|
||
list_style_type::T::lower_alpha => {
|
||
push_alphabetic_representation(value, &LOWER_ALPHA, accumulator)
|
||
}
|
||
list_style_type::T::upper_alpha => {
|
||
push_alphabetic_representation(value, &UPPER_ALPHA, accumulator)
|
||
}
|
||
list_style_type::T::cjk_earthly_branch => {
|
||
push_alphabetic_representation(value, &CJK_EARTHLY_BRANCH, accumulator)
|
||
}
|
||
list_style_type::T::cjk_heavenly_stem => {
|
||
push_alphabetic_representation(value, &CJK_HEAVENLY_STEM, accumulator)
|
||
}
|
||
list_style_type::T::lower_greek => {
|
||
push_alphabetic_representation(value, &LOWER_GREEK, accumulator)
|
||
}
|
||
list_style_type::T::hiragana => {
|
||
push_alphabetic_representation(value, &HIRAGANA, accumulator)
|
||
}
|
||
list_style_type::T::hiragana_iroha => {
|
||
push_alphabetic_representation(value, &HIRAGANA_IROHA, accumulator)
|
||
}
|
||
list_style_type::T::katakana => {
|
||
push_alphabetic_representation(value, &KATAKANA, accumulator)
|
||
}
|
||
list_style_type::T::katakana_iroha => {
|
||
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: list_style_type::T) -> char {
|
||
match list_style_type {
|
||
list_style_type::T::disc => '•',
|
||
list_style_type::T::circle => '◦',
|
||
list_style_type::T::square => '▪',
|
||
list_style_type::T::disclosure_open => '▾',
|
||
list_style_type::T::disclosure_closed => '‣',
|
||
_ => 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 = abs_value - 1;
|
||
// Step 2.
|
||
string.push(system[abs_value % system.len()]);
|
||
// Step 3.
|
||
abs_value = 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 = 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.abs() as usize
|
||
} else {
|
||
value as usize
|
||
}
|
||
}
|