Source code

Revision control

Copy as Markdown

Other Tools

/* 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 https://mozilla.org/MPL/2.0/. */
//! The [`@font-face`][ff] at-rule.
//!
use crate::error_reporting::ContextualParseError;
use crate::parser::{Parse, ParserContext};
#[cfg(feature = "gecko")]
use crate::properties::longhands::font_language_override;
use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
use crate::str::CssStringWriter;
use crate::values::computed::font::{FamilyName, FontStretch};
use crate::values::generics::font::FontStyle as GenericFontStyle;
use crate::values::specified::font::SpecifiedFontStyle;
use crate::values::specified::font::{
AbsoluteFontWeight, FontStretch as SpecifiedFontStretch, MetricsOverride,
};
#[cfg(feature = "gecko")]
use crate::values::specified::font::{FontFeatureSettings, FontVariationSettings};
use crate::values::specified::url::SpecifiedUrl;
use crate::values::specified::{Angle, NonNegativePercentage};
#[cfg(feature = "gecko")]
use cssparser::UnicodeRange;
use cssparser::{
AtRuleParser, CowRcStr, DeclarationParser, Parser, QualifiedRuleParser, RuleBodyItemParser,
RuleBodyParser, SourceLocation,
};
use selectors::parser::SelectorParseErrorKind;
use std::fmt::{self, Write};
use style_traits::{CssWriter, ParseError};
use style_traits::{StyleParseErrorKind, ToCss};
/// A source for a font-face rule.
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)]
pub enum Source {
/// A `url()` source.
Url(UrlSource),
/// A `local()` source.
#[css(function)]
Local(FamilyName),
}
/// A list of sources for the font-face src descriptor.
#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)]
#[css(comma)]
pub struct SourceList(#[css(iterable)] pub Vec<Source>);
// We can't just use OneOrMoreSeparated to derive Parse for the Source list,
// because we want to filter out components that parsed as None, then fail if no
// valid components remain. So we provide our own implementation here.
impl Parse for SourceList {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
// Parse the comma-separated list, then let filter_map discard any None items.
let list = input
.parse_comma_separated(|input| {
let s = input.parse_entirely(|input| Source::parse(context, input));
while input.next().is_ok() {}
Ok(s.ok())
})?
.into_iter()
.filter_map(|s| s)
.collect::<Vec<Source>>();
if list.is_empty() {
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
} else {
Ok(SourceList(list))
}
}
}
/// Keywords for the font-face src descriptor's format() function.
/// ('None' and 'Unknown' are for internal use in gfx, not exposed to CSS.)
#[derive(Clone, Copy, Debug, Eq, Parse, PartialEq, ToCss, ToShmem)]
#[repr(u8)]
#[allow(missing_docs)]
pub enum FontFaceSourceFormatKeyword {
#[css(skip)]
None,
Collection,
EmbeddedOpentype,
Opentype,
Svg,
Truetype,
Woff,
Woff2,
#[css(skip)]
Unknown,
}
/// Flags for the @font-face tech() function, indicating font technologies
/// required by the resource.
#[derive(Clone, Copy, Debug, Eq, PartialEq, ToShmem)]
#[repr(C)]
pub struct FontFaceSourceTechFlags(u16);
bitflags! {
impl FontFaceSourceTechFlags: u16 {
/// Font requires OpenType feature support.
const FEATURES_OPENTYPE = 1 << 0;
/// Font requires Apple Advanced Typography support.
const FEATURES_AAT = 1 << 1;
/// Font requires Graphite shaping support.
const FEATURES_GRAPHITE = 1 << 2;
/// Font requires COLRv0 rendering support (simple list of colored layers).
const COLOR_COLRV0 = 1 << 3;
/// Font requires COLRv1 rendering support (graph of paint operations).
const COLOR_COLRV1 = 1 << 4;
/// Font requires SVG glyph rendering support.
const COLOR_SVG = 1 << 5;
/// Font has bitmap glyphs in 'sbix' format.
const COLOR_SBIX = 1 << 6;
/// Font has bitmap glyphs in 'CBDT' format.
const COLOR_CBDT = 1 << 7;
/// Font requires OpenType Variations support.
const VARIATIONS = 1 << 8;
/// Font requires CPAL palette selection support.
const PALETTES = 1 << 9;
/// Font requires support for incremental downloading.
const INCREMENTAL = 1 << 10;
}
}
impl FontFaceSourceTechFlags {
/// Parse a single font-technology keyword and return its flag.
pub fn parse_one<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
Ok(try_match_ident_ignore_ascii_case! { input,
"features-opentype" => Self::FEATURES_OPENTYPE,
"features-aat" => Self::FEATURES_AAT,
"features-graphite" => Self::FEATURES_GRAPHITE,
"color-colrv0" => Self::COLOR_COLRV0,
"color-colrv1" => Self::COLOR_COLRV1,
"color-svg" => Self::COLOR_SVG,
"color-sbix" => Self::COLOR_SBIX,
"color-cbdt" => Self::COLOR_CBDT,
"variations" => Self::VARIATIONS,
"palettes" => Self::PALETTES,
"incremental" => Self::INCREMENTAL,
})
}
}
impl Parse for FontFaceSourceTechFlags {
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let location = input.current_source_location();
// We don't actually care about the return value of parse_comma_separated,
// because we insert the flags into result as we go.
let mut result = Self::empty();
input.parse_comma_separated(|input| {
let flag = Self::parse_one(input)?;
result.insert(flag);
Ok(())
})?;
if !result.is_empty() {
Ok(result)
} else {
Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
}
}
#[allow(unused_assignments)]
impl ToCss for FontFaceSourceTechFlags {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: fmt::Write,
{
let mut first = true;
macro_rules! write_if_flag {
($s:expr => $f:ident) => {
if self.contains(Self::$f) {
if first {
first = false;
} else {
dest.write_str(", ")?;
}
dest.write_str($s)?;
}
};
}
write_if_flag!("features-opentype" => FEATURES_OPENTYPE);
write_if_flag!("features-aat" => FEATURES_AAT);
write_if_flag!("features-graphite" => FEATURES_GRAPHITE);
write_if_flag!("color-colrv0" => COLOR_COLRV0);
write_if_flag!("color-colrv1" => COLOR_COLRV1);
write_if_flag!("color-svg" => COLOR_SVG);
write_if_flag!("color-sbix" => COLOR_SBIX);
write_if_flag!("color-cbdt" => COLOR_CBDT);
write_if_flag!("variations" => VARIATIONS);
write_if_flag!("palettes" => PALETTES);
write_if_flag!("incremental" => INCREMENTAL);
Ok(())
}
}
/// A POD representation for Gecko. All pointers here are non-owned and as such
/// can't outlive the rule they came from, but we can't enforce that via C++.
///
/// All the strings are of course utf8.
#[cfg(feature = "gecko")]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
#[allow(missing_docs)]
pub enum FontFaceSourceListComponent {
Url(*const crate::gecko::url::CssUrl),
Local(*mut crate::gecko_bindings::structs::nsAtom),
FormatHintKeyword(FontFaceSourceFormatKeyword),
FormatHintString {
length: usize,
utf8_bytes: *const u8,
},
TechFlags(FontFaceSourceTechFlags),
}
#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)]
#[repr(u8)]
#[allow(missing_docs)]
pub enum FontFaceSourceFormat {
Keyword(FontFaceSourceFormatKeyword),
String(String),
}
/// A `UrlSource` represents a font-face source that has been specified with a
/// `url()` function.
///
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[derive(Clone, Debug, Eq, PartialEq, ToShmem)]
pub struct UrlSource {
/// The specified url.
pub url: SpecifiedUrl,
/// The format hint specified with the `format()` function, if present.
pub format_hint: Option<FontFaceSourceFormat>,
/// The font technology flags specified with the `tech()` function, if any.
pub tech_flags: FontFaceSourceTechFlags,
}
impl ToCss for UrlSource {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: fmt::Write,
{
self.url.to_css(dest)?;
if let Some(hint) = &self.format_hint {
dest.write_str(" format(")?;
hint.to_css(dest)?;
dest.write_char(')')?;
}
if !self.tech_flags.is_empty() {
dest.write_str(" tech(")?;
self.tech_flags.to_css(dest)?;
dest.write_char(')')?;
}
Ok(())
}
}
/// A font-display value for a @font-face rule.
/// The font-display descriptor determines how a font face is displayed based
/// on whether and when it is downloaded and ready to use.
#[allow(missing_docs)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[derive(
Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToComputedValue, ToCss, ToShmem,
)]
#[repr(u8)]
pub enum FontDisplay {
Auto,
Block,
Swap,
Fallback,
Optional,
}
macro_rules! impl_range {
($range:ident, $component:ident) => {
impl Parse for $range {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let first = $component::parse(context, input)?;
let second = input
.try_parse(|input| $component::parse(context, input))
.unwrap_or_else(|_| first.clone());
Ok($range(first, second))
}
}
impl ToCss for $range {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: fmt::Write,
{
self.0.to_css(dest)?;
if self.0 != self.1 {
dest.write_char(' ')?;
self.1.to_css(dest)?;
}
Ok(())
}
}
};
}
/// The font-weight descriptor:
///
#[derive(Clone, Debug, PartialEq, ToShmem)]
pub struct FontWeightRange(pub AbsoluteFontWeight, pub AbsoluteFontWeight);
impl_range!(FontWeightRange, AbsoluteFontWeight);
/// The computed representation of the above so Gecko can read them easily.
///
/// This one is needed because cbindgen doesn't know how to generate
/// specified::Number.
#[repr(C)]
#[allow(missing_docs)]
pub struct ComputedFontWeightRange(f32, f32);
#[inline]
fn sort_range<T: PartialOrd>(a: T, b: T) -> (T, T) {
if a > b {
(b, a)
} else {
(a, b)
}
}
impl FontWeightRange {
/// Returns a computed font-stretch range.
pub fn compute(&self) -> ComputedFontWeightRange {
let (min, max) = sort_range(self.0.compute().value(), self.1.compute().value());
ComputedFontWeightRange(min, max)
}
}
/// The font-stretch descriptor:
///
#[derive(Clone, Debug, PartialEq, ToShmem)]
pub struct FontStretchRange(pub SpecifiedFontStretch, pub SpecifiedFontStretch);
impl_range!(FontStretchRange, SpecifiedFontStretch);
/// The computed representation of the above, so that Gecko can read them
/// easily.
#[repr(C)]
#[allow(missing_docs)]
pub struct ComputedFontStretchRange(FontStretch, FontStretch);
impl FontStretchRange {
/// Returns a computed font-stretch range.
pub fn compute(&self) -> ComputedFontStretchRange {
fn compute_stretch(s: &SpecifiedFontStretch) -> FontStretch {
match *s {
SpecifiedFontStretch::Keyword(ref kw) => kw.compute(),
SpecifiedFontStretch::Stretch(ref p) => FontStretch::from_percentage(p.0.get()),
SpecifiedFontStretch::System(..) => unreachable!(),
}
}
let (min, max) = sort_range(compute_stretch(&self.0), compute_stretch(&self.1));
ComputedFontStretchRange(min, max)
}
}
/// The font-style descriptor:
///
#[derive(Clone, Debug, PartialEq, ToShmem)]
#[allow(missing_docs)]
pub enum FontStyle {
Normal,
Italic,
Oblique(Angle, Angle),
}
/// The computed representation of the above, with angles in degrees, so that
/// Gecko can read them easily.
#[repr(u8)]
#[allow(missing_docs)]
pub enum ComputedFontStyleDescriptor {
Normal,
Italic,
Oblique(f32, f32),
}
impl Parse for FontStyle {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let style = SpecifiedFontStyle::parse(context, input)?;
Ok(match style {
GenericFontStyle::Normal => FontStyle::Normal,
GenericFontStyle::Italic => FontStyle::Italic,
GenericFontStyle::Oblique(angle) => {
let second_angle = input
.try_parse(|input| SpecifiedFontStyle::parse_angle(context, input))
.unwrap_or_else(|_| angle.clone());
FontStyle::Oblique(angle, second_angle)
},
})
}
}
impl ToCss for FontStyle {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: fmt::Write,
{
match *self {
FontStyle::Normal => dest.write_str("normal"),
FontStyle::Italic => dest.write_str("italic"),
FontStyle::Oblique(ref first, ref second) => {
dest.write_str("oblique")?;
if *first != SpecifiedFontStyle::default_angle() || first != second {
dest.write_char(' ')?;
first.to_css(dest)?;
}
if first != second {
dest.write_char(' ')?;
second.to_css(dest)?;
}
Ok(())
},
}
}
}
impl FontStyle {
/// Returns a computed font-style descriptor.
pub fn compute(&self) -> ComputedFontStyleDescriptor {
match *self {
FontStyle::Normal => ComputedFontStyleDescriptor::Normal,
FontStyle::Italic => ComputedFontStyleDescriptor::Italic,
FontStyle::Oblique(ref first, ref second) => {
let (min, max) = sort_range(
SpecifiedFontStyle::compute_angle_degrees(first),
SpecifiedFontStyle::compute_angle_degrees(second),
);
ComputedFontStyleDescriptor::Oblique(min, max)
},
}
}
}
/// Parse the block inside a `@font-face` rule.
///
/// Note that the prelude parsing code lives in the `stylesheets` module.
pub fn parse_font_face_block(
context: &ParserContext,
input: &mut Parser,
location: SourceLocation,
) -> FontFaceRuleData {
let mut rule = FontFaceRuleData::empty(location);
{
let mut parser = FontFaceRuleParser {
context,
rule: &mut rule,
};
let mut iter = RuleBodyParser::new(input, &mut parser);
while let Some(declaration) = iter.next() {
if let Err((error, slice)) = declaration {
let location = error.location;
let error = ContextualParseError::UnsupportedFontFaceDescriptor(slice, error);
context.log_css_error(location, error)
}
}
}
rule
}
/// A @font-face rule that is known to have font-family and src declarations.
#[cfg(feature = "servo")]
pub struct FontFace<'a>(&'a FontFaceRuleData);
/// A list of effective sources that we send over through IPC to the font cache.
#[cfg(feature = "servo")]
#[derive(Clone, Debug)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
pub struct EffectiveSources(SourceList);
#[cfg(feature = "servo")]
impl<'a> FontFace<'a> {
/// Returns the list of effective sources for that font-face, that is the
/// sources which don't list any format hint, or the ones which list at
/// least "truetype" or "opentype".
pub fn effective_sources(&self) -> EffectiveSources {
EffectiveSources(
self.sources()
.iter()
.rev()
.filter(|source| {
if let Source::Url(ref url_source) = **source {
// We support only opentype fonts and truetype is an alias for
// that format. Sources without format hints need to be
// downloaded in case we support them.
url_source.format_hint.as_ref().map_or(true, |hint| {
hint == "truetype" || hint == "opentype" || hint == "woff"
})
} else {
true
}
})
.cloned()
.collect(),
)
}
}
#[cfg(feature = "servo")]
impl Iterator for EffectiveSources {
type Item = Source;
fn next(&mut self) -> Option<Source> {
self.0.pop()
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.0.len(), Some(self.0.len()))
}
}
struct FontFaceRuleParser<'a, 'b: 'a> {
context: &'a ParserContext<'b>,
rule: &'a mut FontFaceRuleData,
}
/// Default methods reject all at rules.
impl<'a, 'b, 'i> AtRuleParser<'i> for FontFaceRuleParser<'a, 'b> {
type Prelude = ();
type AtRule = ();
type Error = StyleParseErrorKind<'i>;
}
impl<'a, 'b, 'i> QualifiedRuleParser<'i> for FontFaceRuleParser<'a, 'b> {
type Prelude = ();
type QualifiedRule = ();
type Error = StyleParseErrorKind<'i>;
}
impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>>
for FontFaceRuleParser<'a, 'b>
{
fn parse_qualified(&self) -> bool {
false
}
fn parse_declarations(&self) -> bool {
true
}
}
impl Parse for Source {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Source, ParseError<'i>> {
if input
.try_parse(|input| input.expect_function_matching("local"))
.is_ok()
{
return input
.parse_nested_block(|input| FamilyName::parse(context, input))
.map(Source::Local);
}
let url = SpecifiedUrl::parse(context, input)?;
// Parsing optional format()
let format_hint = if input
.try_parse(|input| input.expect_function_matching("format"))
.is_ok()
{
input.parse_nested_block(|input| {
if let Ok(kw) = input.try_parse(FontFaceSourceFormatKeyword::parse) {
Ok(Some(FontFaceSourceFormat::Keyword(kw)))
} else {
let s = input.expect_string()?.as_ref().to_owned();
Ok(Some(FontFaceSourceFormat::String(s)))
}
})?
} else {
None
};
// Parse optional tech()
let tech_flags = if static_prefs::pref!("layout.css.font-tech.enabled") &&
input
.try_parse(|input| input.expect_function_matching("tech"))
.is_ok()
{
input.parse_nested_block(|input| FontFaceSourceTechFlags::parse(context, input))?
} else {
FontFaceSourceTechFlags::empty()
};
Ok(Source::Url(UrlSource {
url,
format_hint,
tech_flags,
}))
}
}
macro_rules! is_descriptor_enabled {
("font-variation-settings") => {
static_prefs::pref!("layout.css.font-variations.enabled")
};
("size-adjust") => {
static_prefs::pref!("layout.css.size-adjust.enabled")
};
($name:tt) => {
true
};
}
macro_rules! font_face_descriptors_common {
(
$( #[$doc: meta] $name: tt $ident: ident / $gecko_ident: ident: $ty: ty, )*
) => {
/// Data inside a `@font-face` rule.
///
#[derive(Clone, Debug, PartialEq, ToShmem)]
pub struct FontFaceRuleData {
$(
#[$doc]
pub $ident: Option<$ty>,
)*
/// Line and column of the @font-face rule source code.
pub source_location: SourceLocation,
}
impl FontFaceRuleData {
/// Create an empty font-face rule
pub fn empty(location: SourceLocation) -> Self {
FontFaceRuleData {
$(
$ident: None,
)*
source_location: location,
}
}
/// Serialization of declarations in the FontFaceRule
pub fn decl_to_css(&self, dest: &mut CssStringWriter) -> fmt::Result {
$(
if let Some(ref value) = self.$ident {
dest.write_str(concat!($name, ": "))?;
value.to_css(&mut CssWriter::new(dest))?;
dest.write_str("; ")?;
}
)*
Ok(())
}
}
impl<'a, 'b, 'i> DeclarationParser<'i> for FontFaceRuleParser<'a, 'b> {
type Declaration = ();
type Error = StyleParseErrorKind<'i>;
fn parse_value<'t>(
&mut self,
name: CowRcStr<'i>,
input: &mut Parser<'i, 't>,
) -> Result<(), ParseError<'i>> {
match_ignore_ascii_case! { &*name,
$(
$name if is_descriptor_enabled!($name) => {
// DeclarationParser also calls parse_entirely
// so we’d normally not need to,
// but in this case we do because we set the value as a side effect
// rather than returning it.
let value = input.parse_entirely(|i| Parse::parse(self.context, i))?;
self.rule.$ident = Some(value)
},
)*
_ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
}
Ok(())
}
}
}
}
impl ToCssWithGuard for FontFaceRuleData {
// Serialization of FontFaceRule is not specced.
fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
dest.write_str("@font-face { ")?;
self.decl_to_css(dest)?;
dest.write_char('}')
}
}
macro_rules! font_face_descriptors {
(
mandatory descriptors = [
$( #[$m_doc: meta] $m_name: tt $m_ident: ident / $m_gecko_ident: ident: $m_ty: ty, )*
]
optional descriptors = [
$( #[$o_doc: meta] $o_name: tt $o_ident: ident / $o_gecko_ident: ident: $o_ty: ty, )*
]
) => {
font_face_descriptors_common! {
$( #[$m_doc] $m_name $m_ident / $m_gecko_ident: $m_ty, )*
$( #[$o_doc] $o_name $o_ident / $o_gecko_ident: $o_ty, )*
}
impl FontFaceRuleData {
/// is valid as far as the CSS parser is concerned even if it doesn’t have
/// a font-family or src declaration.
///
/// However both are required for the rule to represent an actual font face.
#[cfg(feature = "servo")]
pub fn font_face(&self) -> Option<FontFace> {
if $( self.$m_ident.is_some() )&&* {
Some(FontFace(self))
} else {
None
}
}
}
#[cfg(feature = "servo")]
impl<'a> FontFace<'a> {
$(
#[$m_doc]
pub fn $m_ident(&self) -> &$m_ty {
self.0 .$m_ident.as_ref().unwrap()
}
)*
}
}
}
#[cfg(feature = "gecko")]
font_face_descriptors! {
mandatory descriptors = [
/// The name of this font face
"font-family" family / mFamily: FamilyName,
/// The alternative sources for this font face.
"src" sources / mSrc: SourceList,
]
optional descriptors = [
/// The style of this font face.
"font-style" style / mStyle: FontStyle,
/// The weight of this font face.
"font-weight" weight / mWeight: FontWeightRange,
/// The stretch of this font face.
"font-stretch" stretch / mStretch: FontStretchRange,
/// The display of this font face.
"font-display" display / mDisplay: FontDisplay,
/// The ranges of code points outside of which this font face should not be used.
"unicode-range" unicode_range / mUnicodeRange: Vec<UnicodeRange>,
/// The feature settings of this font face.
"font-feature-settings" feature_settings / mFontFeatureSettings: FontFeatureSettings,
/// The variation settings of this font face.
"font-variation-settings" variation_settings / mFontVariationSettings: FontVariationSettings,
/// The language override of this font face.
"font-language-override" language_override / mFontLanguageOverride: font_language_override::SpecifiedValue,
/// The ascent override for this font face.
"ascent-override" ascent_override / mAscentOverride: MetricsOverride,
/// The descent override for this font face.
"descent-override" descent_override / mDescentOverride: MetricsOverride,
/// The line-gap override for this font face.
"line-gap-override" line_gap_override / mLineGapOverride: MetricsOverride,
/// The size adjustment for this font face.
"size-adjust" size_adjust / mSizeAdjust: NonNegativePercentage,
]
}
#[cfg(feature = "servo")]
font_face_descriptors! {
mandatory descriptors = [
/// The name of this font face
"font-family" family / mFamily: FamilyName,
/// The alternative sources for this font face.
"src" sources / mSrc: SourceList,
]
optional descriptors = [
]
}