Repository: typst/typst
File: crates/typst-library/src/text/mod.rs (Rust)

Code before:
            }
        }

        Self::from_bytes_lossy(v.as_bytes())
    }
}

cast! {
    FontFeatures,
    self => self.0
        .into_iter()
        .map(|(tag, num)| {
            let bytes = tag.to_bytes();
            let key = std::str::from_utf8(&bytes).unwrap_or_default();
            (key.into(), num.into_value())
        })
        .collect::<Dict>()
        .into_value(),
    values: Array => Self(values
        .into_iter()
        .enumerate()
        .map(|(i, v)| Ok((
            v.clone().cast::<Tag>()
                .hint(eco_format!(
                    "occurred in tag at index {i}, '{}'",
                    v.cast::<EcoString>().unwrap_or_default()
                ))
                .hint("to set features with custom values, consider supplying a `dictionary`")?,
            1
        )))
        .collect::<HintedStrResult<_>>()?),
    values: Dict => Self(values
        .into_iter()
        .enumerate()
        .map(|(i, (k, v))| Ok((
            k.clone().into_value().cast::<Tag>().hint(
                eco_format!("occurred in tag at index {i}, '{k}'")
            )?,
            v.cast::<u32>()?
        )))
        .collect::<HintedStrResult<_>>()?),
}

impl Fold for FontFeatures {
    fn fold(self, outer: Self) -> Self {
        Self(self.0.fold(outer.0))
    }
}

/// Collect the OpenType features to apply.
pub fn features(styles: StyleChain) -> Vec<Feature> {

Review comment (bug):
the unwrap_or_default will produce a rather weird hint `occurred in tag at index 0, ''`. we should not add the value if it's not a string maybe? also maybe ``occurred in tag `"wrong"` at index 0`` would be a bit friendlier than the comma?

Code after:
            let bytes = tag.to_bytes();
            let key = std::str::from_utf8(&bytes).unwrap_or_default();
            (key.into(), num.into_value())
        })
        .collect::<Dict>()
        .into_value(),
    values: Array => Self(values
        .into_iter()
        .enumerate()
        .map(|(i, v)| Ok((
            v.clone().cast::<Tag>().hint(tag_hint_helper(i, &v)).map_err(|e| {
                // Append a hint if the value is a string containing the
                // assignment operator `=` or another type was supplied.
                if v.cast::<Str>().map_or(true, |v| v.as_str().contains('=')) {
                    e.with_hint("to set features with custom values, consider supplying a dictionary")
                } else {
                    e
                }
            })?,
            1
        )))
        .collect::<HintedStrResult<_>>()?),
    values: Dict => Self(values
        .into_iter()
        .enumerate()
        .map(|(i, (k, v))| Ok((
            k.clone().into_value().cast::<Tag>().hint(tag_hint_helper(i, &k))?,
            v.cast::<u32>().hint(tag_hint_helper(i, &k))?
        )))
        .collect::<HintedStrResult<_>>()?),
}

fn tag_hint_helper(index: usize, key: &impl Repr) -> EcoString {
    eco_format!("occurred in tag at index {index} (`{}`)", key.repr())
}

impl Fold for FontFeatures {
    fn fold(self, outer: Self) -> Self {
        Self(self.0.fold(outer.0))
    }
}

/// Collect the OpenType features to apply.
pub fn features(styles: StyleChain) -> Vec<Feature> {
    let mut tags = vec![];
    let mut feat = |tag: &[u8; 4], value: u32| {
        tags.push(Feature::new(Tag::from_bytes(tag), value, ..));
    };

    // Features that are on by default in Harfbuzz are only added if disabled.
    if !styles.get(TextElem::kerning) {