Skip to content

Commit 3792662

Browse files
author
bors-servo
authored
Auto merge of #190 - servo:no-fmt, r=emilio
Avoid std::fmt in serialization code <!-- Reviewable:start --> This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/rust-cssparser/190) <!-- Reviewable:end -->
2 parents 6105cb1 + c42f384 commit 3792662

File tree

5 files changed

+90
-39
lines changed

5 files changed

+90
-39
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ encoding_rs = "0.7"
2222
cssparser-macros = {path = "./macros", version = "0.3"}
2323
dtoa-short = "0.3"
2424
heapsize = {version = ">= 0.3, < 0.5", optional = true}
25+
itoa = "0.3"
2526
matches = "0.1"
2627
phf = "0.7"
2728
procedural-masquerade = {path = "./procedural-masquerade", version = "0.1"}

src/color.rs

+18-10
Original file line numberDiff line numberDiff line change
@@ -100,18 +100,26 @@ impl ToCss for RGBA {
100100
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
101101
where W: fmt::Write,
102102
{
103-
// Try first with two decimal places, then with three.
104-
let mut rounded_alpha = (self.alpha_f32() * 100.).round() / 100.;
105-
if clamp_unit_f32(rounded_alpha) != self.alpha {
106-
rounded_alpha = (self.alpha_f32() * 1000.).round() / 1000.;
107-
}
103+
let serialize_alpha = self.alpha != 255;
104+
105+
dest.write_str(if serialize_alpha { "rgba(" } else { "rgb(" })?;
106+
self.red.to_css(dest)?;
107+
dest.write_str(", ")?;
108+
self.green.to_css(dest)?;
109+
dest.write_str(", ")?;
110+
self.blue.to_css(dest)?;
111+
if serialize_alpha {
112+
dest.write_str(", ")?;
113+
114+
// Try first with two decimal places, then with three.
115+
let mut rounded_alpha = (self.alpha_f32() * 100.).round() / 100.;
116+
if clamp_unit_f32(rounded_alpha) != self.alpha {
117+
rounded_alpha = (self.alpha_f32() * 1000.).round() / 1000.;
118+
}
108119

109-
if self.alpha == 255 {
110-
write!(dest, "rgb({}, {}, {})", self.red, self.green, self.blue)
111-
} else {
112-
write!(dest, "rgba({}, {}, {}, {})",
113-
self.red, self.green, self.blue, rounded_alpha)
120+
rounded_alpha.to_css(dest)?;
114121
}
122+
dest.write_char(')')
115123
}
116124
}
117125

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ fn parse_border_spacing(_context: &ParserContext, input: &mut Parser)
6969
#![recursion_limit="200"] // For color::parse_color_keyword
7070

7171
extern crate dtoa_short;
72+
extern crate itoa;
7273
#[macro_use] extern crate cssparser_macros;
7374
#[macro_use] extern crate matches;
7475
#[macro_use] extern crate procedural_masquerade;

src/serializer.rs

+67-26
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44

55
use dtoa_short::{self, Notation};
6+
use itoa;
67
use std::ascii::AsciiExt;
78
use std::fmt::{self, Write};
9+
use std::io;
810
use std::str;
911

1012
use super::Token;
@@ -24,23 +26,6 @@ pub trait ToCss {
2426
self.to_css(&mut s).unwrap();
2527
s
2628
}
27-
28-
/// Serialize `self` in CSS syntax and return a result compatible with `std::fmt::Show`.
29-
///
30-
/// Typical usage is, for a `Foo` that implements `ToCss`:
31-
///
32-
/// ```{rust,ignore}
33-
/// use std::fmt;
34-
/// impl fmt::Show for Foo {
35-
/// #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_to_css(f) }
36-
/// }
37-
/// ```
38-
///
39-
/// (This is a convenience wrapper for `to_css` and probably should not be overridden.)
40-
#[inline]
41-
fn fmt_to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
42-
self.to_css(dest).map_err(|_| fmt::Error)
43-
}
4429
}
4530

4631
#[inline]
@@ -90,7 +75,7 @@ impl<'a> ToCss for Token<'a> {
9075
serialize_unquoted_url(&**value, dest)?;
9176
dest.write_str(")")?;
9277
},
93-
Token::Delim(value) => write!(dest, "{}", value)?,
78+
Token::Delim(value) => dest.write_char(value)?,
9479

9580
Token::Number { value, int_value, has_sign } => {
9681
write_numeric(value, int_value, has_sign, dest)?
@@ -112,7 +97,11 @@ impl<'a> ToCss for Token<'a> {
11297
},
11398

11499
Token::WhiteSpace(content) => dest.write_str(content)?,
115-
Token::Comment(content) => write!(dest, "/*{}*/", content)?,
100+
Token::Comment(content) => {
101+
dest.write_str("/*")?;
102+
dest.write_str(content)?;
103+
dest.write_str("*/")?
104+
}
116105
Token::Colon => dest.write_str(":")?,
117106
Token::Semicolon => dest.write_str(";")?,
118107
Token::Comma => dest.write_str(",")?,
@@ -143,6 +132,32 @@ impl<'a> ToCss for Token<'a> {
143132
}
144133
}
145134

135+
fn to_hex_byte(value: u8) -> u8 {
136+
match value {
137+
0...9 => value + b'0',
138+
_ => value - 10 + b'a',
139+
}
140+
}
141+
142+
fn hex_escape<W>(ascii_byte: u8, dest: &mut W) -> fmt::Result where W:fmt::Write {
143+
let high = ascii_byte >> 4;
144+
let b3;
145+
let b4;
146+
let bytes = if high > 0 {
147+
let low = ascii_byte & 0x0F;
148+
b4 = [b'\\', to_hex_byte(high), to_hex_byte(low), b' '];
149+
&b4[..]
150+
} else {
151+
b3 = [b'\\', to_hex_byte(ascii_byte), b' '];
152+
&b3[..]
153+
};
154+
dest.write_str(unsafe { str::from_utf8_unchecked(&bytes) })
155+
}
156+
157+
fn char_escape<W>(ascii_byte: u8, dest: &mut W) -> fmt::Result where W:fmt::Write {
158+
let bytes = [b'\\', ascii_byte];
159+
dest.write_str(unsafe { str::from_utf8_unchecked(&bytes) })
160+
}
146161

147162
/// Write a CSS identifier, escaping characters as necessary.
148163
pub fn serialize_identifier<W>(mut value: &str, dest: &mut W) -> fmt::Result where W:fmt::Write {
@@ -161,7 +176,7 @@ pub fn serialize_identifier<W>(mut value: &str, dest: &mut W) -> fmt::Result whe
161176
value = &value[1..];
162177
}
163178
if let digit @ b'0'...b'9' = value.as_bytes()[0] {
164-
write!(dest, "\\3{} ", digit as char)?;
179+
hex_escape(digit, dest)?;
165180
value = &value[1..];
166181
}
167182
serialize_name(value, dest)
@@ -182,9 +197,9 @@ fn serialize_name<W>(value: &str, dest: &mut W) -> fmt::Result where W:fmt::Writ
182197
if let Some(escaped) = escaped {
183198
dest.write_str(escaped)?;
184199
} else if (b >= b'\x01' && b <= b'\x1F') || b == b'\x7F' {
185-
write!(dest, "\\{:x} ", b)?;
200+
hex_escape(b, dest)?;
186201
} else {
187-
write!(dest, "\\{}", b as char)?;
202+
char_escape(b, dest)?;
188203
}
189204
chunk_start = i + 1;
190205
}
@@ -202,9 +217,9 @@ fn serialize_unquoted_url<W>(value: &str, dest: &mut W) -> fmt::Result where W:f
202217
};
203218
dest.write_str(&value[chunk_start..i])?;
204219
if hex {
205-
write!(dest, "\\{:X} ", b)?;
220+
hex_escape(b, dest)?;
206221
} else {
207-
write!(dest, "\\{}", b as char)?;
222+
char_escape(b, dest)?;
208223
}
209224
chunk_start = i + 1;
210225
}
@@ -262,7 +277,7 @@ impl<'a, W> fmt::Write for CssStringWriter<'a, W> where W: fmt::Write {
262277
self.inner.write_str(&s[chunk_start..i])?;
263278
match escaped {
264279
Some(x) => self.inner.write_str(x)?,
265-
None => write!(self.inner, "\\{:x} ", b)?,
280+
None => hex_escape(b, self.inner)?,
266281
};
267282
chunk_start = i + 1;
268283
}
@@ -275,7 +290,33 @@ macro_rules! impl_tocss_for_int {
275290
($T: ty) => {
276291
impl<'a> ToCss for $T {
277292
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
278-
write!(dest, "{}", *self)
293+
struct AssumeUtf8<W: fmt::Write>(W);
294+
295+
impl<W: fmt::Write> io::Write for AssumeUtf8<W> {
296+
#[inline]
297+
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
298+
// Safety: itoa only emits ASCII, which is also well-formed UTF-8.
299+
debug_assert!(buf.is_ascii());
300+
self.0.write_str(unsafe { str::from_utf8_unchecked(buf) })
301+
.map_err(|_| io::ErrorKind::Other.into())
302+
}
303+
304+
#[inline]
305+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
306+
self.write_all(buf)?;
307+
Ok(buf.len())
308+
}
309+
310+
#[inline]
311+
fn flush(&mut self) -> io::Result<()> {
312+
Ok(())
313+
}
314+
}
315+
316+
match itoa::write(AssumeUtf8(dest), *self) {
317+
Ok(_) => Ok(()),
318+
Err(_) => Err(fmt::Error)
319+
}
279320
}
280321
}
281322
}

src/tests.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -300,10 +300,10 @@ fn unquoted_url_escaping() {
300300
let serialized = token.to_css_string();
301301
assert_eq!(serialized, "\
302302
url(\
303-
\\1 \\2 \\3 \\4 \\5 \\6 \\7 \\8 \\9 \\A \\B \\C \\D \\E \\F \\10 \
304-
\\11 \\12 \\13 \\14 \\15 \\16 \\17 \\18 \\19 \\1A \\1B \\1C \\1D \\1E \\1F \\20 \
303+
\\1 \\2 \\3 \\4 \\5 \\6 \\7 \\8 \\9 \\a \\b \\c \\d \\e \\f \\10 \
304+
\\11 \\12 \\13 \\14 \\15 \\16 \\17 \\18 \\19 \\1a \\1b \\1c \\1d \\1e \\1f \\20 \
305305
!\\\"#$%&\\'\\(\\)*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]\
306-
^_`abcdefghijklmnopqrstuvwxyz{|}~\\7F é\
306+
^_`abcdefghijklmnopqrstuvwxyz{|}~\\7f é\
307307
)\
308308
");
309309
let mut input = ParserInput::new(&serialized);

0 commit comments

Comments
 (0)