[
  {
    "path": ".github/workflows/main.yml",
    "content": "name: Rust\n\non: [push, pull_request]\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  build-linux:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        rust:\n          - 1.60.0\n          - stable\n    steps:\n    - name: Checkout\n      uses: actions/checkout@v3\n\n    - name: Install toolchain\n      uses: actions-rs/toolchain@v1\n      with:\n        profile: minimal\n        toolchain: ${{ matrix.rust }}\n        override: true\n\n    - name: Build without default features\n      run: cargo build --no-default-features\n\n    - name: Build with fs\n      run: cargo build --no-default-features --features fs\n\n    - name: Build with memmap\n      run: cargo build --no-default-features --features memmap\n\n    - name: Build with fontconfig\n      run: cargo build --no-default-features --features fontconfig\n\n    - name: Run tests\n      run: cargo test\n\n  build-windows:\n    runs-on: windows-latest\n    steps:\n    - name: Checkout\n      uses: actions/checkout@v3\n\n    - name: Install toolchain\n      uses: actions-rs/toolchain@v1\n      with:\n        profile: minimal\n        toolchain: stable\n        override: true\n\n    - name: Build without default features\n      run: cargo build --no-default-features\n\n    - name: Build with fs\n      run: cargo build --no-default-features --features fs\n\n    - name: Build with memmap\n      run: cargo build --no-default-features --features memmap\n\n    - name: Run tests\n      run: cargo test\n\n  build-mac:\n    runs-on: macos-latest\n    steps:\n    - name: Checkout\n      uses: actions/checkout@v3\n\n    - name: Install toolchain\n      uses: actions-rs/toolchain@v1\n      with:\n        profile: minimal\n        toolchain: stable\n        override: true\n\n    - name: Build without default features\n      run: cargo build --no-default-features\n\n    - name: Build with fs\n      run: cargo build --no-default-features --features fs\n\n    - name: Build with memmap\n      run: cargo build --no-default-features --features memmap\n\n    - name: Run tests\n      run: cargo test\n"
  },
  {
    "path": ".gitignore",
    "content": "/target\nCargo.lock\n.DS_Store\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Change Log\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](http://keepachangelog.com/)\nand this project adheres to [Semantic Versioning](http://semver.org/).\n\n## [Unreleased]\n\n## [0.23.0] - 2024-10-09\n### Changed\n- `ttf-parser` updated.\n\n## [0.22.0] - 2024-09-09\n### Changed\n- Fallback to known font dirs if none were loaded via fontconfig. Linux-only.\n  [@MoSal](https://github.com/@MoSal)\n\n## [0.21.0] - 2024-08-06\n### Added\n- Symlinked files and directories will now be included when loading system fonts.\n  [@jcdickinson](https://github.com/@jcdickinson)\n\n## [0.20.0] - 2024-07-02\n### Changed\n- `ttf-parser` updated.\n\n## [0.19.0] - 2024-07-02\n### Changed\n- `ttf-parser` updated.\n\n## [0.18.0] - 2024-06-01\n### Changed\n- `Database::push_face_info` returns an `ID` now.\n  [@laurmaedje](https://github.com/@laurmaedje)\n\n## [0.17.0] - 2024-05-10\n### Added\n- Up to 10% faster `Database::load_system_fonts`.\n  [@qarmin](https://github.com/@qarmin) and [@y5](https://github.com/@y5)\n\n### Changed\n- Latest `ttf-parser`.\n\n## [0.16.2] - 2024-02-19\n### Fixed\n- System fonts loading on Windows when the system drive is not `C:\\\\`.\n  [@tronical](https://github.com/@tronical)\n\n## [0.16.1] - 2024-02-09\n### Fixed\n- Treat fonts with non-zero italic angle as italic.\n\n## [0.16.0] - 2023-10-31\n### Changed\n- `ttf-parser` and `memmap2` dependencies update.\n\n## [0.15.0] - 2023-10-01\n### Changed\n- Enable the `fontconfig` feature by default. Linux-only.\n- MSRV bumped to 1.60 due to `log`.\n\n### Fixed\n- Fix fontconfig alias matching order.\n  [@declantsien](https://github.com/@declantsien)\n\n## [0.14.1] - 2023-05-10\n### Fixed\n- Return valid IDs from `Database::load_font_source()`.\n  [@notgull](https://github.com/notgull)\n\n## [0.14.0] - 2023-05-09\n### Changed\n- `Database::load_font_source()` returns a list of loaded face IDs now.\n  [@notgull](https://github.com/notgull)\n- `ttf-parser` and `memmap2` dependencies update.\n\n## [0.13.1] - 2023-04-23\n### Added\n- Load system fonts on RedoxOS. [@FloVanGH](https://github.com/FloVanGH)\n\n### Fixed\n- Improve missing `XDG_CONFIG_HOME` environment variable handling. Linux only.\n  [@7sDream](https://github.com/7sDream)\n- Improve downloadable fonts detection on macOS. [@messense](https://github.com/messense)\n\n## [0.13.0] - 2023-02-21\n### Added\n- `Database::default()`. [@7sDream](https://github.com/7sDream)\n\n### Changed\n- Database uses `slotmap::SlotMap` instead of `Vec` as an internal storage now.\n  This allows us to have O(1) indexing by `ID` by sacrificing faces iteration speed a bit.\n  [@7sDream](https://github.com/7sDream)\n- `Database::remove_face` no longer returns `bool`.\n- `Database::faces` returns an Iterator and not a slice now.\n- MSRV bumped to 1.49\n\n## [0.12.0] - 2023-02-05\n### Fixed\n- Face weight matching.\n\n## [0.11.2] - 2023-01-10\n### Added\n- Implement `Display` trait for `ID`. [@7sDream](https://github.com/7sDream)\n\n## [0.11.1] - 2022-12-26\n### Fixed\n- Always prefer _Typographic Family_ to _Family Name_ when available.\n  [@CryZe](https://github.com/CryZe)\n- Prevent duplicated family names.\n\n## [0.11.0] - 2022-12-25\n### Added\n- Support localized family names.\n- Improve fontconfig support. [@declantsien](https://github.com/declantsien)\n\n### Changed\n- `FaceInfo::family` was replaced with `FaceInfo::families` and contains a list of family\n  names now.\n\n### Fixed\n- Improve family name detection in variable fonts.\n\n## [0.10.0] - 2022-11-08\n### Added\n- `no_std` support. [@jackpot51](https://github.com/jackpot51)\n\n## [0.9.3] - 2022-10-26\n### Added\n- `Database::family_name` is public now.\n\n## [0.9.2] - 2022-10-22\n### Added\n- `Database::push_face_info`\n- `ID::dummy`\n\n### Fixed\n- Expand home path `~` prefix during fontconfig paths resolving.\n  [@snoyer](https://github.com/snoyer)\n\n## [0.9.1] - 2022-02-21\n### Changed\n- Reduce binary size by 10% using less generic code.\n- Simplify Database::query implementation.\n\n## [0.9.0] - 2022-02-20\n### Added\n- Way faster fonts scanning by using a more low-level `ttf-parser` API\n  which allows us to parse only required TrueType tables.\n  On my hardware, `load_system_fonts()` loaded 898 fonts in 9ms instead of 11ms in the release mode\n  and in 35ms instead of 52ms in debug.\n  Currently, we're parsing only `name`, `OS/2` and `post` tables.\n\n## [0.8.0] - 2022-02-12\n### Added\n- Load user fonts on Windows.\n- `fontconfig` feature to allow retrieving font dirs from the fontconfig config file\n  instead of using hardcoded paths. Linux-only. [@Riey](https://github.com/Riey)\n\n## [0.7.0] - 2021-10-04\n### Changed\n- The `Source` enum has a new variant `SharedFile`, used for unsafe persistent\n  memory mappings.\n- `FaceInfo` stores `Source` directly now, not anymore in an `Arc`. Instead `Source::Binary`\n  now stores an `Arc` of the data.\n\n## [0.6.2] - 2021-09-04\n### Fixed\n- Fix compilation without the `fs` feature.\n\n## [0.6.1] - 2021-09-04\n### Changed\n- Split the `fs` build feature into `fs` and `memmap`. [@neinseg](https://github.com/neinseg)\n\n## [0.6.0] - 2021-08-21\n### Added\n- Search in `$HOME/.fonts` on Linux. [@Linus789](https://github.com/Linus789)\n\n### Changed\n- Generic font families are preset by default instead of being set to `None`.\n\n## [0.5.4] - 2021-05-25\n### Added\n- Implement `Eq`, `Hash` for `Query`, `Family`, `Weight` and `Style`.\n  [@dhardy](https://github.com/dhardy)\n\n### Changed\n- Update `ttf-parser`\n\n## [0.5.3] - 2021-05-19\n### Changed\n- Update `ttf-parser`\n\n## [0.5.2] - 2021-05-19\n### Changed\n- Update `memmap2`\n- Add additional search dir for macOS.\n\n## [0.5.1] - 2020-12-20\n### Fixed\n- Compilation on Windows.\n\n## [0.5.0] - 2020-12-20\n### Added\n- `FaceInfo::post_script_name`\n- `FaceInfo::monospaced`\n- `Database::load_system_fonts`\n\n## [0.4.0] - 2020-12-06\n### Changed\n- Use a simple `u32` for ID instead of UUID.\n\n## [0.3.0] - 2020-12-05\n### Changed\n- `ttf-parser` updated.\n\n## [0.2.0] - 2020-07-21\n### Changed\n- `ttf-parser` updated.\n\n### Fixed\n- Stretch processing. `ttf-parser` was incorrectly parsing this property.\n\n[Unreleased]: https://github.com/RazrFalcon/fontdb/compare/v0.23.0...HEAD\n[0.23.0]: https://github.com/RazrFalcon/fontdb/compare/v0.22.0...v0.23.0\n[0.22.0]: https://github.com/RazrFalcon/fontdb/compare/v0.21.0...v0.22.0\n[0.21.0]: https://github.com/RazrFalcon/fontdb/compare/v0.20.0...v0.21.0\n[0.20.0]: https://github.com/RazrFalcon/fontdb/compare/v0.19.0...v0.20.0\n[0.19.0]: https://github.com/RazrFalcon/fontdb/compare/v0.18.0...v0.19.0\n[0.18.0]: https://github.com/RazrFalcon/fontdb/compare/v0.17.0...v0.18.0\n[0.17.0]: https://github.com/RazrFalcon/fontdb/compare/v0.16.2...v0.17.0\n[0.16.2]: https://github.com/RazrFalcon/fontdb/compare/v0.16.1...v0.16.2\n[0.16.1]: https://github.com/RazrFalcon/fontdb/compare/v0.16.0...v0.16.1\n[0.16.0]: https://github.com/RazrFalcon/fontdb/compare/v0.15.0...v0.16.0\n[0.15.0]: https://github.com/RazrFalcon/fontdb/compare/v0.14.1...v0.15.0\n[0.14.1]: https://github.com/RazrFalcon/fontdb/compare/v0.14.0...v0.14.1\n[0.14.0]: https://github.com/RazrFalcon/fontdb/compare/v0.13.1...v0.14.0\n[0.13.1]: https://github.com/RazrFalcon/fontdb/compare/v0.13.0...v0.13.1\n[0.13.0]: https://github.com/RazrFalcon/fontdb/compare/v0.12.0...v0.13.0\n[0.12.0]: https://github.com/RazrFalcon/fontdb/compare/v0.11.2...v0.12.0\n[0.11.2]: https://github.com/RazrFalcon/fontdb/compare/v0.11.1...v0.11.2\n[0.11.1]: https://github.com/RazrFalcon/fontdb/compare/v0.11.0...v0.11.1\n[0.11.0]: https://github.com/RazrFalcon/fontdb/compare/v0.10.0...v0.11.0\n[0.10.0]: https://github.com/RazrFalcon/fontdb/compare/v0.9.3...v0.10.0\n[0.9.3]: https://github.com/RazrFalcon/fontdb/compare/v0.9.2...v0.9.3\n[0.9.2]: https://github.com/RazrFalcon/fontdb/compare/v0.9.1...v0.9.2\n[0.9.1]: https://github.com/RazrFalcon/fontdb/compare/v0.9.0...v0.9.1\n[0.9.0]: https://github.com/RazrFalcon/fontdb/compare/v0.8.0...v0.9.0\n[0.8.0]: https://github.com/RazrFalcon/fontdb/compare/v0.7.0...v0.8.0\n[0.7.0]: https://github.com/RazrFalcon/fontdb/compare/v0.6.2...v0.7.0\n[0.6.2]: https://github.com/RazrFalcon/fontdb/compare/v0.6.1...v0.6.2\n[0.6.1]: https://github.com/RazrFalcon/fontdb/compare/v0.6.0...v0.6.1\n[0.6.0]: https://github.com/RazrFalcon/fontdb/compare/v0.5.4...v0.6.0\n[0.5.4]: https://github.com/RazrFalcon/fontdb/compare/v0.5.3...v0.5.4\n[0.5.3]: https://github.com/RazrFalcon/fontdb/compare/v0.5.2...v0.5.3\n[0.5.2]: https://github.com/RazrFalcon/fontdb/compare/v0.5.1...v0.5.2\n[0.5.1]: https://github.com/RazrFalcon/fontdb/compare/v0.5.0...v0.5.1\n[0.5.0]: https://github.com/RazrFalcon/fontdb/compare/v0.4.0...v0.5.0\n[0.4.0]: https://github.com/RazrFalcon/fontdb/compare/v0.3.0...v0.4.0\n[0.3.0]: https://github.com/RazrFalcon/fontdb/compare/v0.2.0...v0.3.0\n[0.2.0]: https://github.com/RazrFalcon/fontdb/compare/v0.1.0...v0.2.0\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"fontdb\"\nversion = \"0.23.0\"\nauthors = [\"Yevhenii Reizner <razrfalcon@gmail.com>\"]\nedition = \"2018\"\ndescription = \"A simple, in-memory font database with CSS-like queries.\"\ndocumentation = \"https://docs.rs/fontdb/\"\nreadme = \"README.md\"\nrepository = \"https://github.com/RazrFalcon/fontdb\"\nlicense = \"MIT\"\nkeywords = [\"font\", \"db\", \"css\", \"truetype\", \"ttf\"]\ncategories = [\"text-processing\"]\nrust-version = \"1.60\"\n\n[dependencies]\nlog = \"0.4\"\nmemmap2 = { version = \"0.9\", optional = true }\nslotmap = { version = \"1.0.6\", default-features = false }\ntinyvec = { version = \"1.6.0\", features = [\"alloc\"] }\n\n[dependencies.ttf-parser]\nversion = \"0.25\"\ndefault-features = false\nfeatures = [\"opentype-layout\", \"apple-layout\", \"variable-fonts\", \"glyph-names\", \"no-std-float\"]\n\n[target.'cfg(all(unix, not(any(target_os = \"macos\", target_os = \"android\"))))'.dependencies]\nfontconfig-parser = { version = \"0.5\", optional = true, default-features = false }\n\n[dev-dependencies]\nenv_logger = { version = \"0.10\", default-features = false }\n\n[features]\ndefault = [\"std\", \"fs\", \"memmap\", \"fontconfig\"]\nstd = [\"ttf-parser/std\"]\n# Allows local filesystem interactions.\nfs = [\"std\"]\n# Allows font files memory mapping, greatly improves performance.\nmemmap = [\"fs\", \"memmap2\"]\n# Enables minimal fontconfig support on Linux.\n# Must be enabled for NixOS, otherwise no fonts will be loaded.\nfontconfig = [\"fontconfig-parser\", \"fs\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2020 Yevhenii Reizner\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# fontdb\n[![Build Status](https://github.com/RazrFalcon/fontdb/actions/workflows/main.yml/badge.svg)](https://github.com/RazrFalcon/fontdb/actions/workflows/main.yml)\n[![Crates.io](https://img.shields.io/crates/v/fontdb.svg)](https://crates.io/crates/fontdb)\n[![Documentation](https://docs.rs/fontdb/badge.svg)](https://docs.rs/fontdb)\n[![Rust 1.60+](https://img.shields.io/badge/rust-1.60+-orange.svg)](https://www.rust-lang.org)\n\n`fontdb` is a simple, in-memory font database with CSS-like queries.\n\n## Features\n\n- The database can load fonts from files, directories and raw data (`Vec<u8>`).\n- The database can match a font using CSS-like queries. See `Database::query`.\n- The database can try to load system fonts.\n  Currently, this is implemented by scanning predefined directories.\n  The library does not interact with the system API.\n- Provides a unique ID for each font face.\n\n## Non-goals\n\n- Advanced font properties querying.<br>\n  The database provides only storage and matching capabilities.\n  For font properties querying you can use [ttf-parser].\n\n- A font fallback mechanism.<br>\n  This library can be used to implement a font fallback mechanism, but it doesn't implement one.\n\n- Application's global database.<br>\n  The database doesn't use `static`, therefore it's up to the caller where it should be stored.\n\n- Font types support other than TrueType.\n\n## Font vs Face\n\nA font is a collection of font faces. Therefore, a font face is a subset of a font.\nA simple font (\\*.ttf/\\*.otf) usually contains a single font face,\nbut a font collection (\\*.ttc) can contain multiple font faces.\n\n`fontdb` stores and matches font faces, not fonts.\nTherefore, after loading a font collection with 5 faces (for example), the database will be\npopulated with 5 `FaceInfo` objects, all of which will be pointing to the same file or binary data.\n\n## Performance\n\nThe database performance is largely limited by the storage itself.\nWe are using [ttf-parser], so the parsing should not be a bottleneck.\n\nFor example, on Mac Book Pro 14 with M1 Pro, it takes just ~24ms to load 1361 font faces.\n\n## Safety\n\nThe library relies on memory-mapped files, which is inherently unsafe.\nBut since we do not keep the files open it should be perfectly safe.\n\nIf you would like to use a persistent memory mapping of the font files,\nthen you can use the unsafe `Database::make_shared_face_data` function.\n\n## License\n\nMIT\n\n[ttf-parser]: https://github.com/RazrFalcon/ttf-parser\n"
  },
  {
    "path": "examples/find-system-font.rs",
    "content": "#[cfg(not(feature = \"fs\"))]\nfn main() {}\n\n#[cfg(feature = \"fs\")]\nfn main() {\n    std::env::set_var(\"RUST_LOG\", \"fontdb=trace\");\n    env_logger::init();\n\n    let mut db = fontdb::Database::new();\n    let now = std::time::Instant::now();\n    db.load_system_fonts();\n    db.set_serif_family(\"Times New Roman\");\n    db.set_sans_serif_family(\"Arial\");\n    db.set_cursive_family(\"Comic Sans MS\");\n    db.set_fantasy_family(\"Impact\");\n    db.set_monospace_family(\"Courier New\");\n    println!(\n        \"Loaded {} font faces in {}ms.\",\n        db.len(),\n        now.elapsed().as_millis()\n    );\n\n    const FAMILY_NAME: &str = \"Times New Roman\";\n    let query = fontdb::Query {\n        families: &[fontdb::Family::Name(FAMILY_NAME), fontdb::Family::SansSerif],\n        weight: fontdb::Weight::BOLD,\n        ..fontdb::Query::default()\n    };\n\n    let now = std::time::Instant::now();\n    match db.query(&query) {\n        Some(id) => {\n            let (src, index) = db.face_source(id).unwrap();\n            if let fontdb::Source::File(ref path) = &src {\n                println!(\n                    \"Font '{}':{} found in {}ms.\",\n                    path.display(),\n                    index,\n                    now.elapsed().as_micros() as f64 / 1000.0\n                );\n            }\n        }\n        None => {\n            println!(\"Error: '{}' not found.\", FAMILY_NAME);\n        }\n    }\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "/*!\n`fontdb` is a simple, in-memory font database with CSS-like queries.\n\n# Features\n\n- The database can load fonts from files, directories and raw data (`Vec<u8>`).\n- The database can match a font using CSS-like queries. See `Database::query`.\n- The database can try to load system fonts.\n  Currently, this is implemented by scanning predefined directories.\n  The library does not interact with the system API.\n- Provides a unique ID for each font face.\n\n# Non-goals\n\n- Advanced font properties querying.<br>\n  The database provides only storage and matching capabilities.\n  For font properties querying you can use [ttf-parser].\n\n- A font fallback mechanism.<br>\n  This library can be used to implement a font fallback mechanism, but it doesn't implement one.\n\n- Application's global database.<br>\n  The database doesn't use `static`, therefore it's up to the caller where it should be stored.\n\n- Font types support other than TrueType.\n\n# Font vs Face\n\nA font is a collection of font faces. Therefore, a font face is a subset of a font.\nA simple font (\\*.ttf/\\*.otf) usually contains a single font face,\nbut a font collection (\\*.ttc) can contain multiple font faces.\n\n`fontdb` stores and matches font faces, not fonts.\nTherefore, after loading a font collection with 5 faces (for example), the database will be populated\nwith 5 `FaceInfo` objects, all of which will be pointing to the same file or binary data.\n\n# Performance\n\nThe database performance is largely limited by the storage itself.\nWe are using [ttf-parser], so the parsing should not be a bottleneck.\n\nOn my machine with Samsung SSD 860 and Gentoo Linux, it takes ~20ms\nto load 1906 font faces (most of them are from Google Noto collection)\nwith a hot disk cache and ~860ms with a cold one.\n\nOn Mac Mini M1 it takes just 9ms to load 898 fonts.\n\n# Safety\n\nThe library relies on memory-mapped files, which is inherently unsafe.\nBut since we do not keep the files open it should be perfectly safe.\n\nIf you would like to use a persistent memory mapping of the font files,\nthen you can use the unsafe [`Database::make_shared_face_data`] function.\n\n[ttf-parser]: https://github.com/RazrFalcon/ttf-parser\n*/\n\n#![cfg_attr(not(feature = \"std\"), no_std)]\n#![warn(missing_docs)]\n#![warn(missing_debug_implementations)]\n#![warn(missing_copy_implementations)]\n\nextern crate alloc;\n\n#[cfg(not(feature = \"std\"))]\nuse alloc::{\n    string::{String, ToString},\n    vec::Vec,\n};\n\npub use ttf_parser::Language;\npub use ttf_parser::Width as Stretch;\n\nuse slotmap::SlotMap;\nuse tinyvec::TinyVec;\n\n/// A unique per database face ID.\n///\n/// Since `Database` is not global/unique, we cannot guarantee that a specific ID\n/// is actually from the same db instance. This is up to the caller.\n///\n/// ID overflow will cause a panic, but it's highly unlikely that someone would\n/// load more than 4 billion font faces.\n///\n/// Because the internal representation of ID is private, The `Display` trait\n/// implementation for this type only promise that unequal IDs will be displayed\n/// as different strings, but does not make any guarantees about format or\n/// content of the strings.\n///\n/// [`KeyData`]: https://docs.rs/slotmap/latest/slotmap/struct.KeyData.html\n#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, Debug, Default)]\npub struct ID(InnerId);\n\nslotmap::new_key_type! {\n    /// Internal ID type.\n    struct InnerId;\n}\n\nimpl ID {\n    /// Creates a dummy ID.\n    ///\n    /// Should be used in tandem with [`Database::push_face_info`].\n    #[inline]\n    pub fn dummy() -> Self {\n        Self(InnerId::from(slotmap::KeyData::from_ffi(core::u64::MAX)))\n    }\n}\n\nimpl core::fmt::Display for ID {\n    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n        write!(f, \"{}\", (self.0).0.as_ffi())\n    }\n}\n\n/// A list of possible font loading errors.\n#[derive(Debug)]\nenum LoadError {\n    /// A malformed font.\n    ///\n    /// Typically means that [ttf-parser](https://github.com/RazrFalcon/ttf-parser)\n    /// wasn't able to parse it.\n    MalformedFont,\n    /// A valid TrueType font without a valid *Family Name*.\n    UnnamedFont,\n    /// A file IO related error.\n    #[cfg(feature = \"std\")]\n    IoError(std::io::Error),\n}\n\n#[cfg(feature = \"std\")]\nimpl From<std::io::Error> for LoadError {\n    #[inline]\n    fn from(e: std::io::Error) -> Self {\n        LoadError::IoError(e)\n    }\n}\n\nimpl core::fmt::Display for LoadError {\n    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n        match self {\n            LoadError::MalformedFont => write!(f, \"malformed font\"),\n            LoadError::UnnamedFont => write!(f, \"font doesn't have a family name\"),\n            #[cfg(feature = \"std\")]\n            LoadError::IoError(ref e) => write!(f, \"{}\", e),\n        }\n    }\n}\n\n/// A font database.\n#[derive(Clone, Debug)]\npub struct Database {\n    faces: SlotMap<InnerId, FaceInfo>,\n    family_serif: String,\n    family_sans_serif: String,\n    family_cursive: String,\n    family_fantasy: String,\n    family_monospace: String,\n}\n\nimpl Default for Database {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Database {\n    /// Create a new, empty `Database`.\n    ///\n    /// Generic font families would be set to:\n    ///\n    /// - `serif` - Times New Roman\n    /// - `sans-serif` - Arial\n    /// - `cursive` - Comic Sans MS\n    /// - `fantasy` - Impact (Papyrus on macOS)\n    /// - `monospace` - Courier New\n    #[inline]\n    pub fn new() -> Self {\n        Database {\n            faces: SlotMap::with_key(),\n            family_serif: \"Times New Roman\".to_string(),\n            family_sans_serif: \"Arial\".to_string(),\n            family_cursive: \"Comic Sans MS\".to_string(),\n            #[cfg(not(target_os = \"macos\"))]\n            family_fantasy: \"Impact\".to_string(),\n            #[cfg(target_os = \"macos\")]\n            family_fantasy: \"Papyrus\".to_string(),\n            family_monospace: \"Courier New\".to_string(),\n        }\n    }\n\n    /// Loads a font data into the `Database`.\n    ///\n    /// Will load all font faces in case of a font collection.\n    pub fn load_font_data(&mut self, data: Vec<u8>) {\n        self.load_font_source(Source::Binary(alloc::sync::Arc::new(data)));\n    }\n\n    /// Loads a font from the given source into the `Database` and returns\n    /// the ID of the loaded font.\n    ///\n    /// Will load all font faces in case of a font collection.\n    pub fn load_font_source(&mut self, source: Source) -> TinyVec<[ID; 8]> {\n        let ids = source.with_data(|data| {\n            let n = ttf_parser::fonts_in_collection(data).unwrap_or(1);\n            let mut ids = TinyVec::with_capacity(n as usize);\n\n            for index in 0..n {\n                match parse_face_info(source.clone(), data, index) {\n                    Ok(mut info) => {\n                        let id = self.faces.insert_with_key(|k| {\n                            info.id = ID(k);\n                            info\n                        });\n                        ids.push(ID(id));\n                    }\n                    Err(e) => log::warn!(\n                        \"Failed to load a font face {} from source cause {}.\",\n                        index,\n                        e\n                    ),\n                }\n            }\n\n            ids\n        });\n\n        ids.unwrap_or_default()\n    }\n\n    /// Backend function used by load_font_file to load font files.\n    #[cfg(feature = \"fs\")]\n    fn load_fonts_from_file(&mut self, path: &std::path::Path, data: &[u8]) {\n        let source = Source::File(path.into());\n\n        let n = ttf_parser::fonts_in_collection(data).unwrap_or(1);\n        for index in 0..n {\n            match parse_face_info(source.clone(), data, index) {\n                Ok(info) => {\n                    self.push_face_info(info);\n                }\n                Err(e) => {\n                    log::warn!(\n                        \"Failed to load a font face {} from '{}' cause {}.\",\n                        index,\n                        path.display(),\n                        e\n                    )\n                }\n            }\n        }\n    }\n\n    /// Loads a font file into the `Database`.\n    ///\n    /// Will load all font faces in case of a font collection.\n    #[cfg(all(feature = \"fs\", feature = \"memmap\"))]\n    pub fn load_font_file<P: AsRef<std::path::Path>>(\n        &mut self,\n        path: P,\n    ) -> Result<(), std::io::Error> {\n        self.load_font_file_impl(path.as_ref())\n    }\n\n    // A non-generic version.\n    #[cfg(all(feature = \"fs\", feature = \"memmap\"))]\n    fn load_font_file_impl(&mut self, path: &std::path::Path) -> Result<(), std::io::Error> {\n        let file = std::fs::File::open(path)?;\n        let data: &[u8] = unsafe { &memmap2::MmapOptions::new().map(&file)? };\n\n        self.load_fonts_from_file(path, data);\n        Ok(())\n    }\n\n    /// Loads a font file into the `Database`.\n    ///\n    /// Will load all font faces in case of a font collection.\n    #[cfg(all(feature = \"fs\", not(feature = \"memmap\")))]\n    pub fn load_font_file<P: AsRef<std::path::Path>>(\n        &mut self,\n        path: P,\n    ) -> Result<(), std::io::Error> {\n        self.load_font_file_impl(path.as_ref())\n    }\n\n    // A non-generic version.\n    #[cfg(all(feature = \"fs\", not(feature = \"memmap\")))]\n    fn load_font_file_impl(&mut self, path: &std::path::Path) -> Result<(), std::io::Error> {\n        let data = std::fs::read(path)?;\n\n        self.load_fonts_from_file(path, &data);\n        Ok(())\n    }\n\n    /// Loads font files from the selected directory into the `Database`.\n    ///\n    /// This method will scan directories recursively.\n    ///\n    /// Will load `ttf`, `otf`, `ttc` and `otc` fonts.\n    ///\n    /// Unlike other `load_*` methods, this one doesn't return an error.\n    /// It will simply skip malformed fonts and will print a warning into the log for each of them.\n    #[cfg(feature = \"fs\")]\n    pub fn load_fonts_dir<P: AsRef<std::path::Path>>(&mut self, dir: P) {\n        self.load_fonts_dir_impl(dir.as_ref(), &mut Default::default())\n    }\n\n    #[cfg(feature = \"fs\")]\n    fn canonicalize(\n        &self,\n        path: std::path::PathBuf,\n        entry: std::fs::DirEntry,\n        seen: &mut std::collections::HashSet<std::path::PathBuf>,\n    ) -> Option<(std::path::PathBuf, std::fs::FileType)> {\n        let file_type = entry.file_type().ok()?;\n        if !file_type.is_symlink() {\n            if !seen.is_empty() {\n                if seen.contains(&path) {\n                    return None;\n                }\n                seen.insert(path.clone());\n            }\n\n            return Some((path, file_type));\n        }\n\n        if seen.is_empty() && file_type.is_dir() {\n            seen.reserve(8192 / std::mem::size_of::<std::path::PathBuf>());\n\n            for (_, info) in self.faces.iter() {\n                let path = match &info.source {\n                    Source::Binary(_) => continue,\n                    Source::File(path) => path.to_path_buf(),\n                    #[cfg(feature = \"memmap\")]\n                    Source::SharedFile(path, _) => path.to_path_buf(),\n                };\n                seen.insert(path);\n            }\n        }\n\n        let stat = std::fs::metadata(&path).ok()?;\n        if stat.is_symlink() {\n            return None;\n        }\n\n        let canon = std::fs::canonicalize(path).ok()?;\n        if seen.contains(&canon) {\n            return None;\n        }\n        seen.insert(canon.clone());\n        Some((canon, stat.file_type()))\n    }\n\n    // A non-generic version.\n    #[cfg(feature = \"fs\")]\n    fn load_fonts_dir_impl(\n        &mut self,\n        dir: &std::path::Path,\n        seen: &mut std::collections::HashSet<std::path::PathBuf>,\n    ) {\n        let fonts_dir = match std::fs::read_dir(dir) {\n            Ok(dir) => dir,\n            Err(_) => return,\n        };\n\n        for entry in fonts_dir.flatten() {\n            let (path, file_type) = match self.canonicalize(entry.path(), entry, seen) {\n                Some(v) => v,\n                None => continue,\n            };\n\n            if file_type.is_file() {\n                match path.extension().and_then(|e| e.to_str()) {\n                    #[rustfmt::skip] // keep extensions match as is\n                    Some(\"ttf\") | Some(\"ttc\") | Some(\"TTF\") | Some(\"TTC\") |\n                    Some(\"otf\") | Some(\"otc\") | Some(\"OTF\") | Some(\"OTC\") => {\n                        if let Err(e) = self.load_font_file(&path) {\n                            log::warn!(\"Failed to load '{}' cause {}.\", path.display(), e);\n                        }\n                    },\n                    _ => {}\n                }\n            } else if file_type.is_dir() {\n                self.load_fonts_dir_impl(&path, seen);\n            }\n        }\n    }\n\n    /// Attempts to load system fonts.\n    ///\n    /// Supports Windows, Linux and macOS.\n    ///\n    /// System fonts loading is a surprisingly complicated task,\n    /// mostly unsolvable without interacting with system libraries.\n    /// And since `fontdb` tries to be small and portable, this method\n    /// will simply scan some predefined directories.\n    /// Which means that fonts that are not in those directories must\n    /// be added manually.\n    #[cfg(feature = \"fs\")]\n    pub fn load_system_fonts(&mut self) {\n        #[cfg(target_os = \"windows\")]\n        {\n            let mut seen = Default::default();\n            if let Some(ref system_root) = std::env::var_os(\"SYSTEMROOT\") {\n                let system_root_path = std::path::Path::new(system_root);\n                self.load_fonts_dir_impl(&system_root_path.join(\"Fonts\"), &mut seen);\n            } else {\n                self.load_fonts_dir_impl(\"C:\\\\Windows\\\\Fonts\\\\\".as_ref(), &mut seen);\n            }\n\n            if let Ok(ref home) = std::env::var(\"USERPROFILE\") {\n                let home_path = std::path::Path::new(home);\n                self.load_fonts_dir_impl(\n                    &home_path.join(\"AppData\\\\Local\\\\Microsoft\\\\Windows\\\\Fonts\"),\n                    &mut seen,\n                );\n                self.load_fonts_dir_impl(\n                    &home_path.join(\"AppData\\\\Roaming\\\\Microsoft\\\\Windows\\\\Fonts\"),\n                    &mut seen,\n                );\n            }\n        }\n\n        #[cfg(target_os = \"macos\")]\n        {\n            let mut seen = Default::default();\n            self.load_fonts_dir_impl(\"/Library/Fonts\".as_ref(), &mut seen);\n            self.load_fonts_dir_impl(\"/System/Library/Fonts\".as_ref(), &mut seen);\n            // Downloadable fonts, location varies on major macOS releases\n            if let Ok(dir) = std::fs::read_dir(\"/System/Library/AssetsV2\") {\n                for entry in dir {\n                    let entry = match entry {\n                        Ok(entry) => entry,\n                        Err(_) => continue,\n                    };\n                    if entry\n                        .file_name()\n                        .to_string_lossy()\n                        .starts_with(\"com_apple_MobileAsset_Font\")\n                    {\n                        self.load_fonts_dir_impl(&entry.path(), &mut seen);\n                    }\n                }\n            }\n            self.load_fonts_dir_impl(\"/Network/Library/Fonts\".as_ref(), &mut seen);\n\n            if let Ok(ref home) = std::env::var(\"HOME\") {\n                let home_path = std::path::Path::new(home);\n                self.load_fonts_dir_impl(&home_path.join(\"Library/Fonts\"), &mut seen);\n            }\n        }\n\n        // Redox OS.\n        #[cfg(target_os = \"redox\")]\n        {\n            let mut seen = Default::default();\n            self.load_fonts_dir_impl(\"/ui/fonts\".as_ref(), &mut seen);\n        }\n\n        // Linux.\n        #[cfg(all(unix, not(any(target_os = \"macos\", target_os = \"android\"))))]\n        {\n            #[cfg(feature = \"fontconfig\")]\n            {\n                if !self.load_fontconfig() {\n                    log::warn!(\"Fallback to loading from known font dir paths.\");\n                    self.load_no_fontconfig();\n                }\n            }\n\n            #[cfg(not(feature = \"fontconfig\"))]\n            {\n                self.load_no_fontconfig();\n            }\n        }\n    }\n\n\n    // Linux.\n    #[cfg(all(\n        unix,\n        feature = \"fs\",\n        not(any(target_os = \"macos\", target_os = \"android\"))\n    ))]\n    fn load_no_fontconfig(&mut self) {\n        let mut seen = Default::default();\n        self.load_fonts_dir_impl(\"/usr/share/fonts/\".as_ref(), &mut seen);\n        self.load_fonts_dir_impl(\"/usr/local/share/fonts/\".as_ref(), &mut seen);\n\n        if let Ok(ref home) = std::env::var(\"HOME\") {\n            let home_path = std::path::Path::new(home);\n            self.load_fonts_dir_impl(&home_path.join(\".fonts\"), &mut seen);\n            self.load_fonts_dir_impl(&home_path.join(\".local/share/fonts\"), &mut seen);\n        }\n    }\n\n    // Linux.\n    #[cfg(all(\n        unix,\n        feature = \"fontconfig\",\n        not(any(target_os = \"macos\", target_os = \"android\"))\n    ))]\n    fn load_fontconfig(&mut self) -> bool {\n        use std::path::Path;\n\n        let mut fontconfig = fontconfig_parser::FontConfig::default();\n        let home = std::env::var(\"HOME\");\n\n        if let Ok(ref config_file) = std::env::var(\"FONTCONFIG_FILE\") {\n            let _ = fontconfig.merge_config(Path::new(config_file));\n        } else {\n            let xdg_config_home = if let Ok(val) = std::env::var(\"XDG_CONFIG_HOME\") {\n                Some(val.into())\n            } else if let Ok(ref home) = home {\n                // according to https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html\n                // $XDG_CONFIG_HOME should default to $HOME/.config if not set\n                Some(Path::new(home).join(\".config\"))\n            } else {\n                None\n            };\n\n            let read_global = match xdg_config_home {\n                Some(p) => fontconfig\n                    .merge_config(&p.join(\"fontconfig/fonts.conf\"))\n                    .is_err(),\n                None => true,\n            };\n\n            if read_global {\n                let _ = fontconfig.merge_config(Path::new(\"/etc/fonts/local.conf\"));\n            }\n            let _ = fontconfig.merge_config(Path::new(\"/etc/fonts/fonts.conf\"));\n        }\n\n        for fontconfig_parser::Alias {\n            alias,\n            default,\n            prefer,\n            accept,\n        } in fontconfig.aliases\n        {\n            let name = prefer\n                .get(0)\n                .or_else(|| accept.get(0))\n                .or_else(|| default.get(0));\n\n            if let Some(name) = name {\n                match alias.to_lowercase().as_str() {\n                    \"serif\" => self.set_serif_family(name),\n                    \"sans-serif\" => self.set_sans_serif_family(name),\n                    \"sans serif\" => self.set_sans_serif_family(name),\n                    \"monospace\" => self.set_monospace_family(name),\n                    \"cursive\" => self.set_cursive_family(name),\n                    \"fantasy\" => self.set_fantasy_family(name),\n                    _ => {}\n                }\n            }\n        }\n\n        if fontconfig.dirs.is_empty() {\n            return false;\n        }\n\n        let mut seen = Default::default();\n        for dir in fontconfig.dirs {\n            let path = if dir.path.starts_with(\"~\") {\n                if let Ok(ref home) = home {\n                    Path::new(home).join(dir.path.strip_prefix(\"~\").unwrap())\n                } else {\n                    continue;\n                }\n            } else {\n                dir.path\n            };\n            self.load_fonts_dir_impl(&path, &mut seen);\n        }\n\n        true\n    }\n\n    /// Pushes a user-provided `FaceInfo` to the database.\n    ///\n    /// In some cases, a caller might want to ignore the font's metadata and provide their own.\n    /// This method doesn't parse the `source` font.\n    ///\n    /// The `id` field should be set to [`ID::dummy()`] and will be then overwritten by this method.\n    pub fn push_face_info(&mut self, mut info: FaceInfo) -> ID {\n        ID(self.faces.insert_with_key(|k| {\n            info.id = ID(k);\n            info\n        }))\n    }\n\n    /// Removes a font face by `id` from the database.\n    ///\n    /// Returns `false` while attempting to remove a non-existing font face.\n    ///\n    /// Useful when you want to ignore some specific font face(s)\n    /// after loading a large directory with fonts.\n    /// Or a specific face from a font.\n    pub fn remove_face(&mut self, id: ID) {\n        self.faces.remove(id.0);\n    }\n\n    /// Returns `true` if the `Database` contains no font faces.\n    #[inline]\n    pub fn is_empty(&self) -> bool {\n        self.faces.is_empty()\n    }\n\n    /// Returns the number of font faces in the `Database`.\n    ///\n    /// Note that `Database` stores font faces, not fonts.\n    /// For example, if a caller will try to load a font collection (`*.ttc`) that contains 5 faces,\n    /// then the `Database` will load 5 font faces and this method will return 5, not 1.\n    #[inline]\n    pub fn len(&self) -> usize {\n        self.faces.len()\n    }\n\n    /// Sets the family that will be used by `Family::Serif`.\n    pub fn set_serif_family<S: Into<String>>(&mut self, family: S) {\n        self.family_serif = family.into();\n    }\n\n    /// Sets the family that will be used by `Family::SansSerif`.\n    pub fn set_sans_serif_family<S: Into<String>>(&mut self, family: S) {\n        self.family_sans_serif = family.into();\n    }\n\n    /// Sets the family that will be used by `Family::Cursive`.\n    pub fn set_cursive_family<S: Into<String>>(&mut self, family: S) {\n        self.family_cursive = family.into();\n    }\n\n    /// Sets the family that will be used by `Family::Fantasy`.\n    pub fn set_fantasy_family<S: Into<String>>(&mut self, family: S) {\n        self.family_fantasy = family.into();\n    }\n\n    /// Sets the family that will be used by `Family::Monospace`.\n    pub fn set_monospace_family<S: Into<String>>(&mut self, family: S) {\n        self.family_monospace = family.into();\n    }\n\n    /// Returns the generic family name or the `Family::Name` itself.\n    ///\n    /// Generic family names should be set via `Database::set_*_family` methods.\n    pub fn family_name<'a>(&'a self, family: &'a Family) -> &'a str {\n        match family {\n            Family::Name(name) => name,\n            Family::Serif => self.family_serif.as_str(),\n            Family::SansSerif => self.family_sans_serif.as_str(),\n            Family::Cursive => self.family_cursive.as_str(),\n            Family::Fantasy => self.family_fantasy.as_str(),\n            Family::Monospace => self.family_monospace.as_str(),\n        }\n    }\n\n    /// Performs a CSS-like query and returns the best matched font face.\n    pub fn query(&self, query: &Query) -> Option<ID> {\n        for family in query.families {\n            let name = self.family_name(family);\n            let candidates: Vec<_> = self\n                .faces\n                .iter()\n                .filter(|(_, face)| face.families.iter().any(|family| family.0 == name))\n                .map(|(_, info)| info)\n                .collect();\n\n            if !candidates.is_empty() {\n                if let Some(index) = find_best_match(&candidates, query) {\n                    return Some(candidates[index].id);\n                }\n            }\n        }\n\n        None\n    }\n\n    /// Returns an iterator over the internal storage.\n    ///\n    /// This can be used for manual font matching.\n    #[inline]\n    pub fn faces(&self) -> impl Iterator<Item = &FaceInfo> + '_ {\n        self.faces.iter().map(|(_, info)| info)\n    }\n\n    /// Selects a `FaceInfo` by `id`.\n    ///\n    /// Returns `None` if a face with such ID was already removed,\n    /// or this ID belong to the other `Database`.\n    pub fn face(&self, id: ID) -> Option<&FaceInfo> {\n        self.faces.get(id.0)\n    }\n\n    /// Returns font face storage and the face index by `ID`.\n    pub fn face_source(&self, id: ID) -> Option<(Source, u32)> {\n        self.face(id).map(|info| (info.source.clone(), info.index))\n    }\n\n    /// Executes a closure with a font's data.\n    ///\n    /// We can't return a reference to a font binary data because of lifetimes.\n    /// So instead, you can use this method to process font's data.\n    ///\n    /// The closure accepts raw font data and font face index.\n    ///\n    /// In case of `Source::File`, the font file will be memory mapped.\n    ///\n    /// Returns `None` when font file loading failed.\n    ///\n    /// # Example\n    ///\n    /// ```ignore\n    /// let is_variable = db.with_face_data(id, |font_data, face_index| {\n    ///     let font = ttf_parser::Face::from_slice(font_data, face_index).unwrap();\n    ///     font.is_variable()\n    /// })?;\n    /// ```\n    pub fn with_face_data<P, T>(&self, id: ID, p: P) -> Option<T>\n    where\n        P: FnOnce(&[u8], u32) -> T,\n    {\n        let (src, face_index) = self.face_source(id)?;\n        src.with_data(|data| p(data, face_index))\n    }\n\n    /// Makes the font data that backs the specified face id shared so that the application can\n    /// hold a reference to it.\n    ///\n    /// # Safety\n    ///\n    /// If the face originates from a file from disk, then the file is mapped from disk. This is unsafe as\n    /// another process may make changes to the file on disk, which may become visible in this process'\n    /// mapping and possibly cause crashes.\n    ///\n    /// If the underlying font provides multiple faces, then all faces are updated to participate in\n    /// the data sharing. If the face was previously marked for data sharing, then this function will\n    /// return a clone of the existing reference.\n    #[cfg(all(feature = \"fs\", feature = \"memmap\"))]\n    pub unsafe fn make_shared_face_data(\n        &mut self,\n        id: ID,\n    ) -> Option<(std::sync::Arc<dyn AsRef<[u8]> + Send + Sync>, u32)> {\n        let face_info = self.faces.get(id.0)?;\n        let face_index = face_info.index;\n\n        let old_source = face_info.source.clone();\n\n        let (path, shared_data) = match &old_source {\n            Source::Binary(data) => {\n                return Some((data.clone(), face_index));\n            }\n            Source::File(ref path) => {\n                let file = std::fs::File::open(path).ok()?;\n                let shared_data = std::sync::Arc::new(memmap2::MmapOptions::new().map(&file).ok()?)\n                    as std::sync::Arc<dyn AsRef<[u8]> + Send + Sync>;\n                (path.clone(), shared_data)\n            }\n            Source::SharedFile(_, data) => {\n                return Some((data.clone(), face_index));\n            }\n        };\n\n        let shared_source = Source::SharedFile(path.clone(), shared_data.clone());\n\n        self.faces.iter_mut().for_each(|(_, face)| {\n            if matches!(&face.source, Source::File(old_path) if old_path == &path) {\n                face.source = shared_source.clone();\n            }\n        });\n\n        Some((shared_data, face_index))\n    }\n\n    /// Transfers ownership of shared font data back to the font database. This is the reverse operation\n    /// of [`Self::make_shared_face_data`]. If the font data belonging to the specified face is mapped\n    /// from a file on disk, then that mapping is closed and the data becomes private to the process again.\n    #[cfg(all(feature = \"fs\", feature = \"memmap\"))]\n    pub fn make_face_data_unshared(&mut self, id: ID) {\n        let face_info = match self.faces.get(id.0) {\n            Some(face_info) => face_info,\n            None => return,\n        };\n\n        let old_source = face_info.source.clone();\n\n        let shared_path = match old_source {\n            #[cfg(all(feature = \"fs\", feature = \"memmap\"))]\n            Source::SharedFile(path, _) => path,\n            _ => return,\n        };\n\n        let new_source = Source::File(shared_path.clone());\n\n        self.faces.iter_mut().for_each(|(_, face)| {\n            if matches!(&face.source, Source::SharedFile(path, ..) if path == &shared_path) {\n                face.source = new_source.clone();\n            }\n        });\n    }\n}\n\n/// A single font face info.\n///\n/// A font can have multiple faces.\n///\n/// A single item of the `Database`.\n#[derive(Clone, Debug)]\npub struct FaceInfo {\n    /// An unique ID.\n    pub id: ID,\n\n    /// A font source.\n    ///\n    /// Note that multiple `FaceInfo` objects can reference the same data in case of\n    /// font collections, which means that they'll use the same Source.\n    pub source: Source,\n\n    /// A face index in the `source`.\n    pub index: u32,\n\n    /// A list of family names.\n    ///\n    /// Contains pairs of Name + Language. Where the first family is always English US,\n    /// unless it's missing from the font.\n    ///\n    /// Corresponds to a *Typographic Family* (ID 16) or a *Font Family* (ID 1) [name ID]\n    /// in a TrueType font.\n    ///\n    /// This is not an *Extended Typographic Family* or a *Full Name*.\n    /// Meaning it will contain _Arial_ and not _Arial Bold_.\n    ///\n    /// [name ID]: https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids\n    pub families: Vec<(String, Language)>,\n\n    /// A PostScript name.\n    ///\n    /// Corresponds to a *PostScript name* (6) [name ID] in a TrueType font.\n    ///\n    /// [name ID]: https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids\n    pub post_script_name: String,\n\n    /// A font face style.\n    pub style: Style,\n\n    /// A font face weight.\n    pub weight: Weight,\n\n    /// A font face stretch.\n    pub stretch: Stretch,\n\n    /// Indicates that the font face is monospaced.\n    pub monospaced: bool,\n}\n\n/// A font source.\n///\n/// Either a raw binary data or a file path.\n///\n/// Stores the whole font and not just a single face.\n#[derive(Clone)]\npub enum Source {\n    /// A font's raw data, typically backed by a Vec<u8>.\n    Binary(alloc::sync::Arc<dyn AsRef<[u8]> + Sync + Send>),\n\n    /// A font's path.\n    #[cfg(feature = \"fs\")]\n    File(std::path::PathBuf),\n\n    /// A font's raw data originating from a shared file mapping.\n    #[cfg(all(feature = \"fs\", feature = \"memmap\"))]\n    SharedFile(\n        std::path::PathBuf,\n        std::sync::Arc<dyn AsRef<[u8]> + Sync + Send>,\n    ),\n}\n\nimpl core::fmt::Debug for Source {\n    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n        match self {\n            Self::Binary(arg0) => f\n                .debug_tuple(\"SharedBinary\")\n                .field(&arg0.as_ref().as_ref())\n                .finish(),\n            #[cfg(feature = \"fs\")]\n            Self::File(arg0) => f.debug_tuple(\"File\").field(arg0).finish(),\n            #[cfg(all(feature = \"fs\", feature = \"memmap\"))]\n            Self::SharedFile(arg0, arg1) => f\n                .debug_tuple(\"SharedFile\")\n                .field(arg0)\n                .field(&arg1.as_ref().as_ref())\n                .finish(),\n        }\n    }\n}\n\nimpl Source {\n    fn with_data<P, T>(&self, p: P) -> Option<T>\n    where\n        P: FnOnce(&[u8]) -> T,\n    {\n        match &self {\n            #[cfg(all(feature = \"fs\", not(feature = \"memmap\")))]\n            Source::File(ref path) => {\n                let data = std::fs::read(path).ok()?;\n\n                Some(p(&data))\n            }\n            #[cfg(all(feature = \"fs\", feature = \"memmap\"))]\n            Source::File(ref path) => {\n                let file = std::fs::File::open(path).ok()?;\n                let data = unsafe { &memmap2::MmapOptions::new().map(&file).ok()? };\n\n                Some(p(data))\n            }\n            Source::Binary(ref data) => Some(p(data.as_ref().as_ref())),\n            #[cfg(all(feature = \"fs\", feature = \"memmap\"))]\n            Source::SharedFile(_, ref data) => Some(p(data.as_ref().as_ref())),\n        }\n    }\n}\n\n/// A database query.\n///\n/// Mainly used by `Database::query()`.\n#[derive(Clone, Copy, Default, Debug, Eq, PartialEq, Hash)]\npub struct Query<'a> {\n    /// A prioritized list of font family names or generic family names.\n    ///\n    /// [font-family](https://www.w3.org/TR/2018/REC-css-fonts-3-20180920/#propdef-font-family) in CSS.\n    pub families: &'a [Family<'a>],\n\n    /// Specifies the weight of glyphs in the font, their degree of blackness or stroke thickness.\n    ///\n    /// [font-weight](https://www.w3.org/TR/2018/REC-css-fonts-3-20180920/#font-weight-prop) in CSS.\n    pub weight: Weight,\n\n    /// Selects a normal, condensed, or expanded face from a font family.\n    ///\n    /// [font-stretch](https://www.w3.org/TR/2018/REC-css-fonts-3-20180920/#font-stretch-prop) in CSS.\n    pub stretch: Stretch,\n\n    /// Allows italic or oblique faces to be selected.\n    ///\n    /// [font-style](https://www.w3.org/TR/2018/REC-css-fonts-3-20180920/#font-style-prop) in CSS.\n    pub style: Style,\n}\n\n// Enum value descriptions are from the CSS spec.\n/// A [font family](https://www.w3.org/TR/2018/REC-css-fonts-3-20180920/#propdef-font-family).\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]\npub enum Family<'a> {\n    /// The name of a font family of choice.\n    ///\n    /// This must be a *Typographic Family* (ID 16) or a *Family Name* (ID 1) in terms of TrueType.\n    /// Meaning you have to pass a family without any additional suffixes like _Bold_, _Italic_,\n    /// _Regular_, etc.\n    ///\n    /// Localized names are allowed.\n    Name(&'a str),\n\n    /// Serif fonts represent the formal text style for a script.\n    Serif,\n\n    /// Glyphs in sans-serif fonts, as the term is used in CSS, are generally low contrast\n    /// and have stroke endings that are plain — without any flaring, cross stroke,\n    /// or other ornamentation.\n    SansSerif,\n\n    /// Glyphs in cursive fonts generally use a more informal script style,\n    /// and the result looks more like handwritten pen or brush writing than printed letterwork.\n    Cursive,\n\n    /// Fantasy fonts are primarily decorative or expressive fonts that\n    /// contain decorative or expressive representations of characters.\n    Fantasy,\n\n    /// The sole criterion of a monospace font is that all glyphs have the same fixed width.\n    Monospace,\n}\n\n/// Specifies the weight of glyphs in the font, their degree of blackness or stroke thickness.\n#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug, Hash)]\npub struct Weight(pub u16);\n\nimpl Default for Weight {\n    #[inline]\n    fn default() -> Weight {\n        Weight::NORMAL\n    }\n}\n\nimpl Weight {\n    /// Thin weight (100), the thinnest value.\n    pub const THIN: Weight = Weight(100);\n    /// Extra light weight (200).\n    pub const EXTRA_LIGHT: Weight = Weight(200);\n    /// Light weight (300).\n    pub const LIGHT: Weight = Weight(300);\n    /// Normal (400).\n    pub const NORMAL: Weight = Weight(400);\n    /// Medium weight (500, higher than normal).\n    pub const MEDIUM: Weight = Weight(500);\n    /// Semibold weight (600).\n    pub const SEMIBOLD: Weight = Weight(600);\n    /// Bold weight (700).\n    pub const BOLD: Weight = Weight(700);\n    /// Extra-bold weight (800).\n    pub const EXTRA_BOLD: Weight = Weight(800);\n    /// Black weight (900), the thickest value.\n    pub const BLACK: Weight = Weight(900);\n}\n\n/// Allows italic or oblique faces to be selected.\n#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]\npub enum Style {\n    /// A face that is neither italic not obliqued.\n    Normal,\n    /// A form that is generally cursive in nature.\n    Italic,\n    /// A typically-sloped version of the regular face.\n    Oblique,\n}\n\nimpl Default for Style {\n    #[inline]\n    fn default() -> Style {\n        Style::Normal\n    }\n}\n\nfn parse_face_info(source: Source, data: &[u8], index: u32) -> Result<FaceInfo, LoadError> {\n    let raw_face = ttf_parser::RawFace::parse(data, index).map_err(|_| LoadError::MalformedFont)?;\n    let (families, post_script_name) = parse_names(&raw_face).ok_or(LoadError::UnnamedFont)?;\n    let (mut style, weight, stretch) = parse_os2(&raw_face);\n    let (monospaced, italic) = parse_post(&raw_face);\n\n    if style == Style::Normal && italic {\n        style = Style::Italic;\n    }\n\n    Ok(FaceInfo {\n        id: ID::dummy(),\n        source,\n        index,\n        families,\n        post_script_name,\n        style,\n        weight,\n        stretch,\n        monospaced,\n    })\n}\n\nfn parse_names(raw_face: &ttf_parser::RawFace) -> Option<(Vec<(String, Language)>, String)> {\n    const NAME_TAG: ttf_parser::Tag = ttf_parser::Tag::from_bytes(b\"name\");\n    let name_data = raw_face.table(NAME_TAG)?;\n    let name_table = ttf_parser::name::Table::parse(name_data)?;\n\n    let mut families = collect_families(ttf_parser::name_id::TYPOGRAPHIC_FAMILY, &name_table.names);\n\n    // We have to fallback to Family Name when no Typographic Family Name was set.\n    if families.is_empty() {\n        families = collect_families(ttf_parser::name_id::FAMILY, &name_table.names);\n    }\n\n    // Make English US the first one.\n    if families.len() > 1 {\n        if let Some(index) = families\n            .iter()\n            .position(|f| f.1 == Language::English_UnitedStates)\n        {\n            if index != 0 {\n                families.swap(0, index);\n            }\n        }\n    }\n\n    if families.is_empty() {\n        return None;\n    }\n\n    let post_script_name = name_table\n        .names\n        .into_iter()\n        .find(|name| {\n            name.name_id == ttf_parser::name_id::POST_SCRIPT_NAME && name.is_supported_encoding()\n        })\n        .and_then(|name| name_to_unicode(&name))?;\n\n    Some((families, post_script_name))\n}\n\nfn collect_families(name_id: u16, names: &ttf_parser::name::Names) -> Vec<(String, Language)> {\n    let mut families = Vec::new();\n    for name in names.into_iter() {\n        if name.name_id == name_id && name.is_unicode() {\n            if let Some(family) = name_to_unicode(&name) {\n                families.push((family, name.language()));\n            }\n        }\n    }\n\n    // If no Unicode English US family name was found then look for English MacRoman as well.\n    if !families\n        .iter()\n        .any(|f| f.1 == Language::English_UnitedStates)\n    {\n        for name in names.into_iter() {\n            if name.name_id == name_id && name.is_mac_roman() {\n                if let Some(family) = name_to_unicode(&name) {\n                    families.push((family, name.language()));\n                    break;\n                }\n            }\n        }\n    }\n\n    families\n}\n\nfn name_to_unicode(name: &ttf_parser::name::Name) -> Option<String> {\n    if name.is_unicode() {\n        let mut raw_data: Vec<u16> = Vec::new();\n        for c in ttf_parser::LazyArray16::<u16>::new(name.name) {\n            raw_data.push(c);\n        }\n\n        String::from_utf16(&raw_data).ok()\n    } else if name.is_mac_roman() {\n        // We support only MacRoman encoding here, which should be enough in most cases.\n        let mut raw_data = Vec::with_capacity(name.name.len());\n        for b in name.name {\n            raw_data.push(MAC_ROMAN[*b as usize]);\n        }\n\n        String::from_utf16(&raw_data).ok()\n    } else {\n        None\n    }\n}\n\nfn parse_os2(raw_face: &ttf_parser::RawFace) -> (Style, Weight, Stretch) {\n    const OS2_TAG: ttf_parser::Tag = ttf_parser::Tag::from_bytes(b\"OS/2\");\n    let table = match raw_face\n        .table(OS2_TAG)\n        .and_then(ttf_parser::os2::Table::parse)\n    {\n        Some(table) => table,\n        None => return (Style::Normal, Weight::NORMAL, Stretch::Normal),\n    };\n\n    let style = match table.style() {\n        ttf_parser::Style::Normal => Style::Normal,\n        ttf_parser::Style::Italic => Style::Italic,\n        ttf_parser::Style::Oblique => Style::Oblique,\n    };\n\n    let weight = table.weight();\n    let stretch = table.width();\n\n    (style, Weight(weight.to_number()), stretch)\n}\n\nfn parse_post(raw_face: &ttf_parser::RawFace) -> (bool, bool) {\n    // We need just a single value from the `post` table, while ttf-parser will parse all.\n    // Therefore we have a custom parser.\n\n    const POST_TAG: ttf_parser::Tag = ttf_parser::Tag::from_bytes(b\"post\");\n    let data = match raw_face.table(POST_TAG) {\n        Some(v) => v,\n        None => return (false, false),\n    };\n\n    // All we care about, it that u32 at offset 12 is non-zero.\n    let monospaced = data.get(12..16) != Some(&[0, 0, 0, 0]);\n\n    // Italic angle as f16.16.\n    let italic = data.get(4..8) != Some(&[0, 0, 0, 0]);\n\n    (monospaced, italic)\n}\n\ntrait NameExt {\n    fn is_mac_roman(&self) -> bool;\n    fn is_supported_encoding(&self) -> bool;\n}\n\nimpl NameExt for ttf_parser::name::Name<'_> {\n    #[inline]\n    fn is_mac_roman(&self) -> bool {\n        use ttf_parser::PlatformId::Macintosh;\n        // https://docs.microsoft.com/en-us/typography/opentype/spec/name#macintosh-encoding-ids-script-manager-codes\n        const MACINTOSH_ROMAN_ENCODING_ID: u16 = 0;\n\n        self.platform_id == Macintosh && self.encoding_id == MACINTOSH_ROMAN_ENCODING_ID\n    }\n\n    #[inline]\n    fn is_supported_encoding(&self) -> bool {\n        self.is_unicode() || self.is_mac_roman()\n    }\n}\n\n// https://www.w3.org/TR/2018/REC-css-fonts-3-20180920/#font-style-matching\n// Based on https://github.com/servo/font-kit\n#[inline(never)]\nfn find_best_match(candidates: &[&FaceInfo], query: &Query) -> Option<usize> {\n    debug_assert!(!candidates.is_empty());\n\n    // Step 4.\n    let mut matching_set: Vec<usize> = (0..candidates.len()).collect();\n\n    // Step 4a (`font-stretch`).\n    let matches = matching_set\n        .iter()\n        .any(|&index| candidates[index].stretch == query.stretch);\n    let matching_stretch = if matches {\n        // Exact match.\n        query.stretch\n    } else if query.stretch <= Stretch::Normal {\n        // Closest stretch, first checking narrower values and then wider values.\n        let stretch = matching_set\n            .iter()\n            .filter(|&&index| candidates[index].stretch < query.stretch)\n            .min_by_key(|&&index| {\n                query.stretch.to_number() - candidates[index].stretch.to_number()\n            });\n\n        match stretch {\n            Some(&matching_index) => candidates[matching_index].stretch,\n            None => {\n                let matching_index = *matching_set.iter().min_by_key(|&&index| {\n                    candidates[index].stretch.to_number() - query.stretch.to_number()\n                })?;\n\n                candidates[matching_index].stretch\n            }\n        }\n    } else {\n        // Closest stretch, first checking wider values and then narrower values.\n        let stretch = matching_set\n            .iter()\n            .filter(|&&index| candidates[index].stretch > query.stretch)\n            .min_by_key(|&&index| {\n                candidates[index].stretch.to_number() - query.stretch.to_number()\n            });\n\n        match stretch {\n            Some(&matching_index) => candidates[matching_index].stretch,\n            None => {\n                let matching_index = *matching_set.iter().min_by_key(|&&index| {\n                    query.stretch.to_number() - candidates[index].stretch.to_number()\n                })?;\n\n                candidates[matching_index].stretch\n            }\n        }\n    };\n    matching_set.retain(|&index| candidates[index].stretch == matching_stretch);\n\n    // Step 4b (`font-style`).\n    let style_preference = match query.style {\n        Style::Italic => [Style::Italic, Style::Oblique, Style::Normal],\n        Style::Oblique => [Style::Oblique, Style::Italic, Style::Normal],\n        Style::Normal => [Style::Normal, Style::Oblique, Style::Italic],\n    };\n    let matching_style = *style_preference.iter().find(|&query_style| {\n        matching_set\n            .iter()\n            .any(|&index| candidates[index].style == *query_style)\n    })?;\n\n    matching_set.retain(|&index| candidates[index].style == matching_style);\n\n    // Step 4c (`font-weight`).\n    //\n    // The spec doesn't say what to do if the weight is between 400 and 500 exclusive, so we\n    // just use 450 as the cutoff.\n    let weight = query.weight.0;\n\n    let matching_weight = if matching_set\n        .iter()\n        .any(|&index| candidates[index].weight.0 == weight)\n    {\n        Weight(weight)\n    } else if (400..450).contains(&weight)\n        && matching_set\n            .iter()\n            .any(|&index| candidates[index].weight.0 == 500)\n    {\n        // Check 500 first.\n        Weight::MEDIUM\n    } else if (450..=500).contains(&weight)\n        && matching_set\n            .iter()\n            .any(|&index| candidates[index].weight.0 == 400)\n    {\n        // Check 400 first.\n        Weight::NORMAL\n    } else if weight <= 500 {\n        // Closest weight, first checking thinner values and then fatter ones.\n        let idx = matching_set\n            .iter()\n            .filter(|&&index| candidates[index].weight.0 <= weight)\n            .min_by_key(|&&index| weight - candidates[index].weight.0);\n\n        match idx {\n            Some(&matching_index) => candidates[matching_index].weight,\n            None => {\n                let matching_index = *matching_set\n                    .iter()\n                    .min_by_key(|&&index| candidates[index].weight.0 - weight)?;\n                candidates[matching_index].weight\n            }\n        }\n    } else {\n        // Closest weight, first checking fatter values and then thinner ones.\n        let idx = matching_set\n            .iter()\n            .filter(|&&index| candidates[index].weight.0 >= weight)\n            .min_by_key(|&&index| candidates[index].weight.0 - weight);\n\n        match idx {\n            Some(&matching_index) => candidates[matching_index].weight,\n            None => {\n                let matching_index = *matching_set\n                    .iter()\n                    .min_by_key(|&&index| weight - candidates[index].weight.0)?;\n                candidates[matching_index].weight\n            }\n        }\n    };\n    matching_set.retain(|&index| candidates[index].weight == matching_weight);\n\n    // Ignore step 4d (`font-size`).\n\n    // Return the result.\n    matching_set.into_iter().next()\n}\n\n/// Macintosh Roman to UTF-16 encoding table.\n///\n/// https://en.wikipedia.org/wiki/Mac_OS_Roman\n#[rustfmt::skip]\nconst MAC_ROMAN: &[u16; 256] = &[\n    0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,\n    0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,\n    0x0010, 0x2318, 0x21E7, 0x2325, 0x2303, 0x0015, 0x0016, 0x0017,\n    0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,\n    0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,\n    0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,\n    0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,\n    0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,\n    0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,\n    0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,\n    0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,\n    0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,\n    0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,\n    0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,\n    0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,\n    0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,\n    0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1,\n    0x00E0, 0x00E2, 0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8,\n    0x00EA, 0x00EB, 0x00ED, 0x00EC, 0x00EE, 0x00EF, 0x00F1, 0x00F3,\n    0x00F2, 0x00F4, 0x00F6, 0x00F5, 0x00FA, 0x00F9, 0x00FB, 0x00FC,\n    0x2020, 0x00B0, 0x00A2, 0x00A3, 0x00A7, 0x2022, 0x00B6, 0x00DF,\n    0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8, 0x2260, 0x00C6, 0x00D8,\n    0x221E, 0x00B1, 0x2264, 0x2265, 0x00A5, 0x00B5, 0x2202, 0x2211,\n    0x220F, 0x03C0, 0x222B, 0x00AA, 0x00BA, 0x03A9, 0x00E6, 0x00F8,\n    0x00BF, 0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248, 0x2206, 0x00AB,\n    0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5, 0x0152, 0x0153,\n    0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA,\n    0x00FF, 0x0178, 0x2044, 0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02,\n    0x2021, 0x00B7, 0x201A, 0x201E, 0x2030, 0x00C2, 0x00CA, 0x00C1,\n    0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF, 0x00CC, 0x00D3, 0x00D4,\n    0xF8FF, 0x00D2, 0x00DA, 0x00DB, 0x00D9, 0x0131, 0x02C6, 0x02DC,\n    0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD, 0x02DB, 0x02C7,\n];\n"
  },
  {
    "path": "tests/add_fonts.rs",
    "content": "const DEMO_TTF: &[u8] = include_bytes!(\"./fonts/Tuffy.ttf\");\nuse std::sync::Arc;\n\n#[test]\nfn add_fonts_and_get_ids_back() {\n    env_logger::init();\n    let mut font_db = fontdb::Database::new();\n    let ids = font_db.load_font_source(fontdb::Source::Binary(Arc::new(DEMO_TTF)));\n\n    assert_eq!(ids.len(), 1);\n    let id = ids[0];\n\n    let font = font_db.face(id).unwrap();\n    assert!(font.families.iter().any(|(name, _)| name == \"Tuffy\"));\n}\n"
  },
  {
    "path": "tests/fonts/LICENSE.txt",
    "content": "We, the copyright holders of this work, hereby release it into the\npublic domain. This applies worldwide.\n\nIn case this is not legally possible,\n\nWe grant any entity the right to use this work for any purpose, without\nany conditions, unless such conditions are required by law.\n\nThatcher Ulrich <tu@tulrich.com> http://tulrich.com\nKaroly Barta bartakarcsi@gmail.com\nMichael Evans http://www.evertype.com\n"
  }
]