use std::fmt::Display;
include!("../../generated/generated_meta.rs");
pub const DLNG: Tag = Tag::new(b"dlng");
pub const SLNG: Tag = Tag::new(b"slng");
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Metadata {
ScriptLangTags(Vec<ScriptLangTag>),
Other(Vec<u8>),
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ScriptLangTag(String);
#[derive(Clone, Debug)]
#[non_exhaustive] pub struct InvalidScriptLangTag;
impl ScriptLangTag {
pub fn new(raw: String) -> Result<Self, InvalidScriptLangTag> {
Ok(Self(raw))
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl Display for InvalidScriptLangTag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("ScriptLangTag was malformed")
}
}
impl std::error::Error for InvalidScriptLangTag {}
impl DataMapRecord {
fn validate_data_type(&self, ctx: &mut ValidationCtx) {
if matches!(
(self.tag, self.data.as_ref()),
(SLNG | DLNG, Metadata::Other(_))
) {
ctx.report("'slng' or 'dlng' tags use ScriptLangTag data");
}
}
fn compute_data_len(&self) -> usize {
match self.data.as_ref() {
Metadata::ScriptLangTags(items) => {
let sum_len: usize = items.iter().map(|tag| tag.as_str().len()).sum();
let toss_some_commas_in_there = items.len().saturating_sub(1);
sum_len + toss_some_commas_in_there
}
Metadata::Other(vec) => vec.len(),
}
}
}
impl FontWrite for Metadata {
fn write_into(&self, writer: &mut TableWriter) {
match self {
Metadata::ScriptLangTags(langs) => {
let mut first = true;
for lang in langs {
if !first {
b','.write_into(writer);
}
first = false;
lang.0.as_bytes().write_into(writer);
}
}
Metadata::Other(vec) => {
vec.write_into(writer);
}
};
}
}
impl Validate for Metadata {
fn validate_impl(&self, _ctx: &mut ValidationCtx) {}
}
impl FromObjRef<read_fonts::tables::meta::Metadata<'_>> for Metadata {
fn from_obj_ref(from: &read_fonts::tables::meta::Metadata<'_>, _: FontData) -> Self {
match from {
read_fonts::tables::meta::Metadata::ScriptLangTags(var_len_array) => {
Self::ScriptLangTags(
var_len_array
.iter()
.flat_map(|x| {
x.ok()
.and_then(|x| ScriptLangTag::new(x.as_str().into()).ok())
})
.collect(),
)
}
read_fonts::tables::meta::Metadata::Other(bytes) => Self::Other(bytes.to_vec()),
}
}
}
impl FromTableRef<read_fonts::tables::meta::Metadata<'_>> for Metadata {}
impl Default for Metadata {
fn default() -> Self {
Metadata::ScriptLangTags(Vec::new())
}
}
impl FromObjRef<read_fonts::tables::meta::DataMapRecord> for DataMapRecord {
fn from_obj_ref(obj: &read_fonts::tables::meta::DataMapRecord, offset_data: FontData) -> Self {
let data = obj
.data(offset_data)
.map(|meta| meta.to_owned_table())
.unwrap_or_else(|_| match obj.tag() {
DLNG | SLNG => Metadata::ScriptLangTags(Vec::new()),
_ => Metadata::Other(Vec::new()),
});
DataMapRecord {
tag: obj.tag(),
data: OffsetMarker::new(data),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use font_test_data::meta as test_data;
#[test]
fn convert_from_read() {
let table = Meta::read(test_data::SIMPLE_META_TABLE.into()).unwrap();
let rec1 = &table.data_maps[0];
assert_eq!(
rec1.data.as_ref(),
&Metadata::ScriptLangTags(vec![
ScriptLangTag::new("en-latn".into()).unwrap(),
ScriptLangTag::new("latn".into()).unwrap()
])
);
let round_trip = crate::dump_table(&table).unwrap();
let read_back = Meta::read(round_trip.as_slice().into()).unwrap();
let readr = read_fonts::tables::meta::Meta::read(round_trip.as_slice().into()).unwrap();
dbg!(readr);
assert_eq!(table, read_back);
}
}