Skip to content

Commit c42f384

Browse files
committed
Avoid using std::fmt string interpolation in serialization
… since it involves costly dynamic dispatch.
1 parent be1ebf7 commit c42f384

File tree

2 files changed

+43
-12
lines changed

2 files changed

+43
-12
lines changed

src/serializer.rs

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ impl<'a> ToCss for Token<'a> {
7575
serialize_unquoted_url(&**value, dest)?;
7676
dest.write_str(")")?;
7777
},
78-
Token::Delim(value) => write!(dest, "{}", value)?,
78+
Token::Delim(value) => dest.write_char(value)?,
7979

8080
Token::Number { value, int_value, has_sign } => {
8181
write_numeric(value, int_value, has_sign, dest)?
@@ -97,7 +97,11 @@ impl<'a> ToCss for Token<'a> {
9797
},
9898

9999
Token::WhiteSpace(content) => dest.write_str(content)?,
100-
Token::Comment(content) => write!(dest, "/*{}*/", content)?,
100+
Token::Comment(content) => {
101+
dest.write_str("/*")?;
102+
dest.write_str(content)?;
103+
dest.write_str("*/")?
104+
}
101105
Token::Colon => dest.write_str(":")?,
102106
Token::Semicolon => dest.write_str(";")?,
103107
Token::Comma => dest.write_str(",")?,
@@ -128,6 +132,32 @@ impl<'a> ToCss for Token<'a> {
128132
}
129133
}
130134

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+
}
131161

132162
/// Write a CSS identifier, escaping characters as necessary.
133163
pub fn serialize_identifier<W>(mut value: &str, dest: &mut W) -> fmt::Result where W:fmt::Write {
@@ -146,7 +176,7 @@ pub fn serialize_identifier<W>(mut value: &str, dest: &mut W) -> fmt::Result whe
146176
value = &value[1..];
147177
}
148178
if let digit @ b'0'...b'9' = value.as_bytes()[0] {
149-
write!(dest, "\\3{} ", digit as char)?;
179+
hex_escape(digit, dest)?;
150180
value = &value[1..];
151181
}
152182
serialize_name(value, dest)
@@ -167,9 +197,9 @@ fn serialize_name<W>(value: &str, dest: &mut W) -> fmt::Result where W:fmt::Writ
167197
if let Some(escaped) = escaped {
168198
dest.write_str(escaped)?;
169199
} else if (b >= b'\x01' && b <= b'\x1F') || b == b'\x7F' {
170-
write!(dest, "\\{:x} ", b)?;
200+
hex_escape(b, dest)?;
171201
} else {
172-
write!(dest, "\\{}", b as char)?;
202+
char_escape(b, dest)?;
173203
}
174204
chunk_start = i + 1;
175205
}
@@ -187,9 +217,9 @@ fn serialize_unquoted_url<W>(value: &str, dest: &mut W) -> fmt::Result where W:f
187217
};
188218
dest.write_str(&value[chunk_start..i])?;
189219
if hex {
190-
write!(dest, "\\{:X} ", b)?;
220+
hex_escape(b, dest)?;
191221
} else {
192-
write!(dest, "\\{}", b as char)?;
222+
char_escape(b, dest)?;
193223
}
194224
chunk_start = i + 1;
195225
}
@@ -247,7 +277,7 @@ impl<'a, W> fmt::Write for CssStringWriter<'a, W> where W: fmt::Write {
247277
self.inner.write_str(&s[chunk_start..i])?;
248278
match escaped {
249279
Some(x) => self.inner.write_str(x)?,
250-
None => write!(self.inner, "\\{:x} ", b)?,
280+
None => hex_escape(b, self.inner)?,
251281
};
252282
chunk_start = i + 1;
253283
}
@@ -265,7 +295,8 @@ macro_rules! impl_tocss_for_int {
265295
impl<W: fmt::Write> io::Write for AssumeUtf8<W> {
266296
#[inline]
267297
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
268-
// Safety: itoa only emits ASCII
298+
// Safety: itoa only emits ASCII, which is also well-formed UTF-8.
299+
debug_assert!(buf.is_ascii());
269300
self.0.write_str(unsafe { str::from_utf8_unchecked(buf) })
270301
.map_err(|_| io::ErrorKind::Other.into())
271302
}

src/tests.rs

Lines changed: 3 additions & 3 deletions
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)