Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

63
vendor/fontconfig-parser/src/error.rs vendored Normal file
View File

@@ -0,0 +1,63 @@
use std::fmt;
use std::num::ParseFloatError;
use std::num::ParseIntError;
use std::str::ParseBoolError;
#[derive(Debug)]
pub enum Error {
Xml(roxmltree::Error),
NoFontconfig,
InvalidFormat(String),
IoError(std::io::Error),
ParseEnumError(&'static str, String),
ParseIntError(ParseIntError),
ParseFloatError(ParseFloatError),
ParseBoolError(ParseBoolError),
}
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
Self::IoError(e)
}
}
impl From<roxmltree::Error> for Error {
fn from(e: roxmltree::Error) -> Self {
Self::Xml(e)
}
}
impl From<ParseIntError> for Error {
fn from(e: ParseIntError) -> Self {
Self::ParseIntError(e)
}
}
impl From<ParseFloatError> for Error {
fn from(e: ParseFloatError) -> Self {
Self::ParseFloatError(e)
}
}
impl From<ParseBoolError> for Error {
fn from(e: ParseBoolError) -> Self {
Self::ParseBoolError(e)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Xml(e) => e.fmt(f),
Error::NoFontconfig => write!(f, "Can't find fontconfig element"),
Error::InvalidFormat(msg) => write!(f, "Config format is invalid: {}", msg),
Error::IoError(e) => write!(f, "IO error: {}", e),
Error::ParseEnumError(ty, s) => write!(f, "Unknown variant for {}: {}", ty, s),
Error::ParseIntError(e) => e.fmt(f),
Error::ParseFloatError(e) => e.fmt(f),
Error::ParseBoolError(e) => e.fmt(f),
}
}
}
impl std::error::Error for Error {}

42
vendor/fontconfig-parser/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,42 @@
//! This crate provide parsing fontconfig file but not yet complete all features
//!
//! see <https://www.freedesktop.org/software/fontconfig/fontconfig-user.html> for more detail infomation of fontconfig file
//!
//! # Example
//!
//! ```no_run
//! use fontconfig_parser::FontConfig;
//!
//! let mut config = FontConfig::default();
//!
//! config.merge_config("/etc/fonts/fonts.conf").unwrap();
//! ```
#[macro_use]
mod util;
mod error;
mod parser;
mod types;
pub type Result<T> = core::result::Result<T, Error>;
pub use crate::error::Error;
pub use crate::types::*;
/// Parse as raw config parts use this when you want custom handling config file
///
/// Otherwise, you may want [`FontConfig::merge_config`]
pub fn parse_config_parts(s: &str) -> Result<Vec<ConfigPart>> {
crate::parser::parse_config(&roxmltree::Document::parse_with_options(
s,
roxmltree::ParsingOptions {
allow_dtd: true,
..Default::default()
},
)?)?
.collect()
}
#[cfg(test)]
mod tests {}

391
vendor/fontconfig-parser/src/parser.rs vendored Normal file
View File

@@ -0,0 +1,391 @@
#![allow(clippy::useless_format)]
use crate::*;
use roxmltree::Node;
pub fn parse_config<'a>(
xml_doc: &'a roxmltree::Document,
) -> Result<impl Iterator<Item = Result<ConfigPart>> + 'a> {
let fontconfig = xml_doc.root_element();
if fontconfig.tag_name().name() != "fontconfig" {
return Err(Error::NoFontconfig);
}
Ok(fontconfig
.children()
.filter_map(|c| parse_config_part(c).transpose()))
}
fn parse_config_part(child: Node) -> Result<Option<ConfigPart>> {
let part = match child.tag_name().name() {
"description" => ConfigPart::Description(try_text!(child).into()),
"alias" => {
let mut alias = Alias::default();
for child in child.children() {
let families =
child
.children()
.filter_map(|family| match family.tag_name().name() {
"family" => family.text().map(Into::into),
_ => None,
});
match child.tag_name().name() {
"family" => {
alias.alias = try_text!(child).into();
}
"prefer" => {
alias.prefer.extend(families);
}
"accept" => {
alias.accept.extend(families);
}
"default" => {
alias.default.extend(families);
}
_ => {}
}
}
ConfigPart::Alias(alias)
}
"dir" => {
let mut dir = Dir::default();
parse_attrs!(child, {
"prefix" => dir.prefix,
}, {
"salt" => dir.salt,
});
dir.path = try_text!(child).into();
ConfigPart::Dir(dir)
}
"reset-dirs" => ConfigPart::ResetDirs,
"remap-dir" => {
let mut dir = RemapDir::default();
parse_attrs!(child, {
"prefix" => dir.prefix,
}, {
"salt" => dir.salt,
"as-path" => dir.as_path,
});
dir.path = try_text!(child).into();
ConfigPart::RemapDir(dir)
}
"cachedir" => {
let mut dir = CacheDir::default();
parse_attrs!(child, {
"prefix" => dir.prefix,
});
dir.path = try_text!(child).into();
ConfigPart::CacheDir(dir)
}
"include" => {
let mut dir = Include::default();
let mut ignore_missing = "";
parse_attrs!(child, {
"prefix" => dir.prefix,
}, {
"ignore_missing" => ignore_missing,
});
dir.ignore_missing = matches!(ignore_missing, "yes");
dir.path = try_text!(child).into();
ConfigPart::Include(dir)
}
"config" => {
let mut config = Config::default();
for child in child.children() {
match child.tag_name().name() {
"rescan" => {
if let Some(int) = child.first_element_child() {
if int.tag_name().name() == "int" {
config.rescans.push(try_text!(int).parse()?);
}
}
}
"blank" => {
if let Some(child) = child.first_element_child() {
config.blanks.push(parse_int_or_range(child)?);
}
}
_ => {}
}
}
ConfigPart::Config(config)
}
"selectfont" => {
let mut s = SelectFont::default();
for child in child.children() {
let matches = child.children().filter_map(|c| match c.tag_name().name() {
"pattern" => {
let patelts = c.children().filter_map(|patelt| {
if patelt.tag_name().name() == "patelt" {
let mut kind = PropertyKind::default();
parse_attrs_opt!(patelt, {
"name" => kind,
});
parse_expr(patelt.first_element_child()?)
.ok()
.map(|expr| kind.make_property(expr))
} else {
None
}
});
Some(FontMatch::Pattern(patelts.collect()))
}
"glob" => c.text().map(Into::into).map(FontMatch::Glob),
_ => None,
});
match child.tag_name().name() {
"acceptfont" => {
s.accepts.extend(matches);
}
"rejectfont" => {
s.rejects.extend(matches);
}
_ => {}
}
}
ConfigPart::SelectFont(s)
}
"match" => {
let mut m = Match::default();
parse_attrs!(child, {
"target" => m.target,
});
for child in child.children() {
match child.tag_name().name() {
"test" => {
let mut t = Test::default();
let mut kind = PropertyKind::default();
parse_attrs!(child, {
"name" => kind,
"qual" => t.qual,
"target" => t.target,
"compare" => t.compare,
});
t.value = kind.make_property(parse_expr(
child
.first_element_child()
.ok_or_else(|| Error::InvalidFormat(format!("Empty test value")))?,
)?);
m.tests.push(t);
}
"edit" => {
let mut e = Edit::default();
let mut kind = PropertyKind::default();
parse_attrs!(child, {
"name" => kind,
"mode" => e.mode,
"binding" => e.binding,
});
e.value = kind.make_property(parse_expr(
child
.first_element_child()
.ok_or_else(|| Error::InvalidFormat(format!("Empty edit value")))?,
)?);
m.edits.push(e);
}
_ => {}
}
}
ConfigPart::Match(m)
}
_ => {
return Ok(None);
}
};
Ok(Some(part))
}
fn parse_int_or_range(node: Node) -> Result<IntOrRange> {
let mut texts = get_texts(&node);
match node.tag_name().name() {
"int" => Ok(IntOrRange::Int(try_text!(node).parse()?)),
"range" => Ok(IntOrRange::Range(
try_next!(texts, "Expect int").parse()?,
try_next!(texts, "Expect int").parse()?,
)),
_ => Err(Error::InvalidFormat(format!("Expect IntOrRange"))),
}
}
fn parse_expr(node: Node) -> Result<Expression> {
let mut exprs = get_exprs(&node);
let mut texts = get_texts(&node);
macro_rules! next {
($iter:expr) => {
try_next!($iter, "Expect expression")
};
}
match node.tag_name().name() {
"string" => Ok(Value::String(try_text!(node).into()).into()),
"langset" => Ok(Value::LangSet(try_text!(node).into()).into()),
"double" => Ok(Value::Double(try_text!(node).parse()?).into()),
"int" => Ok(Value::Int(try_text!(node).parse()?).into()),
"bool" => Ok(Value::Bool(try_text!(node).parse()?).into()),
"const" => Ok(Value::Constant(try_text!(node).parse()?).into()),
"matrix" => Ok(Expression::Matrix(Box::new([
next!(exprs)?,
next!(exprs)?,
next!(exprs)?,
next!(exprs)?,
]))),
"charset" => {
let charset = node
.children()
.filter_map(|c| parse_int_or_range(c).ok())
.collect();
Ok(Value::CharSet(charset).into())
}
"range" => Ok(Value::Range(next!(texts).parse()?, next!(texts).parse()?).into()),
"name" => {
let mut target = PropertyTarget::default();
parse_attrs!(node, {
"target" => target,
});
let kind = try_text!(node).parse()?;
Ok(Value::Property(target, kind).into())
}
name => {
if let Ok(list_op) = name.parse() {
Ok(Expression::List(
list_op,
exprs.collect::<Result<Vec<_>>>()?,
))
} else if let Ok(unary_op) = name.parse() {
Ok(Expression::Unary(unary_op, Box::new(next!(exprs)?)))
} else if let Ok(binary_op) = name.parse() {
Ok(Expression::Binary(
binary_op,
Box::new([next!(exprs)?, next!(exprs)?]),
))
} else if let Ok(ternary_op) = name.parse() {
Ok(Expression::Ternary(
ternary_op,
Box::new([next!(exprs)?, next!(exprs)?, next!(exprs)?]),
))
} else {
Err(Error::InvalidFormat(format!(
"Unknown expression: {:?}",
node.tag_name(),
)))
}
}
}
}
fn get_exprs<'a>(node: &'a Node) -> impl Iterator<Item = Result<Expression>> + 'a {
node.children().filter_map(|n| {
if n.is_element() {
Some(parse_expr(n))
} else {
None
}
})
}
fn get_texts<'a>(node: &'a Node) -> impl Iterator<Item = &'a str> {
node.children()
.filter_map(|n| if n.is_element() { n.text() } else { None })
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! make_parse_failed_test {
($name:ident, $test_fn:ident, $text:expr,) => {
#[test]
#[should_panic]
fn $name() {
let doc = roxmltree::Document::parse($text).expect("Parsing xml");
let node = doc.root_element();
$test_fn(node).expect("Run parse");
}
};
}
macro_rules! make_parse_test {
($name:ident, $test_fn:ident, $text:expr, $value:expr,) => {
#[test]
fn $name() {
let doc = roxmltree::Document::parse($text).expect("Parsing xml");
let node = doc.root_element();
let ret = $test_fn(node).expect("Run parse");
let expected = $value;
k9::assert_equal!(expected, ret);
}
};
}
make_parse_test!(
test_parse_charset,
parse_expr,
"<charset><range><int>0</int><int>123</int></range></charset>",
Expression::from(vec![IntOrRange::Range(0, 123)]),
);
make_parse_test!(
test_parse_int,
parse_expr,
"<int>123</int>",
Expression::from(123),
);
make_parse_failed_test!(test_parse_invalid_int, parse_expr, "<int>123f</int>",);
make_parse_test!(
test_parse_range,
parse_expr,
"<range><int>0</int><int>10</int></range>",
Expression::from(Value::Range(0, 10)),
);
make_parse_failed_test!(
test_parse_invalid_range,
parse_expr,
"<range>0<int>10</int></range>",
);
make_parse_test!(
test_langset,
parse_expr,
"<langset>ko-KR</langset>",
Expression::from(Value::LangSet("ko-KR".into())),
);
}

22
vendor/fontconfig-parser/src/types.rs vendored Normal file
View File

@@ -0,0 +1,22 @@
mod alias;
mod config;
mod constant;
mod dir;
mod document;
mod match_;
mod property;
mod selectfont;
mod value;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(untagged))]
pub enum IntOrRange {
Int(Int),
Range(Int, Int),
}
pub use self::{
alias::*, config::*, constant::*, dir::*, document::*, match_::*, property::*, selectfont::*,
value::*,
};

View File

@@ -0,0 +1,14 @@
/// Alias elements provide a shorthand notation for the set of common match operations needed to substitute one font family for another. They contain a <family> element followed by optional <prefer>, <accept> and <default> elements. Fonts matching the <family> element are edited to prepend the list of <prefer>ed families before the matching <family>, append the <accept>able families after the matching <family> and append the <default> families to the end of the family list.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Alias {
/// alias name
pub alias: String,
/// `<prefer>`
pub prefer: Vec<String>,
/// `<accept>`
pub accept: Vec<String>,
/// `<default>`
pub default: Vec<String>,
}

View File

@@ -0,0 +1,8 @@
use crate::{Int, IntOrRange};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Config {
pub blanks: Vec<IntOrRange>,
pub rescans: Vec<Int>,
}

View File

@@ -0,0 +1,134 @@
use crate::PropertyKind;
macro_rules! define_constant {
(
$(
$variant:ident = $(($ty:ident, $value:expr),)+
)+
) => {
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Constant {
$(
$variant,
)+
}
impl Constant {
pub fn get_value(self, kind: PropertyKind) -> Option<u32> {
match (self, kind) {
$(
$(
(Constant::$variant, PropertyKind::$ty) => Some($value),
)+
)+
_ => None,
}
}
}
};
}
define_constant! {
Thin = (Weight, 0),
Extralight = (Weight, 40),
Ultralight = (Weight, 40),
Light = (Weight, 50),
Demilight = (Weight, 55),
Semilight = (Weight, 55),
Book = (Weight, 75),
Regular = (Weight, 80),
Normal = (Weight, 80), (Width, 100),
Medium = (Weight, 100),
Demibold = (Weight, 180),
Semibold = (Weight, 180),
Bold = (Weight, 200),
Extrabold = (Weight, 205),
Black = (Weight, 210),
Heavy = (Weight, 210),
Roman = (Slant, 0),
Italic = (Slant, 100),
Oblique = (Slant, 110),
Ultracondensed = (Width, 50),
Extracondensed = (Width, 63),
Condensed = (Width, 75),
Semicondensed = (Width, 87),
// Merged into above Normal
// Normal = (Width, 100),
Semiexpanded = (Width, 113),
Expanded = (Width, 125),
Extraexpanded = (Width, 150),
Ultraexpanded = (Width, 200),
Proportional = (Spacing, 0),
Dual = (Spacing, 90),
Mono = (Spacing, 100),
Charcell = (Spacing, 110),
Unknown = (Rgba, 0),
Rgb = (Rgba, 1),
Bgr = (Rgba, 2),
Vrgb = (Rgba, 3),
Vbgr = (Rgba, 4),
None = (Rgba, 5),
Lcdnone = (Lcdfilter, 0),
Lcddefault = (Lcdfilter, 1),
Lcdlight = (Lcdfilter, 2),
Lcdlegacy = (Lcdfilter, 3),
Hintnone = (HintStyle, 0),
Hintslight = (HintStyle, 1),
Hintmedium = (HintStyle, 2),
Hintfull = (HintStyle, 3),
}
parse_enum! {
Constant,
(Thin, "thin"),
(Extralight, "extralight"),
(Ultralight, "ultralight"),
(Light, "light"),
(Demilight, "demilight"),
(Semilight, "semilight"),
(Book, "book"),
(Regular, "regular"),
(Normal, "normal"),
(Medium, "medium"),
(Demibold, "demibold"),
(Semibold, "semibold"),
(Bold, "bold"),
(Extrabold, "extrabold"),
(Black, "black"),
(Heavy, "heavy"),
(Roman, "roman"),
(Italic, "italic"),
(Oblique, "oblique"),
(Ultracondensed, "ultracondensed"),
(Extracondensed, "extracondensed"),
(Condensed, "condensed"),
(Semicondensed, "semicondensed"),
(Semiexpanded, "semiexpanded"),
(Expanded, "expanded"),
(Extraexpanded, "extraexpanded"),
(Ultraexpanded, "ultraexpanded"),
(Proportional, "proportional"),
(Dual, "dual"),
(Mono, "mono"),
(Charcell, "charcell"),
(Unknown, "unknown"),
(Rgb, "rgb"),
(Bgr, "bgr"),
(Vrgb, "vrgb"),
(Vbgr, "vbgr"),
(None, "none"),
(Lcdnone, "lcdnone"),
(Lcddefault, "lcddefault"),
(Lcdlight, "lcdlight"),
(Lcdlegacy, "lcdlegacy"),
(Hintnone, "hintnone"),
(Hintslight, "hintslight"),
(Hintmedium, "hintmedium"),
(Hintfull, "hintfull"),
}
#[test]
fn convert_test() {
assert_eq!(Constant::Roman.get_value(PropertyKind::Slant).unwrap(), 0,);
}

View File

@@ -0,0 +1,168 @@
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Dir {
pub prefix: DirPrefix,
pub salt: String,
pub path: String,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CacheDir {
pub prefix: DirPrefix,
pub path: String,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Include {
pub prefix: DirPrefix,
pub ignore_missing: bool,
pub path: String,
}
/// This element contains a directory name where will be mapped as the path 'as-path' in cached information. This is useful if the directory name is an alias (via a bind mount or symlink) to another directory in the system for which cached font information is likely to exist.
/// 'salt' property affects to determine cache filename as same as [`Dir`] element.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RemapDir {
pub prefix: DirPrefix,
pub as_path: String,
pub salt: String,
pub path: String,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DirPrefix {
Default,
Cwd,
Xdg,
Relative,
}
pub enum PrefixBehavior {
Config,
Cwd,
Xdg,
Relative,
}
parse_enum! {
DirPrefix,
(Default, "default"),
(Cwd, "cwd"),
(Xdg, "xdg"),
(Relative, "relative"),
}
impl Default for DirPrefix {
fn default() -> Self {
DirPrefix::Default
}
}
/// Get the location to user home directory.
///
/// This implementation follows `FcConfigHome` function of freedesktop.org's
/// Fontconfig library.
#[allow(unused_mut, clippy::let_and_return)]
fn config_home() -> Result<String, std::env::VarError> {
let mut home = std::env::var("HOME");
#[cfg(target_os = "windows")]
{
home = home.or_else(|_| std::env::var("USERPROFILE"));
}
home
}
/// Given a relative path to a config file, this function returns
/// the complete file name to load.
///
/// This is a simplified version of `FcConfigGetFilename` from the Fontconfig
/// library.
fn config_get_file_name(p: &std::path::PathBuf) -> std::path::PathBuf {
if cfg!(target_os = "windows") {
// TODO: get config file path properly for Windows
return p.clone();
} else {
std::path::Path::new("/etc/fonts").join(p)
}
}
fn expand_tilde(path: &String) -> std::path::PathBuf {
let parsed_path = std::path::Path::new(path);
if let Ok(stripped_path) = parsed_path.strip_prefix("~") {
let home = config_home().unwrap_or("/".to_string());
std::path::Path::new(&home).join(stripped_path)
} else {
parsed_path.into()
}
}
macro_rules! define_calculate_path {
($ty:ident, $xdg_env:expr, $xdg_fallback:expr, $default_prefix_behavior:expr) => {
impl $ty {
/// Environment variable name which used `xdg` prefix
pub const XDG_ENV: &'static str = $xdg_env;
/// Fallback path when `XDG_ENV` is not exists
pub const XDG_FALLBACK_PATH: &'static str = $xdg_fallback;
const DEFAULT_PREFIX_BEHAVIOR: PrefixBehavior = $default_prefix_behavior;
fn get_prefix_behavior(prefix: DirPrefix) -> PrefixBehavior {
match prefix {
DirPrefix::Default => Self::DEFAULT_PREFIX_BEHAVIOR,
DirPrefix::Cwd => PrefixBehavior::Cwd,
DirPrefix::Xdg => PrefixBehavior::Xdg,
DirPrefix::Relative => PrefixBehavior::Relative,
}
}
/// Calculate actual path
pub fn calculate_path<P: AsRef<std::path::Path> + ?Sized>(
&self,
config_file_path: &P,
) -> std::path::PathBuf {
let expanded_path = expand_tilde(&self.path);
if expanded_path.is_absolute() {
return expanded_path;
}
let prefix = Self::get_prefix_behavior(self.prefix);
match prefix {
PrefixBehavior::Config => config_get_file_name(&expanded_path),
PrefixBehavior::Cwd => std::path::Path::new(".").join(expanded_path),
PrefixBehavior::Relative => match config_file_path.as_ref().parent() {
Some(parent) => parent.join(expanded_path),
None => std::path::Path::new(".").join(expanded_path),
},
PrefixBehavior::Xdg => {
let xdg_path =
std::env::var($xdg_env).unwrap_or_else(|_| $xdg_fallback.into());
expand_tilde(&xdg_path).join(expanded_path)
}
}
}
}
};
}
define_calculate_path!(Dir, "XDG_DATA_HOME", "~/.local/share", PrefixBehavior::Cwd);
define_calculate_path!(CacheDir, "XDG_CACHE_HOME", "~/.cache", PrefixBehavior::Cwd);
define_calculate_path!(
Include,
"XDG_CONFIG_HOME",
"~/.config",
PrefixBehavior::Config
);
define_calculate_path!(
RemapDir,
"XDG_CONFIG_HOME",
"~/.config",
PrefixBehavior::Cwd
);

View File

@@ -0,0 +1,183 @@
use crate::parser::parse_config;
use crate::*;
use std::collections::HashSet;
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ConfigPart {
Description(String),
SelectFont(SelectFont),
Dir(Dir),
CacheDir(CacheDir),
Include(Include),
Match(Match),
Config(Config),
Alias(Alias),
RemapDir(RemapDir),
ResetDirs,
}
#[derive(Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FontConfig {
pub select_fonts: Vec<SelectFont>,
pub dirs: Vec<DirData>,
pub cache_dirs: Vec<PathBuf>,
pub remap_dirs: Vec<RemapDirData>,
pub matches: Vec<Match>,
pub config: Config,
pub aliases: Vec<Alias>,
pub config_files: HashSet<PathBuf>,
}
impl FontConfig {
pub fn merge_config<P: AsRef<Path> + ?Sized>(&mut self, config_path: &P) -> Result<()> {
match std::fs::canonicalize(&config_path) {
Ok(p) => {
if !self.config_files.insert(std::path::PathBuf::from(p)) {
return Ok(());
}
}
Err(err) => return Err(Error::IoError(err)),
}
let config = fs::read_to_string(config_path.as_ref())?;
let xml_doc = roxmltree::Document::parse_with_options(
&config,
roxmltree::ParsingOptions {
allow_dtd: true,
..Default::default()
},
)?;
for part in parse_config(&xml_doc)? {
match part? {
ConfigPart::Alias(alias) => self.aliases.push(alias),
ConfigPart::Config(mut c) => {
self.config.rescans.append(&mut c.rescans);
self.config.blanks.append(&mut c.blanks);
}
ConfigPart::Description(_) => {}
ConfigPart::Dir(dir) => self.dirs.push(DirData {
path: dir.calculate_path(config_path),
salt: dir.salt,
}),
ConfigPart::CacheDir(dir) => self.cache_dirs.push(dir.calculate_path(config_path)),
ConfigPart::Match(m) => self.matches.push(m),
ConfigPart::ResetDirs => self.dirs.clear(),
ConfigPart::SelectFont(s) => self.select_fonts.push(s),
ConfigPart::RemapDir(remap) => self.remap_dirs.push(RemapDirData {
path: remap.calculate_path(config_path),
salt: remap.salt,
as_path: remap.as_path,
}),
ConfigPart::Include(dir) => {
let include_path = dir.calculate_path(config_path);
match self.include(&include_path) {
Ok(_) => {}
#[allow(unused_variables)]
Err(err) => {
if !dir.ignore_missing {
#[cfg(feature = "log")]
log::warn!("Failed to include {}: {}", include_path.display(), err);
}
}
}
}
}
}
Ok(())
}
fn include(&mut self, include_path: &Path) -> Result<()> {
let meta = fs::metadata(include_path)?;
let ty = meta.file_type();
// fs::metadata follow symlink so ty is never symlink
if ty.is_file() {
self.merge_config(include_path)?;
} else if ty.is_dir() {
let dir = std::fs::read_dir(include_path)?;
let mut config_paths: Vec<_> = dir
.filter_map(|entry| {
let entry = entry.ok()?;
let ty = entry.file_type().ok()?;
if ty.is_file() || ty.is_symlink() {
Some(entry.path())
} else {
None
}
})
.collect();
// Configs MUST be sorted in lexicographic order,
// otherwise `ConfigPart::ResetDirs` can occur out of intended order.
// See https://www.freedesktop.org/software/fontconfig/fontconfig-user.html#:~:text=sorted%20in%20lexicographic%20order
config_paths.sort();
for config_path in config_paths {
match self.merge_config(&config_path) {
Ok(_) => {}
#[allow(unused_variables)]
Err(err) => {
#[cfg(feature = "log")]
log::warn!("Failed to merge {}: {}", config_path.display(), err);
}
}
}
}
Ok(())
}
}
macro_rules! define_config_part_from {
($($f:ident,)+) => {
$(
impl From<$f> for ConfigPart {
fn from(v: $f) -> Self {
ConfigPart::$f(v)
}
}
)+
};
}
define_config_part_from! {
SelectFont,
Dir,
CacheDir,
Include,
Match,
Config,
Alias,
RemapDir,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// Final dir data
pub struct DirData {
/// dir path
pub path: PathBuf,
/// 'salt' property affects to determine cache filename. this is useful for example when having different fonts sets on same path at container and share fonts from host on different font path.
pub salt: String,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// Final remap-dirs data
pub struct RemapDirData {
/// dir path will be mapped as the path [`as-path`](Self::as_path) in cached information. This is useful if the directory name is an alias (via a bind mount or symlink) to another directory in the system for which cached font information is likely to exist.
pub path: PathBuf,
/// 'salt' property affects to determine cache filename. this is useful for example when having different fonts sets on same path at container and share fonts from host on different font path.
pub salt: String,
// remapped path
pub as_path: String,
}

View File

@@ -0,0 +1,34 @@
mod edit;
mod test;
pub use self::edit::*;
pub use self::test::*;
#[derive(Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Match {
pub target: MatchTarget,
pub tests: Vec<Test>,
pub edits: Vec<Edit>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum MatchTarget {
Pattern,
Font,
Scan,
}
parse_enum! {
MatchTarget,
(Pattern, "pattern"),
(Font, "font"),
(Scan, "scan"),
}
impl Default for MatchTarget {
fn default() -> Self {
MatchTarget::Pattern
}
}

View File

@@ -0,0 +1,61 @@
use crate::Property;
#[derive(Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Edit {
pub mode: EditMode,
pub binding: EditBinding,
pub value: Property,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum EditBinding {
Strong,
Weak,
Same,
}
parse_enum! {
EditBinding,
(Strong, "strong"),
(Weak, "weak"),
(Same, "same"),
}
impl Default for EditBinding {
fn default() -> Self {
EditBinding::Weak
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum EditMode {
Assign,
AssignReplace,
Prepend,
PrependFirst,
Append,
AppendLast,
Delete,
DeleteAll,
}
parse_enum! {
EditMode,
(Assign, "assign"),
(AssignReplace, "assign_replace"),
(Prepend, "prepend"),
(PrependFirst, "prepend_first"),
(Append, "append"),
(AppendLast, "append_last"),
(Delete, "delete"),
(DeleteAll, "delete_all"),
}
impl Default for EditMode {
fn default() -> Self {
EditMode::Assign
}
}

View File

@@ -0,0 +1,90 @@
use crate::Property;
/// This element contains a single value which is compared with the target ('pattern', 'font', 'scan' or 'default') property "property" (substitute any of the property names seen above).
/// 'compare' can be one of "eq", "not_eq", "less", "less_eq", "more", "more_eq", "contains" or "not_contains".
/// 'qual' may either be the default, "any", in which case the match succeeds if any value associated with the property matches the test value,
/// or "all", in which case all of the values associated with the property must match the test value. 'ignore-blanks' takes a boolean value.
/// if 'ignore-blanks' is set "true", any blanks in the string will be ignored on its comparison. this takes effects only when compare="eq" or compare="not_eq".
/// When used in a <match target="font"> element, the target= attribute in the <test> element selects between matching the original pattern or the font.
/// "default" selects whichever target the outer <match> element has selected.
#[derive(Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Test {
pub qual: TestQual,
pub target: TestTarget,
pub compare: TestCompare,
pub value: Property,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TestTarget {
Default,
Pattern,
Font,
Scan,
}
parse_enum! {
TestTarget,
(Default, "default"),
(Pattern, "pattern"),
(Font, "font"),
(Scan, "scan"),
}
impl Default for TestTarget {
fn default() -> Self {
TestTarget::Default
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TestCompare {
Eq,
NotEq,
Less,
LessEq,
More,
MoreEq,
Contains,
NotContains,
}
parse_enum! {
TestCompare,
(Eq, "eq"),
(NotEq, "not_eq"),
(Less, "less"),
(LessEq, "less_eq"),
(More, "more"),
(MoreEq, "more_eq"),
(Contains, "contains"),
(NotContains, "not_contains"),
}
impl Default for TestCompare {
fn default() -> Self {
TestCompare::Eq
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TestQual {
Any,
All,
}
parse_enum! {
TestQual,
(Any, "any"),
(All, "all"),
}
impl Default for TestQual {
fn default() -> Self {
TestQual::Any
}
}

View File

@@ -0,0 +1,176 @@
use crate::{Expression, Value};
macro_rules! define_property {
(
$(
$(#[$attr:meta])*
$variant:ident($value_ty:ident, $name:expr),
)+
) => {
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Property {
$(
$(#[$attr])*
$variant(Expression),
)+
Dynamic(String, Expression),
}
impl Property {
pub fn kind(&self) -> PropertyKind {
match self {
$(
Property::$variant(_) => PropertyKind::$variant,
)+
Property::Dynamic(s, _) => PropertyKind::Dynamic(s.clone()),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PropertyKind {
$(
$(#[$attr])*
$variant,
)+
Dynamic(String),
}
parse_enum! {
PropertyKind,
$(
($variant, $name),
)+
|s| Ok(PropertyKind::Dynamic(s.into())),
}
impl PropertyKind {
pub fn make_property(self, expr: Expression) -> Property {
match self {
$(
PropertyKind::$variant => Property::$variant(expr),
)+
PropertyKind::Dynamic(name) => Property::Dynamic(name.clone(), expr),
}
}
}
};
}
define_property! {
/// Font family names
Family(String, "family"),
/// Languages corresponding to each family
FamilyLang(String, "familylang"),
/// Font style. Overrides weight and slant
Style(String, "style"),
/// Languages corresponding to each style
StyleLang(String, "stylelang"),
/// Font full names (often includes style)
FullName(String, "fullname"),
/// Languages corresponding to each fullname
FullNameLang(String, "fullnamelang"),
/// Italic, oblique or roman
Slant(Int, "slant"),
/// Light, medium, demibold, bold or black
Weight(Int, "weight"),
/// Point size
Size(Double, "size"),
/// Condensed, normal or expanded
Width(Int, "width"),
/// Stretches glyphs horizontally before hinting
Aspect(Double, "aspect"),
/// Pixel size
PixelSize(Double, "pixelsize"),
/// Proportional, dual-width, monospace or charcell
Spacing(Int, "spacing"),
/// Font foundry name
Foundry(String, "foundry"),
/// Whether glyphs can be antialiased
Antialias(Bool, "antialias"),
/// Whether the rasterizer should use hinting
Hinting(Bool, "hinting"),
/// Automatic hinting style
HintStyle(Int, "hintstyle"),
/// Automatic hinting style
VerticalLayout(Bool, "verticallayout"),
/// Use autohinter instead of normal hinter
AutoHint(Bool, "autohint"),
/// Use font global advance data (deprecated)
GlobalAdvance(Bool, "globaladvance"),
/// The filename holding the font
File(String, "file"),
/// The index of the font within the file
Index(Int, "index"),
// TODO:
// /// Use the specified FreeType face object
// Ftface(FT_Face),
/// Which rasterizer is in use (deprecated)
Rasterizer(String, "rasterizer"),
/// Whether the glyphs are outlines
Outline(Bool, "outline"),
/// Whether glyphs can be scaled
Scalable(Bool, "scalable"),
/// Whether any glyphs have color
Color(Bool, "color"),
/// Scale factor for point->pixel conversions (deprecated)
Scale(Double, "scale"),
/// Target dots per inch
Dpi(Double, "dpi"),
/// unknown, rgb, bgr, vrgb, vbgr, none - subpixel geometry
Rgba(Int, "rgba"),
/// Type of LCD filter
Lcdfilter(Int, "lcdfilter"),
/// Eliminate leading from line spacing
Minspace(Bool, "minspace"),
/// Unicode chars encoded by the font
Charset(CharSet, "charset"),
/// List of RFC-3066-style languages this font supports
Lang(String, "lang"),
/// Version number of the font
Fontversion(Int, "fontversion"),
/// List of layout capabilities in the font
Capability(String, "capability"),
/// String name of the font format
Fontformat(String, "fontformat"),
/// Rasterizer should synthetically embolden the font
Embolden(Bool, "embolden"),
/// Use the embedded bitmap instead of the outline
Embeddedbitmap(Bool, "embeddedbitmap"),
/// Whether the style is a decorative variant
Decorative(Bool, "decorative"),
/// List of the feature tags in OpenType to be enabled
Fontfeatures(String, "fontfeatures"),
/// Language name to be used for the default value of familylang, stylelang, and fullnamelang
Namelang(String, "namelang"),
/// String Name of the running program
Prgname(String, "prgname"),
/// Font family name in PostScript
Postscriptname(String, "postscriptname"),
/// Whether the font has hinting
Fonthashint(Bool, "fonthashint"),
/// Order number of the font
Order(Int, "order"),
// custom
Matrix(Matrix, "matrix"),
PixelSizeFixupFactor(Double, "pixelsizefixupfactor"),
ScalingNotNeeded(Bool, "scalingnotneeded"),
}
impl Default for Property {
fn default() -> Self {
Property::Family(Expression::Simple(Value::String(String::default())))
}
}
impl Default for PropertyKind {
fn default() -> Self {
PropertyKind::Family
}
}

View File

@@ -0,0 +1,15 @@
use crate::Property;
#[derive(Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SelectFont {
pub rejects: Vec<FontMatch>,
pub accepts: Vec<FontMatch>,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum FontMatch {
Glob(String),
Pattern(Vec<Property>),
}

View File

@@ -0,0 +1,188 @@
use crate::{Constant, IntOrRange, PropertyKind};
pub type Bool = bool;
pub type Int = u32;
pub type Double = f64;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ListOp {
Times,
Divide,
Or,
And,
Plus,
Minus,
}
parse_enum! {
ListOp,
(Times, "times"),
(Divide, "divide"),
(Or, "or"),
(And, "and"),
(Plus, "plus"),
(Minus, "minus"),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum UnaryOp {
Not,
// these operator not exists in document page but exists in dtd
Cecil,
Floor,
Round,
Trunc,
}
parse_enum! {
UnaryOp,
(Not, "not"),
(Cecil, "cecil"),
(Floor, "floor"),
(Round, "round"),
(Trunc, "trunc"),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum BinaryOp {
Eq,
NotEq,
Less,
LessEq,
More,
MoreEq,
Contains,
NotContains,
}
parse_enum! {
BinaryOp,
(Eq, "eq"),
(NotEq, "not_eq"),
(Less, "less"),
(LessEq, "less_eq"),
(More, "more"),
(MoreEq, "more_eq"),
(Contains, "contains"),
(NotContains, "not_contains"),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TernaryOp {
If,
}
parse_enum! {
TernaryOp,
(If, "if"),
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Expression {
Simple(Value),
Unary(UnaryOp, Box<Self>),
Binary(BinaryOp, Box<[Self; 2]>),
Ternary(TernaryOp, Box<[Self; 3]>),
List(ListOp, Vec<Self>),
Matrix(Box<[Self; 4]>),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PropertyTarget {
Default,
Font,
Pattern,
}
parse_enum! {
PropertyTarget,
(Default, "default"),
(Font, "font"),
(Pattern, "pattern"),
}
impl Default for PropertyTarget {
fn default() -> Self {
PropertyTarget::Default
}
}
pub type CharSet = Vec<IntOrRange>;
/// Runtime typed fontconfig value
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Value {
/// `<int>0</int>`
Int(Int),
/// `<double>1.5</double>`
Double(Double),
/// `<string>str</string>`
String(String),
/// `<const>hintslight</const>`
Constant(Constant),
/// `<bool>false</bool>`
Bool(Bool),
/// This element holds the two [`Value::Int`] elements of a range representation.
Range(Int, Int),
/// This element holds at least one [`Value::String`] element of a RFC-3066-style languages or more.
LangSet(String),
/// This element holds at least one [`Value::Int`] element of an Unicode code point or more.
CharSet(CharSet),
/// `<name target="font">pixelsize</name>`
Property(PropertyTarget, PropertyKind),
}
macro_rules! from_value {
($($name:ident,)+) => {
$(
impl From<$name> for Value {
fn from(v: $name) -> Value {
Value::$name(v)
}
}
)+
};
}
from_value! {
Int,
Bool,
Double,
Constant,
CharSet,
}
impl<'a> From<&'a str> for Value {
fn from(s: &'a str) -> Self {
Value::String(s.into())
}
}
impl From<String> for Value {
fn from(s: String) -> Self {
Value::String(s)
}
}
impl From<(PropertyTarget, PropertyKind)> for Value {
fn from((target, kind): (PropertyTarget, PropertyKind)) -> Self {
Value::Property(target, kind)
}
}
impl<V> From<V> for Expression
where
Value: From<V>,
{
fn from(v: V) -> Self {
Expression::Simple(Value::from(v))
}
}

92
vendor/fontconfig-parser/src/util.rs vendored Normal file
View File

@@ -0,0 +1,92 @@
macro_rules! try_next {
($iter:expr, $($tt:tt)*) => {
match $iter.next() {
Some(e) => e,
None => return Err(crate::Error::InvalidFormat(format!($($tt)*))),
}
}
}
macro_rules! try_text {
($node:expr) => {
match $node.text() {
Some(t) => t,
None => return Err(crate::Error::InvalidFormat("Can't get text".into())),
}
};
}
macro_rules! parse_attrs_opt {
($node:expr, { $($key:expr => $lvalue:expr,)+ } $(, { $($str_key:expr => $str_lvalue:expr,)+ } )?) => {
for attr in $node.attributes() {
match attr.name() {
$(
$key => $lvalue = attr.value().parse().ok()?,
)+
$(
$(
$str_key => $str_lvalue = attr.value().into(),
)+
)?
_ => {}
}
}
};
}
macro_rules! parse_attrs {
($node:expr, { $($key:expr => $lvalue:expr,)+ } $(, { $($str_key:expr => $str_lvalue:expr,)+ } )?) => {
for attr in $node.attributes() {
match attr.name() {
$(
$key => $lvalue = attr.value().parse()?,
)+
$(
$(
$str_key => $str_lvalue = attr.value().into(),
)+
)?
_ => {}
}
}
};
}
macro_rules! parse_enum {
(
$ty:ty,
$(
($variant:ident, $text:expr),
)+
|$arg:ident| $fallback:expr,
) => {
impl core::str::FromStr for $ty {
type Err = crate::Error;
fn from_str($arg: &str) -> crate::Result<$ty> {
match $arg {
$(
$text => Ok(<$ty>::$variant),
)+
_ => {
$fallback
}
}
}
}
};
(
$ty:ty,
$(
($variant:ident, $text:expr),
)+
) => {
parse_enum! {
$ty,
$(
($variant, $text),
)+
|s| Err(crate::Error::ParseEnumError(core::any::type_name::<$ty>(), s.into())),
}
};
}