Source code

Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5
//! The [`@font-face`][ff] at-rule.
6
//!
8
9
use crate::error_reporting::ContextualParseError;
10
use crate::parser::{Parse, ParserContext};
11
#[cfg(feature = "gecko")]
12
use crate::properties::longhands::font_language_override;
13
use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
14
use crate::str::CssStringWriter;
15
use crate::values::computed::font::FamilyName;
16
use crate::values::generics::font::FontStyle as GenericFontStyle;
17
#[cfg(feature = "gecko")]
18
use crate::values::specified::font::SpecifiedFontFeatureSettings;
19
use crate::values::specified::font::SpecifiedFontStyle;
20
#[cfg(feature = "gecko")]
21
use crate::values::specified::font::SpecifiedFontVariationSettings;
22
use crate::values::specified::font::{AbsoluteFontWeight, FontStretch};
23
use crate::values::specified::url::SpecifiedUrl;
24
use crate::values::specified::Angle;
25
#[cfg(feature = "gecko")]
26
use cssparser::UnicodeRange;
27
use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser};
28
use cssparser::{CowRcStr, SourceLocation};
29
use selectors::parser::SelectorParseErrorKind;
30
use std::fmt::{self, Write};
31
use style_traits::values::SequenceWriter;
32
use style_traits::{Comma, CssWriter, OneOrMoreSeparated, ParseError};
33
use style_traits::{StyleParseErrorKind, ToCss};
34
35
/// A source for a font-face rule.
36
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
37
#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)]
38
pub enum Source {
39
/// A `url()` source.
40
Url(UrlSource),
41
/// A `local()` source.
42
#[css(function)]
43
Local(FamilyName),
44
}
45
46
impl OneOrMoreSeparated for Source {
47
type S = Comma;
48
}
49
50
/// A POD representation for Gecko. All pointers here are non-owned and as such
51
/// can't outlive the rule they came from, but we can't enforce that via C++.
52
///
53
/// All the strings are of course utf8.
54
#[cfg(feature = "gecko")]
55
#[repr(u8)]
56
#[allow(missing_docs)]
57
pub enum FontFaceSourceListComponent {
58
Url(*const crate::gecko::url::CssUrl),
59
Local(*mut crate::gecko_bindings::structs::nsAtom),
60
FormatHint {
61
length: usize,
62
utf8_bytes: *const u8,
63
},
64
}
65
66
/// A `UrlSource` represents a font-face source that has been specified with a
67
/// `url()` function.
68
///
70
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
71
#[derive(Clone, Debug, Eq, PartialEq, ToShmem)]
72
pub struct UrlSource {
73
/// The specified url.
74
pub url: SpecifiedUrl,
75
/// The format hints specified with the `format()` function.
76
pub format_hints: Vec<String>,
77
}
78
79
impl ToCss for UrlSource {
80
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
81
where
82
W: fmt::Write,
83
{
84
self.url.to_css(dest)?;
85
if !self.format_hints.is_empty() {
86
dest.write_str(" format(")?;
87
{
88
let mut writer = SequenceWriter::new(dest, ", ");
89
for hint in self.format_hints.iter() {
90
writer.item(hint)?;
91
}
92
}
93
dest.write_char(')')?;
94
}
95
Ok(())
96
}
97
}
98
99
/// A font-display value for a @font-face rule.
100
/// The font-display descriptor determines how a font face is displayed based
101
/// on whether and when it is downloaded and ready to use.
102
#[allow(missing_docs)]
103
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
104
#[derive(
105
Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToComputedValue, ToCss, ToShmem,
106
)]
107
#[repr(u8)]
108
pub enum FontDisplay {
109
Auto,
110
Block,
111
Swap,
112
Fallback,
113
Optional,
114
}
115
116
macro_rules! impl_range {
117
($range:ident, $component:ident) => {
118
impl Parse for $range {
119
fn parse<'i, 't>(
120
context: &ParserContext,
121
input: &mut Parser<'i, 't>,
122
) -> Result<Self, ParseError<'i>> {
123
let first = $component::parse(context, input)?;
124
let second = input
125
.try(|input| $component::parse(context, input))
126
.unwrap_or_else(|_| first.clone());
127
Ok($range(first, second))
128
}
129
}
130
impl ToCss for $range {
131
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
132
where
133
W: fmt::Write,
134
{
135
self.0.to_css(dest)?;
136
if self.0 != self.1 {
137
dest.write_str(" ")?;
138
self.1.to_css(dest)?;
139
}
140
Ok(())
141
}
142
}
143
};
144
}
145
146
/// The font-weight descriptor:
147
///
149
#[derive(Clone, Debug, PartialEq, ToShmem)]
150
pub struct FontWeightRange(pub AbsoluteFontWeight, pub AbsoluteFontWeight);
151
impl_range!(FontWeightRange, AbsoluteFontWeight);
152
153
/// The computed representation of the above so Gecko can read them easily.
154
///
155
/// This one is needed because cbindgen doesn't know how to generate
156
/// specified::Number.
157
#[repr(C)]
158
#[allow(missing_docs)]
159
pub struct ComputedFontWeightRange(f32, f32);
160
161
#[inline]
162
fn sort_range<T: PartialOrd>(a: T, b: T) -> (T, T) {
163
if a > b {
164
(b, a)
165
} else {
166
(a, b)
167
}
168
}
169
170
impl FontWeightRange {
171
/// Returns a computed font-stretch range.
172
pub fn compute(&self) -> ComputedFontWeightRange {
173
let (min, max) = sort_range(self.0.compute().0, self.1.compute().0);
174
ComputedFontWeightRange(min, max)
175
}
176
}
177
178
/// The font-stretch descriptor:
179
///
181
#[derive(Clone, Debug, PartialEq, ToShmem)]
182
pub struct FontStretchRange(pub FontStretch, pub FontStretch);
183
impl_range!(FontStretchRange, FontStretch);
184
185
/// The computed representation of the above, so that
186
/// Gecko can read them easily.
187
#[repr(C)]
188
#[allow(missing_docs)]
189
pub struct ComputedFontStretchRange(f32, f32);
190
191
impl FontStretchRange {
192
/// Returns a computed font-stretch range.
193
pub fn compute(&self) -> ComputedFontStretchRange {
194
fn compute_stretch(s: &FontStretch) -> f32 {
195
match *s {
196
FontStretch::Keyword(ref kw) => kw.compute().0,
197
FontStretch::Stretch(ref p) => p.get(),
198
FontStretch::System(..) => unreachable!(),
199
}
200
}
201
202
let (min, max) = sort_range(compute_stretch(&self.0), compute_stretch(&self.1));
203
ComputedFontStretchRange(min, max)
204
}
205
}
206
207
/// The font-style descriptor:
208
///
210
#[derive(Clone, Debug, PartialEq, ToShmem)]
211
#[allow(missing_docs)]
212
pub enum FontStyle {
213
Normal,
214
Italic,
215
Oblique(Angle, Angle),
216
}
217
218
/// The computed representation of the above, with angles in degrees, so that
219
/// Gecko can read them easily.
220
#[repr(u8)]
221
#[allow(missing_docs)]
222
pub enum ComputedFontStyleDescriptor {
223
Normal,
224
Italic,
225
Oblique(f32, f32),
226
}
227
228
impl Parse for FontStyle {
229
fn parse<'i, 't>(
230
context: &ParserContext,
231
input: &mut Parser<'i, 't>,
232
) -> Result<Self, ParseError<'i>> {
233
let style = SpecifiedFontStyle::parse(context, input)?;
234
Ok(match style {
235
GenericFontStyle::Normal => FontStyle::Normal,
236
GenericFontStyle::Italic => FontStyle::Italic,
237
GenericFontStyle::Oblique(angle) => {
238
let second_angle = input
239
.try(|input| SpecifiedFontStyle::parse_angle(context, input))
240
.unwrap_or_else(|_| angle.clone());
241
242
FontStyle::Oblique(angle, second_angle)
243
},
244
})
245
}
246
}
247
248
impl ToCss for FontStyle {
249
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
250
where
251
W: fmt::Write,
252
{
253
match *self {
254
FontStyle::Normal => dest.write_str("normal"),
255
FontStyle::Italic => dest.write_str("italic"),
256
FontStyle::Oblique(ref first, ref second) => {
257
dest.write_str("oblique")?;
258
if *first != SpecifiedFontStyle::default_angle() || first != second {
259
dest.write_char(' ')?;
260
first.to_css(dest)?;
261
}
262
if first != second {
263
dest.write_char(' ')?;
264
second.to_css(dest)?;
265
}
266
Ok(())
267
},
268
}
269
}
270
}
271
272
impl FontStyle {
273
/// Returns a computed font-style descriptor.
274
pub fn compute(&self) -> ComputedFontStyleDescriptor {
275
match *self {
276
FontStyle::Normal => ComputedFontStyleDescriptor::Normal,
277
FontStyle::Italic => ComputedFontStyleDescriptor::Italic,
278
FontStyle::Oblique(ref first, ref second) => {
279
let (min, max) = sort_range(
280
SpecifiedFontStyle::compute_angle_degrees(first),
281
SpecifiedFontStyle::compute_angle_degrees(second),
282
);
283
ComputedFontStyleDescriptor::Oblique(min, max)
284
},
285
}
286
}
287
}
288
289
/// Parse the block inside a `@font-face` rule.
290
///
291
/// Note that the prelude parsing code lives in the `stylesheets` module.
292
pub fn parse_font_face_block(
293
context: &ParserContext,
294
input: &mut Parser,
295
location: SourceLocation,
296
) -> FontFaceRuleData {
297
let mut rule = FontFaceRuleData::empty(location);
298
{
299
let parser = FontFaceRuleParser {
300
context: context,
301
rule: &mut rule,
302
};
303
let mut iter = DeclarationListParser::new(input, parser);
304
while let Some(declaration) = iter.next() {
305
if let Err((error, slice)) = declaration {
306
let location = error.location;
307
let error = ContextualParseError::UnsupportedFontFaceDescriptor(slice, error);
308
context.log_css_error(location, error)
309
}
310
}
311
}
312
rule
313
}
314
315
/// A @font-face rule that is known to have font-family and src declarations.
316
#[cfg(feature = "servo")]
317
pub struct FontFace<'a>(&'a FontFaceRuleData);
318
319
/// A list of effective sources that we send over through IPC to the font cache.
320
#[cfg(feature = "servo")]
321
#[derive(Clone, Debug)]
322
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
323
pub struct EffectiveSources(Vec<Source>);
324
325
#[cfg(feature = "servo")]
326
impl<'a> FontFace<'a> {
327
/// Returns the list of effective sources for that font-face, that is the
328
/// sources which don't list any format hint, or the ones which list at
329
/// least "truetype" or "opentype".
330
pub fn effective_sources(&self) -> EffectiveSources {
331
EffectiveSources(
332
self.sources()
333
.iter()
334
.rev()
335
.filter(|source| {
336
if let Source::Url(ref url_source) = **source {
337
let hints = &url_source.format_hints;
338
// We support only opentype fonts and truetype is an alias for
339
// that format. Sources without format hints need to be
340
// downloaded in case we support them.
341
hints.is_empty() ||
342
hints.iter().any(|hint| {
343
hint == "truetype" || hint == "opentype" || hint == "woff"
344
})
345
} else {
346
true
347
}
348
})
349
.cloned()
350
.collect(),
351
)
352
}
353
}
354
355
#[cfg(feature = "servo")]
356
impl Iterator for EffectiveSources {
357
type Item = Source;
358
fn next(&mut self) -> Option<Source> {
359
self.0.pop()
360
}
361
362
fn size_hint(&self) -> (usize, Option<usize>) {
363
(self.0.len(), Some(self.0.len()))
364
}
365
}
366
367
struct FontFaceRuleParser<'a, 'b: 'a> {
368
context: &'a ParserContext<'b>,
369
rule: &'a mut FontFaceRuleData,
370
}
371
372
/// Default methods reject all at rules.
373
impl<'a, 'b, 'i> AtRuleParser<'i> for FontFaceRuleParser<'a, 'b> {
374
type PreludeNoBlock = ();
375
type PreludeBlock = ();
376
type AtRule = ();
377
type Error = StyleParseErrorKind<'i>;
378
}
379
380
impl Parse for Source {
381
fn parse<'i, 't>(
382
context: &ParserContext,
383
input: &mut Parser<'i, 't>,
384
) -> Result<Source, ParseError<'i>> {
385
if input
386
.try(|input| input.expect_function_matching("local"))
387
.is_ok()
388
{
389
return input
390
.parse_nested_block(|input| FamilyName::parse(context, input))
391
.map(Source::Local);
392
}
393
394
let url = SpecifiedUrl::parse(context, input)?;
395
396
// Parsing optional format()
397
let format_hints = if input
398
.try(|input| input.expect_function_matching("format"))
399
.is_ok()
400
{
401
input.parse_nested_block(|input| {
402
input.parse_comma_separated(|input| Ok(input.expect_string()?.as_ref().to_owned()))
403
})?
404
} else {
405
vec![]
406
};
407
408
Ok(Source::Url(UrlSource {
409
url: url,
410
format_hints: format_hints,
411
}))
412
}
413
}
414
415
macro_rules! is_descriptor_enabled {
416
("font-display") => {
417
static_prefs::pref!("layout.css.font-display.enabled")
418
};
419
("font-variation-settings") => {
420
static_prefs::pref!("layout.css.font-variations.enabled")
421
};
422
($name:tt) => {
423
true
424
};
425
}
426
427
macro_rules! font_face_descriptors_common {
428
(
429
$( #[$doc: meta] $name: tt $ident: ident / $gecko_ident: ident: $ty: ty, )*
430
) => {
431
/// Data inside a `@font-face` rule.
432
///
434
#[derive(Clone, Debug, PartialEq, ToShmem)]
435
pub struct FontFaceRuleData {
436
$(
437
#[$doc]
438
pub $ident: Option<$ty>,
439
)*
440
/// Line and column of the @font-face rule source code.
441
pub source_location: SourceLocation,
442
}
443
444
impl FontFaceRuleData {
445
/// Create an empty font-face rule
446
pub fn empty(location: SourceLocation) -> Self {
447
FontFaceRuleData {
448
$(
449
$ident: None,
450
)*
451
source_location: location,
452
}
453
}
454
455
/// Serialization of declarations in the FontFaceRule
456
pub fn decl_to_css(&self, dest: &mut CssStringWriter) -> fmt::Result {
457
$(
458
if let Some(ref value) = self.$ident {
459
dest.write_str(concat!(" ", $name, ": "))?;
460
ToCss::to_css(value, &mut CssWriter::new(dest))?;
461
dest.write_str(";\n")?;
462
}
463
)*
464
Ok(())
465
}
466
}
467
468
impl<'a, 'b, 'i> DeclarationParser<'i> for FontFaceRuleParser<'a, 'b> {
469
type Declaration = ();
470
type Error = StyleParseErrorKind<'i>;
471
472
fn parse_value<'t>(&mut self, name: CowRcStr<'i>, input: &mut Parser<'i, 't>)
473
-> Result<(), ParseError<'i>> {
474
match_ignore_ascii_case! { &*name,
475
$(
476
$name if is_descriptor_enabled!($name) => {
477
// DeclarationParser also calls parse_entirely
478
// so we’d normally not need to,
479
// but in this case we do because we set the value as a side effect
480
// rather than returning it.
481
let value = input.parse_entirely(|i| Parse::parse(self.context, i))?;
482
self.rule.$ident = Some(value)
483
},
484
)*
485
_ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
486
}
487
Ok(())
488
}
489
}
490
}
491
}
492
493
impl ToCssWithGuard for FontFaceRuleData {
494
// Serialization of FontFaceRule is not specced.
495
fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
496
dest.write_str("@font-face {\n")?;
497
self.decl_to_css(dest)?;
498
dest.write_str("}")
499
}
500
}
501
502
macro_rules! font_face_descriptors {
503
(
504
mandatory descriptors = [
505
$( #[$m_doc: meta] $m_name: tt $m_ident: ident / $m_gecko_ident: ident: $m_ty: ty, )*
506
]
507
optional descriptors = [
508
$( #[$o_doc: meta] $o_name: tt $o_ident: ident / $o_gecko_ident: ident: $o_ty: ty, )*
509
]
510
) => {
511
font_face_descriptors_common! {
512
$( #[$m_doc] $m_name $m_ident / $m_gecko_ident: $m_ty, )*
513
$( #[$o_doc] $o_name $o_ident / $o_gecko_ident: $o_ty, )*
514
}
515
516
impl FontFaceRuleData {
517
/// Per https://github.com/w3c/csswg-drafts/issues/1133 an @font-face rule
518
/// is valid as far as the CSS parser is concerned even if it doesn’t have
519
/// a font-family or src declaration.
520
///
521
/// However both are required for the rule to represent an actual font face.
522
#[cfg(feature = "servo")]
523
pub fn font_face(&self) -> Option<FontFace> {
524
if $( self.$m_ident.is_some() )&&* {
525
Some(FontFace(self))
526
} else {
527
None
528
}
529
}
530
}
531
532
#[cfg(feature = "servo")]
533
impl<'a> FontFace<'a> {
534
$(
535
#[$m_doc]
536
pub fn $m_ident(&self) -> &$m_ty {
537
self.0 .$m_ident.as_ref().unwrap()
538
}
539
)*
540
}
541
}
542
}
543
544
#[cfg(feature = "gecko")]
545
font_face_descriptors! {
546
mandatory descriptors = [
547
/// The name of this font face
548
"font-family" family / mFamily: FamilyName,
549
550
/// The alternative sources for this font face.
551
"src" sources / mSrc: Vec<Source>,
552
]
553
optional descriptors = [
554
/// The style of this font face.
555
"font-style" style / mStyle: FontStyle,
556
557
/// The weight of this font face.
558
"font-weight" weight / mWeight: FontWeightRange,
559
560
/// The stretch of this font face.
561
"font-stretch" stretch / mStretch: FontStretchRange,
562
563
/// The display of this font face.
564
"font-display" display / mDisplay: FontDisplay,
565
566
/// The ranges of code points outside of which this font face should not be used.
567
"unicode-range" unicode_range / mUnicodeRange: Vec<UnicodeRange>,
568
569
/// The feature settings of this font face.
570
"font-feature-settings" feature_settings / mFontFeatureSettings: SpecifiedFontFeatureSettings,
571
572
/// The variation settings of this font face.
573
"font-variation-settings" variation_settings / mFontVariationSettings: SpecifiedFontVariationSettings,
574
575
/// The language override of this font face.
576
"font-language-override" language_override / mFontLanguageOverride: font_language_override::SpecifiedValue,
577
]
578
}
579
580
#[cfg(feature = "servo")]
581
font_face_descriptors! {
582
mandatory descriptors = [
583
/// The name of this font face
584
"font-family" family / mFamily: FamilyName,
585
586
/// The alternative sources for this font face.
587
"src" sources / mSrc: Vec<Source>,
588
]
589
optional descriptors = [
590
]
591
}