/* 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/. */ //! https://drafts.csswg.org/css-syntax/#urange use crate::tokenizer::Token; use crate::{BasicParseError, Parser, ToCss}; use std::char; use std::fmt; /// One contiguous range of code points. /// /// Can not be empty. Can represent a single code point when start == end. #[derive(PartialEq, Eq, Clone, Hash)] #[repr(C)] pub struct UnicodeRange { /// Inclusive start of the range. In [0, end]. pub start: u32, /// Inclusive end of the range. In [0, 0x10FFFF]. pub end: u32, } impl UnicodeRange { /// https://drafts.csswg.org/css-syntax/#urange-syntax pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { // = // u '+' '?'* | // u '?'* | // u '?'* | // u | // u | // u '+' '?'+ input.expect_ident_matching("u")?; let after_u = input.position(); parse_tokens(input)?; // This deviates from the spec in case there are CSS comments // between tokens in the middle of one , // but oh well… let concatenated_tokens = input.slice_from(after_u); let range = match parse_concatenated(concatenated_tokens.as_bytes()) { Ok(range) => range, Err(()) => { return Err(input .new_basic_unexpected_token_error(Token::Ident(concatenated_tokens.into()))) } }; if range.end > char::MAX as u32 || range.start > range.end { Err(input.new_basic_unexpected_token_error(Token::Ident(concatenated_tokens.into()))) } else { Ok(range) } } } fn parse_tokens<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(), BasicParseError<'i>> { match input.next_including_whitespace()?.clone() { Token::Delim('+') => { match *input.next_including_whitespace()? { Token::Ident(_) => {} Token::Delim('?') => {} ref t => { let t = t.clone(); return Err(input.new_basic_unexpected_token_error(t)); } } parse_question_marks(input) } Token::Dimension { .. } => parse_question_marks(input), Token::Number { .. } => { let after_number = input.state(); match input.next_including_whitespace() { Ok(&Token::Delim('?')) => parse_question_marks(input), Ok(&Token::Dimension { .. }) => {} Ok(&Token::Number { .. }) => {} _ => input.reset(&after_number), } } t => return Err(input.new_basic_unexpected_token_error(t)), } Ok(()) } /// Consume as many '?' as possible fn parse_question_marks(input: &mut Parser) { loop { let start = input.state(); match input.next_including_whitespace() { Ok(&Token::Delim('?')) => {} _ => { input.reset(&start); return; } } } } fn parse_concatenated(text: &[u8]) -> Result { let mut text = match text.split_first() { Some((&b'+', text)) => text, _ => return Err(()), }; let (first_hex_value, hex_digit_count) = consume_hex(&mut text); let question_marks = consume_question_marks(&mut text); let consumed = hex_digit_count + question_marks; if consumed == 0 || consumed > 6 { return Err(()); } if question_marks > 0 { if text.is_empty() { return Ok(UnicodeRange { start: first_hex_value << (question_marks * 4), end: ((first_hex_value + 1) << (question_marks * 4)) - 1, }); } } else if text.is_empty() { return Ok(UnicodeRange { start: first_hex_value, end: first_hex_value, }); } else { if let Some((&b'-', mut text)) = text.split_first() { let (second_hex_value, hex_digit_count) = consume_hex(&mut text); if hex_digit_count > 0 && hex_digit_count <= 6 && text.is_empty() { return Ok(UnicodeRange { start: first_hex_value, end: second_hex_value, }); } } } Err(()) } fn consume_hex(text: &mut &[u8]) -> (u32, usize) { let mut value = 0; let mut digits = 0; while let Some((&byte, rest)) = text.split_first() { if let Some(digit_value) = (byte as char).to_digit(16) { value = value * 0x10 + digit_value; digits += 1; *text = rest } else { break; } } (value, digits) } fn consume_question_marks(text: &mut &[u8]) -> usize { let mut question_marks = 0; while let Some((&b'?', rest)) = text.split_first() { question_marks += 1; *text = rest } question_marks } impl fmt::Debug for UnicodeRange { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { self.to_css(formatter) } } impl ToCss for UnicodeRange { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write, { write!(dest, "U+{:X}", self.start)?; if self.end != self.start { write!(dest, "-{:X}", self.end)?; } Ok(()) } }